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

UDP协议的更多相关文章

  1. TODO:Golang语言TCP/UDP协议重用地址端口

    TODO:Golang语言TCP/UDP协议重用地址端口 这是一个简单的包来解决重用地址的问题. go net包(据我所知)不允许设置套接字选项. 这在尝试进行TCP NAT时尤其成问题,其需要在同一 ...

  2. 闲来无事,写个基于UDP协议的Socket通讯Demo

    项目一期已经做完,二期需求还没定稿,所以最近比较闲. 上一篇写的是TCP协议,今天写一下UDP协议.TCP是有连接协议,所以发送和接收消息前客户端和服务端需要建立连接:UDP是无连接协议,所以发送消息 ...

  3. UDP协议开发

    UDP是用户数据报协议(User Datagram Protocol,UDP)的简称,其主要作用是将网络数据流量压缩成数据报形式,提供面向事务的简单信息传送服务.与TCP协议不同,UDP协议直接利用I ...

  4. 基于UDP协议模拟的一个TCP协议传输系统

    TCP协议以可靠性出名,这其中包括三次握手建立连接,流控制和拥塞控制等技术.详细介绍如下: 1. TCP协议将需要发送的数据分割成数据块.数据块大小是通过MSS(maximum segment siz ...

  5. TCP协议与UDP协议的区别

    TCP协议与UDP协议的区别(转) 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UDP协议的区别,我觉得这是没有从本质上弄清楚网络通信! ...

  6. 采用UDP协议的PIC32MZ ethernet bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 经过千辛万苦,今天终于 ...

  7. 采用UDP协议实现PIC18F97J60 ethernet bootloader

    了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). TCP/IP Stac ...

  8. 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程

    Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...

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

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

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

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

随机推荐

  1. 洛谷P2678跳石头题解

    题目 这个题也是一个很经典的题了.其主要思想也是二分答案,原因就是题目中只要出现最大值最小或最小值最大,这种描述十有八九就是二分答案. 这个题原题也是让我们求最短的跳跃距离的最大值. 显而易见,最大值 ...

  2. Django+Xadmin打造在线教育系统(四)

    完成授课机构的功能 模板继承 在templates目录下,新建base.html,剪切org-list.html内容到里面 编写org-list.html内容 继承base.html,将里面的面包屑和 ...

  3. Navicat再次激活

    换了个新电脑,上一次激活用的注册机老被杀掉,defender什么的都关了,不知道是谁在暗中保护我的电脑.. 上个激活参考:https://www.cnblogs.com/MC-Curry/p/9765 ...

  4. MT【308】投影的定义

    已知向量$\overrightarrow{a},\overrightarrow{b}$满足:$|\overrightarrow{a}|=2$,向量$\overrightarrow{b}$与$\over ...

  5. Qt Creator 搭配Git 版本控制

    再次介绍一下Git的使用,这次是在Coding.net上部署项目的.这个是写给大作业合作的小伙伴们(我和我的A奶朋友们和某A的男朋友)看的. 安装Git 首先安装Git(msysGit) 下载地址 h ...

  6. 【BZOJ5285】[HNOI2018]寻宝游戏(神仙题)

    [BZOJ5285][HNOI2018]寻宝游戏(神仙题) 题面 BZOJ 洛谷 题解 既然是二进制按位的运算,显然按位考虑. 发现这样一个关系,如果是\(or\)的话,只要\(or\ 1\),那么无 ...

  7. IntelliJ IDEA快捷键总结

    原文: IntelliJ IDEA快捷键 下面只列出Windows系统下的快捷键,Mac下的快捷键参考上面的链接. Remember these Shortcuts 功能 快捷键 Smart code ...

  8. JavaWeb架构发展

    原文:JavaWeb项目为什么我们要放弃jsp?为什么要前后端解耦?为什么要前后端分离?2.0版,为分布式架构打基础 前戏 前后端分离已成为互联网项目开发的业界标准使用方式,通过Nginx + Tom ...

  9. 第二届强网杯wp

    web web签到 利用了md5碰撞 payload为 param1 =%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b% ...

  10. nginx常用配置

    nginx.conf配置文件详解 其主要分为几个模块 全局快 从开始到events块之间的一部分内容,其作用域为全局作用域 events块 主要负责Nginx服务器与用户的网络连接 常用设置: 是否开 ...