TCP/IP协议栈初始化(九) ICMP带来的一段插曲

时间:2022-06-01 19:41:00

TCP的初始化告一段落。沿着inet_init函数继续向下看。(尝试用MarkDown)
1422行 注册了个轻量级的UDP协议,有什么用处暂时还不知道。根据之前TCP等协议的注册情况,只需要记着udplite_prot是它资源管理者,udplite_protocol是面向IP的接口,udplite4_protows是面向socket的接口,就可以了。
1428 icmp_init(&inet_family_ops);
很显然是初始化ICMP协议了。ICMP协议全称是Internet Control Message Protocol叫Internet控制报文协议。是IP协议层的好伙伴,之前有提到过。传入的数据结构实例,也很眼熟,之前介绍过的用户socket向inet socket传递的入口数据结构实例。去看下源码。

1069 void __init icmp_init(struct net_proto_family *ops)
1070 {
1071    struct inet_sock *inet;
1072    int i;
1074    for_each_possible_cpu(i) {
1075        int err;
1077        err = sock_create_kern(PF_INET, SOCK_RAW, IPPROTO_ICMP,
1078                       &per_cpu(__icmp_socket, i));
1080        if (err < 0)
1081            panic("Failed to create the ICMP control socket.\n");
1083        per_cpu(__icmp_socket, i)->sk->sk_allocation = GFP_ATOMIC;
1088        per_cpu(__icmp_socket, i)->sk->sk_sndbuf =
1089            (2 * ((64 * 1024) + sizeof(struct sk_buff)));
1091        inet = inet_sk(per_cpu(__icmp_socket, i)->sk); 1092 inet->uc_ttl = -1;
1093        inet->pmtudisc = IP_PMTUDISC_DONT;
1099        per_cpu(__icmp_socket, i)->sk->sk_prot->unhash(per_cpu(__icmp_socket, i)->sk); 1100 } 1101 }

又是一个简单的函数。一个循环,为每个可能的CPU都创建一个控制sock。和之前说过的TCP的控制sock有点像。剩下的内容就是初始化其相应的成员变量。不过这里还是要展开说一下的,因为看到没有现在已经可以创建socket了。之前TCP的控制socket创建时就应该展开说一个的。来吧,跟踪一下sock_create_kern的执行流程。
socket_create_kern定义在net/socket.c中,只是一层包装,真正调用的是__sock_create函数。__sock_create定义在同一个函数中。

1197 int sock_create_kern(int family, int type, int protocol, struct socket **res) 1198 { 1199    return __sock_create(&init_net, family, type, protocol, res, 1);
}

注意到,__sock_create此时多了一个参数init_net,定义在net/core/net_namespace.c上,类型为struct net,用来内核中网络子系统的命名空间管理。__sock_create代码如下:

1079 static int __sock_create(struct net *net, int family, int type, int protocol,
1080             struct socket **res, int kern)
1081 {
1082    int err;
1083    struct socket *sock;
1084    const struct net_proto_family *pf;
1089    if (family < 0 || family >= NPROTO)
1090        return -EAFNOSUPPORT;
1091    if (type < 0 || type >= SOCK_MAX)
1092        return -EINVAL;
1094    /* Compatibility.
1096       This uglymoron is moved from INET layer to here to avoid
1097       deadlock in module load.
1099    if (family == PF_INET && type == SOCK_PACKET) {
1100        static int warned;
1101        if (!warned) {
1102            warned = 1;
1103            printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n",
1104                   current->comm);
1105        }
1106        family = PF_PACKET;
1107    }
1109    err = security_socket_create(family, type, protocol, kern);
1110    if (err)
1111        return err;
1118    sock = sock_alloc();
1119    if (!sock) {
1120        if (net_ratelimit())
1121            printk(KERN_WARNING "socket: no more sockets\n");
1122        return -ENFILE; /* Not exactly a match, but its the
1123                   closest posix thing */
1124    }
1126    sock->type = type;
1139    rcu_read_lock();
1140    pf = rcu_dereference(net_families[family]);
1141    err = -EAFNOSUPPORT;
1142    if (!pf)
1143        goto out_release;
1149    if (!try_module_get(pf->owner))
1150        goto out_release;
1153    rcu_read_unlock();
1155    err = pf->create(net, sock, protocol);
1156    if (err < 0)
1157        goto out_module_put;
1163    if (!try_module_get(sock->ops->owner))
1164        goto out_module_busy;
1170    module_put(pf->owner);
1171    err = security_socket_post_create(sock, family, type, protocol, kern);
1172    if (err)
1173        goto out_sock_release;
1174    *res = sock;
1176    return 0;
1190 }

函数的流程也不复杂,先是进行了一系列的参数检查,linux稳定的性能离不开对参数的严格检查,这几乎是linux所有函数的特点。我们假设传入的参数都没有问题。来看下主要的函数调用。
1109 security_socket_create 如果内核在编译时支持了网络安全模块,这个函数就会做一些实际的工作,我们假设不支持此选项。这个函数就简单地返回0。同样的还有 security_socket_post_create这个函数。
1118. sock_alloc 这个函数调用才是生成了真正的socket,注意不是strcut sock而是struct socket,面向用户的socket。这个函数的内容并不值得去看,和协议没有什么关系。只是分配了文件系统里的结点而已,分配了一定的内存,由于系统现在还不知道底层具体是什么样的协议,需要多少内存还不确定。
1155 这里是一个成员函数的调用。这是我们的兴趣点,要想知道底层是什么样socket被创建,就要追溯下这个成员函数的前因后果了。
首先看pf,在1140行被赋值,是一个解引用操作,决定性的变量是net_families和family两个。其中net_families在之前已经有讲过。family是我们调用sock_create_kern时传入的参数,值为PF_INET。在本系列第3篇中有说到过inet_family_ops被挂到net_families中时,它的索引就是inet_family_ops.family也就是PF_INET。
1140 pf = rcu_dereference(net_families[family]);
所以,这里pf就是指向inet_family_ops的指针了。那么pf->create调用的自然就是inet_create了。这就是为什么说inet_family_ops,在“创建特定协议的SOCKET时,系统正是在这里找到对应的协议入口”。
inet_create的内容很长。先说一半吧。定义在net/ipv4/af_inet.c中。从函数开始到289行。用来寻找协议与类型对应的inet_protosw。还记得循环中的inetsw列表吗?在本系列第四篇中被写入了tcp_prot udp_prot raw_prot三个成员。这里是根据sock->type来索引,而sock在创建之后sock->type被置为最初调用时的type参数就是SOCK_RAW。解引用时,得到是raw_prot对应的实例。第二参数protocol为IPPROTO_ICMP,最后得到的answer是raw_prot对应的在inetsw中元素。
剩下的部分里,313-320 根据索引到的answer对sock进行赋值,初始化。

244 static int inet_create(struct net *net, struct socket *sock, int protocol)
245 {
246     struct sock *sk;
247     struct list_head *p;
248     struct inet_protosw *answer;
249     struct inet_sock *inet;
250     struct proto *answer_prot;
251     unsigned char answer_flags;
252     char answer_no_check;
253     int try_loading_module = 0;
254     int err;
256     if (net != &init_net)
257         return -EAFNOSUPPORT;
259     if (sock->type != SOCK_RAW &&
260         sock->type != SOCK_DGRAM &&
261         !inet_ehash_secret)
262         build_ehash_secret();
264     sock->state = SS_UNCONNECTED;
267     answer = NULL;
268 lookup_protocol:
269     err = -ESOCKTNOSUPPORT;
270     rcu_read_lock();
271     list_for_each_rcu(p, &inetsw[sock->type]) {
272         answer = list_entry(p, struct inet_protosw, list);
275         if (protocol == answer->protocol) {
276             if (protocol != IPPROTO_IP)
277                 break;
278         } else {
280             if (IPPROTO_IP == protocol) {
281                 protocol = answer->protocol;
282                 break;
283             }
284             if (IPPROTO_IP == answer->protocol)
285                 break;
286         }
287         err = -EPROTONOSUPPORT;
288         answer = NULL;
289     }

313     err = -EPERM;
314     if (answer->capability > 0 && !capable(answer->capability))
315         goto out_rcu_unlock;
317     sock->ops = answer->ops;
318     answer_prot = answer->prot;
319     answer_no_check = answer->no_check;
320     answer_flags = answer->flags;

这个函数余下的代码下次再说。不难看到,通过一次创建socket的函数调用,可以把整个协议栈的数据结构实例串起来了。