概述
网络层是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头部结构
/**
* 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数据包接收处理
/**
* 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数据包发送处理
/**
* 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分片处理
/**
* 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重组处理
/**
* 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 路由表结构
/**
* 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消息处理
/**
* 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
7. 关键函数与调用链/时序图/结构体关系
7.1 关键函数核心代码与功能说明
/* 接收路径 */
int ip_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev);
static struct sk_buff *ip_rcv_core(struct sk_buff *skb, struct net *net);
static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb);
int ip_local_deliver(struct sk_buff *skb);
static int ip_local_deliver_finish(struct net *net, struct sock *sk,
struct sk_buff *skb);
/* 发送路径 */
int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl);
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);
int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb);
int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb);
/* 分片与重组 */
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 sk_buff *ip_defrag(struct net *net, struct sk_buff *skb, u32 user);
- 功能说明
ip_rcv/ip_rcv_core/ip_rcv_finish
:接收入口、合法性校验/ECN/长度/校验和、路由输入与下一步分发。ip_local_deliver/_finish
:本地交付路径,按协议inet_protos[protocol]
调度到 TCP/UDP/ICMP。ip_queue_xmit/ip_build_and_send_pkt
:构造或补全IP头、路由输出、PMTU/DF处理,交由ip_local_out
。ip_local_out/__ip_local_out
:LOCAL_OUT 钩子/分片/接力到dst_output
与设备层。ip_fragment/ip_defrag
:按MTU分片、在接收侧基于frag队列重组。
7.2 关键函数调用链
-
接收(本机)
- 驱动
poll
->napi_gro_receive
->netif_receive_skb
->ip_rcv
->NF_INET_PRE_ROUTING
->ip_rcv_finish
(route input) ->ip_local_deliver
->ip_local_deliver_finish
->tcp_v4_rcv
/__udp4_lib_rcv
/icmp_rcv
- 驱动
-
转发
ip_rcv
->PRE_ROUTING
->ip_rcv_finish
(route input) ->FORWARD
->dst_output
-> 设备发送
-
发送(本机)
tcp_transmit_skb
/udp_sendmsg
->ip_queue_xmit
->ip_local_out
->LOCAL_OUT
->POST_ROUTING
->dst_output
->dev_queue_xmit
-
分片/重组
- 发送分片:
__ip_local_out
检查大小 ->ip_fragment
/ip_do_fragment
->output
- 接收重组:
ip_local_deliver
检测ip_is_fragment
->ip_defrag
-> 重组完成后继续本地交付
- 发送分片:
7.3 网络层典型时序图(接收/发送/转发)
sequenceDiagram
participant L2 as L2
participant IP as IPv4输入
participant NF as Netfilter
participant RT as 路由
participant L4 as 传输层
L2->>IP: ip_rcv()
IP->>IP: ip_rcv_core()
IP->>NF: PRE_ROUTING
NF-->>IP: verdict(ACCEPT)
IP->>RT: ip_rcv_finish()->route input
alt 本机
IP->>IP: ip_local_deliver()
IP->>L4: ip_local_deliver_finish()
else 转发
IP->>NF: FORWARD
NF-->>IP: ACCEPT
IP->>L2: dst_output()
end
sequenceDiagram
participant L4 as 传输层
participant IP as IPv4输出
participant NF as Netfilter
participant L2 as 设备层
L4->>IP: ip_queue_xmit()/ip_build_and_send_pkt()
IP->>IP: ip_local_out()/__ip_local_out
IP->>NF: LOCAL_OUT/POST_ROUTING
NF-->>IP: ACCEPT
IP->>L2: dst_output()->dev_queue_xmit()
7.4 关键结构体关系图(路由/目的/协议分发)
classDiagram
class sk_buff {+protocol +dst +network_header +transport_header}
class dst_entry {+dev +ops}
class rtable {+dst}
class net_protocol {+handler +no_policy}
class fib_table {+tb_id +tb_data}
sk_buff --> dst_entry : skb_dst
rtable --> dst_entry : 组合
net_protocol <.. sk_buff : inet_protos[protocol]
fib_table <.. rtable : 查找来源