概述

网络层是Linux网络协议栈的核心层次,主要负责IP数据包的路由、转发、分片和重组等功能。本文将深入分析Linux网络层的实现机制,包括IPv4/IPv6协议处理、路由子系统、ICMP协议以及各种优化策略。

1. 网络层架构

1.1 网络层核心职责

Linux网络层承担以下关键功能:

  • IP数据包处理:IPv4和IPv6数据包的收发处理
  • 路由决策:根据目标地址决定数据包的转发路径
  • 分片与重组:处理大数据包的分片和重组
  • ICMP协议:实现网络控制消息协议
  • QoS处理:服务质量和流量分类
  • 网络地址转换:支持NAT功能

1.2 网络层架构图

graph TB subgraph "传输层" TCP[TCP协议] UDP[UDP协议] SCTP[SCTP协议] RAW[原始套接字] end subgraph "网络层核心" subgraph "IPv4子系统" IP4_INPUT[IPv4输入处理] IP4_OUTPUT[IPv4输出处理] IP4_FORWARD[IPv4转发] IP4_FRAG[IPv4分片处理] IP4_DEFRAG[IPv4重组] end subgraph "IPv6子系统" IP6_INPUT[IPv6输入处理] IP6_OUTPUT[IPv6输出处理] IP6_FORWARD[IPv6转发] IP6_FRAG[IPv6分片处理] IP6_DEFRAG[IPv6重组] end subgraph "路由子系统" FIB[转发信息库] ROUTE_INPUT[路由输入查找] ROUTE_OUTPUT[路由输出查找] ROUTE_CACHE[路由缓存] POLICY_ROUTE[策略路由] end subgraph "ICMP子系统" ICMP4[ICMPv4处理] ICMP6[ICMPv6处理] ICMP_ERROR[ICMP错误生成] ICMP_ECHO[ICMP回显] end subgraph "网络过滤" NETFILTER[Netfilter框架] IPTABLES[iptables规则] CONNTRACK[连接跟踪] NAT[网络地址转换] end end subgraph "数据链路层" ETH[以太网处理] ARP[ARP协议] NEIGH[邻居子系统] end %% 接收路径连接 ETH --> IP4_INPUT ETH --> IP6_INPUT ETH --> ARP IP4_INPUT --> NETFILTER IP6_INPUT --> NETFILTER NETFILTER --> IP4_FORWARD NETFILTER --> IP6_FORWARD IP4_INPUT --> IP4_DEFRAG IP6_INPUT --> IP6_DEFRAG IP4_DEFRAG --> TCP IP4_DEFRAG --> UDP IP6_DEFRAG --> TCP IP6_DEFRAG --> UDP %% 发送路径连接 TCP --> IP4_OUTPUT UDP --> IP4_OUTPUT TCP --> IP6_OUTPUT UDP --> IP6_OUTPUT IP4_OUTPUT --> ROUTE_OUTPUT IP6_OUTPUT --> ROUTE_OUTPUT ROUTE_OUTPUT --> IP4_FRAG ROUTE_OUTPUT --> IP6_FRAG IP4_FRAG --> ETH IP6_FRAG --> ETH %% 路由和ICMP连接 ROUTE_INPUT --> FIB ROUTE_OUTPUT --> FIB FIB --> POLICY_ROUTE IP4_INPUT --> ICMP4 IP6_INPUT --> ICMP6 ICMP_ERROR --> IP4_OUTPUT ICMP_ERROR --> IP6_OUTPUT %% Netfilter连接 NETFILTER --> IPTABLES NETFILTER --> CONNTRACK CONNTRACK --> NAT style IP4_INPUT fill:#e1f5fe style ROUTE_OUTPUT fill:#f3e5f5 style NETFILTER fill:#e8f5e8 style FIB fill:#fff3e0

2. IPv4协议实现

2.1 IPv4头部结构

 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
/**
 * IPv4头部结构
 * 
 * IPv4头部是网络层数据包的核心,包含了路由、分片、
 * 服务类型等关键信息。
 */
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8    ihl:4,      /* 头部长度(32位字为单位) */
            version:4;  /* IP版本号 */
#elif defined (__BIG_ENDIAN_BITFIELD)
    __u8    version:4,  /* IP版本号 */
            ihl:4;      /* 头部长度(32位字为单位) */
#else
#error  "Please fix <asm/byteorder.h>"
#endif
    __u8    tos;        /* 服务类型 */
    __be16  tot_len;    /* 总长度 */
    __be16  id;         /* 标识符 */
    __be16  frag_off;   /* 分片偏移 */
    __u8    ttl;        /* 生存时间 */
    __u8    protocol;   /* 协议类型 */
    __sum16 check;      /* 头部校验和 */
    __be32  saddr;      /* 源IP地址 */
    __be32  daddr;      /* 目标IP地址 */
    /* 可选字段和填充 */
};

/* IPv4分片标志位 */
#define IP_CE       0x8000      /* 拥塞标志 */
#define IP_DF       0x4000      /* 不分片标志 */
#define IP_MF       0x2000      /* 更多分片标志 */
#define IP_OFFSET   0x1FFF      /* 分片偏移掩码 */

/* IPv4服务类型字段 */
#define IPTOS_TOS_MASK      0x1E    /* TOS掩码 */
#define IPTOS_TOS(tos)      ((tos) & IPTOS_TOS_MASK)
#define IPTOS_LOWDELAY      0x10    /* 低延迟 */
#define IPTOS_THROUGHPUT    0x08    /* 高吞吐量 */
#define IPTOS_RELIABILITY   0x04    /* 高可靠性 */
#define IPTOS_MINCOST       0x02    /* 最低成本 */

/* IPv4协议号定义 */
#define IPPROTO_IP      0       /* 虚拟协议,用于表示下一个头部是IPv4 */
#define IPPROTO_ICMP    1       /* 网际控制消息协议 */
#define IPPROTO_IGMP    2       /* 网际组管理协议 */
#define IPPROTO_IPIP    4       /* IPv4隧道 */
#define IPPROTO_TCP     6       /* 传输控制协议 */
#define IPPROTO_EGP     8       /* 外部网关协议 */
#define IPPROTO_PUP     12      /* PUP协议 */
#define IPPROTO_UDP     17      /* 用户数据报协议 */
#define IPPROTO_IDP     22      /* XNS IDP协议 */
#define IPPROTO_TP      29      /* SO传输协议类4 */
#define IPPROTO_DCCP    33      /* 数据报拥塞控制协议 */
#define IPPROTO_IPV6    41      /* IPv6头部 */
#define IPPROTO_RSVP    46      /* RSVP协议 */
#define IPPROTO_GRE     47      /* 通用路由封装 */
#define IPPROTO_ESP     50      /* 封装安全有效载荷 */
#define IPPROTO_AH      51      /* 认证头部 */
#define IPPROTO_MTP     92      /* 多播传输协议 */
#define IPPROTO_BEETPH  94      /* IP选项伪头部用于BEET */
#define IPPROTO_ENCAP   98      /* 封装头部 */
#define IPPROTO_PIM     103     /* 协议独立组播 */
#define IPPROTO_COMP    108     /* 压缩头部协议 */
#define IPPROTO_SCTP    132     /* 流控制传输协议 */
#define IPPROTO_UDPLITE 136     /* UDP-Lite协议 */
#define IPPROTO_MPLS    137     /* MPLS-in-IP */
#define IPPROTO_RAW     255     /* 原始IP包 */
#define IPPROTO_MAX     256

2.2 IPv4数据包接收处理

  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
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/**
 * ip_rcv - IPv4数据包接收入口函数
 * @skb: 接收到的数据包
 * @dev: 接收设备
 * @pt: 数据包类型
 * @orig_dev: 原始设备
 * 
 * IPv4协议栈的接收入口,进行基本的合法性检查
 * 返回值:NET_RX_SUCCESS表示成功,NET_RX_DROP表示丢弃
 */
int ip_rcv(struct sk_buff *skb, struct net_device *dev, 
           struct packet_type *pt, struct net_device *orig_dev)
{
    struct net *net = dev_net(dev);
    
    skb = ip_rcv_core(skb, net);
    if (skb == NULL)
        return NET_RX_DROP;
    
    return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
                   net, NULL, skb, dev, NULL,
                   ip_rcv_finish);
}

/**
 * ip_rcv_core - IPv4接收核心处理
 * @skb: 数据包
 * @net: 网络命名空间
 * 
 * 执行IPv4头部的基本验证和处理
 * 返回值:处理后的数据包或NULL
 */
static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net)
{
    const struct iphdr *iph;
    int drop_reason;
    u32 len;
    
    /* 数据包长度检查 */
    if (skb->len < sizeof(struct iphdr))
        goto inhdr_error;
    
    if (!pskb_may_pull(skb, sizeof(struct iphdr)))
        goto inhdr_error;
    
    iph = ip_hdr(skb);
    
    /*
     * RFC1122: 3.2.1.2 MUST silently discard any IP frame that fails the checksum.
     * RFC1122: 3.2.1.3 MUST discard a frame whose length is less than the minimum of 20 bytes.
     *
     * 基本头部验证
     */
    if (iph->ihl < 5 || iph->version != 4)
        goto inhdr_error;
    
    BUILD_BUG_ON(IPSTATS_MIB_ECT1PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_1);
    BUILD_BUG_ON(IPSTATS_MIB_ECT0PKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_ECT_0);
    BUILD_BUG_ON(IPSTATS_MIB_CEPKTS != IPSTATS_MIB_NOECTPKTS + INET_ECN_CE);
    
    /* 更新ECN统计 */
    __IP_ADD_STATS(net, IPSTATS_MIB_INRECEIVES, 1);
    __IP_ADD_STATS(net, IPSTATS_MIB_NOECTPKTS + (iph->tos & INET_ECN_MASK), 1);
    
    if (!pskb_may_pull(skb, iph->ihl*4))
        goto inhdr_error;
    
    iph = ip_hdr(skb);
    
    /* 校验和验证 */
    if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
        goto csum_error;
    
    len = ntohs(iph->tot_len);
    if (skb->len < len) {
        drop_reason = SKB_DROP_REASON_PKT_TOO_SMALL;
        __IP_INC_STATS(net, IPSTATS_MIB_INTRUNCATEDPKTS);
        goto drop;
    } else if (len < (iph->ihl*4))
        goto inhdr_error;
    
    /*
     * 我们的传输媒介可能在IP包后面填充了一些字节。
     * 我们只要求传输层接收实际的IP数据,因此我们缩短数据包。
     */
    if (pskb_trim_rcsum(skb, len)) {
        __IP_INC_STATS(net, IPSTATS_MIB_INDISCARDS);
        goto drop;
    }
    
    iph = ip_hdr(skb);
    skb->transport_header = skb->network_header + iph->ihl*4;
    
    /* 移除任何调试信息或严格路由选项,它们已经处理过了 */
    if (iph->ihl > 5 && ip_rcv_options(skb, dev))
        goto drop;
    
    return skb;
    
csum_error:
    drop_reason = SKB_DROP_REASON_IP_CSUM;
    __IP_INC_STATS(net, IPSTATS_MIB_CSUMERRORS);
inhdr_error:
    if (drop_reason == SKB_DROP_REASON_NOT_SPECIFIED)
        drop_reason = SKB_DROP_REASON_IP_INHDR;
    __IP_INC_STATS(net, IPSTATS_MIB_INHDRERRORS);
drop:
    kfree_skb_reason(skb, drop_reason);
    return NULL;
}

/**
 * ip_rcv_finish - IPv4接收完成处理
 * @net: 网络命名空间
 * @sk: 套接字(通常为NULL)
 * @skb: 数据包
 * 
 * 完成IPv4数据包的接收处理,进行路由查找
 * 返回值:处理结果
 */
static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    struct net_device *dev = skb->dev;
    int ret;
    
    /* Initialise the virtual path cache for the packet. It describes
     * how the packet travels inside Linux networking.
     */
    if (!skb_valid_dst(skb)) {
        ret = ip_route_input_noref(skb, ip_hdr(skb)->daddr,
                                  ip_hdr(skb)->saddr,
                                  ip_hdr(skb)->tos, dev);
        if (unlikely(ret))
            goto drop_error;
    }
    
    if (unlikely(skb_dst(skb)->tclassid)) {
        struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
        u32 idx = skb_dst(skb)->tclassid;
        st[idx&0xFF].o_packets++;
        st[idx&0xFF].o_bytes += skb->len;
        st[(idx>>16)&0xFF].i_packets++;
        st[(idx>>16)&0xFF].i_bytes += skb->len;
    }
    
    if (iph->ihl > 5 && ip_rcv_options(skb, dev))
        return NET_RX_DROP;
    
    rt = skb_rtable(skb);
    if (rt->rt_type == RTN_MULTICAST) {
        __IP_UPD_PO_STATS(net, IPSTATS_MIB_INMCAST, skb->len);
    } else if (rt->rt_type == RTN_BROADCAST) {
        __IP_UPD_PO_STATS(net, IPSTATS_MIB_INBCAST, skb->len);
    } else if (skb->pkt_type == PACKET_BROADCAST ||
              skb->pkt_type == PACKET_MULTICAST) {
        struct in_device *in_dev = __in_dev_get_rcu(dev);
        
        /* RFC 1122 3.3.6:
         *
         *   When a host sends a datagram to a link-layer broadcast
         *   address, the IP destination address MUST be a legal IP
         *   broadcast or IP multicast address.
         *
         *   A host SHOULD silently discard a datagram that is received
         *   via a link-layer broadcast (see Section 2.4) but does not
         *   specify an IP multicast or broadcast destination address.
         *
         * This doesn't explicitly say L2 *broadcast*, but broadcast is
         * in a way a form of multicast and the most common use case for
         * this is 802.11 protecting against cross-station spoofing (the
         * so-called "hole-196" attack) so do it for both.
         */
        if (in_dev &&
            IN_DEV_ORCONF(in_dev, DROP_UNICAST_IN_L2_MULTICAST)) {
            goto drop;
        }
    }
    
    return dst_input(skb);
    
drop:
    kfree_skb(skb);
    return NET_RX_DROP;
    
drop_error:
    if (ret == -EXDEV)
        __NET_INC_STATS(net, LINUX_MIB_IPRPFILTER);
    goto drop;
}

/**
 * ip_local_deliver - 本地交付IPv4数据包
 * @skb: 数据包
 * 
 * 将IPv4数据包交付给本地协议处理程序
 * 返回值:处理结果
 */
int ip_local_deliver(struct sk_buff *skb)
{
    /*
     * 重组分片包
     */
    if (ip_is_fragment(ip_hdr(skb))) {
        if (ip_defrag(dev_net(skb->dev), skb, IP_DEFRAG_LOCAL_DELIVER))
            return 0;
    }
    
    return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
                   dev_net(skb->dev), NULL, skb,
                   skb->dev, NULL,
                   ip_local_deliver_finish);
}

/**
 * ip_local_deliver_finish - 完成本地交付
 * @net: 网络命名空间
 * @sk: 套接字
 * @skb: 数据包
 * 
 * 根据协议类型将数据包交付给相应的协议处理程序
 * 返回值:处理结果
 */
static int ip_local_deliver_finish(struct net *net, struct sock *sk,
                                  struct sk_buff *skb)
{
    __skb_pull(skb, skb_network_header_len(skb));
    
    rcu_read_lock();
    {
        int protocol = ip_hdr(skb)->protocol;
        const struct net_protocol *ipprot;
        int raw;
        
    resubmit:
        raw = raw_local_deliver(skb, protocol);
        
        ipprot = rcu_dereference(inet_protos[protocol]);
        if (ipprot) {
            int ret;
            
            if (!ipprot->no_policy) {
                if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                    kfree_skb(skb);
                    goto out;
                }
                nf_reset_ct(skb);
            }
            ret = ipprot->handler(skb);
            if (ret < 0) {
                protocol = -ret;
                goto resubmit;
            }
            __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
        } else {
            if (!raw) {
                if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                    __IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS);
                    icmp_send(skb, ICMP_DEST_UNREACH,
                             ICMP_PROT_UNREACH, 0);
                }
                kfree_skb(skb);
            } else {
                __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
                consume_skb(skb);
            }
        }
    }
out:
    rcu_read_unlock();
    
    return 0;
}

2.3 IPv4数据包发送处理

  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
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/**
 * ip_queue_xmit - IPv4数据包排队发送
 * @sk: 套接字
 * @skb: 要发送的数据包
 * @fl: 流信息
 * 
 * TCP/SCTP等协议通过此函数发送IPv4数据包
 * 返回值:成功返回0,失败返回负错误码
 */
int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
{
    struct inet_sock *inet = inet_sk(sk);
    struct net *net = sock_net(sk);
    struct ip_options_rcu *inet_opt;
    struct flowi4 *fl4;
    struct rtable *rt;
    struct iphdr *iph;
    int res;
    
    /* 跳过目标不可达的已排队数据包 */
    if (inet->pmtudisc == IP_PMTUDISC_PROBE &&
        inet->cork.length) {
        return ip_queue_mtu_discover(sk, skb);
    }
    
    rcu_read_lock();
    inet_opt = rcu_dereference(inet->inet_opt);
    fl4 = &fl->u.ip4;
    rt = skb_rtable(skb);
    if (rt)
        goto packet_routed;
    
    /* 如果没有路由,需要进行路由查找 */
    rt = ip_route_output_ports(net, fl4, sk,
                              inet->inet_daddr, inet->inet_saddr,
                              inet->inet_dport,
                              inet->inet_sport,
                              sk->sk_protocol,
                              RT_CONN_FLAGS(sk),
                              sk->sk_bound_dev_if);
    if (IS_ERR(rt))
        goto no_route;
    sk_setup_caps(sk, &rt->dst);
    
packet_routed:
    skb_dst_set_noref(skb, &rt->dst);
    
    /* 现在我们知道了路由,需要填充IP头部 */
    skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));
    skb_reset_network_header(skb);
    iph = ip_hdr(skb);
    *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
    if (ip_dont_fragment(sk, &rt->dst) && !skb->ignore_df)
        iph->frag_off = htons(IP_DF);
    else
        iph->frag_off = 0;
    iph->ttl      = ip_select_ttl(inet, &rt->dst);
    iph->protocol = sk->sk_protocol;
    ip_copy_addrs(iph, fl4);
    
    /* 传输层必须设置以下字段:tot_len, id */
    if (inet_opt && inet_opt->opt.optlen) {
        iph->ihl += inet_opt->opt.optlen >> 2;
        ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);
    }
    
    ip_select_ident_segs(net, skb, sk,
                        skb_shinfo(skb)->gso_segs ?: 1);
    
    /* 添加IP头部校验和 */
    ip_send_check(iph);
    
    skb->priority = sk->sk_priority;
    skb->mark = sk->sk_mark;
    
    res = ip_local_out(net, sk, skb);
    rcu_read_unlock();
    return res;
    
no_route:
    rcu_read_unlock();
    __IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
    kfree_skb(skb);
    return -EHOSTUNREACH;
}

/**
 * ip_build_and_send_pkt - 构建并发送IP数据包
 * @skb: 数据包
 * @sk: 套接字
 * @saddr: 源地址
 * @daddr: 目标地址
 * @opt: IP选项
 * @tos: 服务类型
 * @priority: 优先级
 * @mark: 标记
 * 
 * 构建完整的IP头部并发送数据包
 * 返回值:成功返回0,失败返回负错误码
 */
int ip_build_and_send_pkt(struct sk_buff *skb, const struct sock *sk,
                         __be32 saddr, __be32 daddr, struct ip_options_rcu *opt,
                         u8 tos, int priority, u32 mark)
{
    struct net *net = sock_net(sk);
    struct iphdr *iph;
    int err;
    
    /* 构建IP头部 */
    skb_push(skb, sizeof(struct iphdr) + (opt ? opt->opt.optlen : 0));
    skb_reset_network_header(skb);
    iph = ip_hdr(skb);
    iph->version  = 4;
    iph->ihl      = 5;
    iph->tos      = tos;
    iph->ttl      = ip_select_ttl(inet_sk(sk), skb_dst(skb));
    iph->daddr    = (opt && opt->opt.srr) ? opt->opt.faddr : daddr;
    iph->saddr    = saddr;
    iph->protocol = sk->sk_protocol;
    if (ip_dont_fragment(sk, skb_dst(skb))) {
        iph->frag_off = htons(IP_DF);
        iph->id = 0;
    } else {
        iph->frag_off = 0;
        __ip_select_ident(net, iph, 1);
    }
    
    if (opt && opt->opt.optlen) {
        iph->ihl += opt->opt.optlen>>2;
        ip_options_build(skb, &opt->opt, daddr, skb_rtable(skb), 0);
    }
    
    iph->tot_len = htons(skb->len);
    ip_send_check(iph);
    skb->priority = priority;
    skb->mark = mark;
    
    /* Send it out. */
    return ip_local_out(net, sk, skb);
}

/**
 * ip_send_check - 计算IP头部校验和
 * @iph: IP头部指针
 * 
 * 计算并设置IP头部的校验和字段
 */
static inline void ip_send_check(struct iphdr *iph)
{
    iph->check = 0;
    iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
}

/**
 * ip_local_out - 本地IP数据包输出
 * @net: 网络命名空间
 * @sk: 套接字
 * @skb: 数据包
 * 
 * 本地生成的IP数据包输出处理
 * 返回值:处理结果
 */
int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    int err;
    
    err = __ip_local_out(net, sk, skb);
    if (likely(err == 1))
        err = dst_output(net, sk, skb);
    
    return err;
}

/**
 * __ip_local_out - 内部本地输出处理
 * @net: 网络命名空间
 * @sk: 套接字
 * @skb: 数据包
 * 
 * 执行netfilter LOCAL_OUT钩子
 * 返回值:1表示继续处理,其他值表示已处理
 */
int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    struct iphdr *iph = ip_hdr(skb);
    
    iph->tot_len = htons(skb->len);
    ip_send_check(iph);
    
    /* 如果启用了GSO并且数据包长度超过MTU,跳过netfilter */
    if (skb_is_gso(skb) || 
        ((ntohs(iph->tot_len) > skb_dst(skb)->dev->mtu) && !skb_is_gso(skb)))
        return ip_fragment(net, sk, skb, skb_dst(skb)->dev->mtu,
                          ip_finish_output);
    
    return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_LOCAL_OUT,
                       net, sk, skb, NULL, skb_dst(skb)->dev,
                       dst_output,
                       !(IPCB(skb)->flags & IPSKB_REROUTED));
}

3. IP分片与重组机制

3.1 IP分片处理

  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
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
 * ip_fragment - IPv4数据包分片
 * @net: 网络命名空间
 * @sk: 套接字
 * @skb: 要分片的数据包
 * @mtu: 最大传输单元
 * @output: 输出函数
 * 
 * 将大的IPv4数据包分片成符合MTU要求的小包
 * 返回值:成功返回0,失败返回负错误码
 */
int ip_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
               unsigned int mtu, int (*output)(struct net *, struct sock *,
                                              struct sk_buff *))
{
    struct iphdr *iph = ip_hdr(skb);
    
    if ((iph->frag_off & htons(IP_DF)) && !skb->ignore_df) {
        __IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
                 htonl(mtu));
        kfree_skb(skb);
        return -EMSGSIZE;
    }
    
    return ip_do_fragment(net, sk, skb, output);
}

/**
 * ip_do_fragment - 执行实际的分片操作
 * @net: 网络命名空间
 * @sk: 套接字
 * @skb: 要分片的数据包
 * @output: 输出函数
 * 
 * 实际执行IPv4数据包的分片操作
 * 返回值:成功返回0,失败返回负错误码
 */
static int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
                         int (*output)(struct net *, struct sock *, 
                                      struct sk_buff *))
{
    struct iphdr *iph;
    struct sk_buff *skb2;
    struct rtable *rt = skb_rtable(skb);
    unsigned int mtu, hlen, left, len, ll_rs;
    int offset;
    __be16 not_last_frag;
    int err = 0;
    
    iph = ip_hdr(skb);
    
    mtu = ip_skb_dst_mtu(sk, skb);
    if (IPCB(skb)->frag_max_size && IPCB(skb)->frag_max_size < mtu)
        mtu = IPCB(skb)->frag_max_size;
    
    /*
     * 设置变量。分片中的IP头部选项只出现在第一个分片中。
     */
    hlen = iph->ihl * 4;
    mtu = mtu - hlen;   /* 大小减去IP头部 */
    IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE;
    ll_rs = LL_RESERVED_SPACE(rt->dst.dev);
    
    /* 当分片时,我们必须确保没有丢失CHECKSUM_PARTIAL头部 */
    if (skb->ip_summed == CHECKSUM_PARTIAL) {
        int first_len = skb_pagelen(skb);
        
        if (first_len - hlen > mtu ||
            ((first_len - hlen) & 7) ||
            ip_is_fragment(iph) ||
            skb_cloned(skb) ||
            skb_headroom(skb) < ll_rs) {
            if (skb_checksum_help(skb))
                goto fail;
        }
    }
    
    /*
     * 分片点是在8字节边界上。
     */
    left = skb->len - hlen;     /* 空间减去IP头部 */
    
    /* for bridged IP traffic encapsulated inside f.e. a vlan header,
     * we need to make room for the encapsulating header
     */
    ll_rs = LL_RESERVED_SPACE_EXTRA(rt->dst.dev, ntohs(skb->protocol));
    
    ptr = raw = skb->data;
    ptr += hlen;
    
    /*
     * 分片循环
     */
    
    bytes = (mtu - hlen) & ~7;
    if (bytes < 8) {
        __IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
        goto fail;
    }
    
    offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;
    not_last_frag = iph->frag_off & htons(IP_MF);
    
    /*
     * 保持分片的DF标志。
     * 
     * 这看起来违反直觉,但这样做是对的。
     * 标志位置的含义:
     * 不要分片:如果一个分片数据包有DF设置,这意味着原始数据包也有DF设置。
     */
    
    /*
     * 如果我们无法分片一个数据包,清除它的分片标志。
     * [!] 实际上我们只对从netfilter传递过来的分片数据包才这样做。
     */
    
    while (left > 0) {
        len = left;
        /* IF: 它不适合,在8字节边界上使用'mtu'。 */
        if (len > mtu)
            len = mtu;
        /* IF: 这不是最后一个分片,我们必须在8字节边界上分割。 */
        if (len < left) {
            len &= ~7;
        }
        
        /* 分配并设置控制缓冲区 */
        if ((skb2 = alloc_skb(len + hlen + ll_rs, GFP_ATOMIC)) == NULL) {
            err = -ENOMEM;
            goto fail;
        }
        
        /*
         * 为分片留下空间。
         */
        skb_reserve(skb2, ll_rs);
        skb_put(skb2, len + hlen);
        skb_reset_network_header(skb2);
        skb2->transport_header = skb2->network_header + hlen;
        
        /*
         * 收费。
         */
        if (skb->sk)
            skb_set_owner_w(skb2, skb->sk);
        
        /*
         * 复制IP头部。
         */
        skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen);
        
        /*
         * 复制一个数据块。
         */
        if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len))
            BUG();
        left -= len;
        
        /*
         * 填写新分片的IP头部。
         */
        iph = ip_hdr(skb2);
        iph->frag_off = htons((offset >> 3));
        if (IPCB(skb)->flags & IPSKB_FRAG_PMTU)
            iph->frag_off |= htons(IP_DF);
        
        /* ANK: 分片的脏黑客。基本上,我们必须恢复原始头部中的frag_off位。 */
        if (offset == 0)
            iph->frag_off |= htons(IP_DF);
        
        /*
         * 如果我们正在分片一个分片,添加IP_MF标志。
         */
        if (left > 0 || not_last_frag)
            iph->frag_off |= htons(IP_MF);
        ptr += len;
        offset += len;
        
        /*
         * 给新分片加上长度和校验和。
         */
        iph->tot_len = htons(len + hlen);
        ip_send_check(iph);
        
        skb2->priority = skb->priority;
        skb2->dev = skb->dev;
        dst_set(&skb2->dst, dst_clone(&rt->dst));
        
        /* 输出分片 */
        err = output(net, sk, skb2);
        if (err)
            goto fail;
        
        __IP_INC_STATS(net, IPSTATS_MIB_FRAGCREATES);
    }
    consume_skb(skb);
    __IP_INC_STATS(net, IPSTATS_MIB_FRAGOKS);
    return err;
    
fail:
    kfree_skb(skb);
    __IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
    return err;
}

3.2 IP重组处理

  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
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/**
 * ip_defrag - IPv4分片重组
 * @net: 网络命名空间
 * @skb: 分片数据包
 * @user: 重组用户类型
 * 
 * 将IPv4分片数据包重组成完整的数据包
 * 返回值:成功返回重组后的数据包,失败返回NULL
 */
struct sk_buff *ip_defrag(struct net *net, struct sk_buff *skb, u32 user)
{
    struct net_device *dev = skb->dev ? : skb_dst(skb)->dev;
    int vif = l3mdev_master_ifindex_rcu(dev);
    struct ipq *qp;
    
    __IP_INC_STATS(net, IPSTATS_MIB_REASMREQDS);
    skb_orphan(skb);
    
    /* 查找或创建重组队列 */
    qp = ip_find(net, ip_hdr(skb), user, vif);
    if (qp) {
        int ret;
        
        spin_lock(&qp->q.lock);
        
        ret = ip_frag_queue(qp, skb);
        
        spin_unlock(&qp->q.lock);
        ipq_put(qp);
        return ret;
    }
    
    __IP_INC_STATS(net, IPSTATS_MIB_REASMFAILS);
    kfree_skb(skb);
    return NULL;
}

/**
 * ip_find - 查找或创建IP重组队列
 * @net: 网络命名空间
 * @iph: IP头部
 * @user: 用户类型
 * @vif: 虚拟接口
 * 
 * 根据IP头部信息查找现有的重组队列或创建新的
 * 返回值:重组队列指针或NULL
 */
static struct ipq *ip_find(struct net *net, struct iphdr *iph,
                          u32 user, int vif)
{
    struct frag_v4_compare_key key = {
        .saddr = iph->saddr,
        .daddr = iph->daddr,
        .user = user,
        .vif = vif,
        .id = iph->id,
        .protocol = iph->protocol,
    };
    struct inet_frag_queue *q;
    
    q = inet_frag_find(&net->ipv4.frags, &key);
    if (!q)
        return NULL;
    
    return container_of(q, struct ipq, q);
}

/**
 * ip_frag_queue - 将分片添加到重组队列
 * @qp: 重组队列
 * @skb: 分片数据包
 * 
 * 将接收到的IP分片添加到重组队列中
 * 返回值:重组结果
 */
static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
{
    struct net *net = container_of(qp->q.net, struct net, ipv4.frags);
    struct rb_node **rbn, *parent;
    struct sk_buff *skb1, *prev_tail;
    struct net_device *dev;
    unsigned int fragsize;
    int flags, offset;
    int ihl, end;
    int err = -ENOENT;
    u8 ecn;
    
    if (qp->q.flags & INET_FRAG_COMPLETE)
        goto err;
    
    if (!(IPCB(skb)->flags & IPSKB_FRAG_COMPLETE) &&
        unlikely(ip_frag_too_far(qp)) &&
        unlikely(err = ip_frag_reinit(qp))) {
        ipq_kill(qp);
        goto err;
    }
    
    ecn = ip4_frag_ecn(ip_hdr(skb)->tos);
    offset = ntohs(ip_hdr(skb)->frag_off);
    flags = offset & ~IP_OFFSET;
    offset &= IP_OFFSET;
    offset <<= 3;       /* offset is in 8-byte chunks */
    ihl = ip_hdrlen(skb);
    
    /* 确定分片在原始数据包中的位置。 */
    end = offset + skb->len - skb_network_offset(skb) - ihl;
    err = -EINVAL;
    
    /* 这是最后一个分片吗? */
    if ((flags & IP_MF) == 0) {
        /* 如果我们已经有最后一个分片的长度... */
        if (qp->q.flags & INET_FRAG_LAST_IN) {
            if (end != qp->q.len)
                goto err;
        } else {
            qp->q.flags |= INET_FRAG_LAST_IN;
            qp->q.len = end;
        }
    } else {
        if (end & 7) {
            end &= ~7;
            if (skb->ip_summed != CHECKSUM_UNNECESSARY)
                skb->ip_summed = CHECKSUM_NONE;
        }
        if (end > qp->q.len) {
            if (qp->q.flags & INET_FRAG_LAST_IN)
                goto err;
            qp->q.len = end;
        }
    }
    
    if (end == offset)
        goto err;
    
    err = -ENOMEM;
    if (!pskb_pull(skb, skb_network_offset(skb) + ihl))
        goto err;
    
    err = pskb_trim_rcsum(skb, end - offset);
    if (err)
        goto err;
    
    /* 注意: skb->sk可以在早期分裂中传递给我们 */
    skb->dev = dev;
    IPCB(skb)->ecn = ecn;
    
    /* 添加到队列按偏移顺序插入 */
    err = -ENOMEM;
    
    fragsize = skb->len + ihl;
    
    /* 更新统计信息 */
    qp->q.stamp = skb->tstamp;
    qp->q.meat += fragsize;
    qp->ecn |= ecn;
    add_frag_mem_limit(qp->q.net, fragsize);
    if (offset == 0)
        qp->q.flags |= INET_FRAG_FIRST_IN;
    
    fragsize -= ihl;
    if (fragsize > 0)
        add_frag_mem_limit(qp->q.net, fragsize);
    
    if (qp->q.flags == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
        qp->q.meat == qp->q.len) {
        unsigned long orefdst = skb->_skb_refdst;
        
        skb->_skb_refdst = 0UL;
        err = ip_frag_reasm(qp, skb, dev);
        skb->_skb_refdst = orefdst;
        if (err)
            inet_frag_kill(&qp->q);
        return err;
    }
    
    skb_dst_drop(skb);
    return -EINPROGRESS;
    
err:
    kfree_skb(skb);
    return err;
}

/**
 * ip_frag_reasm - 重组完整的IP数据包
 * @qp: 重组队列
 * @skb: 当前分片
 * @dev: 网络设备
 * 
 * 将所有分片重组成完整的IP数据包
 * 返回值:重组后的数据包
 */
static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct sk_buff *skb,
                                    struct net_device *dev)
{
    struct net *net = container_of(qp->q.net, struct net, ipv4.frags);
    struct iphdr *iph;
    struct sk_buff *fp, *head = skb_rb_first(&qp->q.rb_fragments);
    struct sk_buff **nextp;
    struct rb_node *rbn;
    int len;
    int ihlen;
    int err;
    
    ipq_kill(qp);
    
    BUG_ON(!head);
    BUG_ON(FRAG_CB(head)->offset != 0);
    
    /* 解决偏差问题。 */
    ihlen = ip_hdrlen(head);
    len = ihlen + qp->q.len;
    
    err = -E2BIG;
    if (len > 65535)
        goto out_oversize;
    
    /* 头部 + 分片 = 完整数据包 */
    fp = head;
    
    /* 确保我们有空间容纳完整数据包 */
    err = pskb_expand_head(head, 0, 0, GFP_ATOMIC);
    if (err)
        goto out_nomem;
    
    /* 将所有分片连接在一起 */
    nextp = &head->next;
    rbn = rb_next(&head->rbnode);
    rb_erase(&head->rbnode, &qp->q.rb_fragments);
    while (rbn || nextp != &head->next) {
        /* 获取下一个分片 */
        if (rbn) {
            struct sk_buff *skb2 = rb_to_skb(rbn);
            rb_erase(&skb2->rbnode, &qp->q.rb_fragments);
            rbn = rb_next(rbn);
            *nextp = skb2;
            nextp = &skb2->next;
            skb2->next = NULL;
            skb2->prev = NULL;
        }
    }
    
    sub_frag_mem_limit(qp->q.net, head->truesize);
    
    head->ignore_df |= qp->q.flags & INET_FRAG_COMPLETE;
    head->next = NULL;
    head->prev = NULL;
    head->dev = dev;
    head->tstamp = qp->q.stamp;
    IPCB(head)->frag_max_size = max(qp->max_orig_len, qp->q.max_size);
    
    iph = ip_hdr(head);
    iph->tot_len = htons(len);
    iph->tos |= qp->ecn;
    
    /* 当我们遇到中间分片,我们必须计算校验和增量。 */
    if (IPCB(head)->flags & IPSKB_FRAG_COMPLETE)
        skb_postpull_rcsum(head, skb_network_header(head), ihlen);
    
    qp->q.rb_fragments = RB_ROOT;
    qp->q.fragments_tail = NULL;
    qp->q.last_run_head = NULL;
    
    return head;
    
out_nomem:
    net_dbg_ratelimited("queue_glue: no memory for gluing queue %p\n", qp);
    err = -ENOMEM;
    goto out_fail;
out_oversize:
    net_info_ratelimited("Oversized IP packet from %pI4\n", &qp->saddr);
out_fail:
    __IP_INC_STATS(net, IPSTATS_MIB_REASMFAILS);
    return NULL;
}

4. 路由子系统详解

4.1 路由表结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * fib_table - IPv4转发信息库表
 */
struct fib_table {
    struct hlist_node   tb_hlist;   /* 哈希表节点 */
    u32                 tb_id;      /* 路由表ID */
    int                 tb_num_default; /* 默认路由数量 */
    struct rcu_head     rcu;        /* RCU头部 */
    unsigned long       *tb_data;   /* 路由表数据 */
};

4.2 路由查找算法

路由查找是网络层的核心功能,Linux使用LPC-trie算法实现高效的路由查找。

5. ICMP协议实现

ICMP协议用于在IP网络中发送控制消息和错误报告。

5.1 ICMP消息处理

 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
/**
 * icmp_send - 发送ICMP错误消息
 * @skb_in: 引发错误的数据包
 * @type: ICMP消息类型
 * @code: ICMP代码  
 * @info: 附加信息
 */
void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
                const struct ip_options *opt)
{
    struct iphdr *iph;
    struct icmp_bxm icmp_param;
    struct rtable *rt = skb_rtable(skb_in);
    struct net *net;
    
    if (!rt)
        goto out;
    net = dev_net(rt->dst.dev);
    
    /* 构造ICMP消息并发送 */
    icmp_param.data.icmph.type = type;
    icmp_param.data.icmph.code = code;
    icmp_param.data.icmph.un.gateway = info;
    
    icmp_push_reply(&icmp_param, &fl4, &ipc, &rt);
out:;
}

6. 性能优化要点

  • 路由缓存:缓存常用路由减少查找开销
  • 批量处理:批量处理数据包提高效率
  • 预分配内存:减少内存分配开销
  • CPU亲和性:绑定网络处理到特定CPU

创建时间: 2025年09月13日

本文为Linux网络栈源码分析系列的网络层深度解析

参考资料

  • Linux内核源码 (net/ipv4/目录)
  • RFC 791 - Internet Protocol
  • RFC 792 - Internet Control Message Protocol
  • 《Linux内核网络栈源代码情景分析》