本文分析基于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. 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. }

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

Linux内核--网络栈实现分析(五)--传输层之UDP协议(上)的更多相关文章

  1. Linux内核--网络栈实现分析(七)--数据包的传递过程(下)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7545855 更多请查看专栏,地 ...

  2. Linux内核--网络栈实现分析(二)--数据包的传递过程--转

    转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的”(上 ...

  3. Linux内核--网络栈实现分析(十一)--驱动程序层(下)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7555870 更多请查看专栏,地 ...

  4. Linux内核--网络栈实现分析(一)--网络栈初始化

    本文分析基于内核Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7488828 更多请看专栏, ...

  5. Linux内核--网络栈实现分析(一)--网络栈初始化--转

    转载地址 http://blog.csdn.net/yming0221/article/details/7488828 作者:闫明 本文分析基于内核Linux Kernel 1.2.13 以后的系列博 ...

  6. Linux内核--网络栈实现分析(三)--驱动程序层+链路层(上)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7497260 更多请看专栏,地址 ...

  7. Linux内核--网络栈实现分析(六)--应用层获取数据包(上)

    本文分析基于内核Linux 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7541907 更多请看专栏,地址http: ...

  8. Linux内核--网络栈实现分析(九)--传输层之UDP协议(下)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7549340 更多请查看专栏,地 ...

  9. Linux内核--网络栈实现分析(四)--网络层之IP协议(上)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7514017 更多请看专栏,地址 ...

随机推荐

  1. [刘阳Java]_快速搭建MyBatis环境_第2讲

    1.MyBatis的环境配置 导入MyBatis包, mybatis-3.2.8.jar 导入MySQL驱动包, mysql-connector-java-5.1.24-bin.jar 创建表的实体类 ...

  2. 分析‖为什么越来越多厂商开始发力VR一体机?

    2015年下半年,国内VR头显市场的主旋律还是PC头显和手机盒子.到了2016年上半年,一体机逐渐上位,成为发布会上的主角. 近期IDEALENS启视在北京召开发布会,发布会的主角K2和K2Pro正是 ...

  3. REDIS源码中一些值得学习的技术细节01

    redis.c/exitFromChild函数: void exitFromChild(int retcode) { #ifdef COVERAGE_TEST exit(retcode); #else ...

  4. android基于GPS实现定位操作

    一.定位的三种方式 1.wifi定位,ip地址定位,通过ip地址进行查询实际地址: 2.基站定位,信号塔,基站:手机通讯服务的设备 ,信号的格数决定了手机距离基站远近,精确度:几十米到几公里,精确度来 ...

  5. workflow createPath

    针对不同的流程,createpath不同,但是创建审批链,和创建表都有 1.GetUserInfoByListColumn 控件已创建的.先Rebuild,属性需要注意 2.CreateListIte ...

  6. c#遍历目录及子目录下某类11型的所有的文件

    DirectoryInfo directory = new DirectoryInfo("D:\\aa\\"); FileInfo[] files = directory.GetF ...

  7. 怎么用sublime text 3搭建python 的ide

    安装目录的Packages目录下的python文件夹下的Python.sublime-build复制以下内容,保存 {"cmd": ["python", &qu ...

  8. 16条Web2.0法则的编程思想

    1.在你开始之前,先定一个简单的目标.无论你是一个Web 2.0应用的创建者还是用户,请清晰的构思你的目标.就像“我需要保存一个书签”或者“我准 备帮助人们创建可编辑的.共享的页面”这样的目标,让你保 ...

  9. 用Backbone.js教程系列的链接

    整理了一下用Backbone.js系列教程链接. Backbone.js入门教程 用Backbone.js创建一个联系人管理系统(一) 用Backbone.js创建一个联系人管理系统(二) 用Back ...

  10. parseInt 和parseFloat 区别

    parseInt();返回整数, 有第二个参数,第二个参数是多少进制 parseFloat(); 可返回浮点数:没有第二个参数,默认10进制