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()函数中可以看到
- /*
- * Pass on the datagram to each protocol that wants it,
- * based on the datagram protocol. We should really
- * check the protocol handler's return values here...
- */
- ipprot->handler(skb2, dev, opts_p ? &opt : 0, iph->daddr,
- (ntohs(iph->tot_len) - (iph->ihl * 4)),
- iph->saddr, 0, ipprot);
这里调用指定协议的handler函数,如果是UDP协议,该函数的定义 udp_protocol如下
- static struct inet_protocol udp_protocol = {
- udp_rcv, /* UDP handler */
- NULL, /* Will be UDP fraglist handler */
- udp_err, /* UDP error control */
- &tcp_protocol, /* next */
- IPPROTO_UDP, /* protocol ID */
- 0, /* copy */
- NULL, /* data */
- "UDP" /* name */
- };
先看UDP协议数据报的报头定义如下:比较简单

- struct udphdr {
- unsigned short source;//源端口
- unsigned short dest;//目的端口
- unsigned short len;//数据包长度
- unsigned short check;//检验和
- };
下面就分析下udp_rcv()函数,流程图:

- /*
- * All we need to do is get the socket, and then do a checksum.
- */
- int udp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
- unsigned long daddr, unsigned short len,
- unsigned long saddr, int redo, struct inet_protocol *protocol)
- {
- struct sock *sk;
- struct udphdr *uh;
- unsigned short ulen;
- int addr_type = IS_MYADDR;
- if(!dev || dev->pa_addr!=daddr)//检查这个数据包是不是发送给本地的数据包
- addr_type=ip_chk_addr(daddr);//该函数定义在devinet.c中,用于检查ip地址是否是本地或多播、广播地址
- /*
- * Get the header.
- */
- uh = (struct udphdr *) skb->h.uh;//获得UDP数据报的报头
- ip_statistics.IpInDelivers++;
- /*
- * Validate the packet and the UDP length.
- */
- ulen = ntohs(uh->len);
- //参数len表示ip负载长度(IP数据报的数据部分长度)= UDP数据包头+UDP数据包的数据部分+填充部分长度
- //ulen表示的是UDP数据报首部和负载部分的长度,所以正常情况下len>=ulen
- if (ulen > len || len < sizeof(*uh) || ulen < sizeof(*uh))
- {
- printk("UDP: short packet: %d/%d\n", ulen, len);
- udp_statistics.UdpInErrors++;
- kfree_skb(skb, FREE_WRITE);
- return(0);
- }
- if (uh->check && udp_check(uh, len, saddr, daddr)) //进行UDP数据包校验
- {
- /* <mea@utu.fi> wants to know, who sent it, to
- go and stomp on the garbage sender... */
- printk("UDP: bad checksum. From %08lX:%d to %08lX:%d ulen %d\n",
- ntohl(saddr),ntohs(uh->source),
- ntohl(daddr),ntohs(uh->dest),
- ulen);
- udp_statistics.UdpInErrors++;
- kfree_skb(skb, FREE_WRITE);
- return(0);
- }
- len=ulen;//对len赋值为实际的UDP数据报长度
- #ifdef CONFIG_IP_MULTICAST//对多播情况进行处理
- if (addr_type!=IS_MYADDR)
- {
- /*
- * Multicasts and broadcasts go to each listener.
- */
- struct sock *sknext=NULL;//next指针
- /*get_sock_mcast 获取在对应端口的多播套接字队列
- *下面函数的参数依次表示:sock结构指针,本地端口,远端地址,远端端口,本地地址
- */
- sk=get_sock_mcast(udp_prot.sock_array[ntohs(uh->dest)&(SOCK_ARRAY_SIZE-1)], uh->dest,
- saddr, uh->source, daddr);
- if(sk)
- {
- do
- {
- struct sk_buff *skb1;
- sknext=get_sock_mcast(sk->next, uh->dest, saddr, uh->source, daddr);//下一个满足条件的套接字
- if(sknext)
- skb1=skb_clone(skb,GFP_ATOMIC);
- else
- skb1=skb;
- if(skb1)
- udp_deliver(sk, uh, skb1, dev,saddr,daddr,len);//对满足条件的套接字调用发送函数发送
- sk=sknext;
- }
- while(sknext!=NULL);
- }
- else
- kfree_skb(skb, FREE_READ);
- return 0;
- }
- #endif
- sk = get_sock(&udp_prot, uh->dest, saddr, uh->source, daddr);
- if (sk == NULL) //没有找到本地对应的套接字,则进行出错处理
- {
- udp_statistics.UdpNoPorts++;
- if (addr_type == IS_MYADDR)
- {
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0, dev);//回复ICMP出错报文,目的主机不可达
- }
- /*
- * Hmm. We got an UDP broadcast to a port to which we
- * don't wanna listen. Ignore it.
- */
- skb->sk = NULL;
- kfree_skb(skb, FREE_WRITE);
- return(0);
- }
- return udp_deliver(sk,uh,skb,dev, saddr, daddr, len);//调用函数发送套接字
- }
上面函数中调用了get_sock_mcast()函数,下面具体分析一下该函数的功能,该函数定义的位置在文件af_inet.c中
- /*
- * Deliver a datagram to broadcast/multicast sockets.
- */
- struct sock *get_sock_mcast(struct sock *sk, //套接字指针
- unsigned short num,//本地端口
- unsigned long raddr,//远端地址
- unsigned short rnum,//远端端口
- unsigned long laddr)//本地地址
- {
- struct sock *s;
- unsigned short hnum;
- hnum = ntohs(num);
- /*
- * SOCK_ARRAY_SIZE must be a power of two. This will work better
- * than a prime unless 3 or more sockets end up using the same
- * array entry. This should not be a problem because most
- * well known sockets don't overlap that much, and for
- * the other ones, we can just be careful about picking our
- * socket number when we choose an arbitrary one.
- */
- s=sk;
- for(; s != NULL; s = s->next)
- {
- if (s->num != hnum) //本地端口不符合,跳过
- continue;
- if(s->dead && (s->state == TCP_CLOSE))//dead=1表示该sock结构已经处于释放状态
- continue;
- if(s->daddr && s->daddr!=raddr)//sock的远端地址不等于条件中的远端地址
- continue;
- if (s->dummy_th.dest != rnum && s->dummy_th.dest != 0)
- continue;
- if(s->saddr && s->saddr!=laddr)//sock的本地地址不等于条件的本地地址
- continue;
- return(s);
- }
- return(NULL);
- }
下面是udp_rcv调用的udp_deliver()函数
- static int udp_deliver(struct sock *sk,//sock结构指针
- struct udphdr *uh,//UDP头指针
- struct sk_buff *skb,//sk_buff
- struct device *dev,//接收的网络设备
- long saddr,//本地地址
- long daddr,//远端地址
- int len)//数据包的长度
- {
- //对skb结构相应字段赋值
- skb->sk = sk;
- skb->dev = dev;
- //skb->len = len;
- /*
- * These are supposed to be switched.
- */
- skb->daddr = saddr;//设置目的地址为本地地址
- skb->saddr = daddr;//设置源地址为远端地址
- /*
- * Charge it to the socket, dropping if the queue is full.
- */
- skb->len = len - sizeof(*uh);
- if (sock_queue_rcv_skb(sk,skb)<0) //调用sock_queu_rcv_skb()函数,将skb挂到sk接构中的接收队列中
- {
- udp_statistics.UdpInErrors++;
- ip_statistics.IpInDiscards++;
- ip_statistics.IpInDelivers--;
- skb->sk = NULL;
- kfree_skb(skb, FREE_WRITE);
- release_sock(sk);
- return(0);
- }
- udp_statistics.UdpInDatagrams++;
- release_sock(sk);
- return(0);
- }
sock_queu_rcv_skb()函数的实现如下:
- /*
- * Queue a received datagram if it will fit. Stream and sequenced protocols
- * can't normally use this as they need to fit buffers in and play with them.
- */
- int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
- {
- unsigned long flags;
- if(sk->rmem_alloc + skb->mem_len >= sk->rcvbuf)
- return -ENOMEM;
- save_flags(flags);
- cli();
- sk->rmem_alloc+=skb->mem_len;
- skb->sk=sk;
- restore_flags(flags);
- skb_queue_tail(&sk->receive_queue,skb);
- if(!sk->dead)
- sk->data_ready(sk,skb->len);
- return 0;
- }
这里就完成了数据包从网络层到传输层的传输。下面的博文将会分析数据包的从上到下的传输过程。
Linux内核--网络栈实现分析(五)--传输层之UDP协议(上)的更多相关文章
- Linux内核--网络栈实现分析(七)--数据包的传递过程(下)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7545855 更多请查看专栏,地 ...
- Linux内核--网络栈实现分析(二)--数据包的传递过程--转
转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的”(上 ...
- Linux内核--网络栈实现分析(十一)--驱动程序层(下)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7555870 更多请查看专栏,地 ...
- Linux内核--网络栈实现分析(一)--网络栈初始化
本文分析基于内核Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7488828 更多请看专栏, ...
- Linux内核--网络栈实现分析(一)--网络栈初始化--转
转载地址 http://blog.csdn.net/yming0221/article/details/7488828 作者:闫明 本文分析基于内核Linux Kernel 1.2.13 以后的系列博 ...
- Linux内核--网络栈实现分析(三)--驱动程序层+链路层(上)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7497260 更多请看专栏,地址 ...
- Linux内核--网络栈实现分析(六)--应用层获取数据包(上)
本文分析基于内核Linux 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7541907 更多请看专栏,地址http: ...
- Linux内核--网络栈实现分析(九)--传输层之UDP协议(下)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7549340 更多请查看专栏,地 ...
- Linux内核--网络栈实现分析(四)--网络层之IP协议(上)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7514017 更多请看专栏,地址 ...
随机推荐
- js 让浏览器全屏模式的方法launchFullscreen
浏览器全屏模式的启动函数requestFullscreen仍然需要附带各浏览器的js方言前缀 // 判断各种浏览器,找到正确的方法 function launchFullscreen(element) ...
- grunt 基本使用使用(一)。
使用grunt 之前,需要做一些基本工作. 1.在E盘 新建空文件夹 grunt. 2.在grunt目录下新建package.json 文件,用了存储 npm模块的依赖项.基本依赖块代码如下: { & ...
- JAVA面向对象初步知识总结:封装、继承、多态
1.封装 把数据和方法包装进类中,以及具体实现的隐藏,常共同被称作是是封装.其结果是一个同时带有特征和行为的数据类型.所谓具体实现的隐藏是通过访问权限控制实现的.JAVA 子类重写继承的方法时,不可以 ...
- 金蝶K/3 Cloud 界面解析过程
服务端 目前也就是iis服务器生成Json描述返回给不同的展现端最解析. 不同的展现端,可以有Silverlight.WPF.Html5.Winform 当然还有IOS和Android端做解析展现 对 ...
- 关于安装sql2012出现的netfx3功能问题
这个问题需要下载framework3.5即可继续正常安装,所以说低版本的framework也是有必要安装的
- 搭建调用 WebService 的 ASP.NET 网站 (VS2010, C#)
[系统环境]Windows 7 / 2008r2 [软件环境]Visual Studio 2010 [开发语言]C# [感谢]本文是在 <C#开发和调用Web Service> 一文的基础 ...
- 《笨办法学C》笔记之基础语法
这篇笔记不怎么系统,只记录自己比较生疏的知识 变量定义 类型 定义 格式化符号 备注 整数 int %d 长整型 long %ld 单精度浮点 float %f 双精度浮点 double %f 字符 ...
- 韩服LOL
※◆☆★☆◆※欢迎使用韩服LOL辅助,如有疑问请联系作者QQ:82850696*2*测试版已停用*1*2014-8-27 14:05:59*哈密*E2873D0137C6D04F42E088AA46E ...
- 如何用Tacker将NFV带入OpenStack?
最初社区里很多人争论过NFV是否属于OpenStack,而后来可以确定的是OpenStack的确占据了NFV会话中的很大一部分,并且形象地反映在了下面的ETSI MANO概念架构图中,OpenStac ...
- 关于Linux 下 Mysql 远程访问时出现的Access denied for user '用户名'@'IP地址' (using password:NO)
大概是因为MySQL不允许远程访问时候不带密码吧,所以还是设定一个密码 如下这样做: 打开终端 ,即terminal的那个(RedHat5.x为例 在左上角(可能会移位)那个带着红帽的家伙点击,--- ...