转自:http://blog.csdn.net/rstevens/archive/2007/04/10/1559289.aspx
1. 网络子系统
1.1. 网络子系统概述
Linux 内核中,与网络相关的代码是一个相对独立的子系统,称为网络子系统。
网络子系统是一个层次化的结构,可分为以下几个层次:
1、 Socket 层
Linux 在发展过程中,采用 BSD socket APIs 作为自己的网络相关的 API 接口。同时, Linux 的目标又要能支持各种不同的协议族,而且这些协议族都可以使用 BSD socket APIs 作为应用层的编程接口。因此,在 socket APIs 与协议族层之间抽象出一个 socket 层,用于将 user space 的 socket API 调用,转给具体的协议族做处理。
2、 协议族层( INET 协议族、 INET6 协议族等)
Linux 网络子系统功能上相当完备,它不仅支持 INET 协议族(也就是通常所说的 TCP/IP stack ),而且还支持其它很多种协议族,如 DECnet, ROSE, NETBEUI 等。 INET6 就是一种新增加的协议族。
对于 INET 、 INET6 协议族来说, 又进一步划分为传输层和网络层。
3、 设备驱动层
设备驱动层则主要将协议族层与物理的网络设备隔离开。它不在本文的讨论范围之内。
下图是 Linux 网络系统层次结构图。
1.2. 网络子系统的初始化
· Socket 层的初始化:
Init()->do_basic_setup()->sock_init()
Sock_init() :对 sock 和 skbuff 结构进行 SLAB 内存的初始化工作
· 各种网络协议族的初始化:
Do_initcalls() :
对于编译到内核中的功能模块(而不是以模块的形式动态加载),它的初始化函数会在这个地方被调用到。
内核映象中专门有一个初始化段,所有编译到内核中的功能模块的初始化函数都会加入到这个段中;而 do_initcalls() 就是依次执行初始化段中的这些函数。
INET 协议族通常是被编译进内核的;它的模块初始化函数是 net/ipv4/af_inet.c 中的 inet_init()
而 INET6 是作为一个模块编译的。它的模块初始化函数是 net/ipv6/af_inet6.c 中的 inet6_init()
2. 协议族
Linux 网络子系统可以支持不同的协议族, Linux 所支持的协议族定义在 include/linux/socket.h
2.1. 协议族数据结构
协议族数据结构是 struct net_proto_family 。
int family;
int ( * create)( struct socket * sock, int protocol);
short authentication;
short encryption;
short encrypt_net;
struct module * owner;
};
这个结构中,最重要的是 create 函数,一个新的协议族,必须提供此函数的实现。这是因为:
不同的网络协议族,从 user space 的使用方法来说,都是一样的,都是先调用 socket() 来创建一个 socket fd ,然后通过这个 fd 发送 / 接收数据。
在 user space 通过 socket() 系统调用进入内核后,根据第一个参数协议族类型,来调用相应协议族的 create() 函数。对 INET6 来说,这个函数是 inet6_create() 。
因此,要实现一个新的协议族,首先需要提供一个 create() 的实现。关于 create() 里面具体做了什么,后面再叙述。
Linux 系统通过这种方式,可以很方便的支持新的网络协议族,而不用修改已有的代码。这很好的符合了 “开 - 闭原则”,对扩展开放,对修改封闭。
2.2. 协议族注册
Linux 维护一个 struct net_proto_family 的数组 net_families[]
如果要支持一个新的网络协议族,那么需要定义自己的 struct net_proto_family ,并且通过调用 sock_register 将它注册到 net_families[] 中。
3. socket 层的主要数据结构
socket 层又叫 “socket access protocol layer” 。它处于 BSD socket APIs 与底层具体的协议族之间。这是一个抽象层,它起着承上启下的作用。在这一层的数据结构也有着这种特点。
3.1. Struct socket
在 user space ,通过 socket() 创建的 socket fd ,在内核中对应的就是一个 struct socket 。
socket_state state;
unsigned long flags;
struct proto_ops * ops;
struct fasync_struct * fasync_list;
struct file * file;
struct sock * sk;
wait_queue_head_t wait;
short type;
};
它定义于 include/linux/net.h 中 。
3.2. Struct proto_ops
Struct socket 的 ops 域指向一个 struct proto_ops 结构, struct proto_ops 定义于 include/linux/net.h 中,它是 socket 层提供给上层的接口,这个结构中,都是 BSD socket API 的具体实现的函数指针。
int family;
struct module * owner;
int ( * release) ( struct socket * sock);
int ( * bind) ( struct socket * sock,
struct sockaddr * myaddr,
int sockaddr_len);
int ( * connect) ( struct socket * sock,
struct sockaddr * vaddr,
int sockaddr_len, int flags);
int ( * socketpair)( struct socket * sock1,
struct socket * sock2);
int ( * accept) ( struct socket * sock,
struct socket * newsock, int flags);
int ( * getname) ( struct socket * sock,
struct sockaddr * addr,
int * sockaddr_len, int peer);
unsigned int ( * poll) ( struct file * file, struct socket * sock,
struct poll_table_struct * wait);
int ( * ioctl) ( struct socket * sock, unsigned int cmd,
unsigned long arg);
int ( * listen) ( struct socket * sock, int len);
int ( * shutdown) ( struct socket * sock, int flags);
int ( * setsockopt)( struct socket * sock, int level,
int optname, char __user * optval, int optlen);
int ( * getsockopt)( struct socket * sock, int level,
int optname, char __user * optval, int __user * optlen);
int ( * sendmsg) ( struct kiocb * iocb, struct socket * sock,
struct msghdr * m, size_t total_len);
int ( * recvmsg) ( struct kiocb * iocb, struct socket * sock,
struct msghdr * m, size_t total_len,
int flags);
int ( * mmap) ( struct file * file, struct socket * sock,
struct vm_area_struct * vma);
ssize_t ( * sendpage) ( struct socket * sock, struct page * page,
int offset, size_t size, int flags);
};
一个 socket API 通过系统调用进入内核后,首先由 socket 层处理。 Socket 层找到对应的 struct socket ,通过它找到 struct proto_ops ,然后由它所指向的函数进行进一步处理。
以 sendmsg() 这个函数为例,从 user space 通过系统调用进入 kernel 后,由 sys_sendmsg() 、 sock_sendmsg() 依次处理,然后交给 struct proto_ops 的 sendmsg() 处理。
4. 传输层、网络层的主要数据结构
Socket 层之下,是具体的协议族。
对 INET 和 INET6 来说,又分为传输层和网络层。
这两层重要的数据结构有 struct sock 和 struct proto 。
4.1. Struct sock
struct sock 定义于 include/net/sock.h 中,用于 INET 和 INET6 协议族。
应用层的 socket fd ,在 socket 层对应的是 struct socket 。 struct socket 很简单,并不做什么具体的事情,它通过 sk 域与一个 struct sock 关联。因此,对应用层的一个 socket fd 来说,在内核中对应的是一个 struct socket 加上一个 struct sock 结构, struct socket 负责 socket 层的处理, struct sock 负责传输层、网络层的处理。
在 2.4 内核中,这个结构非常杂乱,到 2.6 内核,对它做了简化,但仍然有很多成员。我们在这里不具体了解它的作用,只要知道它所处的层次即可。
4.2. Struct proto
struct sock 通过 sk_prot 域指向 struct proto 结构。
void ( * close)( struct sock * sk,
long timeout);
int ( * connect)( struct sock * sk,
struct sockaddr * uaddr,
int addr_len);
int ( * disconnect)( struct sock * sk, int flags);
struct sock * ( * accept) ( struct sock * sk, int flags, int * err);
int ( * ioctl)( struct sock * sk, int cmd,
unsigned long arg);
int ( * init)( struct sock * sk);
int ( * destroy)( struct sock * sk);
void ( * shutdown)( struct sock * sk, int how);
int ( * setsockopt)( struct sock * sk, int level,
int optname, char __user * optval,
int optlen);
int ( * getsockopt)( struct sock * sk, int level,
int optname, char __user * optval,
int __user * option);
int ( * sendmsg)( struct kiocb * iocb, struct sock * sk,
struct msghdr * msg, size_t len);
int ( * recvmsg)( struct kiocb * iocb, struct sock * sk,
struct msghdr * msg,
size_t len, int noblock, int flags,
int * addr_len);
int ( * sendpage)( struct sock * sk, struct page * page,
int offset, size_t size, int flags);
int ( * bind)( struct sock * sk,
struct sockaddr * uaddr, int addr_len);
int ( * backlog_rcv) ( struct sock * sk,
struct sk_buff * skb);
…
}
struct proto 是传输层提供给 socket 层的接口。可以看到,它的成员也都是 BSD socket API 相关的函数指针。
应用层的 socket API 调用进入内核空间后,首先由 socket 层的 struct proto_ops 结构做处理,此后,对于 INET 和 INET6 协议族来说,进一步由 struct proto 的相应函数做处理。
还是以 sendmsg() 为例,数据在 socket 层由 struct proto_ops 的 sendmsg() 处理完毕之后,会由 struct proto 的 sendmsg() 进行传输层的处理。
5. Socket 层与传输层的关联
INET 和 INET6 这两种协议族,可以支持多种传输层协议,包括 TCP 、 UDP 、 RAW ,在 2.6 内核中,又增加了一种新的传输层协议: SCTP 。
从内核角度看,要实现 INET6 协议族的某种传输层协议,则必须既提供 socket 层的 struct proto_ops 的实现,也提供 struct proto 的实现。除此之外,还需要提供一种手段,把这两个结构关联起来,也就是把 socket 层和传输层关联起来。
Linux 提供了一个 struct inet_protosw 的结构,用于 socket 层与传输层的关联。
5.1. struct inet_protosw
struct list_head list;
/* These two fields form the lookup key. */
unsigned short type; /* This is the 2nd argument to socket(2). */
int protocol; /* This is the L4 protocol number. */
struct proto * prot;
struct proto_ops * ops;
int capability; /* Which (if any) capability do
* we need to use this socket
* interface?
*/
char no_check; /* checksum on rcv/xmit/none? */
unsigned char flags; /* See INET_PROTOSW_* below. */
};
这个结构定义于 include/net/protocol.h 中,从它的命名上可以看到它属于 INET 和 INET6 协议族,但是没有查到资料为什么叫做 protosw 。
这个结构中, ops 指向 socket 层的 struct proto_ops , prot 指向传输层的 struct proto 。
因此,对 INET6 这种要支持多种传输层协议的协议族,从内核的角度来说,只需要为每一种传输层协议定义相应的 struct proto_ops 、 struct proto ,然后再定义 struct inet_protosw ,并将三者关联起来即可:
以 INET6 所支持的 TCP 为例:
.family = PF_INET6,
.owner = THIS_MODULE,
.release = inet6_release,
.bind = inet6_bind,
.connect = inet_dgram_connect, /* ok */
.socketpair = sock_no_socketpair, /* a do nothing */
.accept = sock_no_accept, /* a do nothing */
.getname = inet6_getname,
.poll = datagram_poll, /* ok */
.ioctl = inet6_ioctl, /* must change */
.listen = sock_no_listen, /* ok */
.shutdown = inet_shutdown, /* ok */
.setsockopt = sock_common_setsockopt, /* ok */
.getsockopt = sock_common_getsockopt, /* ok */
.sendmsg = inet_sendmsg, /* ok */
.recvmsg = sock_common_recvmsg, /* ok */
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
struct proto tcpv6_prot = {
.name = " TCPv6 " ,
.owner = THIS_MODULE,
.close = tcp_close,
.connect = tcp_v6_connect,
.disconnect = tcp_disconnect,
.accept = inet_csk_accept,
.ioctl = tcp_ioctl,
.init = tcp_v6_init_sock,
.destroy = tcp_v6_destroy_sock,
.shutdown = tcp_shutdown,
.setsockopt = tcp_setsockopt,
.getsockopt = tcp_getsockopt,
.sendmsg = tcp_sendmsg,
.recvmsg = tcp_recvmsg,
.backlog_rcv = tcp_v6_do_rcv,
.hash = tcp_v6_hash,
.unhash = tcp_unhash,
.get_port = tcp_v6_get_port,
.enter_memory_pressure = tcp_enter_memory_pressure,
.sockets_allocated = & tcp_sockets_allocated,
.memory_allocated = & tcp_memory_allocated,
.memory_pressure = & tcp_memory_pressure,
.orphan_count = & tcp_orphan_count,
.sysctl_mem = sysctl_tcp_mem,
.sysctl_wmem = sysctl_tcp_wmem,
.sysctl_rmem = sysctl_tcp_rmem,
.max_header = MAX_TCP_HEADER,
.obj_size = sizeof ( struct tcp6_sock),
.twsk_obj_size = sizeof ( struct tcp6_timewait_sock),
.rsk_prot = & tcp6_request_sock_ops,
};
static struct inet_protosw tcpv6_protosw = {
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = & tcpv6_prot,
.ops = & inet6_stream_ops,
.capability = - 1 ,
.no_check = 0 ,
.flags = INET_PROTOSW_PERMANENT,
};
5.2. Socket 层和传输层关联表
Linux 为 INET6 协议族定义一个 struct inet_protosw 的链表数组 inetsw6[] 。
要支持某种传输层协议,首先实现相应的 struct proto_ops 、 struct proto ,然后实现 struct inet_protosw ,将两者关联,最后,通过 inet6_register_protosw() ,将此 struct inet_protosw 注册到 inet6_sw[] 中。
注册的时候,根据 struct inet_protosw 的 type ,将它放到 inet6_sw[type] 所在的链表中,相同的 type, 不同的 protocol ,会在同一个链表上。
6. 建立数据结构之间的关系
从 user space 角度看,要使用 INET6 协议族的某种传输层协议,首先需要通过 socket() 调用创建一个相应的 socket fd ,然后再通过这个 socket fd ,接收和发送数据。
socket() 的原型是:
int socket(int domain, int type, int protocol);
domain 指定了协议族 .
type 表明在网络中通信所遵循的模式。主要的值有: SOCK_STREAM 、 SOCK_DGRAM 、 SOCK_RAW 等。
SOCK_STREAM 是面向流的通信方式,而 SOCK_DGRAM 是面向报文的通信方式。不同的通信方式,在接收数据和发送数据时,具有不同的处理方式。
Protocol 则指明具体的传输层协议。不同的传输层协议,可能具有相同的 type ,例如 TCP 和 SCTP 都是 SOCK_STREAM 类型。
以 socket(PF_INET6, SOCK_STREAM, 0) 为例,在进入内核空间后,
根据 domain ,找到 inet6_family_ops 。
创建 struct socket
调用 inet6_family_opsde create() ,也就是 inet6_create()
inet6_create() 根据 type 和 protocol 在 inet6_sw[] 中找到对应的 struct inet_protosw ,也就是 tcpv6_protosw
创建 struct sock ,将 struct socket 和 struct sock 关联起来
将 struct socket 和 tcpv6_protosw 的 ops ,也就是 inet6_stream_ops 关联起来
将 struct sock 和 tcpv6_protosw 的 prot ,也就是 tcpv6_prot 关联起来。
这样, socket 层和传输层的数据结构之间的关系建立起来了,此后,应用层通过 socket fd 接收或者发送数据的时候,就会先经过 socket 层 inet6_stream_ops 处理,然后经过传输层的 tcpv6_prot 处理。
下图描述了 socket 层与传输层之间数据结构关系的建立过程,以及发送数据时,是如何通过这些数据结构进行交互的。
7. 网络层协议类型
注册协议族,是从面向 user space 的角度来考虑的。当注册一个新的协议族后, user space 就可以创建此协议族的 socket ,并通过此 socket 来接收和发送数据了。
而从面向设备驱动层的角度来考虑,通常需要为新的协议族注册一个网络层协议类型(或者叫“包类型”)。我们知道当网络设备接收到数据包之后,经过 L2 处理之后,需要根据其网络层协议类型,进行进一步处理。对于 INET 协议族来说,就是 IP 协议,而对于 INET6 协议族来说,就是 IPv6 协议。
因此,要实现一个新的协议族,还必须提供对网络层协议包进行处理的方法。
这个结构是 struct packet_type 。
__be16 type; /* This is really htons(ether_type). */
struct net_device * dev; /* NULL is wildcarded here */
int ( * func) ( struct sk_buff * ,
struct net_device * ,
struct packet_type * ,
struct net_device * );
void * af_packet_priv;
struct list_head list;
};
这个结构中中的 func ,就是在网络层对接收到的数据包进行处理的方法。
Linux 系统中维护一个 struct packet_type 的数组 ptype_base[] ,一个新的 struct packet_type 变量通过调用 dev_add_packet() ,注册到这个数组中。
在接收数据包的过程中,根据网络层协议类型,到 ptype_base[] 数组中寻找对应的 struct packet_type 变量,然后由它的 func() 做进一步处理。
8. 传输层协议类型
同样,网络层处理完毕之后,需要交给传输层处理。因此,一个新的传输层协议需要向网络层注册处理函数。
对 INET6 协议来说,这个结构是 struct inet6_protocol
{
int ( * handler)( struct sk_buff ** skb, unsigned int * nhoffp);
void ( * err_handler)( struct sk_buff * skb,
struct inet6_skb_parm * opt,
int type, int code, int offset,
__u32 info);
unsigned int flags; /* INET6_PROTO_xxx */
};
这个结构中的 handler ,就是传输层提供给网络层的处理函数。
Linux 系统中维护一个 struct inet6_protocol 的数组 inet6_protos[] ,一个新的 struct inet6_protocol 变量通过调用 inet6_add_protocol() ,注册到这个数组中。在接收数据包的过程中,根据传输层协议类型,到 inet6_protos[] 数组中寻找对应的 struct inet6_protocol 变量,然后由它的 handler() 做进一步处理。
9. 数据包从设备驱动层向上传递处理的过程
10. INET6 的初始化
经过前面的分析,现在可以理解 INET6 协议族在初始化的时候要做哪些事情:
1、 注册 INET6 协议族,提供协议族的创建函数。
2、 为所支持的传输层协议分别提供 struct proto_ops 、 struct proto 和 struct inet_protosw 结构,并注册到关联表中。
3、 向设备驱动层注册 IPv6 数据包的处理函数
4、 向网络层注册 TCP 、 UDP 、 RAW 等传输层的处理函数。
5、 其它初始化工作
10.1. 注册 INET6 协议族
对于 INET6 的实现来说,第一步是要注册 INET6 协议族。
.family = PF_INET6,
.create = inet6_create,
.owner = THIS_MODULE,
};
sock_register( & inet6_family_ops);
inet6_create() 的实现: TBW
10.2. 为 TCP 、 UDP 、 RAW 等传输层协议提供关联表
1 、初始化关联表
INIT_LIST_HEAD(r);
2 、 RAW 的关联
.family = PF_INET6,
.owner = THIS_MODULE,
.release = inet6_release,
.bind = inet6_bind,
.connect = inet_dgram_connect, /* ok */
.socketpair = sock_no_socketpair, /* a do nothing */
.accept = sock_no_accept, /* a do nothing */
.getname = inet6_getname,
.poll = datagram_poll, /* ok */
.ioctl = inet6_ioctl, /* must change */
.listen = sock_no_listen, /* ok */
.shutdown = inet_shutdown, /* ok */
.setsockopt = sock_common_setsockopt, /* ok */
.getsockopt = sock_common_getsockopt, /* ok */
.sendmsg = inet_sendmsg, /* ok */
.recvmsg = sock_common_recvmsg, /* ok */
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
struct proto rawv6_prot = {
.name = " RAWv6 " ,
.owner = THIS_MODULE,
.close = rawv6_close,
.connect = ip6_datagram_connect,
.disconnect = udp_disconnect,
.ioctl = rawv6_ioctl,
.init = rawv6_init_sk,
.destroy = inet6_destroy_sock,
.setsockopt = rawv6_setsockopt,
.getsockopt = rawv6_getsockopt,
.sendmsg = rawv6_sendmsg,
.recvmsg = rawv6_recvmsg,
.bind = rawv6_bind,
.backlog_rcv = rawv6_rcv_skb,
.hash = raw_v6_hash,
.unhash = raw_v6_unhash,
.obj_size = sizeof ( struct raw6_sock),
};
static struct inet_protosw rawv6_protosw = {
.type = SOCK_RAW,
.protocol = IPPROTO_IP, /* wild card */
.prot = & rawv6_prot,
.ops = & inet6_sockraw_ops,
.capability = CAP_NET_RAW,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_REUSE,
};
inet6_register_protosw( & rawv6_protosw);
3 、 UDP 的关联
.family = PF_INET6,
.owner = THIS_MODULE,
.release = inet6_release,
.bind = inet6_bind,
.connect = inet_dgram_connect, /* ok */
.socketpair = sock_no_socketpair, /* a do nothing */
.accept = sock_no_accept, /* a do nothing */
.getname = inet6_getname,
.poll = udp_poll, /* ok */
.ioctl = inet6_ioctl, /* must change */
.listen = sock_no_listen, /* ok */
.shutdown = inet_shutdown, /* ok */
.setsockopt = sock_common_setsockopt, /* ok */
.getsockopt = sock_common_getsockopt, /* ok */
.sendmsg = inet_sendmsg, /* ok */
.recvmsg = sock_common_recvmsg, /* ok */
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
struct proto udpv6_prot = {
.name = " UDPv6 " ,
.owner = THIS_MODULE,
.close = udpv6_close,
.connect = ip6_datagram_connect,
.disconnect = udp_disconnect,
.ioctl = udp_ioctl,
.destroy = udpv6_destroy_sock,
.setsockopt = udpv6_setsockopt,
.getsockopt = udpv6_getsockopt,
.sendmsg = udpv6_sendmsg,
.recvmsg = udpv6_recvmsg,
.backlog_rcv = udpv6_queue_rcv_skb,
.hash = udp_v6_hash,
.unhash = udp_v6_unhash,
.get_port = udp_v6_get_port,
.obj_size = sizeof ( struct udp6_sock),
};
static struct inet_protosw udpv6_protosw = {
.type = SOCK_DGRAM,
.protocol = IPPROTO_UDP,
.prot = & udpv6_prot,
.ops = & inet6_dgram_ops,
.capability =- 1 ,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_PERMANENT,
};
inet6_register_protosw( & udpv6_protosw);
4 、 TCP 的关联
前面已经看过 TCP 相关的结构。
inet6_register_protosw(&tcpv6_protosw);
10.3. 注册 IPv6 包的接收函数
.type = __constant_htons(ETH_P_IPV6),
.func = ipv6_rcv,
};
ipv6_packet_init() ==> dev_add_pack(&ipv6_packet_type);
ipv6_rcv 的具体实现不在本文范围之内。
10.4. 注册传输层协议
.handler = udpv6_rcv,
.err_handler = udpv6_err,
.flags = INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL,
};
static struct inet6_protocol tcpv6_protocol = {
.handler = tcp_v6_rcv,
.err_handler = tcp_v6_err,
.flags = INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL,
};
inet6_add_protocol( & udpv6_protocol, IPPROTO_UDP);
inet6_add_protocol( & tcpv6_protocol, IPPROTO_TCP);
RAW 不需要注册。
Udpv6_rc 、 tcp_v6_rcv 的具体实现不在本文范围之内。
10.5. 其它
此外,还要做其它初始化工作,包括 ICMPv6 、 IGMPv6 、 Neighbor discovery 、 route 等等的初始化。本文不具体分析。
11. 附录:
| Structure | Register functions | description |
| net_proto_family | sock_register | 注册协议族 |
| packet_type | dev_add_pack | 向设备驱动层注册网络层协议处理函数 |
| inet6_protocol | inet6_add_protocol | 向网络层注册传输层协议处理函数 |
| proto_ops BSD APIs 与 socket 层的接口 |
|
|
| Proto Socket 层与传输层的接口 |
|
|
| inet_protosw 将 struct proto_ops 与 struct proto 对应起来 | inet6_register_protosw | 注册到系统的 struct inet_protosw 数组 inetsw6 中 此数组用于创建 socket 之用。 |
| Proto Socket 层与传输层的接口 | proto_register | 将传输层协议处理函数注册到系统中的 struct proto 的链表 proto_list 。 这个目的是为了在 proc 系统中显示各种协议的信息
|