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

时间:2023-12-06 16:14:44

本文分析基于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协议数据报的报头定义如下:比较简单

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

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

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

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

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

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

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

下面是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. *  These are supposed to be switched.
  15. */
  16. skb->daddr = saddr;//设置目的地址为本地地址
  17. skb->saddr = daddr;//设置源地址为远端地址
  18. /*
  19. *  Charge it to the socket, dropping if the queue is full.
  20. */
  21. skb->len = len - sizeof(*uh);
  22. if (sock_queue_rcv_skb(sk,skb)<0) //调用sock_queu_rcv_skb()函数,将skb挂到sk接构中的接收队列中
  23. {
  24. udp_statistics.UdpInErrors++;
  25. ip_statistics.IpInDiscards++;
  26. ip_statistics.IpInDelivers--;
  27. skb->sk = NULL;
  28. kfree_skb(skb, FREE_WRITE);
  29. release_sock(sk);
  30. return(0);
  31. }
  32. udp_statistics.UdpInDatagrams++;
  33. release_sock(sk);
  34. return(0);
  35. }

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. int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
  6. {
  7. unsigned long flags;
  8. if(sk->rmem_alloc + skb->mem_len >= sk->rcvbuf)
  9. return -ENOMEM;
  10. save_flags(flags);
  11. cli();
  12. sk->rmem_alloc+=skb->mem_len;
  13. skb->sk=sk;
  14. restore_flags(flags);
  15. skb_queue_tail(&sk->receive_queue,skb);
  16. if(!sk->dead)
  17. sk->data_ready(sk,skb->len);
  18. return 0;
  19. }

这里就完成了数据包从网络层到传输层的传输。下面的博文将会分析数据包的从上到下的传输过程。