学习Linux-4.12内核网路协议栈(1.5)——协议栈的初始化(inet_init主要数据结构)

前面了解到网络初始化申请了两块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_INETAF_INET6AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
  • type:指定socket类型。常用的socket类型有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等(socket的类型有哪些?)。
  • protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_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 
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;
第一个重要的结构体是 net_proto_family。

在这之前必须知道 一些概念——地址族和套接字类型。 大家都知道所谓的套接字都有地址族,实际就是套接字接口的种类, 每种套接字种类有自己的通信寻址方法。 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 = {
1015     .family = PF_INET,
1016     .create = inet_create,
1017     .owner  = THIS_MODULE,
1018 };
结构体的内容比较简单,一个是地址族的标号,一个是在该地址族里创建socket时调用的创建函数inet_create。当我们通过系统调用socket()创建PF_INET地址族的套接字时,内核使用的创建函数时inet_create,这里只需要记住,后面会分析创建过程。
下面是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);
3055
3056         if (prot->slab == NULL) {
3057             pr_crit("%s: Can't create sock SLAB cache!\n",
3058                 prot->name);
3059             goto out;
3060         }
3061
3062         if (req_prot_init(prot))
3063             goto out_free_request_sock_slab;
3064
3065         if (prot->twsk_prot != NULL) {
3066             prot->twsk_prot->twsk_slab_name = kasprintf(GFP_KERNEL, "tw_sock_%s", prot->name);
3067
3068             if (prot->twsk_prot->twsk_slab_name == NULL)
3069                 goto out_free_request_sock_slab;
3070
3071             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,则我们会从任何一个网络接口上收到包

3. af_packet_priv: 如果某个 packet_type{}被注册到系统中, 那么它就被挂接到全局链表中( 有 2 个,见下面的解说),list 就是代表链表节点

如果某个 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 = {
1792     .type = cpu_to_be16(ETH_P_IP),
1793     .func = ip_rcv,
1794 };
inet_init函数通过调用dev_add_pack(&ip_packet_type)将IP协议添加到ptype_base[]数组中而成为一员,而且对应的处理函数是ip_rcv. 当网络层收到一个包时,它检查完包的合理性后,查看这个包的网络层协议是哪个,匹配到是ETH_P_IP后,就调用ip_rcv函数进行处理,这样一个包就从设备接口层进入到了IP层。

那一个包是怎么从IP层进入到传输层的呢?这个我们后面分析。


6.总结







  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值