上一章我们分析了ip_rcv,这个函数主要是对数据包做各种正确性验证,然后调用掉网络过滤子系统的在PRE_ROUTEING链上的回调函数,经过网络子系统的处理在调用ip_rec_finish,ip_rcv_finish主要的工作:
1)、确定数据包是前送还是在本机协议栈上传,如果是前送要确保输出网络设备和下一个接受栈的地址。
2)、解析和处理部分IP选项。
1、获取路由信息
skb->dst该数据域包含了如何到达目的地址的路由信息,如果该数据域是NULL,就通过路由子系统函数ip_route_input_noref路由,p_route_input_noref的输入参数有源IP地址、目的IP地址、服务类型、接受数据包的网络设备,根据这5个参数决策路由。
static int ip_rcv_finish(struct sk_buff *skb)
{
//获取IP协议头
const struct iphdr *iph = ip_hdr(skb);
struct rtable *rt;
//获取路由信息,如果skb->dst是空,调用ip_route_input_noref获取路由
if (skb_dst(skb) == NULL) {
int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
iph->tos, skb->dev);
if (unlikely(err)) {
if (err == -EHOSTUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),
IPSTATS_MIB_INADDRERRORS);
else if (err == -ENETUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),
IPSTATS_MIB_INNOROUTES);
goto drop;
}
}
...
2、更新路由流量控制系统QoS的统计信息
主要更新接受数据包的数量和接受数据包总的长度。
#ifdef CONFIG_NET_CLS_ROUTE
if (unlikely(skb_dst(skb)->tclassid)) {
struct ip_rt_acct *st = per_cpu_ptr(ip_rt_acct, smp_processor_id());
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;
}
#endif
3、IP选项处理
如果IP协议头的长度大于20字节(5*4),就表示有IP选项需要处理,IP选项处理的函数是ip_rcv_options,如果Socket Buffer有别的进程共享,首先要调用skb_cow拷贝一个,因为处理IP协议选项可能会修改IP协议头信息。
//ip长度大于5(20字节)需要处理IP选项
if (iph->ihl > 5 && ip_rcv_options(skb))
goto drop;
rt = skb_rtable(skb);
//统计各类数据包数量,组传送包、广播包
if (rt->rt_type == RTN_MULTICAST) {
IP_UPD_PO_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INMCAST,
skb->len);
} else if (rt->rt_type == RTN_BROADCAST)
IP_UPD_PO_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INBCAST,
skb->len);
//调用skb->dst->input指向的处理函数
return dst_input(skb);
4、函数接受处理
ip_rcv_finish的结束是调用了dst_input,实际是调用存放在skb->dst->input的数据域。该函数确定了下一步对数据包的处理,根据数据包的目的地地址,可能是往本地协议栈上传就调用 ip_local_deliver,如果是前送转发就调用ip_forward。
ip_rcv_finish完成代码:
static int ip_rcv_finish(struct sk_buff *skb)
{
//获取IP协议头
const struct iphdr *iph = ip_hdr(skb);
struct rtable *rt;
/*
* Initialise the virtual path cache for the packet. It describes
* how the packet travels inside Linux networking.
*/
//获取路由信息,如果skb->dst是空,调用ip_route_input_noref获取路由
if (skb_dst(skb) == NULL) {
int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
iph->tos, skb->dev);
if (unlikely(err)) {
if (err == -EHOSTUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),
IPSTATS_MIB_INADDRERRORS);
else if (err == -ENETUNREACH)
IP_INC_STATS_BH(dev_net(skb->dev),
IPSTATS_MIB_INNOROUTES);
goto drop;
}
}
#ifdef CONFIG_NET_CLS_ROUTE
if (unlikely(skb_dst(skb)->tclassid)) {
struct ip_rt_acct *st = per_cpu_ptr(ip_rt_acct, smp_processor_id());
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;
}
#endif
//ip长度大于5(20字节)需要处理IP选项
if (iph->ihl > 5 && ip_rcv_options(skb))
goto drop;
rt = skb_rtable(skb);
//统计各类数据包数量,组传送包、广播包
if (rt->rt_type == RTN_MULTICAST) {
IP_UPD_PO_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INMCAST,
skb->len);
} else if (rt->rt_type == RTN_BROADCAST)
IP_UPD_PO_STATS_BH(dev_net(rt->u.dst.dev), IPSTATS_MIB_INBCAST,
skb->len);
//调用skb->dst->input指向的处理函数
return dst_input(skb);
drop:
kfree_skb(skb);
return NET_RX_DROP;
}