# Linux E 100 网卡 与 TCP 层原理 三

# ip_rcv 函数

前面我们看到从网卡接受到的包 sk_buff *skb 将会找到 list_head ptype_base[16] hash 表中注册的 packet_type ,随后调用该结构中设置的回调函数:ip_rcv 。至此,从数据链路层的数据包正式进入 网络层处理。该函数的处理较为简单:对原始 skb 数据包(包含 IP 头部 和 数据)检测处理,如果一切没问题那么便将该数据包 调用 NF_HOOK 宏定义完成处理。

int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
    struct iphdr *iph;// 包类型为其他主机的数据包,直接丢弃
    if (skb->pkt_type == PACKET_OTHERHOST)
        goto drop;IP_INC_STATS_BH(IpInReceives); // 增加接受IP数据包分析计数if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) { // 检测数据包是否为共享数据包,若是,那么将该 skb clone 一个新的处理,并减少一个 旧 skb 的引用计数
        IP_INC_STATS_BH(IpInDiscards);
        goto out;
    }if (!pskb_may_pull(skb, sizeof(struct iphdr))) // 检测并处理数据包IP头部
        goto inhdr_error;
    iph = skb->nh.iph; // IP 数据头部/*
     *  RFC1122: 3.1.2.2 规定必须静默丢弃任何校验和失败的IP帧
     *
     *  检测IP数据报是否可以被接受:
     *
     *  1.  IP 数据报的长度至少是 ip 头的大小
     *  2.  IP 数据报的版本必须为 IPV4(因为我们这里的源码为ipv4下的处理代码,而不是ipv6)
     *  3.  IP 数据报的校验和正确
     *  4.  IP 数据报不存在伪长度
     */if (iph->ihl < 5 || iph->version != 4)
        goto inhdr_error;if (!pskb_may_pull(skb, iph->ihl*4))
        goto inhdr_error;
​
    iph = skb->nh.iph;if (ip_fast_csum((u8 *)iph, iph->ihl) != 0) // 校验和检测失败
        goto inhdr_error;{
        __u32 len = ntohs(iph->tot_len); // 获取 ip 头部总长度
        if (skb->len < len || len < (iph->ihl<<2)) // skb 在ip头部处理后长度小于 IP头部长度 那么 IP头部处理失败
            goto inhdr_error;
        if (skb->len > len) {  // 若 skb 的总长度 大于 ip 头部长度,那么尝试修正 skb 真实的长度(比如:如果我们填充了padding 对齐数据,那么将该数据去除)
            __pskb_trim(skb, len);
            if (skb->ip_summed == CHECKSUM_HW)
                skb->ip_summed = CHECKSUM_NONE;
        }
    }return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
               ip_rcv_finish); // 调用 NETFILTER 模块完成处理
​
inhdr_error:
    IP_INC_STATS_BH(IpInHdrErrors);
drop:
        kfree_skb(skb);
out:
        return NET_RX_DROP;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

# NETFILTER 模块定义

NETFILTER 模块 是 Linux 网络处理的核心:对不同阶段的 IP 数据报进行拦截处理,比如防火墙机制便是通过该机制完成。它定义了如下五个处理回调钩子。

/* IP 数据报进行 路由 之前 回调 */
#define NF_IP_PRE_ROUTING   0/* IP 数据报需要递交到 TCP 层时 回调  */
#define NF_IP_LOCAL_IN      1/* IP 数据报需要递交给 网络中其他主机,并且在本机进行 forward 转发机制时 回调 */
#define NF_IP_FORWARD       2/* IP 数据报需要从本机传输到驱动层处理前 回调*/
#define NF_IP_LOCAL_OUT     3/* IP 数据报递交到网卡驱动层前 回调*/
#define NF_IP_POST_ROUTING  4
1
2
3
4
5
6
7
8
9
10
11
12
13
14

看如下的图描述,其中 IN 、OUT 上层 为 TCP 层,INPUT 、OUTPUT 下层 为数据链路层(网卡驱动层),方框为上述的钩子名,方框中的白色为 利用这些钩子的功能(比如:mangle 防火墙、NAT 网络地址转换)。

img

接下来我们来看看 NETFILTER 定义的宏定义。我们看到当定义了 NETFITLER 那么 NF_HOOK 宏定义会进入 NETFILTER层处理,如果没有,那么直接执行 okfn 回调函数 ,我打算 NETFILTER 后面的文章单独讲解,所以这里有个概念即可。

#ifdef CONFIG_NETFILTER // 配置了 NETFILTER 框架#ifdef CONFIG_NETFILTER_DEBUG  // 配置了 NETFILTER 调试机制,那么定义如下宏定义#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)  nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn), INT_MIN)#define NF_HOOK_THRESH nf_hook_slow#else // 没有配置 NETFILTER 调试机制,那么定义如下宏定义 #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (list_empty(&nf_hooks[(pf)][(hook)])? (okfn)(skb) : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn), INT_MIN))#define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh) (list_empty(&nf_hooks[(pf)][(hook)]) ? (okfn)(skb) : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn), (thresh)))#endif#else /* !CONFIG_NETFILTER */#define NF_HOOK(pf, hook, skb, indev, outdev, okfn 回调函数) (okfn)(skb) // 若没有配置 NETFILTER 框架,那么这里直接调用传入的 okfn 回调函数#endif /*CONFIG_NETFILTER*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# ip_rcv_finish 函数

static inline int ip_rcv_finish(struct sk_buff *skb)
{
    struct net_device *dev = skb->dev;
    struct iphdr *iph = skb->nh.iph;//  初始化数据包的虚拟路径缓存,它描述了包如何在Linux网络中传递执行(也即路由信息)
    if (skb->dst == NULL) {
        if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))
            goto drop; 
    }if (iph->ihl > 5) { // IP 头部长度大于5( 20 字节,IP包头最小长度为 20 字节,最大长度为:一个IP包头的长度最长为 1111 ,即:15*4 = 60 字节,所以这里大于 正常长度的20字节,所以存在其他 IP 额外 options,所以这里处理这些选项)了解即可
        ...
    }return dst_input(skb); // 执行进一步处理
​
inhdr_error:
    IP_INC_STATS_BH(IpInHdrErrors);
drop:
        kfree_skb(skb);
        return NET_RX_DROP;
}
​
​
// 将数据包传递给传输层
static inline int dst_input(struct sk_buff *skb)
{
    int err;
    for (;;) {
        err = skb->dst->input(skb); // 回调 dst 的 input 函数
        if (likely(err == 0))
            return err;
        if (unlikely(err != NET_XMIT_BYPASS))
            return err;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# ip_route_input 函数

ip_rcv_finish 函数将会调用该函数完成对 skb->dst->input 函数的设置,由于该方法很大很长,我这里关注重点即可:函数如何设置。

int ip_route_input(struct sk_buff *skb, u32 daddr, u32 saddr,
           u8 tos, struct net_device *dev)
{
    ...
    for (rth = rt_hash_table[hash].chain; rth; rth = rth->u.rt_next) { // 查找路由缓存,若存在,那么直接返回,缓存使用 hash 表(链地址法确定)
        smp_read_barrier_depends();
        if (rth->fl.fl4_dst == daddr &&
            rth->fl.fl4_src == saddr &&
            rth->fl.iif == iif &&
            rth->fl.oif == 0 &&
            rth->fl.fl4_tos == tos) {
            ...
            return 0;
        }
        RT_CACHE_STAT_INC(in_hlist_search);
    }
    rcu_read_unlock();if (MULTICAST(daddr)) { // 数据报的目的地址为 广播地址
        ...
    }
    return ip_route_input_slow(skb, daddr, saddr, tos, dev); // 其他数据报
}int ip_route_input_slow(struct sk_buff *skb, u32 daddr, u32 saddr, u8 tos, struct net_device *dev)
{
   ...
   rth->u.dst.input = ip_forward; // 设置 input 为 ip_forward 函数,将数据包在本机路由到其他主机
   ...
   local_input:
       rth->u.dst.input= ip_local_deliver;  // 设置 input 为 ip_local_deliver 函数,传递到本机 TCP 层
   
   ...
}// 将 IP 数据报传递到传输层
int ip_local_deliver(struct sk_buff *skb)
{
    if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) { // IP 是分片数据,那么重组 IP 分片,然后再传递
        skb = ip_defrag(skb);
        if (!skb)
            return 0;
    }return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
               ip_local_deliver_finish); // 否则进入 NF_IP_LOCAL_IN 钩子,如果钩子确定进入 传输层,那么回调 ip_local_deliver_finish 函数
}static inline int ip_local_deliver_finish(struct sk_buff *skb)
{
    ...
    // TCP 层的数据
    skb->h.raw = skb->data;
    {
        //  找到可以处理该 IP 数据报的网络层
        int protocol = skb->nh.iph->protocol;
        int hash;
        struct sock *raw_sk;
        struct inet_protocol *ipprot;
        ...
        if ((ipprot = inet_protos[hash]) != NULL) {
            int ret;
            smp_read_barrier_depends();
            ...
            ret = ipprot->handler(skb); // 回调处理函数
            if (ret < 0) {
                protocol = -ret;
                goto resubmit;
            }
            IP_INC_STATS_BH(IpInDelivers);
        } else { // 无网络层可以处理,那么释放 SKB
            ...
            kfree_skb(skb);
        }
    }
 out:
    rcu_read_unlock();return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

# TCP 层回调函数注册

接下来我们看看 ipprot->handler(skb); 回调处理函数的注册过程。

#define MAX_INET_PROTOS 256 
struct inet_protocol *inet_protos[MAX_INET_PROTOS]; 
​
​
// 添加处理协议
int inet_add_protocol(struct inet_protocol *prot, unsigned char protocol)
{
    int hash, ret;
​
    hash = protocol & (MAX_INET_PROTOS - 1);
    spin_lock_bh(&inet_proto_lock);
    if (inet_protos[hash]) { // 已经添加过协议
        ret = -1;
    } else { // 否则将 prot 放入数组
        inet_protos[hash] = prot;
        ret = 0;
    }
    spin_unlock_bh(&inet_proto_lock);
    return ret;
}static int __init inet_init(void){
    ...
    if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0) // 添加 ICMP 处理结构
        printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");
    
    if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0) // 添加 UDP 处理结构
        printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");    
    
    if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0) // 添加 TCP 处理结构
        printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");    
    ...
}static struct inet_protocol tcp_protocol = {
    .handler =  tcp_v4_rcv, // tcp 处理协议回调函数
    .err_handler =  tcp_v4_err,
    .no_policy =    1,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39