//参考 深入理解linux网络技术内幕
// 注册l3协议
// ptype_all链表,链接所有ETH_P_ALL类型的l3协议
// ptype_base哈希表,非ETH_P_ALL类型的l3协议
// 注:l3协议可以使用相同的协议号
1.1 void dev_add_pack(struct packet_type *pt)
{
int hash;
//ptype_all ptype_base共用一把锁 ptype_lock
spin_lock_bh(&ptype_lock);
//ETH_P_ALL类型的l3协议,从外接收到的数据帧,和从本地发送的数据帧,都会向ptype_all链表中的l3协议,传递一份
if (pt->type == htons(ETH_P_ALL)) {
netdev_nit++;
list_add_rcu(&pt->list, &ptype_all);
} else {
hash = ntohs(pt->type) & 15;//ptype_base有16个bucket
list_add_rcu(&pt->list, &ptype_base[hash]);//链接到bucket的链表中
}
spin_unlock_bh(&ptype_lock);
}
// ip协议控制块
// 每个l3协议,通过struct packet_type描述自己
1.2 static struct packet_type ip_packet_type = {
.type = __constant_htons(ETH_P_IP),//.type使用网络字节序,大端模式
.func = ip_rcv,//ip接收函数
};
// ip协议数据接收
// 调用路径:netif_receive_skb->ip_rcv
// 函数主要任务:
// 1.使处理程序拥有独立的skb
// 2.检验ip数据包的有效性
// 3.处理l2填充
// ip数据包有效性:
// 1.报头长度
// 2.协议号
// 3.ip校验和
// 4.ip报文总长度
// 处理l2填充:
// 1.当skb中指示的报文总长度大于ip报头指示的数据包总长度时,说明发生了l2填充
// 2.以ip报头指示的数据包总长度为正确,去掉末尾多余的填充数据,同时使l4校验和失效
1.3 int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
struct iphdr *iph;
//skb->pkt_type由接收设备的驱动程序,调用eth_type_trans设定
//当设备处于混杂模式下,对于非本机,非广播,非多播的数据帧,设置其pkt_type=PACKET_OTHERHOST
//ip协议不处理l2地址非本机的封包,此类型的封包应该在ip协议之前被bonding,或者网桥来处理
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop;
//更新snmp统计信息
IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);
//如果skb引用计数不为1,则拷贝一份
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto out;
}
//使skb->data到skb->tail之间足够20字节的ip头(此时不包括选项字段)
if (!pskb_may_pull(skb, sizeof(struct iphdr)))
goto inhdr_error;
iph = skb->nh.iph;//ip头
//报文有效性检查
// 1.报头长度
// 2.协议号
// 3.ip校验和
// 4.ip报文总长度
if (iph->ihl < 5 || iph->version != 4)
goto inhdr_error;
//ihl以4字节为单位,表示ip头的长度
if (!pskb_may_pull(skb, iph->ihl*4))
goto inhdr_error;
//对ip头和选项(如果存在的话),计算校验和
if (ip_fast_csum((u8 *)iph, iph->ihl) != 0)
goto inhdr_error;
{
__u32 len = ntohs(iph->tot_len); //ip报文总长(包括ip报头,选项,ip有效载荷)
if (skb->len < len || len < (iph->ihl<<2))//skb中l3数据总长度小于接收到的长度,或者接收到的长度不足ip报头和选项的长度
goto inhdr_error;
//处理l2填充:
// 1.当skb中指示的报文总长度大于ip报头指示的数据包总长度时,说明发生了l2填充
// 2.以ip报头指示的数据包总长度为正确,去掉末尾多余的填充数据,同时使l4校验和失效
if (skb->len > len) {
__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钩子函数的处理,最终由ip_rcv_finish完成处理
inhdr_error:
IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
out:
return NET_RX_DROP;
}
// ip协议数据接收
// 函数主要任务:
// 1.路由数据包
// 2.如果ip报文存在选项,分析ip选项
// 3.处理源路由选项
// 4.根据路由子系统对skb->dst的设置,继续处理skb
1.4 static inline int ip_rcv_finish(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct iphdr *iph = skb->nh.iph;
if (skb->dst == NULL) {//目标路由未设置
if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))//输入路径上的路由,设置skb->dst
goto drop;
}
...
if (iph->ihl > 5) {//需要处理ip选项
struct ip_options *opt;
//当ip报头的长度大于20字节,说明有选项需要处理,此时通过skb_cow,如果缓存区和别人共享,就会做出该缓冲区的副本,因为对缓存区具有排他拥有权
if (skb_cow(skb, skb_headroom(skb))) {//是必要的,因为需要处理选项,就可能会修改ip报头,复制缓存区时,保证副本预留16字节的l2头
IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto drop;
}
iph = skb->nh.iph;
//在报文接收路径的多处,由不同函数对各个选项进行处理
if (ip_options_compile(NULL, skb))//此处分析ip选项,初始化skb->cb中ip_options结构,为后边处理ip选项做准备。
goto inhdr_error;
opt = &(IPCB(skb)->opt);
if (opt->srr) {//ip_options_compile分析ip报文中的选项,设置opt->srr为源路由选项相对于ip头的起始位置
struct in_device *in_dev = in_dev_get(dev);
if (in_dev) {
if (!IN_DEV_SOURCE_ROUTE(in_dev)) {//入口设备的inet配置不支持源路由选项
in_dev_put(in_dev);
goto drop;//直接丢弃封包
}
in_dev_put(in_dev);
}
if (ip_options_rcv_srr(skb))//处理源路由选项
goto drop;
}
}
//skb->dst->input处理,input函数指针由ip_route_input,根据目标地址,进行设置。
return dst_input(skb);
inhdr_error:
IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}