• Linux内核网络栈实现分析(五)传输层之UDP协议(上)


    本文分析基于Linux Kernel 1.2.13

    原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/7532512

    更多请看专栏,地址http://blog.csdn.net/column/details/linux-kernel-net.html

    作者:闫明

    注:标题中的”(上)“,”(下)“表示分析过程基于数据包的传递方向:”(上)“表示分析是从底层向上分析、”(下)“表示分析是从上向下分析。


    这里看看数据包从IP层是如何交给传输层来处理的,为了方便,这里传输层以UDP协议为例来分析。

    从ip_rcv()函数中可以看到

    1.       /* 
    2. * Pass on the datagram to each protocol that wants it, 
    3. * based on the datagram protocol.  We should really 
    4. * check the protocol handler's return values here... 
    5. */  
    6. ipprot->handler(skb2, dev, opts_p ? &opt : 0, iph->daddr,  
    7.         (ntohs(iph->tot_len) - (iph->ihl * 4)),  
    8.         iph->saddr, 0, ipprot);  

    这里调用指定协议的handler函数,如果是UDP协议,该函数的定义 udp_protocol如下

    1. static struct inet_protocol udp_protocol = {  
    2.   udp_rcv,      /* UDP handler      */  
    3.   NULL,         /* Will be UDP fraglist handler */  
    4.   udp_err,      /* UDP error control    */  
    5.   &tcp_protocol,    /* next         */  
    6.   IPPROTO_UDP,      /* protocol ID      */  
    7.   0,            /* copy         */  
    8.   NULL,         /* data         */  
    9.   "UDP"         /* name         */  
    10. };  

    先看UDP协议数据报的报头定义如下:比较简单


    1. struct udphdr {  
    2.   unsigned short    source;//源端口  
    3.   unsigned short    dest;//目的端口  
    4.   unsigned short    len;//数据包长度  
    5.   unsigned short    check;//检验和  
    6. };  


    下面就分析下udp_rcv()函数,流程图:



    1. /* 
    2.  *  All we need to do is get the socket, and then do a checksum.  
    3.  */  
    4.    
    5. int udp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,  
    6.     unsigned long daddr, unsigned short len,  
    7.     unsigned long saddr, int redo, struct inet_protocol *protocol)  
    8. {  
    9.     struct sock *sk;  
    10.     struct udphdr *uh;  
    11.     unsigned short ulen;  
    12.     int addr_type = IS_MYADDR;  
    13.       
    14.     if(!dev || dev->pa_addr!=daddr)//检查这个数据包是不是发送给本地的数据包  
    15.         addr_type=ip_chk_addr(daddr);//该函数定义在devinet.c中,用于检查ip地址是否是本地或多播、广播地址  
    16.           
    17.     /* 
    18.      *  Get the header. 
    19.      */  
    20.     uh = (struct udphdr *) skb->h.uh;//获得UDP数据报的报头  
    21.       
    22.     ip_statistics.IpInDelivers++;  
    23.   
    24.     /* 
    25.      *  Validate the packet and the UDP length. 
    26.      */  
    27.        
    28.     ulen = ntohs(uh->len);  
    29.     //参数len表示ip负载长度(IP数据报的数据部分长度)= UDP数据包头+UDP数据包的数据部分+填充部分长度  
    30.     //ulen表示的是UDP数据报首部和负载部分的长度,所以正常情况下len>=ulen  
    31.     if (ulen > len || len < sizeof(*uh) || ulen < sizeof(*uh))   
    32.     {  
    33.         printk("UDP: short packet: %d/%d\n", ulen, len);  
    34.         udp_statistics.UdpInErrors++;  
    35.         kfree_skb(skb, FREE_WRITE);  
    36.         return(0);  
    37.     }  
    38.   
    39.     if (uh->check && udp_check(uh, len, saddr, daddr)) //进行UDP数据包校验  
    40.     {  
    41.         /* <mea@utu.fi> wants to know, who sent it, to 
    42.            go and stomp on the garbage sender... */  
    43.         printk("UDP: bad checksum. From %08lX:%d to %08lX:%d ulen %d\n",  
    44.                ntohl(saddr),ntohs(uh->source),  
    45.                ntohl(daddr),ntohs(uh->dest),  
    46.                ulen);  
    47.         udp_statistics.UdpInErrors++;  
    48.         kfree_skb(skb, FREE_WRITE);  
    49.         return(0);  
    50.     }  
    51.   
    52.   
    53.     len=ulen;//对len赋值为实际的UDP数据报长度  
    54.   
    55. #ifdef CONFIG_IP_MULTICAST//对多播情况进行处理  
    56.     if (addr_type!=IS_MYADDR)  
    57.     {  
    58.         /* 
    59.          *  Multicasts and broadcasts go to each listener. 
    60.          */  
    61.         struct sock *sknext=NULL;//next指针  
    62.           
    63.         /*get_sock_mcast 获取在对应端口的多播套接字队列 
    64.         *下面函数的参数依次表示:sock结构指针,本地端口,远端地址,远端端口,本地地址 
    65.         */  
    66.           
    67.         sk=get_sock_mcast(udp_prot.sock_array[ntohs(uh->dest)&(SOCK_ARRAY_SIZE-1)], uh->dest,  
    68.                 saddr, uh->source, daddr);  
    69.         if(sk)  
    70.         {         
    71.             do  
    72.             {  
    73.                 struct sk_buff *skb1;  
    74.   
    75.                 sknext=get_sock_mcast(sk->next, uh->dest, saddr, uh->source, daddr);//下一个满足条件的套接字  
    76.                 if(sknext)  
    77.                     skb1=skb_clone(skb,GFP_ATOMIC);  
    78.                 else  
    79.                     skb1=skb;  
    80.                 if(skb1)  
    81.                     udp_deliver(sk, uh, skb1, dev,saddr,daddr,len);//对满足条件的套接字调用发送函数发送  
    82.                 sk=sknext;  
    83.             }  
    84.             while(sknext!=NULL);  
    85.         }  
    86.         else  
    87.             kfree_skb(skb, FREE_READ);  
    88.         return 0;  
    89.     }     
    90. #endif  
    91.     sk = get_sock(&udp_prot, uh->dest, saddr, uh->source, daddr);  
    92.     if (sk == NULL) //没有找到本地对应的套接字,则进行出错处理  
    93.     {  
    94.         udp_statistics.UdpNoPorts++;  
    95.         if (addr_type == IS_MYADDR)   
    96.         {  
    97.             icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0, dev);//回复ICMP出错报文,目的主机不可达  
    98.         }  
    99.         /* 
    100.          * Hmm.  We got an UDP broadcast to a port to which we 
    101.          * don't wanna listen.  Ignore it. 
    102.          */  
    103.         skb->sk = NULL;  
    104.         kfree_skb(skb, FREE_WRITE);  
    105.         return(0);  
    106.     }  
    107.   
    108.     return udp_deliver(sk,uh,skb,dev, saddr, daddr, len);//调用函数发送套接字  
    109. }  

    上面函数中调用了get_sock_mcast()函数,下面具体分析一下该函数的功能,该函数定义的位置在文件af_inet.c中

    1. /* 
    2.  *  Deliver a datagram to broadcast/multicast sockets. 
    3.  */  
    4.    
    5. struct sock *get_sock_mcast(struct sock *sk, //套接字指针  
    6.                 unsigned short num,//本地端口  
    7.                 unsigned long raddr,//远端地址  
    8.                 unsigned short rnum,//远端端口  
    9.                 unsigned long laddr)//本地地址   
    10. {  
    11.     struct sock *s;  
    12.     unsigned short hnum;  
    13.   
    14.     hnum = ntohs(num);  
    15.   
    16.     /* 
    17.      * SOCK_ARRAY_SIZE must be a power of two.  This will work better 
    18.      * than a prime unless 3 or more sockets end up using the same 
    19.      * array entry.  This should not be a problem because most 
    20.      * well known sockets don't overlap that much, and for 
    21.      * the other ones, we can just be careful about picking our 
    22.      * socket number when we choose an arbitrary one. 
    23.      */  
    24.       
    25.     s=sk;  
    26.   
    27.     for(; s != NULL; s = s->next)   
    28.     {  
    29.         if (s->num != hnum) //本地端口不符合,跳过  
    30.             continue;  
    31.         if(s->dead && (s->state == TCP_CLOSE))//dead=1表示该sock结构已经处于释放状态  
    32.             continue;  
    33.         if(s->daddr && s->daddr!=raddr)//sock的远端地址不等于条件中的远端地址  
    34.             continue;  
    35.         if (s->dummy_th.dest != rnum && s->dummy_th.dest != 0)  
    36.             continue;  
    37.         if(s->saddr  && s->saddr!=laddr)//sock的本地地址不等于条件的本地地址  
    38.             continue;  
    39.         return(s);  
    40.     }  
    41.     return(NULL);  
    42. }  

    下面是udp_rcv调用的udp_deliver()函数

    1. static int udp_deliver(struct sock *sk,//sock结构指针  
    2.                         struct udphdr *uh,//UDP头指针  
    3.                         struct sk_buff *skb,//sk_buff  
    4.                         struct device *dev,//接收的网络设备  
    5.                         long saddr,//本地地址  
    6.                         long daddr,//远端地址  
    7.                         int len)//数据包的长度  
    8. {  
    9.     //对skb结构相应字段赋值  
    10.     skb->sk = sk;  
    11.     skb->dev = dev;  
    12.     //skb->len = len;  
    13.   
    14.     /* 
    15.      *  These are supposed to be switched.  
    16.      */  
    17.        
    18.     skb->daddr = saddr;//设置目的地址为本地地址  
    19.     skb->saddr = daddr;//设置源地址为远端地址  
    20.   
    21.   
    22.     /* 
    23.      *  Charge it to the socket, dropping if the queue is full. 
    24.      */  
    25.   
    26.     skb->len = len - sizeof(*uh);    
    27.        
    28.     if (sock_queue_rcv_skb(sk,skb)<0) //调用sock_queu_rcv_skb()函数,将skb挂到sk接构中的接收队列中  
    29.     {  
    30.         udp_statistics.UdpInErrors++;  
    31.         ip_statistics.IpInDiscards++;  
    32.         ip_statistics.IpInDelivers--;  
    33.         skb->sk = NULL;  
    34.         kfree_skb(skb, FREE_WRITE);  
    35.         release_sock(sk);  
    36.         return(0);  
    37.     }  
    38.     udp_statistics.UdpInDatagrams++;  
    39.     release_sock(sk);  
    40.     return(0);  
    41. }  
    sock_queu_rcv_skb()函数的实现如下:

    1. /* 
    2.  *  Queue a received datagram if it will fit. Stream and sequenced protocols 
    3.  *  can't normally use this as they need to fit buffers in and play with them. 
    4.  */  
    5.   
    6. int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)  
    7. {  
    8.     unsigned long flags;  
    9.     if(sk->rmem_alloc + skb->mem_len >= sk->rcvbuf)  
    10.         return -ENOMEM;  
    11.     save_flags(flags);  
    12.     cli();  
    13.     sk->rmem_alloc+=skb->mem_len;  
    14.     skb->sk=sk;  
    15.     restore_flags(flags);  
    16.     skb_queue_tail(&sk->receive_queue,skb);  
    17.     if(!sk->dead)  
    18.         sk->data_ready(sk,skb->len);  
    19.     return 0;  
    20. }  

    这里就完成了数据包从网络层到传输层的传输。下面的博文将会分析数据包的从上到下的传输过程。
  • 相关阅读:
    C# 编译机器码过程原理之再谈反射
    百度Echarts中国地图经纬度
    网页客服思路以及QQ截图粘贴到聊天框功能
    Linux查看CPU和内存使用情况
    Java 打包方式
    电商系统 常用代码 MyBatis-Plus
    Java cnpm install 没有反应
    Java 项目无法运行 解决
    电商系统 常用代码 VUE
    电商系统 常用代码段 Element-ui
  • 原文地址:https://www.cnblogs.com/wangfengju/p/6173202.html
Copyright © 2020-2023  润新知