目录
12.8 网络层
12.8.1 IPv4
12.8.2 接受分组
12.8.3 交付到本地传输层
本专栏文章将有70篇左右,欢迎+关注,查看后续文章。
12.8 网络层
12.8.1 IPv4
IPv4首部:
version: 值为4。
IHL: IP header length。至少20字节。
Codepoint或Type of Service:ToS
Length: 首部 + 数据的总长度。
Fragment ID: 分片标识,一个分组的所有分片都相同,结合Fragment Offset来合并分片。
标志: 不同位代表不同特性。如:
DF位: 不可分片。
MF位: 有更多分片。
Fragment Offset: 该分片在分组的偏移。
TTL: 最大跳数。
Protocol: 传输层协议,如TCP or UDP。
IP头结构体
struct iphdr {
__u8 version:4,
__u8 ihl:4;
__u8 tos;
__be16 tot_len;
__be16 id;
__be16 frag_off;
__u8 ttl;
__u8 protocol;
__sum16 check;
__be32 saddr;
__be32 daddr;
};
注册IP协议
inet_init()
-> dev_add_pack( &ip_packet_type );
将IP协议注册到ptype_base[ ] 数组中。
struct packet_type ip_packet_type = {
.type = cpu_to_be16( ETH_P_IP ),
.func = ip_rcv, // 网络层入口
}
问:ip_rcv () 如何被调用?
答:1. 收包时,__netif_receive_skb_core () 中调用 deliver_ptype_list_skb ()
2. deliver_ptype_list_skb:
1. 遍历所有注册的网络层协议(packet_type)。
2. 比较 packet_type->type与 skb->protocol 是否相等。
3. 若相等,调用 packet_type->func(),即 ip_rcv。
12.8.2 接受分组
ip_rcv 收到分组后,流程:
struct rtable { // IPv4 路由决策信息。
struct dst_entry dst;
unsigned int rt_flags;
//值有RTCF_LOCAL、RTCF_MULTICAST
__u16 rt_type;
//表示路由类型。
举例:
RTN_UNICAST:单播路由。
RTN_LOCAL:本地路由(直接交付)。
int rt_genid;
// 路由表变化时,rt_genid会递增。
u32 rt_pmtu;
// pmtu值,即路径MTU。
__be32 rt_gateway;
// 下一跳
........
};
IPv4
struct rtable *rt;
本地接收单播:
rth->dst.input = ip_local_deliver;
转发单播:
rth->dst.input = ip_forward;
接收组播
rth->dst.input = ip_mr_input;
IPv6
struct rt6_info *rt;
本地接收:
rt->dst.input = ip6_input;
转发:
rt->dst.input = ip6_forward;
ip_rcv () 内容:
1. 检测报文首部校验,其他字段检查。
2. 调用 netfilter的 PRE_ROUTING钩子函数。
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
// 1. 各种错误检查
// 2. 执行netfilter的pre_route钩子函数
NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
}
ip_rcv_finish:
1. 根据 sk_buff 计算得到struct dst_entry *dst
2. 执行dst->input() ,即:
ip_local_deliver:本地接收。传给传输层协议。
或
ip_forward:转发。
12.8.3 交付到本地传输层
对应函数:
ip_local_deliver
int ip_local_deliver(struct sk_buff *skb)
{
if ( ip_is_fragment( ip_hdr(skb) ) ) {
if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER ))
return 0; // 分片合并失败,如分片还没全部收到。
}
return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
1. 分片重组合并
ip_defrag
分片缓存:
同一分组的各个分片缓存在一个独立的等待队列,直到所有分片到达。
ip_find ():
对分片ID,源目IP,协议类型等进行hash,以检查是否为该分组创建了等待队列。
1. 若没有,则创建新队列。
2. 否则,通过 ip_frag_queue () 将分组置于队列中。
ip_frag_reasm ():
实现合并分组动作。
若一个分组的分片没有全部到达,ip_defrag () 返回NULL,终止处理,等待分片到达。
2. 交付到传输层
ip_local_deliver () 分片合并后,调用:
NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish);
每个传输层协议,定义一个 struct net_protocol 实例
struct net_protocol {
void (*early_demux)(struct sk_buff *skb);
int (*handler)(struct sk_buff *skb);
void (*err_handler)(struct sk_buff *skb, u32 info);
unsigned int no_policy:1,
unsigned int icmp_strict_tag_validation:1;
};
struct net_protocol tcp_protocol = {
.handler = tcp_v4_rcv,
};
struct net_protocol udp_protocol = {
.handler = udp_rcv,
};
struct net_protocol icmp_protocol = {
.handler = icmp_rcv,
.err_handler = icmp_err, // 处理收到的ICMP差错消息。
}
注册传输层协议方法:
inet_add_protocol( &tcp_protocol, IPPROTO_TCP);
//添加到struct net_protocol *inet_protos[MAX_INET_PROTOS];
//数组索引由hash得到。
所以最终由 udp_rcv () ,tcp_v4_rcv () 等函数接收分组。