`
thecloud
  • 浏览: 872109 次
文章分类
社区版块
存档分类
最新评论

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内核学习资料

    此Linux内核学习资料包中有Linux内核--网络栈实现分析(二)--数据包的...-网络栈实现分析(五)--传输层之UDP协议(上).pdf Linux内核--网络栈实现分析(一)--网络栈初始化.pdf ,希望对正在学习Linux的各位有所帮助

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part5

    第2~5章在介绍了实现网络体系结构、协议栈、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part3.rar

    第2~5章在介绍了实现网络体系结构、协议栈、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part4.rar

    第2~5章在介绍了实现网络体系结构、协议栈、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part1

    第2~5章在介绍了实现网络体系结构、协议栈、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part2.rar

    第2~5章在介绍了实现网络体系结构、协议栈、设备驱动程序的两个最重要的数据结构sk_buff和net_device的基础上,展示了Linux内核中为网络设备驱动程序设计和开发而建立的系统构架,最后以两个实例来具体说明如何着手...

    Linux内核:从底层向上分析传输层之UDP协议

    这里看看数据包从IP层是如何交给传输层来处理的,为了方便,这里传输层以UDP协议为例来分析。  从ip_rcv()函数中可以看到  /*  * Pass on the datagram to each protocol that wants it,  * based on the ...

    Linux协议栈阅读笔记

    网络协议栈的实现基本采用TCP/IP的四层架构(链路、网络、传输、应用)。不过在实际学习中通常讲到的是5层架构(物理、链路、网络、传输、应用)。  BSD风格 BSD风格就是通常说的 socket、bind、connect、listen、...

    Linux网络编程 视频 教程

    Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP...

    linux网络编程-宋敬彬-part1

    1.5.1 Linux内核的主要模块 7 1.5.2 Linux的文件结构 9 1.6 GNU通用公共许可证 10 1.6.1 GPL许可证的历史 10 1.6.2 GPL的自由理念 10 1.6.3 GPL的基本条款 11 1.6.4 关于GPL许可证的争议 12 1.7 Linux...

    C++教程网《Linux网络编程》视频百度云地址

    Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP...

    [免费]2018年C++教程网的linux网络编程视频百度云下载链接.rar

    Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念...

    c++教程网的linux网络编程视频下载

    Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念...

    C++教程网视频:linux网络编程

    Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP...

    Linux防火墙.pdf

     2.3.7 Linux内核IGMP攻击 32  2.4 网络层回应 33  2.4.1 网络层过滤回应 33  2.4.2 网络层阈值回应 33  2.4.3 结合多层的回应 34  第3章 传输层的攻击与防御 35  3.1 使用iptables记录传输层首部 35 ...

    linux网络编程

    Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础...

    2018年C++教程网的linux网络编程视频共41集百度云下载链接.rar

    Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇...

    lksctp-rs:Rust 的 Linux 内核 SCTP 低级绑定

    它直接位于 IP 之上,与 TCP、UDP 和其他协议并排。 在许多方面,它是 TCP 的新改进版本,最初是为数字电话的需要而设计的。 特别是,它支持 TCP 支持的所有以下功能: 面向连接 可靠的 序列保存 流量控制和拥塞...

    入门学习Linux常用必会60个命令实例详解doc/txt

    文件为doc版,可自行转成txt,在手机上看挺好的。 本资源来自网络,如有纰漏还请告知,如觉得还不错,请留言告知后来人,谢谢!!!!! 入门学习Linux常用必会60个命令实例详解 Linux必学的60个命令 Linux提供...

    Linux系统故障诊断与排除--James Kirkland

    13.2.3 诊断传输层(TCP和UDP)问题 314 13.2.4 诊断应用程序级的问题:TCP/IP模型的最后一层 329 13.3 小结 329 第14章 登录问题 330 14.1 /etc/password,/etc/shadow和密码时效 331 14.1.1 /etc/...

Global site tag (gtag.js) - Google Analytics