网络层具体就是IP协议层,处理发送和接收数据外,还需要进行转发和路由分组。在查找最佳路由并选择适当网卡的时候也会涉及对底层地址族的处理,例如MAC地址。
ip头数据结构定义在:include/uapi/linux/ip.h。
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;
#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;
__be32 daddr;
/*The options start here. */
};
1.1.1.1 接收
ipv4数据包的主接收方法是ip_rcv()函数,会检测报到类似如果是PACKET_OTHERHOST(定义在include/uapi/linux/if_packet.h文件中)则直接丢弃。检查是否是共享的包,如果是分享的包则克隆它。获取ip头数据结构,来操作相关协议事务。然后调用NK_HOOK,NF_HOOK定义在include/linux/netfilter.h文件中。
static inline int
NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb,
struct net_device *in, struct net_device *out,
int (*okfn)(struct net *, struct sock *, struct sk_buff *))
它是netfilter钩子函数,如果允许包传递则返回1。如果返回其他值说明这个包被hook给消耗掉了。
其中hook为NF_INET_PRE_ROUTING(定义在
include/uapi/linux/netfilter.h)
okfn指向ip_rcv_finish()函数。ip_rcv_finish()也定义在:net/ipv4/ip_input.c
在ip网络层中需要分段、重组的实现。须在路由选在子系统中查找,确定是发给当前主机还是转发。
如果是当前主机则依次调用方法ip_local_deliver()和ip_local_deliver_finish()函数。如果需要转发则调用ip_forward()函数。
1.1.1.1.1 分片合并
IP分组可能是分片的,可以通过ip_defrag重新组合分片分组的各个部分。代码流程如下图(图摘自《深入linux内核架构》):
内核在独立的缓存中管理原本属于一个分组的各个分片,该缓存称为分片缓存(fragment cache)。属于同一个分组的各个分片保存在一个独立的等待队列中,直至该分组的所有分片到达。
ip_frag_reasm将各个分片重新组合起来。
1.1.1.1.2 交付到传输层
如果数据是给本机的,那么返回到ip_local_deliver。调用netfilter挂钩NF_IP_LOCAL_IN恢复在ip_local_deliver_finish函数中的处理。
分组的协议标识符确定一个传输层的函数,分组将传递给该函数。每个协议都有一个net_protocol结构的实例。定义在:
include/net/protocol.h文件中
struct net_protocol {
int (*early_demux)(struct sk_buff *skb);
int (*early_demux_handler)(struct sk_buff *skb);
int (*handler)(struct sk_buff *skb);
void (*err_handler)(struct sk_buff *skb, u32 info);
unsigned int no_policy:1,
netns_ok:1,
/* does the protocol do more stringent
* icmp tag validation than simple
* socket lookup?
*/
icmp_strict_tag_validation:1;
};
1.1.1.1.3 分组转发
ip分组也可能转发到另一台计算机。这就需要调用ip_forward函数。
ip_forward使用NF_HOOK挂钩函数,挂钩编号为NF_INET_FORWARD,回调函数为ip_forward_finish。
1.1.1.2 发送
由ip_queue_xmit函数将数据包从L4移到L3
网络层要选择合适的网间路由和交换结点,确保数据及时传送。其主要任务包括 (1)路由处理,即选择下一跳 (2)添加 IP header(3)计算 IP header checksum,用于检测 IP 报文头部在传播过程中是否出错 (4)可能的话,进行 IP 分片(5)处理完毕,获取下一跳的 MAC 地址,设置链路层报文头,然后转入链路层处理。
发送和接收操作的流程并不总是分离的,如果分组只通过当前计算机转发,那么发送和接收操作是交织的。