Ubuntu 2.6.18 内核数据包游历过程分析

从NIC上获得数据包首先进入处理函数ip_rcv(),其原形如下:
/*
*    Main IP Receive routine
*/
int ip_rcv(struct sk_buff * skb,struct net_device *dev,struct packet_type *pt,struct net_device *orig_dev);
本函数处理过程如下:
if 数据包是其他主机的 then
直接丢弃;
if 该数据包已被其他人引用,则clone一份。如果clone不成功 then
直接丢弃;
if 头部长度<5 || 版本不是4 then
直接丢弃;
if IP头部校验和错误 then
直接丢弃;
清除套接字数据包中的cb成员中的标志信息;
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,ip_rcv_finish);
可以看到,从NIC接收到的数据包,会在钓鱼点NF_IP_PRE_ROUTING被转送至ip_rcv_finish()处理函数,其原形如下:
static inline int ip_rcv_finish(struct sk_buff *skb);
本函数处理过程如下:
首先为该数据包初始化虚路缓存(virtual path cache),它描述了该数据包在linux networking中的游历过程;
调用ip_route_input()函数进行路由检索;    //路由表是一个大的hash表,在该函数内,首先计算hash值并定位到相应的桶中进行路由检索
//        if 检索成功 then
//            直接返回;
//        if 目的地址为多播地址 then
//            检索MAC-address,获取相对应IP地址
//                return ip_route_input_mc(skb, daddr, saddr,tos, dev, our);
//        return ip_route_input_slow(skb, daddr, saddr, tos, dev);
return dst_input(skb);

2010-1-14
可以看出,数据包又被传送至dst_input()函数,其原形如下:
/* Input packet from network to transport.  */
static inline int dst_input(struct sk_buff *skb)
{              
int err;

for (;;) {
err = skb->dst->input(skb);

if (likely(err == 0))
return err;
/* Oh, Jamal... Seems, I will not forgive you this mess. :-) */
if (unlikely(err != NET_XMIT_BYPASS))
return err;
}
}
可以看出,该处理函数的处理过程很简单,就是一个无限for循环,不断的将数据包传送至函数指针input所指向的处理函数。跟踪可以发现此时的处理函数是ip_forward()、ip_local_deliver()。至于这里是如何确定将数据包传送给哪一个处理函数,暂时先不深究。为了项目需要,这里仅跟踪ip_local_deliver()处理函数。其定义如下:(若时间宽裕,再去跟踪ip_forward())
/*
*      Deliver IP Packets to the higher protocol layers.
*/
int ip_local_deliver(struct sk_buff *skb)
{
/*
*      Reassemble IP fragments.
*/

if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER);
if (!skb)
return 0;
}

return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
}
可以看出,在该函数中首先判断该数据包是否为其中一个分片(fragment),若是则调用函数ip_defrag()进行分片重组,否则通过钩子函数NF_HOOK()在钓鱼点NF_IP_LOCAL_IN上,将数据包传送至ip_local_deliver_finish()处理函数。该函数定义如下:
static inline int ip_local_deliver_finish(struct sk_buff *skb);
处理过程如下:
首先通过如下语句:skb->h.raw = skb->data;指向数据包的数据部分;
取出协议hash链表中struct sock结构链表首地址;
如果是原始套接字数据流,则调用raw_v4_input()函数通过如下过程:raw_rcv->raw_rcv_skb->sock_queue_rcv_skb->'skb->rcvbuf'从接受缓冲区中读取数据包,并进行相应处理。否则,(struct sock *)raw_sk = NULL;
if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL)    then //这里就是取出相应协议hash链表的地址
int ret = ipprot->handler(skb);//调用handler进行数据包处理
这里handler是函数指针,它定义在struct net_protocol中,此时指向的处理函数是:tcp_v4_rcv
(在文件af_inet.c中进行了如下初始化工作:
static struct net_protocol tcp_protocol = {
.handler =    tcp_v4_rcv,
.err_handler =    tcp_v4_err,
.gso_send_check = tcp_v4_gso_send_check,
.gso_segment =    tcp_tso_segment,
.no_policy =    1,
};)
函数tcp_v4_rcv原型为:
/*
*      From tcp_input.c
*/

int tcp_v4_rcv(struct sk_buff *skb);
函数处理过程如下:
if 本机的回环数据包 then
直接丢掉;
if tcp头部错误 then
释放数据包所占的page,并丢弃该数据报;
if skb还没有校验 then
调用tcp_v4_checksum_init(skb)进行校验;
初始化skb->cb中的标志控制信息;//这些标志信息只在数据包在内核游历时起作用,并且此刻起,他会一直存在skb之中,当然中间过程可能会对其进行修改
调用sk=__inet_lookup()首先通过函数__inet_lookup_established()查找已经建立链接的hash表,若找到则直接返回该链接条目信息地址;否则调用inet_lookup_listener()查找监听hash链表,若成功则返回该条目信息地址,否则返回NULL;
if (!sk)
goto no_tcp_socket;
process:
检查数据包状态(超时?)、重置netfilter过滤规则、匹配netfilter规则链;
(执行到这里,说明已经找到了相对应的套接口(established?listener?)。后续处理过程,见如下代码:)
bh_lock_sock_nested(sk);
ret = 0;
if (!sock_owned_by_user(sk)) {
#ifdef CONFIG_NET_DMA
struct tcp_sock *tp = tcp_sk(sk);
if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
tp->ucopy.dma_chan = get_softnet_dma();
if (tp->ucopy.dma_chan)
ret = tcp_v4_do_rcv(sk, skb);
else
#endif
{
if (!tcp_prequeue(sk, skb))
ret = tcp_v4_do_rcv(sk, skb);
}
} else
sk_add_backlog(sk, skb);
bh_unlock_sock(sk);
sock_put(sk);

return ret;

no_tcp_socket:
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
goto discard_it;

if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
bad_packet:
TCP_INC_STATS_BH(TCP_MIB_INERRS);
} else {
tcp_v4_send_reset(skb);
}

discard_it:
/* Discard frame. */
kfree_skb(skb);
return 0;
discard_and_relse:
sock_put(sk);
goto discard_it;

do_time_wait:
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
inet_twsk_put((struct inet_timewait_sock *) sk);
goto discard_it;
}

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wwpdjj.html