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

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

 
阅读更多

本文分析基于内核Linux 1.2.13

原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7541907

更多请看专栏,地址http://blog.csdn.net/column/details/linux-kernel-net.html

作者:闫明

注:标题中的”(上)“,”(下)“表示分析过程基于数据包的传递方向:”(上)“表示分析是从底层向上分析、”(下)“表示分析是从上向下分析。


上篇博文分析了传输层从网络层获取数据包后将数据包缓存结构sk_buff挂载到特定的sock结构的接收队列中。

这里接着分析应用程序是如何从传输层获取网络数据包的。应用层要得到传输层的数据包有两种主要的方式:系统调用和文件操作


系统调用:

Linux下用户程序是通过系统调用来从用户态到内核态,调用内核功能来完成相应的服务。

网络栈的一些功能是通过系统调用sys_socketcall来完成的

具体的代码在net/socket.c中,该文件中的函数就相当于一个桥梁,在系统调用和内核网络栈之间。

/*
 *	System call vectors. Since I (RIB) want to rewrite sockets as streams,
 *	we have this level of indirection. Not a lot of overhead, since more of
 *	the work is done via read/write/select directly.
 *
 *	I'm now expanding this up to a higher level to separate the assorted
 *	kernel/user space manipulations and global assumptions from the protocol
 *	layers proper - AC.
 */

asmlinkage int sys_socketcall(int call, unsigned long *args)
{
	int er;
	switch(call) 
	{
		case SYS_SOCKET:
			er=verify_area(VERIFY_READ, args, 3 * sizeof(long));
			if(er)
				return er;
			return(sock_socket(get_fs_long(args+0),
				get_fs_long(args+1),
				get_fs_long(args+2)));
		case SYS_BIND:
			er=verify_area(VERIFY_READ, args, 3 * sizeof(long));
			if(er)
				return er;
			return(sock_bind(get_fs_long(args+0),
				(struct sockaddr *)get_fs_long(args+1),
				get_fs_long(args+2)));
		case SYS_CONNECT:
			er=verify_area(VERIFY_READ, args, 3 * sizeof(long));
			if(er)
				return er;
			return(sock_connect(get_fs_long(args+0),
				(struct sockaddr *)get_fs_long(args+1),
				get_fs_long(args+2)));
		case SYS_LISTEN:
			er=verify_area(VERIFY_READ, args, 2 * sizeof(long));
			if(er)
				return er;
			return(sock_listen(get_fs_long(args+0),
				get_fs_long(args+1)));
		case SYS_ACCEPT:
			er=verify_area(VERIFY_READ, args, 3 * sizeof(long));
			if(er)
				return er;
			return(sock_accept(get_fs_long(args+0),
				(struct sockaddr *)get_fs_long(args+1),
				(int *)get_fs_long(args+2)));
		case SYS_GETSOCKNAME:
			er=verify_area(VERIFY_READ, args, 3 * sizeof(long));
			if(er)
				return er;
			return(sock_getsockname(get_fs_long(args+0),
				(struct sockaddr *)get_fs_long(args+1),
				(int *)get_fs_long(args+2)));
		case SYS_GETPEERNAME:
			er=verify_area(VERIFY_READ, args, 3 * sizeof(long));
			if(er)
				return er;
			return(sock_getpeername(get_fs_long(args+0),
				(struct sockaddr *)get_fs_long(args+1),
				(int *)get_fs_long(args+2)));
		case SYS_SOCKETPAIR:
			er=verify_area(VERIFY_READ, args, 4 * sizeof(long));
			if(er)
				return er;
			return(sock_socketpair(get_fs_long(args+0),
				get_fs_long(args+1),
				get_fs_long(args+2),
				(unsigned long *)get_fs_long(args+3)));
		case SYS_SEND:
			er=verify_area(VERIFY_READ, args, 4 * sizeof(unsigned long));
			if(er)
				return er;
			return(sock_send(get_fs_long(args+0),
				(void *)get_fs_long(args+1),
				get_fs_long(args+2),
				get_fs_long(args+3)));
		case SYS_SENDTO:
			er=verify_area(VERIFY_READ, args, 6 * sizeof(unsigned long));
			if(er)
				return er;
			return(sock_sendto(get_fs_long(args+0),
				(void *)get_fs_long(args+1),
				get_fs_long(args+2),
				get_fs_long(args+3),
				(struct sockaddr *)get_fs_long(args+4),
				get_fs_long(args+5)));
		case SYS_RECV:
			er=verify_area(VERIFY_READ, args, 4 * sizeof(unsigned long));
			if(er)
				return er;
			return(sock_recv(get_fs_long(args+0),
				(void *)get_fs_long(args+1),
				get_fs_long(args+2),
				get_fs_long(args+3)));
		case SYS_RECVFROM:
			er=verify_area(VERIFY_READ, args, 6 * sizeof(unsigned long));
			if(er)
				return er;
			return(sock_recvfrom(get_fs_long(args+0),
				(void *)get_fs_long(args+1),
				get_fs_long(args+2),
				get_fs_long(args+3),
				(struct sockaddr *)get_fs_long(args+4),
				(int *)get_fs_long(args+5)));
		case SYS_SHUTDOWN:
			er=verify_area(VERIFY_READ, args, 2* sizeof(unsigned long));
			if(er)
				return er;
			return(sock_shutdown(get_fs_long(args+0),
				get_fs_long(args+1)));
		case SYS_SETSOCKOPT:
			er=verify_area(VERIFY_READ, args, 5*sizeof(unsigned long));
			if(er)
				return er;
			return(sock_setsockopt(get_fs_long(args+0),
				get_fs_long(args+1),
				get_fs_long(args+2),
				(char *)get_fs_long(args+3),
				get_fs_long(args+4)));
		case SYS_GETSOCKOPT:
			er=verify_area(VERIFY_READ, args, 5*sizeof(unsigned long));
			if(er)
				return er;
			return(sock_getsockopt(get_fs_long(args+0),
				get_fs_long(args+1),
				get_fs_long(args+2),
				(char *)get_fs_long(args+3),
				(int *)get_fs_long(args+4)));
		default:
			return(-EINVAL);
	}
}

上面系统调用的宏定义如下:

#define SYS_SOCKET	1		/* sys_socket(2)		*/
#define SYS_BIND	2		/* sys_bind(2)			*/
#define SYS_CONNECT	3		/* sys_connect(2)		*/
#define SYS_LISTEN	4		/* sys_listen(2)		*/
#define SYS_ACCEPT	5		/* sys_accept(2)		*/
#define SYS_GETSOCKNAME	6		/* sys_getsockname(2)		*/
#define SYS_GETPEERNAME	7		/* sys_getpeername(2)		*/
#define SYS_SOCKETPAIR	8		/* sys_socketpair(2)		*/
#define SYS_SEND	9		/* sys_send(2)			*/
#define SYS_RECV	10		/* sys_recv(2)			*/
#define SYS_SENDTO	11		/* sys_sendto(2)		*/
#define SYS_RECVFROM	12		/* sys_recvfrom(2)		*/
#define SYS_SHUTDOWN	13		/* sys_shutdown(2)		*/
#define SYS_SETSOCKOPT	14		/* sys_setsockopt(2)		*/
#define SYS_GETSOCKOPT	15		/* sys_getsockopt(2)		*/

应用层在一系列操作后就可以通过参数SYS_RECV或SYS_RECVFROM来获取数据包。由于UDP是无连接的,所以如果需要回复,必须使用recvfrom才能得知是谁发送的数据包。当然UDP也可以用recv类函数,只是它不能回复,只能接收。

这里还是以INET中UDP来举例说明。

如果系统调用参数是SYS_RECVFROM,则会进行内存校验后执行函数socket_recvform()函数。

/*
 *	Receive a frame from the socket and optionally record the address of the 
 *	sender. We verify the buffers are writable and if needed move the
 *	sender address from kernel to user space.
 */

static int sock_recvfrom(int fd, void * buff, int len, unsigned flags,
	     struct sockaddr *addr, int *addr_len)
{
	struct socket *sock;
	struct file *file;
	char address[MAX_SOCK_ADDR];
	int err;
	int alen;
	if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL))
		return(-EBADF);
	if (!(sock = sockfd_lookup(fd, NULL))) 
	  	return(-ENOTSOCK);
	if(len<0)
		return -EINVAL;
	if(len==0)
		return 0;

	err=verify_area(VERIFY_WRITE,buff,len);
	if(err)
	  	return err;
  	//进行相应检查后调用下层函数,INET域则为inet_recvfrom()函数
	len=sock->ops->recvfrom(sock, buff, len, (file->f_flags & O_NONBLOCK),
		     flags, (struct sockaddr *)address, &alen);

	if(len<0)
	 	return len;
	if(addr!=NULL && (err=move_addr_to_user(address,alen, addr, addr_len))<0)//将发送发地址从内核空间COPY到用户空间
	  	return err;

	return len;
}

在inet_recvfrom()函数中会调用具体的协议操作函数。UDP的协议操作函数定义如下:

struct proto udp_prot = {
	sock_wmalloc,
	sock_rmalloc,
	sock_wfree,
	sock_rfree,
	sock_rspace,
	sock_wspace,
	udp_close,
	udp_read,
	udp_write,
	udp_sendto,
	udp_recvfrom,
	ip_build_header,
	udp_connect,
	NULL,
	ip_queue_xmit,
	NULL,
	NULL,
	NULL,
	udp_rcv,
	datagram_select,
	udp_ioctl,
	NULL,
	NULL,
	ip_setsockopt,
	ip_getsockopt,
	128,
	0,
	{NULL,},
	"UDP",
	0, 0
};

可以看到,其对应的函数对udp_recvfrom()

/*
 * 	This should be easy, if there is something there we\
 * 	return it, otherwise we block.
 */

int udp_recvfrom(struct sock *sk, unsigned char *to, int len,
	     int noblock, unsigned flags, struct sockaddr_in *sin,
	     int *addr_len)
{
  	int copied = 0;
  	int truesize;
  	struct sk_buff *skb;
  	int er;

	/*
	 *	Check any passed addresses
	 */
	 
  	if (addr_len) 
  		*addr_len=sizeof(*sin);
  
	/*
	 *	From here the generic datagram does a lot of the work. Come
	 *	the finished NET3, it will do _ALL_ the work!
	 */
	 	
	skb=skb_recv_datagram(sk,flags,noblock,&er);
	if(skb==NULL)
  		return er;
  
  	truesize = skb->len;
  	copied = min(len, truesize);

  	/*
  	 *	FIXME : should use udp header size info value 
  	 */
  	 
	skb_copy_datagram(skb,sizeof(struct udphdr),to,copied);//从sk_buff结构中取出数据部分
	sk->stamp=skb->stamp;

	/* Copy the address. */
	if (sin) 
	{
		sin->sin_family = AF_INET;
		sin->sin_port = skb->h.uh->source;
		sin->sin_addr.s_addr = skb->daddr;
  	}
  
  	skb_free_datagram(skb);
  	release_sock(sk);
  	return(truesize);
}

这样数据就到达了用户空间。



普通文件操作函数接口

最主要的函数就是读写函数:sock_read和sock_write,可以通过文件操作来完成网络数据的读写。谈到文件,就得有文件描述符,文件描述符中的f_inode指针指向文件的存储结点结构。

文件操作集定义如下:

static struct file_operations socket_file_ops = {
	sock_lseek,
	sock_read,
	sock_write,
	sock_readdir,
	sock_select,
	sock_ioctl,
	NULL,			/* mmap */
	NULL,			/* no special open code... */
	sock_close,
	NULL,			/* no fsync */
	sock_fasync
};

read函数和write函数与recvfrom和send类似,这里列出函数,方便查看。

/*
 *	Read data from a socket. ubuf is a user mode pointer. We make sure the user
 *	area ubuf...ubuf+size-1 is writable before asking the protocol.
 */

static int sock_read(struct inode *inode, struct file *file, char *ubuf, int size)
{
	struct socket *sock;
	int err;
  
	if (!(sock = socki_lookup(inode))) 
	{
		printk("NET: sock_read: can't find socket for inode!\n");
		return(-EBADF);
	}
	if (sock->flags & SO_ACCEPTCON) 
		return(-EINVAL);

	if(size<0)
		return -EINVAL;
	if(size==0)
		return 0;
	if ((err=verify_area(VERIFY_WRITE,ubuf,size))<0)
	  	return err;
	return(sock->ops->read(sock, ubuf, size, (file->f_flags & O_NONBLOCK)));//和recvfrom函数类似,调用INET域相应函数
}
上面会调用inet_read()函数,inet_read()函数会调用udp_read()函数,而udp_read()是通过调用udp_recvfrom()完成功能的。
这两种方式是内核网络栈对用户的接口。


分享到:
评论

相关推荐

    Linux内核数据包处理流程

    Linux内核的数据包处理流程,分层的处理,直至应用层。

    LINUX内核网卡驱动解析

    Linux具有作为网络操作系统尤其是服务器端操作系统的优势。网络部分代码量很大,TCP/IP协议本身就很复杂,因而 本章就主要介绍数据包的传递过程、与应用层的接口、与底层的接口及网络驱动程序的编写。

    基于Linux 的防火墙技术研究

    ipables 在应用层调用setsockopt 进入内核,然后调用netfilter. c 又件中 nbetsockopt()实现交互,这样通过配置防火墙就可以按需要处理网络数据包。只有熟悉了iptables 提供的众多命令、选项等,在明白其工作原理...

    新版Android开发教程.rar

    的 Android SDK 提供了在 Android 平台上使用 JaVa 语言进行 Android 应用开发必须的工具和 API 接口。 特性 • 应用程序框架 支持组件的重用与替换 • Dalvik Dalvik Dalvik Dalvik 虚拟机 专为移动设备优化 • ...

    Netfilter架构基本数据流程.doc

    Linux内核中的netfilter架构有以太网层netfilter,主要管理工具为ebtables,有网络层的netfilter,主要管理工具为iptables,本文主要从总体上讲述网络层ipv4 netfilter架构中的基本数据流程,即一个数据所从进入网络...

    IP层保密通讯技术研究及设计实现

    针对Windows2000下自带的IPSec与Linux的IPSec相互通讯比较困难的问题,在对LinuxIPSec、NDIS网络驱动程序以及对WDM设备驱动程序的研究基础上,设计并实现了一个基于中间层驱动程序的、能与LinuxIPSCE通讯的Windows...

    Linux防火墙.pdf

    4.1 使用iptables实现应用层字符串匹配 4.2 应用层攻击的定义 4.3 滥用应用层 4.4 加密和应用层编码 4.5 应用层回应 第5章 端口扫描攻击检测程序psad简介 第6章 psad运作:检测可疑流量 第7章 psad高级主题:从签名...

    Linux 应用程序connect timeout原因

    应用报connect timeout的原因, 从物理网卡,网卡驱动,内核,协议栈,应用层 整个数据包的流向。

    网络课程设计,使用交换机

    利用常见的协议分析软件对常见网络通信服务中使用的协议进行分析,针对分析对象设计相关应用软件。 3.1S2116交换机做接入层交换机,S3928做汇聚层交换机; 3.2 使用一台AR 28-11做核心层路由器,设该台路由器为KR,...

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

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

    网络编程中使用tun虚拟网络接口建立IP隧道的实例

    TUN和TAP是Linuxn内核的虚拟网络设备,不同于普通靠硬件网络适配器实现的设备,这些虚拟的网络设备全部用软件实现,并可以向运行于Linux上的应用软件提供与硬件的网络设备完全相同的功能; TUN模拟了网络层设备,...

    Android驱动开发权威指南

    第3章Linux内核综述 3.1 OS基本概念 3.1.1多用户系统 3.1.2用户和组 3.1.3进程 3.1.4 Linux单核架构 3.2 Linux内核综述 3.2.1进程/内核模型综述 3.2.2内存管理综述 3.2.3文件系统综述 3.2.4设备驱动简述 第4章Linux...

    LINUX设备驱动第三版_588及代码.rar

    LINUX设备驱动第三版_ 前言 第一章 设备驱动程序简介 设备驱动程序的作用 内核功能划分 设备和模块的分类 安全问题 版本编号 许可证条款 加入内核开发社团 本书概要 第二章 构造和运行模块 设置测试系统 ...

    通信与网络中的Linux串口上网的简单实现

    比如PPP(Peer-to-Peer Protocol, 端对端协议)和SLIP(Serial Line Interface Protocol, 非常老的串行线路接口协议),这里所说的"上网"是指把串口当成一个网络接口,通过封装网络数据包(如IP包)以达到无网卡的...

    Linux DeviceDrivers 3rd Edition

    核心模块与应用程序的对比 24 编译和装载 28 内核符号表 33 预备知识 35 初始化和关闭 36 模块参数 40 在用户空间编写驱动程序 42 .快速参考 44 第三章 字符设备驱动程序 46 scull的设计 46 主设备号和次...

    Linux平台下电信级计费网关研究 (2006年)

    论文的主要研究工作:使用Nemlter框架,完成了网络层的数据包重组、以及应用层的报文头分析;实现了在内核中对HTTP协议的数据流进行解析及过滤,并提取出计费所需的重要信息:使用x86电信服务器对系统原型进行了实例...

    IP层保密通讯技术研究及设计实现 (2003年)

    针对Windows2000下自带的IPsec与Linux的IPSec相互通讯比较困难的问题,在对LinuxIPSec、NDIS网络驱动程序以及对wDM设备驱动程序的研究基础上,设计并实现了一个基于中间层驱动程序的、能与LinuxIPSCE通讯的Windows...

    Linux高性能服务器编程

    目录前言第一篇 TCPIP协议详解第1章 TCPIP协议族 1.1 TCPIP协议族体系结构以及主要协议 1.1.1 数据链路层 1.1.2 网络层 1.1.3 传输层 1.1.4 应用层 1.2 封装 1.3 分用 1.4 测试网络 1.5 ARP协议工作原理 ...

Global site tag (gtag.js) - Google Analytics