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;
- }
UDP协议的更多相关文章
- TODO:Golang语言TCP/UDP协议重用地址端口
TODO:Golang语言TCP/UDP协议重用地址端口 这是一个简单的包来解决重用地址的问题. go net包(据我所知)不允许设置套接字选项. 这在尝试进行TCP NAT时尤其成问题,其需要在同一 ...
- 闲来无事,写个基于UDP协议的Socket通讯Demo
项目一期已经做完,二期需求还没定稿,所以最近比较闲. 上一篇写的是TCP协议,今天写一下UDP协议.TCP是有连接协议,所以发送和接收消息前客户端和服务端需要建立连接:UDP是无连接协议,所以发送消息 ...
- UDP协议开发
UDP是用户数据报协议(User Datagram Protocol,UDP)的简称,其主要作用是将网络数据流量压缩成数据报形式,提供面向事务的简单信息传送服务.与TCP协议不同,UDP协议直接利用I ...
- 基于UDP协议模拟的一个TCP协议传输系统
TCP协议以可靠性出名,这其中包括三次握手建立连接,流控制和拥塞控制等技术.详细介绍如下: 1. TCP协议将需要发送的数据分割成数据块.数据块大小是通过MSS(maximum segment siz ...
- TCP协议与UDP协议的区别
TCP协议与UDP协议的区别(转) 首先咱们弄清楚,TCP协议和UCP协议与TCP/IP协议的联系,很多人犯糊涂了,一直都是说TCP/IP协议与UDP协议的区别,我觉得这是没有从本质上弄清楚网络通信! ...
- 采用UDP协议的PIC32MZ ethernet bootloader
了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). 经过千辛万苦,今天终于 ...
- 采用UDP协议实现PIC18F97J60 ethernet bootloader
了解更多关于bootloader 的C语言实现,请加我QQ: 1273623966 (验证信息请填 bootloader),欢迎咨询或定制bootloader(在线升级程序). TCP/IP Stac ...
- 网络编程——基于TCP协议的Socket编程,基于UDP协议的Socket编程
Socket编程 目前较为流行的网络编程模型是客户机/服务器通信模式 客户进程向服务器进程发出要求某种服务的请求,服务器进程响应该请求.如图所示,通常,一个服务器进程会同时为多个客户端进程服务,图中服 ...
- Linux内核--网络栈实现分析(九)--传输层之UDP协议(下)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7549340 更多请查看专栏,地 ...
- Linux内核--网络栈实现分析(五)--传输层之UDP协议(上)
本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明出处http://blog.csdn.net/yming0221/article/details/7532512 更多请看专栏, ...
随机推荐
- Git——取消merge状态
MERGING状态 取消MERGING 查看更新历史 $ git reflog 恢复之前状态 $ git reset --hard 06a5578
- POJ 3074 Sudoku(算竞进阶习题)
二进制优化+dfs 话说这题数据中真的丧心病狂..不加inline还过不去.. 因为不会DLX只好用二进制来优化了...万万没想到还是低空飘过 我们在行.列.格分别用一个9位二进制常数来记录什么数能放 ...
- ☆ [NOIp2016] 天天爱跑步 「树上差分」
题目类型:LCA+思维 传送门:>Here< 题意:给出一棵树,有\(M\)个人在这棵树上跑步.每个人都从自己的起点\(s[i]\)跑到终点\(t[i]\),跑过一条边的时间为1秒.现在每 ...
- codeforces 1065F Up and Down the Tree
题目链接:codeforces 1065F Up and Down the Tree 题意:给出一棵树的节点数\(n\)以及一次移动的最大距离\(k\),现在有一个标记在根节点1处,每一次可以进行一下 ...
- 【AGC005F】Many Easy Problems FFT 容斥原理
题目大意 给你一棵树,有\(n\)个点.还给你了一个整数\(k\). 设\(S\)为树上某些点的集合,定义\(f(S)\)为最小的包含\(S\)的联通子图的大小. \(n\)个点选\(k\)个点一共有 ...
- Markdown文本的学习
大标题 小标题 小小标题 小小小标题 萌啊萌啊萌 斜体 萌啊萌啊萌 粗体 萌啊萌啊萌 又粗又斜 萌啊萌啊萌 我是分割线 p.s. 标准Markdown换行要打两个空格 短代码 #include < ...
- 自学华为IoT物联网_10 IoT联接管理平台配置及开发实验1
点击返回自学华为IoT物流网 自学华为IoT物联网_10 IoT联接管理平台配置及开发实验1 实验1:OceanConnect平台实验 通过基本的编程操作与配置,帮助读者熟悉O ...
- rsyncd 配置使用
查询rpm -qa | grep rsync 配置文件需手动创建: touch /etc/rsyncd.conf 配置/etc/rsyncd.conf: (全局配置) uid = root //rsy ...
- 添加默认安全组规则-openstack
if [ "$1" ] ;then neutron security-group-rule-create --direction ingress --ethertype ipv4 ...
- VSCode创建自定义代码段
上一篇:PyCharm创建自定义代码段(JetBrains系列通用) 设置方法 很简单,快速过一下,F1,然后输入snippets 然后选择对应语言 Python案例 内容和使用: { // pref ...