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;
}
}
可以看出,www.6688.cc该处理函数的处理过程很简单,就是一个无限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;
}

if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
TCP_INC_STATS_BH(TCP_MIB_INERRS);
inet_twsk_put((struct inet_timewait_sock *) sk);
goto discard_it;
}
switch (tcp_timewait_state_process((struct inet_timewait_sock *)sk,
skb, th)) {
case TCP_TW_SYN: {
struct sock *sk2 = inet_lookup_listener(&tcp_hashinfo,
skb->nh.iph->daddr,
ntohs(th->dest),
inet_iif(skb));
if (sk2) {
inet_twsk_deschedule((struct inet_timewait_sock *)sk,
&tcp_death_row);
inet_twsk_put((struct inet_timewait_sock *)sk);
sk = sk2;
goto process;
}
/* Fall through to ACK */
}
case TCP_TW_ACK:
tcp_v4_timewait_ack(sk, skb);
break;
case TCP_TW_RST:
goto no_tcp_socket;
case TCP_TW_SUCCESS:;
}
goto discard_it;
在上面的处理函数中发现,数据包通过一个名为tcp_v4_do_rcv()的函数传递了下去。下面跟踪这个函数,其原形如下:
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb);
函数处理过程如下:
由于caller在查找hash链表时,有可能返回established链表中条目信息,也可能返回listener链表中的条目信息,所以这里首先判断该数据报的链接状态是否为TCP_ESTABLISHED,若是则调用tcp_rcv_established(sk, skb, skb->h.th, skb->len)函数进行处理。为项目需要,这里不是跟踪的重点,暂时暂停,使执行流继续向下执行。其实这里我应经进行了详尽的跟踪,由于时间关系,这里就先不写了,若时间宽裕,再将其补充上,嘎嘎...
if (skb->len < (skb->h.th->doff << 2) || TCP数据包校验和错误) then
goto csum_err;
if 数据包状态为 TCP_LISTEN then
struct sock *nsk = tcp_v4_hnd_req(sk, skb);    //tcp三次握手请求处理函数
if (!nsk)                    //若nsk指针值为NULL,说明该三次握手过程没有成功,可能的原因是accept链接队列已满!
goto discard;

if (nsk != sk) {                //若由tcp_v4_hnd_req返回的hash表相应链表条目地址于caller传进来的条目地址信
//息不同的话,说明已通过inet_csk_clone()生成了新的struct sock,此时则进入
//tcp_child_process()处理函数
if (tcp_child_process(sk, nsk, skb))-->    //
goto reset;
return 0;
}
tcp_rcv_state_process(sk, skb, skb->h.th, skb->len);    //数据包状态处理函数
这里跟踪三次握手处理过程函数tcp_v4_hnd_req,其原型如下:
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb);

  • 1
  • 2
  • 下一页

相关内容