前面了解到网络初始化申请了两块skb高速缓存和创建了一个/proc/net/protocols文件,现在开始重头戏,网络协议栈的初始化。这篇文章主要介绍网络栈中使用到的主要数据结构。
网络协议栈的内核实现和理论上的分层有些不一样,在代码里面的分层如下图:
开始前,先回顾一下应用层socket函数的调用,它会创建一个socket并返回对应的描述符:
int socket(int domain, int type, int protocol);
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
- domain:即协议域,又称为协议族(family)。常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
- type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
- protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
1. static struct net_proto_family *net_families[NPROTO]
~/linux-4.12/include/linux/net.h第一个重要的结构体是 net_proto_family。
200 struct net_proto_family {
201 int family; //地址族类型
202 int (*create)(struct net *net, struct socket *sock, //套接字的创建方法
203 int protocol, int kern);
204 struct module *owner;
205 };
socket.c
210 #define AF_MAX 44 /* For now.. */
24 #define NPROTO AF_MAX
163 static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly;
在这之前必须知道 一些概念——地址族和套接字类型。 大家都知道所谓的套接字都有地址族,实际就是套接字接口的种类, 每种套接字种类有自己的通信寻址方法。 Linux 将不同的地址族抽象统一为 BSD 套接字接口,应用程序 关心的只是 BSD 套接字接口,通过参数来指定所使用的套接字地址族。
Linux 内 核 中 为 了 支 持 多 个 地 址 族 , 定 义 了 这 么 一 个 变 量 : static struct net_proto_family *net_families[NPROTO], NPROTO 等于 44, 也就是说 Linux 内核支持最多 44种地址族。不过目前已经 够用了, 我们常用的不外乎就是 PF_UNIX( 1)、 PF_INET( 2)、 PF_NETLINK( 16), Linux 还有一个自 有的 PF_PACKET( 17),即对网卡进行操作的选项。所以这个链表里面存放的是应用层socket()的第一个参数,它决定了这个参数可以取哪些值。当系统调用socket转到内核处理的时候,它首先会用第一个参数查找需要在哪个域里面创建套接字。
在网络子系统中,net_families[NPROTO]是一个全局的链表,它存放的是不同的地址族,不同地址族套接字有不同的创建方法,下面我们关注的是PF_INET地址族的注册。
在inet_init()中会调用这个函数注册地址族:(void)sock_register(&inet_family_ops); 其中inet_family_ops是struct net_proto_family的结构体对象
1014 static const struct net_proto_family inet_family_ops = {结构体的内容比较简单,一个是地址族的标号,一个是在该地址族里创建socket时调用的创建函数inet_create。当我们通过系统调用socket()创建PF_INET地址族的套接字时,内核使用的创建函数时inet_create,这里只需要记住,后面会分析创建过程。
1015 .family = PF_INET,
1016 .create = inet_create,
1017 .owner = THIS_MODULE,
1018 };
下面是sock_register的实现过程,详细内容也可以 查看这里
2490 int sock_register(const struct net_proto_family *ops)
2491 {
2492 int err;
2493
2494 if (ops->family >= NPROTO) {
2495 pr_crit("protocol %d >= NPROTO(%d)\n", ops->family, NPROTO);
2496 return -ENOBUFS;
2497 }
2498
2499 spin_lock(&net_family_lock);
2500 if (rcu_dereference_protected(net_families[ops->family],
2501 lockdep_is_held(&net_family_lock)))
2502 err = -EEXIST;
2503 else {
2504 rcu_assign_pointer(net_families[ops->family], ops); //将inet_family_ops对象添加到net_families全局数组里面,就完成了初始化
2505 err = 0;
2506 }
2507 spin_unlock(&net_family_lock);
2508
2509 pr_info("NET: Registered protocol family %d\n", ops->family);
2510 return err;
2511 }
2512 EXPORT_SYMBOL(sock_register);
2. static struct inet_protosw inetsw_array[]
79 /* This is used to register socket interfaces for IP protocols. */
80 struct inet_protosw {
81 struct list_head list;
82
83 /* These two fields form the lookup key. */
84 unsigned short type; /* This is the 2nd argument to socket(2). */
85 unsigned short protocol; /* This is the L4 protocol number. */
86
87 struct proto *prot;
88 const struct proto_ops *ops;
89
90 unsigned char flags; /* See INET_PROTOSW_* below. */
91 };
92 #define INET_PROTOSW_REUSE 0x01 /* Are ports automatically reusable? */
93 #define INET_PROTOSW_PERMANENT 0x02 /* Permanent protocols are unremovable. */
94 #define INET_PROTOSW_ICSK 0x04 /* Is this an inet_connection_sock? */
前面一直疑惑inet_protosw中的sw表示什么意思,后来才知道,原来是switch的缩写,表示inet层的协议切换,inetsw串联着PF_INET地址族所支持的协议类型,比如tcp, udp等。
这个函数起着承上启下的作用,它上层对应的是BSD层,下层对应的是inet层,它在网络协议栈的最上两层扮演着重要的角色。
这个结构体每个注释已经比较清楚了,但是这里还是再讲一下:
1. type和protocol两个域组成了socket的查找key,他们分别对应着应用层socket函数的第二和第三个参数,通过这两个参数来确定使用的是哪种socket类型。查找的时候主要是查找type,protocol只是用来防止初始化好的协议被再次初始化,比如开机的时候已经初始化好了TCP协议,如果应用层又调用了该协议的初始化函数,将直接退出。
2. prot表示的是该协议相关的处理函数,比如tcp_v4_connect和tcp_v4_init_sock等相关的协议具体处理函数
3. ops表示的是该socket类型的套接字的管理函数,比如处理inet_bind和inet_accept等对inet层的套接字调用
prot和ops都是操作函数的集合,非常重要,我们会在后面进一步分析
4. flags用来标识该协议的一些特性,比如TCP协议的端口是否可自动重用,该协议是否可以被移除,也就是说该协议是否可以从inetsw_arry全局数组中删除,当然对于TCP,UDP这些必备的协议是不能被移除的
static struct list_head inetsw[SOCK_MAX];inetsw是一个数组,它里面存放的对象是inet_protosw,也就是说PF_INET地址族中所支持的socket类型都存放在这个数组中,当socke调用的时候,将使用第二个参数type到这个数组中查找对应的inet_protosw对象。
1020 /* Upon startup we insert all the elements in inetsw_array[] into
1021 * the linked list inetsw.
1022 */
1023 static struct inet_protosw inetsw_array[] =
1024 {
1025 {
1026 .type = SOCK_STREAM,
1027 .protocol = IPPROTO_TCP,
1028 .prot = &tcp_prot,
1029 .ops = &inet_stream_ops,
1030 .flags = INET_PROTOSW_PERMANENT |
1031 INET_PROTOSW_ICSK,
1032 },
1033
1034 {
1035 .type = SOCK_DGRAM,
1036 .protocol = IPPROTO_UDP,
1037 .prot = &udp_prot,
1038 .ops = &inet_dgram_ops,
1039 .flags = INET_PROTOSW_PERMANENT,
1040 },
1041
1042 {
1043 .type = SOCK_DGRAM,
1044 .protocol = IPPROTO_ICMP,
1045 .prot = &ping_prot,
1046 .ops = &inet_sockraw_ops,
1047 .flags = INET_PROTOSW_REUSE,
1048 },
1050 {
1051 .type = SOCK_RAW,
1052 .protocol = IPPROTO_IP, /* wild card */
1053 .prot = &raw_prot,
1054 .ops = &inet_sockraw_ops,
1055 .flags = INET_PROTOSW_REUSE,
1056 }
1057 };
1058
1059 #define INETSW_ARRAY_LEN ARRAY_SIZE(inetsw_array)
inetsw_array只在初始化的时候用到,这个数组里面写死了初始化的时候哪些协议和套接字类型是必须支持的,在初始化的时候,这些套接字类型都会添加到inetsw数组中:
1845 /* Register the socket-side information for inet_create. */
1846 for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
1847 INIT_LIST_HEAD(r);
1848
1849 for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
1850 inet_register_protosw(q);
1851
3. struct proto_ops
这个结构体的定义就不贴上来了,我们直接看一下它初始化好的一个对象就知道怎么回事了:
926 const struct proto_ops inet_stream_ops = {
927 .family = PF_INET,
928 .owner = THIS_MODULE,
929 .release = inet_release,
930 .bind = inet_bind,
931 .connect = inet_stream_connect,
932 .socketpair = sock_no_socketpair,
933 .accept = inet_accept,
934 .getname = inet_getname,
935 .poll = tcp_poll,
936 .ioctl = inet_ioctl,
937 .listen = inet_listen,
938 .shutdown = inet_shutdown,
939 .setsockopt = sock_common_setsockopt,
940 .getsockopt = sock_common_getsockopt,
941 .sendmsg = inet_sendmsg,
942 .recvmsg = inet_recvmsg,
943 .mmap = sock_no_mmap,
944 .sendpage = inet_sendpage,
945 .splice_read = tcp_splice_read,
946 .read_sock = tcp_read_sock,
947 .peek_len = tcp_peek_len,
948 #ifdef CONFIG_COMPAT
949 .compat_setsockopt = compat_sock_common_setsockopt,
950 .compat_getsockopt = compat_sock_common_getsockopt,
951 .compat_ioctl = inet_compat_ioctl,
952 #endif
953 };
954 EXPORT_SYMBOL(inet_stream_ops);
可以看到,这个proto_ops存放的是不同套接字类型的套接字管理函数集,也就是socket函数的第二个参数指定的type,不同的套接字类型有不同的管理函数集,我们常常接触到的套接字类型有以下类型:
15 /** sock_type - Socket types
16 *
17 * Please notice that for binary compat reasons MIPS has to
18 * override the enum sock_type in include/linux/net.h, so
19 * we define ARCH_HAS_SOCKET_TYPES here.
20 *
21 * @SOCK_DGRAM - datagram (conn.less) socket
22 * @SOCK_STREAM - stream (connection) socket
23 * @SOCK_RAW - raw socket
24 * @SOCK_RDM - reliably-delivered message
25 * @SOCK_SEQPACKET - sequential packet socket
26 * @SOCK_PACKET - linux specific way of getting packets at the dev level.
27 * For writing rarp and other similar things on the user level.
28 */
29 enum sock_type {
30 SOCK_DGRAM = 1,
31 SOCK_STREAM = 2,
32 SOCK_RAW = 3,
33 SOCK_RDM = 4,
34 SOCK_SEQPACKET = 5,
35 SOCK_DCCP = 6,
36 SOCK_PACKET = 10,
37 };
所以不同的套接字类型都有不同的套接字管理函数集体,在初始化的时候,会将proto_ops对象赋值给net_protosw结构体中的ops指针,从而建立两者的联系
1023 static struct inet_protosw inetsw_array[] =
1024 {
1025 {
1026 .type = SOCK_STREAM,
1027 .protocol = IPPROTO_TCP,
1028 .prot = &tcp_prot,
1029 .ops = &inet_stream_ops,
1030 .flags = INET_PROTOSW_PERMANENT |
1031 INET_PROTOSW_ICSK,
1032 },
4. struct proto
这个结构体里面存放的是不同协议的操作函数集,也就是具体协议的实现。这里需要注意和proto_ops的区分,struct proto_ops是根据套接字的类型(参数二type: SOCK_STREAM, SOCK_DGRAM...)不同而组成不同的套接字管理函数集,它面向的是套接字的管理; struct proto是根据套接字的协议类型(参数三protocol: IPPROTO_TCP, IPPROTO_UDP,)不同而组成的不同协议管理函数集,它面向的是具体协议的实现。
27 enum {
28 IPPROTO_IP = 0, /* Dummy protocol for TCP */
29 #define IPPROTO_IP IPPROTO_IP
30 IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
31 #define IPPROTO_ICMP IPPROTO_ICMP
32 IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
33 #define IPPROTO_IGMP IPPROTO_IGMP
34 IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
35 #define IPPROTO_IPIP IPPROTO_IPIP
36 IPPROTO_TCP = 6, /* Transmission Control Protocol */
37 #define IPPROTO_TCP IPPROTO_TCP
38 IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
39 #define IPPROTO_EGP IPPROTO_EGP
40 IPPROTO_PUP = 12, /* PUP protocol */
41 #define IPPROTO_PUP IPPROTO_PUP
42 IPPROTO_UDP = 17, /* User Datagram Protocol */
43 #define IPPROTO_UDP IPPROTO_UDP
....
下面主要贴上TCP和UDP的实例,篇幅有点多,但是很重要,真的很重要!
2365 struct proto tcp_prot = {
2366 .name = "TCP",
2367 .owner = THIS_MODULE,
2368 .close = tcp_close,
2369 .connect = tcp_v4_connect,
2370 .disconnect = tcp_disconnect,
2371 .accept = inet_csk_accept,
2372 .ioctl = tcp_ioctl,
2373 .init = tcp_v4_init_sock,
2374 .destroy = tcp_v4_destroy_sock,
2375 .shutdown = tcp_shutdown,
2376 .setsockopt = tcp_setsockopt,
2377 .getsockopt = tcp_getsockopt,
2378 .keepalive = tcp_set_keepalive,
2379 .recvmsg = tcp_recvmsg,
2380 .sendmsg = tcp_sendmsg,
2381 .sendpage = tcp_sendpage,
2382 .backlog_rcv = tcp_v4_do_rcv,
2383 .release_cb = tcp_release_cb,
2384 .hash = inet_hash,
2385 .unhash = inet_unhash,
2386 .get_port = inet_csk_get_port,
2387 .enter_memory_pressure = tcp_enter_memory_pressure,
2388 .stream_memory_free = tcp_stream_memory_free,
2389 .sockets_allocated = &tcp_sockets_allocated,
2390 .orphan_count = &tcp_orphan_count,
2391 .memory_allocated = &tcp_memory_allocated,
2392 .memory_pressure = &tcp_memory_pressure,
2393 .sysctl_mem = sysctl_tcp_mem,
2394 .sysctl_wmem = sysctl_tcp_wmem,
2395 .sysctl_rmem = sysctl_tcp_rmem,
2396 .max_header = MAX_TCP_HEADER,
2397 .obj_size = sizeof(struct tcp_sock),
2398 .slab_flags = SLAB_TYPESAFE_BY_RCU,
2399 .twsk_prot = &tcp_timewait_sock_ops,
2400 .rsk_prot = &tcp_request_sock_ops,
2401 .h.hashinfo = &tcp_hashinfo,
2402 .no_autobind = true,
2403 #ifdef CONFIG_COMPAT
2404 .compat_setsockopt = compat_tcp_setsockopt,
2405 .compat_getsockopt = compat_tcp_getsockopt,
2406 #endif
2407 .diag_destroy = tcp_abort,
2408 };
2409 EXPORT_SYMBOL(tcp_prot);
2354 struct proto udp_prot = {
2355 .name = "UDP",
2356 .owner = THIS_MODULE,
2357 .close = udp_lib_close,
2358 .connect = ip4_datagram_connect,
2359 .disconnect = udp_disconnect,
2360 .ioctl = udp_ioctl,
2361 .init = udp_init_sock,
2362 .destroy = udp_destroy_sock,
2363 .setsockopt = udp_setsockopt,
2364 .getsockopt = udp_getsockopt,
2365 .sendmsg = udp_sendmsg,
2366 .recvmsg = udp_recvmsg,
2367 .sendpage = udp_sendpage,
2368 .release_cb = ip4_datagram_release_cb,
2369 .hash = udp_lib_hash,
2370 .unhash = udp_lib_unhash,
2371 .rehash = udp_v4_rehash,
2372 .get_port = udp_v4_get_port,
2373 .memory_allocated = &udp_memory_allocated,
2374 .sysctl_mem = sysctl_udp_mem,
2375 .sysctl_wmem = &sysctl_udp_wmem_min,
2376 .sysctl_rmem = &sysctl_udp_rmem_min,
2377 .obj_size = sizeof(struct udp_sock),
2378 .h.udp_table = &udp_table,
2379 #ifdef CONFIG_COMPAT
2380 .compat_setsockopt = compat_udp_setsockopt,
2381 .compat_getsockopt = compat_udp_getsockopt,
2382 #endif
2383 .diag_destroy = udp_abort,
2384 };
2385 EXPORT_SYMBOL(udp_prot);
所以不同的协议类型都有不同的协议实现函数集体,在初始化的时候,会将proto对象赋值给net_protosw结构体中的prot指针,从而建立两者的联系
1023 static struct inet_protosw inetsw_array[] =
1024 {
1025 {
1026 .type = SOCK_STREAM,
1027 .protocol = IPPROTO_TCP,
1028 .prot = &tcp_prot,
1029 .ops = &inet_stream_ops,
1030 .flags = INET_PROTOSW_PERMANENT |
1031 INET_PROTOSW_ICSK,
1032 },
不仅如此, stuct_proto对象自己也维护了一个链表,串连在proto_list全局链表后面, 下面来看看它是怎么做的
146 static LIST_HEAD(proto_list);
1796 static int __init inet_init(void)
1797 {
1798 struct inet_protosw *q;
1799 struct list_head *r;
1800 int rc = -EINVAL;
1801
1802 sock_skb_cb_check_size(sizeof(struct inet_skb_parm));
1803
1804 rc = proto_register(&tcp_prot, 1);
1805 if (rc)
1806 goto out;
3049 int proto_register(struct proto *prot, int alloc_slab)3050 {3051 if (alloc_slab) {3052 prot->slab = kmem_cache_create(prot->name, prot->obj_size, 0,3053 SLAB_HWCACHE_ALIGN | prot->slab_flags,3054 NULL);30553056 if (prot->slab == NULL) {3057 pr_crit("%s: Can't create sock SLAB cache!\n",3058 prot->name);3059 goto out;3060 }30613062 if (req_prot_init(prot))3063 goto out_free_request_sock_slab;30643065 if (prot->twsk_prot != NULL) {3066 prot->twsk_prot->twsk_slab_name = kasprintf(GFP_KERNEL, "tw_sock_%s", prot->name);30673068 if (prot->twsk_prot->twsk_slab_name == NULL)3069 goto out_free_request_sock_slab;30703071 prot->twsk_prot->twsk_slab =3072 kmem_cache_create(prot->twsk_prot->twsk_slab_name,3073 ....3080 }3082 mutex_lock(&proto_list_mutex);3083 list_add(&prot->node, &proto_list);3084 assign_proto_idx(prot);3085 mutex_unlock(&proto_list_mutex);3086 return 0;3087 .....3097 }3098 EXPORT_SYMBOL(proto_register);
我们可以看到,在inet_inet函数刚开始就调用了proto_register注册struct proto对象,第一个参数传的是协议,第二个传的是内存分配方式,如果是1表示在高速缓存中分配空间,0则在内存中分配。因为tcp这些协议经常使用,所以分配在高速缓存里面比较合适。
从proto_register的实现可以看出,申请好空间以后,则将tcp_proto对象添加到全局链表proto_list里面,方便后面的查找。其他协议也一样,比如tcp_prot、udp_proto、raw_proto、ping_proto都会在初始化的时候添加到proto_list链表里面,初始化时候添加的都是不可卸载的,然而关于其他的一些协议则会在开机完后通过注册的方式,动态注册添加或卸载。
5. struct net_protocol
40 /* This is used to register protocols. */
41 struct net_protocol {
42 void (*early_demux)(struct sk_buff *skb);
43 void (*early_demux_handler)(struct sk_buff *skb);
44 int (*handler)(struct sk_buff *skb);
45 void (*err_handler)(struct sk_buff *skb, u32 info);
46 unsigned int no_policy:1,
47 netns_ok:1,
48 /* does the protocol do more stringent
49 * icmp tag validation than simple
50 * socket lookup?
51 */
52 icmp_strict_tag_validation:1;
53 };
这个结构体比较简单,但他是inet层和网络层(IP层)之间的连接,所以也很重要。
1. 第一个和第二个参数是查找基于包多路径选路,对于需要打了转包的设备,还是需要关闭这个功能,可以查看这里了解它
2. handler表示对应协议包的收包处理函数,当收到一个包的时候,在IP层将会判断这个包的协议,然后根据协议类型调用该结构中的收包函数,进而将包传给传输层处理
3. netns_ok表示是否支持虚拟网络? namespace?
下面列以下该对象的一些实例:
1598 #ifdef CONFIG_IP_MULTICAST
1599 static const struct net_protocol igmp_protocol = {
1600 .handler = igmp_rcv,
1601 .netns_ok = 1,
1602 };
1603 #endif
1604
1605 static struct net_protocol tcp_protocol = {
1606 .early_demux = tcp_v4_early_demux,
1607 .early_demux_handler = tcp_v4_early_demux,
1608 .handler = tcp_v4_rcv,
1609 .err_handler = tcp_v4_err,
1610 .no_policy = 1,
1611 .netns_ok = 1,
1612 .icmp_strict_tag_validation = 1,
1613 };
1614
1615 static struct net_protocol udp_protocol = {
1616 .early_demux = udp_v4_early_demux,
1617 .early_demux_handler = udp_v4_early_demux,
1618 .handler = udp_rcv,
1619 .err_handler = udp_err,
1620 .no_policy = 1,
1621 .netns_ok = 1,
1622 };
1623
1624 static const struct net_protocol icmp_protocol = {
1625 .handler = icmp_rcv,
1626 .err_handler = icmp_err,
1627 .no_policy = 1,
1628 .netns_ok = 1,
1629 };
这些实例在inet_init函数中,通过以下代码将不同的协议接收函数添加到inet_protos[protocol] 全局链表中,从而完成IP层和传输层的衔接
1830 /*
1831 * Add all the base protocols.
1832 */
1833
1834 if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
1835 pr_crit("%s: Cannot add ICMP protocol\n", __func__);
1836 if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
1837 pr_crit("%s: Cannot add UDP protocol\n", __func__);
1838 if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
1839 pr_crit("%s: Cannot add TCP protocol\n", __func__);
1840 #ifdef CONFIG_IP_MULTICAST
1841 if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
1842 pr_crit("%s: Cannot add IGMP protocol\n", __func__);
1843 #endif
31 struct net_protocol __rcu *inet_protos[MAX_INET_PROTOS] __read_mostly;
32 const struct net_offload __rcu *inet_offloads[MAX_INET_PROTOS] __read_mostly;
33 EXPORT_SYMBOL(inet_offloads);
34
35 int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol)
36 {
37 if (!prot->netns_ok) {
38 pr_err("Protocol %u is not namespace aware, cannot register.\n",
39 protocol);
40 return -EINVAL;
41 }
42
43 return !cmpxchg((const struct net_protocol **)&inet_protos[protocol],
44 NULL, prot) ? 0 : -1;
45 }
46 EXPORT_SYMBOL(inet_add_protocol);
5. struct packet_type ptype_base[]
以上是关于协议栈框架的搭建,对于传输层以上的协议实现来说,已经差不多初始化好了,但是对于 IP 层接收流程,则还不够。因为对于 发送过程,直接调用 的是IP 层函数;而对于内核接收过程则分为 2 层: 上层需要有一个接收函数解复用传输 协议报文,我们已经介绍了, 而下层需要一个接收函数解复用网络层报文。对报文感兴趣的底层(IP层)协议目 前有两个,一个是 ARP,一个是 IP, 报文从设备层送到上层之前,必须区分是 IP 报文还是 ARP 报文。 然后才能往上层送。 这个过程由一个数据结构来抽象,叫 packet_type{},定义在linux/netdevice.h
2204 struct packet_type {
2205 __be16 type; /* This is really htons(ether_type). */
2206 struct net_device *dev; /* NULL is wildcarded here */
2207 int (*func) (struct sk_buff *,
2208 struct net_device *,
2209 struct packet_type *,
2210 struct net_device *);
2211 bool (*id_match)(struct packet_type *ptype,
2212 struct sock *sk);
2213 void *af_packet_priv;
2214 struct list_head list;
2215 };
1. type:网络层的报文类型,目前主要是IP和ARP
2. dev:指向我们希望接收到包的那个接口的 net device 结构。如果是 NULL,则我们会从任何一个网络接口上收到包
如果某个 packet_type{}被注册到系统中, 那么它就被挂接到全局链表中( 有 2 个,见下面的解说),list 就是代表链表节点inet_init 函数最后调用了一个 dev_add_pack 函数,不仅是 inet_init 函数调用,有一个很很重要的模块也调用了它,就是 ARP 模块,我们会在后面的章节看到它是如何调用 dev_add_pack 函数的。也就是说在网络栈初始化的时候,会添加IP协议到链表中,在ARP初始化的时候,会将ARP协议也添加到这个链表中
下面来看看他的初始化过程:
~/linux-4.12/include/uapi/linux/if_ether.h
45 #define ETH_P_LOOP 0x0060 /* Ethernet Loopback packet */
46 #define ETH_P_PUP 0x0200 /* Xerox PUP packet */
47 #define ETH_P_PUPAT 0x0201 /* Xerox PUP Addr Trans packet */
48 #define ETH_P_TSN 0x22F0 /* TSN (IEEE 1722) packet */
49 #define ETH_P_IP 0x0800 /* Internet Protocol packet */
50 #define ETH_P_X25 0x0805 /* CCITT X.25 */
51 #define ETH_P_ARP 0x0806 /* Address Resolution packet */
117 #define ETH_P_ALL 0x0003 /* Every packet (be careful!!!) */ 抓包模式
1791 static struct packet_type ip_packet_type __read_mostly = {
1792 .type = cpu_to_be16(ETH_P_IP),
1793 .func = ip_rcv,
1794 };
1903 dev_add_pack(&ip_packet_type);
364 /* 365 * Add a protocol ID to the list. Now that the input handler is 366 * smarter we can dispense with all the messy stuff that used to be 367 * here. 368 * 369 * BEWARE!!! Protocol handlers, mangling input packets, 370 * MUST BE last in hash buckets and checking protocol handlers 371 * MUST start from promiscuous ptype_all chain in net_bh. 372 * It is true now, do not change it. 373 * Explanation follows: if protocol handler, mangling packet, will 374 * be the first on list, it is not able to sense, that packet 375 * is cloned and should be copied-on-write, so that it will 376 * change it and subsequent readers will get broken packet. 377 * --ANK (980803) 378 */ 379 380 static inline struct list_head *ptype_head(const struct packet_type *pt) 381 { 382 if (pt->type == htons(ETH_P_ALL)) 383 return pt->dev ? &pt->dev->ptype_all : &ptype_all; 384 else 385 return pt->dev ? &pt->dev->ptype_specific : 386 &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK]; 387 }
389 /** 390 * dev_add_pack - add packet handler 391 * @pt: packet type declaration 392 * 393 * Add a protocol handler to the networking stack. The passed &packet_type 394 * is linked into kernel lists and may not be freed until it has been 395 * removed from the kernel lists. 396 * 397 * This call does not sleep therefore it can not 398 * guarantee all CPU's that are in middle of receiving packets 399 * will see the new packet type (until the next received packet). 400 */ 401 402 void dev_add_pack(struct packet_type *pt) 403 { 404 struct list_head *head = ptype_head(pt); 405 406 spin_lock(&ptype_lock); 407 list_add_rcu(&pt->list, head); 408 spin_unlock(&ptype_lock); 409 } 410 EXPORT_SYMBOL(dev_add_pack);
在~/linux-4.12/include/uapi/linux/if_ether.h这个文件里面有定义不同 网络层的协议,其中包括IP、ARP、RARP,IPX,IPV6等等协议,每一种协议通过向ptype_base[]数组张注册一个元素进行登记,当然不是每个协议都会进行登记,有用到的才会。这样就维护了一张全局的ptype_base[]数组,每一个包网络层使用的是哪种协议都可以来这里来查,查到匹配的协议以后,就调用对应的处理函数。
比如下面这个是pcket_type结构体指定的IP协议的实例,名字是ip_packet_type.
1791 static struct packet_type ip_packet_type __read_mostly = {inet_init函数通过调用dev_add_pack(&ip_packet_type)将IP协议添加到ptype_base[]数组中而成为一员,而且对应的处理函数是ip_rcv. 当网络层收到一个包时,它检查完包的合理性后,查看这个包的网络层协议是哪个,匹配到是ETH_P_IP后,就调用ip_rcv函数进行处理,这样一个包就从设备接口层进入到了IP层。
1792 .type = cpu_to_be16(ETH_P_IP),
1793 .func = ip_rcv,
1794 };
那一个包是怎么从IP层进入到传输层的呢?这个我们后面分析。