存档在 2012年8月

Netlink编程-使用NETLINK_INET_DIAG协议

2012年8月25日

Netlink可以使得内核和用户进程进行双向通信,前文已经介绍过用户进程主动发起会话请求的例子。在那个示例程序中,必须同时编写用户态程序和内核模块,因为他们之间通信的协议是我们自己设定的,并没有使用netlink已有的通信协议。如果使用netlink已有的通信协议,那么我们无需编写内核模块,只需编写用户态程序即可。

本文将说明如何在用户态使用NETLINK_INET_DIAG协议。

1.创建netlink套接字

Netlink的使用方法与普通套接字并无太大差异,前文已经说明参数的差异,这里不再赘述。

struct sk_req {
	struct nlmsghdr nlh;
	struct inet_diag_req r;
};

int main(int argc, char **argv)
{
	int fd;
	struct sk_req req;
	struct sockaddr_nl dest_addr;
	struct msghdr msg;
	char buf[8192];
	char src_ip[20];
	char dest_ip[20];
	struct iovec iov;

	if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG)) < 0) {
		eprint(__LINE__, errno, "socket");
		return -1;
	}

2.发送消息到内核

用户进程通过msghdr结构将消息发送到内核中,因此必须首先初始化msghdr类型的变量msg。该数据结构与iovec类型的变量iov和sockaddr_nl类型的变量dest_addr关联,iov指向数据缓冲区,dest_addr用于描述目的套接字地址。

这里需要将nlmsghdr结构中的nlmsg_type指定为TCPDIAG_GETSOCK,说明获取的是TCP套接字。同时需要将nlmsg_flags字段指定为NLM_F_REQUEST |  NLM_F_ROOT,NLM_F_REQUEST是所有向内核发出消息请求的用户进程所必须所设置的,NLM_F_ROOT则指明返回所有的套接字。

	req.nlh.nlmsg_len = sizeof(req);
	req.nlh.nlmsg_type = TCPDIAG_GETSOCK;
	req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
	req.nlh.nlmsg_pid = 0;

	memset(&req.r, 0, sizeof(req.r));
	req.r.idiag_family = AF_INET;
	req.r.idiag_states = ((1 << TCP_CLOSING + 1) - 1);

	iov.iov_base = &req;
	iov.iov_len = sizeof(req);

	memset(&dest_addr, 0, sizeof(dest_addr));
	dest_addr.nl_family = AF_NETLINK;
	dest_addr.nl_pid = 0;
	dest_addr.nl_groups = 0;

	memset(&msg, 0, sizeof(msg));
	msg.msg_name = (void *)&dest_addr;
	msg.msg_namelen = sizeof(dest_addr);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

数据缓冲区通过req结构来表示,它封装了两个数据结构nlmsghdr和inet_diag_req。前者用来表示netlink消息头,它是必须封装的数据结构。后者是NETLINK_INET_DIAG协议所特有的请求会话的数据结构,具体结构如下:

struct inet_diag_req {
        __u8    idiag_family;           /* Family of addresses. */
        __u8    idiag_src_len;
        __u8    idiag_dst_len;
        __u8    idiag_ext;              /* Query extended information */

        struct inet_diag_sockid id;

        __u32   idiag_states;           /* States to dump */
        __u32   idiag_dbs;              /* Tables to dump (NI) */
};

这里需要特别注意的是inet_diag_req结构中的idiag_states字段,它用来表示内核将要反馈哪些状态的套接字到用户空间。用户空间通过一个枚举类型来表示套接字状态:

enum
{
  TCP_ESTABLISHED = 1,
  TCP_SYN_SENT,
  TCP_SYN_RECV,
  TCP_FIN_WAIT1,
  TCP_FIN_WAIT2,
  TCP_TIME_WAIT,
  TCP_CLOSE,
  TCP_CLOSE_WAIT,
  TCP_LAST_ACK,
  TCP_LISTEN,
  TCP_CLOSING
};

idiag_states字段的每一位表示一个状态,因此通过位偏移可以将具体某个状态位置1。上述的实例程序中,将表示所有状态的位都置1,因此内核将向用户进程反馈所有状态的套接字。

	if (sendmsg(fd, &msg, 0) < 0) {
		eprint(__LINE__, errno, "sendmsg");
		return -1;
	}

初始化相关的数据结构之后,接下来用户进程通过sendmsg函数发送消息到内核中。

3.用户进程接收消息

用户进程通过两层循环来接受并处理内核发送的消息。外层循环通过recvmsg函数不断接收内核发送的数据,在接收数据之前还要将新的数据缓冲区buf与iov进行绑定。内层循环将内核通过一次系统调用所发送的数据进行分批处理。对于本文所描述的NETLINK_INET_DIAG协议,内核每次向用户进程发送的消息通过inet_diag_msg结构描述:

struct inet_diag_msg {
        __u8    idiag_family;
        __u8    idiag_state;
        __u8    idiag_timer;
        __u8    idiag_retrans;

        struct inet_diag_sockid id;

        __u32   idiag_expires;
        __u32   idiag_rqueue;
        __u32   idiag_wqueue;
        __u32   idiag_uid;
        __u32   idiag_inode;
};

每一次外层循环将接收到的数据存放在buf缓冲区中,该缓冲区中存放了多条消息,结构如下:

struct nlhdrmsg struct inet_idiag_msg || struct nlhdrmsg struct inet_idiag_msg || ……

按照这样的数据存储方式,内层循环要做的就是依次获取这些数据结构。由于每条数据报都至少封装了nlmsghdr结构,因此具体的处理方法通过NLMSG_XXX宏即可完成。

	memset(buf, 0 ,sizeof(buf));
	iov.iov_base = buf;
	iov.iov_len = sizeof(buf);

	while (1) {
		int status;
		struct nlmsghdr *h;

		msg = (struct msghdr) {
			(void *)&dest_addr, sizeof(dest_addr),
				&iov, 1, NULL, 0, 0
		};
		status = recvmsg(fd, &msg, 0);
		if (status < 0) {
			if (errno == EINTR)
				continue;
			eprint(__LINE__, errno, "recvmsg");
			continue;
		}

		if (status == 0) {
			printf("EOF on netlink\n");
			close(fd);
			return 0;
		}

		h = (struct nlmsghdr *)buf;

		while (NLMSG_OK(h, status)) {
			struct inet_diag_msg *pkg = NULL;

			if (h->nlmsg_type == NLMSG_DONE) {
				close(fd);
				printf("NLMSG_DONE\n");
				return 0;
			}

			if (h->nlmsg_type == NLMSG_ERROR) {
				struct nlmsgerr *err;
				err = (struct nlmsgerr*)NLMSG_DATA(h);
				fprintf(stderr, "%d Error %d:%s\n", __LINE__, -(err->error), strerror(-(err->error)));
				close(fd);
				printf("NLMSG_ERROR\n");
				return 0;
			}

			pkg = (struct inet_diag_msg *)NLMSG_DATA(h);
			print_skinfo(pkg);
			get_tcp_state(pkg->idiag_state);
			h = NLMSG_NEXT(h, status);
		}//while
	}//while
	close(fd);
	return 0;

NLMSG_OK宏每次判断buf中的数据是否读取完毕,NLMSG_DATA取当前netlink消息头结构紧邻的inet_diag_msg结构,NLMSG_NEXT则取下一个netlink消息头结构。

pkg指向当前获取到的消息,接下来具体需求处理各个字段即可。上述程序中,print_skinfo函数打印pkg中的各个字段,get_tcp_state则是打印每个套接字连接的状态。

Netlink编程-用户主动发起会话

2012年8月19日

Netlink是一种在内核态和用户态可以进行双向通信的机制,也就是说,用户进程既可以作为服务器端又可以作为客户端,内核也是如此。用户进程和内核谁是服务器端谁是客户端,这个问题与谁先主动发起数据交互会话有关。

用户进程主动向内核发起会话在Linux内核中很常见,比如系统调用、对/proc的操作等。本文通过详解一个简单的实例程序来说明用户进程通过netlink机制如何主动向内核发起会话。在该程序中,用户进程向内核发送一段字符串,内核接收到后再将该字符串后再重新发给用户进程。

用户态程序

netlink是一种特殊的套接字,在用户态除了一些参数的传递对其使用的方法与一般套接字无较大差异,。

1.宏与数据结构的定义

在使用netlink进行用户进程和内核的数据交互时,最重要的是定义好通信协议。协议一词直白的说就是用户进程和内核应该以什么样的形式发送数据,以什么样的形式接收数据。而这个“形式”通常对应程序中的一个特定数据结构。

本文所演示的程序并没有使用netlink已有的通信协议,因此我们自定义一种协议类型NETLINK_TEST。

#define NETLINK_TEST 18
#define MAX_PAYLOAD 1024

struct req {
	struct nlmsghdr nlh;
	char buf[MAX_PAYLOAD];
};

除此之外,我们应该再自定义一个数据报类型req,该结构包含了netlink数据包头结构的变量nlh和一个MAX_PAYLOAD大小的缓冲区。这里我们为了演示简单,并没有像上文中描述的那样将一个特定数据结构与nlmsghdr封装起来。

2.创建netlink套接字

要使用netlink,必须先创建一个netlink套接字。创建方法同样采用socket(),只是这里需要注意传递的参数:

	int sock_fd;
	sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
	if (sock_fd < 0) {
		eprint(errno, "socket", __LINE__);
		return errno;
	}

第一个参数必须指定为PF_NETLINK或AF_NETLINK。第二个参数必须指定为SOCK_RAW或SOCK_DGRAM,因为netlink提供的是一种无连接的数据报服务。第三个参数则指定具体的协议类型,我们这里使用自定义的协议类型NETLINK_TEST。

另外,eprint()是一个自定义的出错处理函数,实现如下:

void eprint(int err_no, char *str, int line)
{
	printf("Error %d in line %d:%s() with %s\n", err_no, line, str, strerror(errno));
}

3.将本地套接字与源地址绑定

将本地的套接字与源地址进行绑定通过bind()完成。在绑定之前,需要将源地址进行初始化,nl_pid字段指明发送消息一方的pid,nl_groups表示多播组的掩码,这里我们并没有涉及多播,因此默认为0。

	struct sockaddr_nl src_addr;
	memset(&src_addr, 0, sizeof(src_addr));
	src_addr.nl_family = AF_NETLINK;
	src_addr.nl_pid = getpid();
	src_addr.nl_groups = 0;

	if (bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0) {
		eprint(errno, "bind", __LINE__);
		return errno;
	}

4.初始化msghdr结构

用户进程最终发送的是msghdr结构的消息,因此必须对这个结构进行初始化。而此结构又与sockaddr_nl,iovec和nlmsghdr三个结构相关,因此必须依次对这些数据结构进行初始化。

首先初始化目的套接字的地址结构,该结构与源套接字地址结构初始化的方法稍有不同,即nl_pid必须为0,表示接收方为内核。

	struct sockaddr_nl dest_addr;
	memset(&dest_addr, 0, sizeof(dest_addr));
	dest_addr.nl_family = AF_NETLINK;
	dest_addr.nl_pid = 0;
	dest_addr.nl_groups = 0;

接下来对req类型的数据报进行初始化,即依次对其封装的两个数据结构初始化:

	struct req r;
	r.nlh.nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
	r.nlh.nlmsg_pid = getpid();
	r.nlh.nlmsg_flags = 0;
	memset(r.buf, 0, MAX_PAYLOAD);
	strcpy(NLMSG_DATA(&(r.nlh)), "hello, I am edsionte!");

这里的nlmsg_len为为sizeof(struct nlmsghdr)+MAX_PAYLOAD的总和。宏NLMSG_SPACE会自动将两者的长度相加。接下来对缓冲区向量iov进行初始化,让iov_base字段指向数据报结构,而iov_len为数据报长度。

	struct iovec iov;
	iov.iov_base = (void *)&r;
	iov.iov_len = sizeof(r);

一切就绪后,将目的套接字地址与当前要发送的消息msg绑定,即将目的套接字地址复制给msg_name。再将要发送的数据iov与msg_iov绑定,如果一次性要发送多个数据包,则创建一个iovec类型的数组。

	struct msghdr msg;
	msg.msg_name = (void *)&dest_addr;
	msg.msg_namelen = sizeof(dest_addr);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

5.向内核发送消息

发送消息则很简单,通过sendmsg函数即可完成,前提是正确的创建netlink套接字和要发送的消息。

	if (sendmsg(sock_fd, &msg, 0) < 0) {
		eprint(errno, "sendmsg", __LINE__);
		return errno;
	}

6.接受内核发来的消息

如果用户进程需要接收内核发送的消息,则需要通过recvmsg完成,只不过在接收之前需要将数据报r重新初始化,因为发送和接收时传递的数据结构可能是不同的。

为了简单演示netlink的用法,本文所述的用户进程发送的是一段字符串,这一点从数据报结构req的定义可以看出。而内核向用户进程发送的也是一段字符串,具体情况下面将会具体说明。

        memset(&r.nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
        if (recvmsg(sock_fd, &msg, 0) < 0) {
                eprint(errno, "recvmsg", __LINE__);
                return errno;
        }

        printf("Received message payload:%s\n", (char *)NLMSG_DATA(&r.nlh));
        close(sock_fd);

接收完毕后,通过专门的宏NLMSG_DATA对数据报进行操作。 netlink对数据报的的访问和操作都是通过一系列标准的宏NLMSG_XXX来完成的,具体的说明可以通过man netlink查看。这里的NLMSG_DATA传递进去的是nlh,但它获取的是紧邻nlh的真正数据。本程序中传递的是字符串,所以取数据时候用char *强制类型转换,如果传递的是其他数据结构,则相应转换数据类型即可。

内核模块

netlink既然是一种用户态和内核态之间的双向通信机制,那么除了编写用户程序还要编写内核模块,也就是说用户进程和内核模块之间对数据的处理要彼此对应起来。

1.内核模块加载和卸载函数

内核模块加载函数主要通过netlink_kernel_create函数申请服务器端的套接字nl_sk,内核中对套接字表示为sock结构。另外,在创建套接字时还需要传递和用户进程相同的netlink协议类型NETLINK_TEST。创建套接字函数的第一个参数默认为init_net,第三个参数为多播时使用,我们这里不使用多播因此默认值为0。nl_data_handler是一个钩子函数,每当内核接收到一个消息时,这个钩子函数就被回调对用户数据进行处理。

#define NETLINK_TEST 17
struct sock *nl_sk = NULL;
static int __init hello_init(void)
{
	printk("hello_init is starting..\n");
	nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 0, nl_data_ready, NULL, THIS_MODULE);
	if (nl_sk == 0)
	{
		printk("can not create netlink socket.\n");
		return -1;
	}
	return 0;
}

内核模块卸载函数所做的工作与加载函数相反,通过sock_release函数释放一开始申请的套接字。

static void __exit hello_exit(void)
{
	sock_release(nl_sk->sk_socket);
	printk("hello_exit is leaving..\n");
}

2.钩子函数的实现

在内核创建netlink套接字时,必须绑定一个钩子函数,该钩子函数原型为:

 void (*input)(struct sk_buff *skb);

钩子函数的实现主要是先接收用户进程发送的消息,接收以后内核再发送一条消息到用户进程。

在钩子函数中,先通过skb_get函数对套接字缓冲区增加一次引用值,再通过nlmsg_hdr函数获取netlink消息头指针nlh。接着使用NLMSG_DATA宏获取用户进程发送过来的数据str。除此之外,再打印发送者的pid。

void nl_data_handler(struct sk_buff *__skb)
{
	struct sk_buff *skb;
	struct nlmsghdr *nlh;
	u32 pid;
	int rc;
	char str[100];
	int len = NLMSG_SPACE(MAX_PAYLOAD);

	printk("read data..\n");
	skb = skb_get(__skb);

	if (skb->len >= NLMSG_SPACE(0)) {
		nlh = nlmsg_hdr(skb);
		printk("Recv: %s\n", (char *)NLMSG_DATA(nlh));
		memcpy(str, NLMSG_DATA(nlh), sizeof(str));
		pid = nlh->nlmsg_pid;
		printk("pid is %d\n", pid);
		kfree_skb(skb);

接下来重新申请一个套接字缓冲区,为内核发送消息到用户进程做准备,nlmsg_put函数将填充netlink数据报头。接下来将用户进程发送的字符串复制到nlh紧邻的数据缓冲区中,等待内核发送。netlink_unicast函数将以非阻塞的方式发送数据包到用户进程,pid具体指明了接收消息的进程。

		skb = alloc_skb(len, GFP_ATOMIC);
		if (!skb){
			printk(KERN_ERR "net_link: allocate failed.\n");
			return;
		}
		nlh = nlmsg_put(skb, 0, 0, 0, MAX_PAYLOAD, 0);
		NETLINK_CB(skb).pid = 0;

		memcpy(NLMSG_DATA(nlh), str, sizeof(str));
		printk("net_link: going to send.\n");
		rc = netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
		if (rc < 0) {
			printk(KERN_ERR "net_link: can not unicast skb (%d)\n", rc);
		}
		printk("net_link: send is ok.\n");
	}
}

这样就完成了内核模块的编写,它与用户进程通信共同完成数据交互。

Netlink编程-数据结构

2012年8月18日

内核态与用户态进行数据交互的方法很多,分为用户程序主动发起的消息交交互和内核主动发起的信息交互。通常我们所熟知的系统调用、对/proc进行读写操作、mmap和编写驱动程序等都属于前者,而由内核主动发起的信息交互方法比较少,最典型的方法为内核发送信号给当前进程。上述这些方法都是单向通信的,也就是说要么是用户态进程主动发起数据交互会话,要么是内核主动发起会话。有没有一种可以在用户态和内核态进行双向数据交互的方法呢?

Netlink是一种在内核态和用户态可以进行双向数据交互的通信机制。在用户态,我们可以将netlink看作是一种特殊的socket,因此通过socket接口来就可以直接使用netlink;在内核态,则需要通过一组特殊的内核接口编写内核模块。

如果初次编写netlink程序,可能会对它所涉及的数据结构感到困惑,本文将简单介绍一下netlink编程中遇到的基本数据结构。

struct msghdr

如果使用sendmsg()发送数据,那么必须使用msghdr结构,该结构内部封装了本次发送数据的一些参数:

           struct msghdr {
               void         *msg_name;       /* optional address */
               socklen_t     msg_namelen;    /* size of address */
               struct iovec *msg_iov;        /* scatter/gather array */
               size_t        msg_iovlen;     /* # elements in msg_iov */
               void         *msg_control;    /* ancillary data, see below */
               size_t        msg_controllen; /* ancillary data buffer len */
               int           msg_flags;      /* flags on received message */
           };

netlink提供的是一种基于数据报的通信服务,因此与UDP通信协议类似,必须通过msg_name指明目的套接字地址结构,而msg_namelen则指明该地址结构的长度。msg_iov指定数据缓冲区数组,而msg_iovlen指明了该数组的元素个数。

struct iovec

iovec结构表示一个向量元素,它定义了一个标准的数据缓冲区格式。其包含两个字段:指向数据的指针和数据长度。

struct iovec
{
    void *iov_base;     /* Pointer to data.  */
    size_t iov_len;     /* Length of data.  */
};

当使用iovec结构类型的数组传递数据时,可以将多个消息通过一次系统调用进行发送。

struct nlmsghdr

如果使用sendmsg函数来发送netlink数据报,那么iovec结构中iov_base字段应该根据具体协议指向一个自定义的数据结构。一般这个自定义的数据结构如下所示:

struct req {
        struct nlmsghdr *nlh;
        struct special_struct *r;
};

其中nlmsghdr结构为必须所有的,它用来描述netlink消息头。

           struct nlmsghdr {
               __u32 nlmsg_len;    /* Length of message including header. */
               __u16 nlmsg_type;   /* Type of message content. */
               __u16 nlmsg_flags;  /* Additional flags. */
               __u32 nlmsg_seq;    /* Sequence number. */
               __u32 nlmsg_pid;    /* PID of the sending process. */
           };

对于special_struct结构则要根据具体需求而定。如果使用netlink现有的协议,比如NETLINK_INET_DIAG,那么special_struct结构在用户程序向内核发送消息时应该为inet_diag_req结构,如果当内核发送消息给用户进程那么special_struct结构又必须定义为inet_diag_msg。这些结构体都是事先定义好的,我们直接拿来用,按需获取这些结构中的某些字段的值即可。

如果是自己定义的通信协议,那么则根据具体需求自定义数据结构即可。比如用户进程想通过pid在内核中获取该进程亲属的pid,那么用户进程在发送消息和接受消息时的special_struct分别可以按照如下定义:

struct get_pid_req {
        int pid;
};

struct get_pid_msg {
        int parent_pid;
        int sibling_pid;
        int child_pid;
}

为netlink消息确定好了结构以后,则将该结构赋值给iovec结构的iov_base字段。

struct sockaddr_nl

netlink是一种特殊的无连接套接字,因此必须在msghdr结构的msg_name中指明目的netlink套接字的地址。套接字地址的通用结构为struct sockaddr,但对于具体类型的套接字则有不同的地址结构,比如TCP/IP协议族为sockaddr_in,对于netlink则为sockaddr_nl结构:

           struct sockaddr_nl {
               sa_family_t     nl_family;  /* AF_NETLINK */
               unsigned short  nl_pad;     /* Zero. */
               pid_t           nl_pid;     /* Process ID. */
               __u32           nl_groups;  /* Multicast groups mask. */
           };

其中该结构中的nl_family字段填充为AF_NETLINK。

以上这些结构在netlink编程中都会涉及到,但是通过上面描述的关系来看,它们最终都是与msghdr结构有关的。而msghdr结构作为参数通过sendmsg函数就可以netlink数据包发送至内核。

网络协议栈入门知识

2012年8月9日

Linux内核将一切都看作为文件,因此网络协议栈以文件系统sockfs的形式存在于内核中,该文件系统中的文件则是我们熟知的套接字。稍微熟知Linux文件系统的人都会知道内核中将所有文件系统进行抽象,通过虚拟文件系统(VFS)来统一管理。网络文件系统也是如此,也就是说超级块,索引节点,目录项等概念在网路文件系统中也存在。

在proc文件系统下,我们可以通过下面的方式得到内核当前所有文件系统的名称:

[root@TENCENT64 ~]# cat /proc/filesystems
nodev   sysfs
nodev   rootfs
nodev   bdev
nodev   proc
nodev   tmpfs
nodev   debugfs
nodev   sockfs
nodev   pipefs
nodev   anon_inodefs
nodev   rpc_pipefs
nodev   inotifyfs
…………
        ext3
        ext2
        ext4

网络文件系统和ext3这样的文件系统有什么区别?前者只存在于内存中,而后者则存在于磁盘中。比如上述信息中的sockfs和tmpfs,甚至是proc,在第一列均显示nodev,也就是说这些文件系统并没有存储在磁盘这样的块设备中。相反,它们存储在内存中,即采用的是一种非持久性的存储方式。

这里应该注意的是sockfs的“虚拟”和VFS的“虚拟”是两个不同的概念。前者表示sockfs这种虚拟文件系统不同于ext2这样的磁盘文件系统,它们存储在内存中,是一种非持久的存储,只要系统重启信息全部丢失。而虚拟文件系统中的“虚拟”表示的是对所有文件系统的一种抽象,它将所有不同种类文件系统的共同信息放入同一组数据结构当中,为上层用户提供了统一的接口。

VFS中用于描述文件系统类型的数据结构为file_system_type,所有文件系统都对应该类型的变量:

static struct file_system_type sock_fs_type = {
        .name =         "sockfs",
        .mount =        sockfs_mount,
        .kill_sb =      kill_anon_super,
};

static struct file_system_type proc_fs_type = {
        .name           = "proc",
        .mount          = proc_mount,
        .kill_sb        = proc_kill_sb,
};

可以看到,/proc/filesystem中的文件系统名称即从file_system_type结构的实例中获得。

socket结构

内核中通过socket结构体来表示套接字。该结构中包含的字段比较少,比如描述套接字状态的state,套接字标志位flags,等待队列wp以及与该套接字关联的文件对象。

 struct socket {
         socket_state            state;
         kmemcheck_bitfield_begin(type);
         short                   type;
         kmemcheck_bitfield_end(type);
         unsigned long           flags;
         struct socket_wq __rcu  *wq;
         struct file             *file;
         struct sock             *sk;
         const struct proto_ops  *ops;
 };

在socket结构中包含了一个与它比较相似的结构sock,该结构也是对套接字进行描述的。

sock结构体

sock结构中包含大量的字段,这些字段更多的是用来描述协议的通用属性,而socket结构则是更多的描述与应用程序相关的部分。

sk_buff结构体

sk_buff用于对网络协议中的数据包进行描述,不管该数据包位于那一层协议,都会有一个sk_buff结构与之对应。而sk_buff结构通过其内部的几个指针字段就可以轻松完成加包头去包头的操作。

三者的关系

每个socket结构都有一个sk字段指向sock结构,而sock结构中通过sk_socket字段反指向socket结构。sock结构通过sk_receive_queue和sk_write_queue等字段指向数据包头结构(struct sk_buff_head),而这个结构内部又通过prev和next两个字段指向真正的数据包结构sk_buff。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key