http://blog.chinaunix.net/uid-13746440-id-3073729.html
http://blog.chinaunix.net/uid-13746440-id-3076317.html
inet_bind
说 bind(2)的最重要的作用就是为套接字绑定地址和端口,那么要分析inet_bind()之前,要搞清楚
的一件事情就是,这个绑定是绑定到哪儿?或者说是绑定到内核的哪个数据结构的哪个成员变量上面?
有三个地方是可以考虑的:socket 结构,包括 sock 和 sk,inet结构,以及 protoname_sock 结构。
绑定在 socket 结构上是可行的,这样可以实现最高层面上的抽像,但是因为每一类协议簇 socket 的地址及端口表现
形式差异很大,这样就得引入专门的转换处理功能。绑定在 protoname_sock 也是可行的,但是却是最笨拙的,因为
例如 tcp 和 udp,它们的地址及端口表现形式是一样的,这样就浪费了空间加大了代码处理量。
所以inet 做为一个协议类型的抽像是最理想的地方了,再来回顾一下它的定义
108 struct inet_sock {
114 /* Socket demultiplex comparisons on incoming packets. */
115 __u32 daddr;
116 __u32 rcv_saddr;
117 __u16 dport;
118 __u16 num;
119 __u32 saddr;
去掉了其它成员保留了与地址及端口相关的成员变量,从注释中可以清楚地了解它们的作用。所以我们说的 bind(2)之
绑定主要就是对这几个成员变量赋值的过程了.
397 int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
398 {
/* 获取地址参数 */
399 struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
/* 获取 sock 对应的 sk */
400 struct sock *sk = sock->sk;
/* 获取 sk 对应的inet */
401 struct inet_sock *inet = inet_sk(sk);
/* 这个临时变量用来保存用户态传递下来的端口参数 */
402 unsigned short snum;
403 int chk_addr_ret;
404 int err;
405
/* 如果协议簇对应的协议自身还有bind函数调用之,例如 SOCK_RAW 就还有一个raw_bind */
406 /* If the socket has its own bind function then use it. (RAW) */
407 if (sk->sk_prot->bind) {
408 err = sk->sk_prot->bind(sk, uaddr, addr_len);
409 goto out;
410 }
411 err = -EINVAL;
/* 校验地址长度 */
412 if (addr_len < sizeof(struct sockaddr_in))
413 goto out;
414 /* 判断地址类型:广播?多播?单播? */
415 chk_addr_ret = inet_addr_type(addr->sin_addr.s_addr);
416
/* ipv4 有一个 ip_nonlocal_bind标志,表示是否绑定非本地址 IP地址,可以通过
* cat /proc/sys/net/ipv4/ip_nonlocal_bind查看到。
* 它用来解决某些服务绑定动态 IP地址的情况。作者在注释中已有详细说明.
* 这里判断,用来确认如果没有开启“绑定非本地址 IP”,地址值及类型是正确的
417 /* Not specified by any standard per-se, however it breaks too
418 * many applications when removed. It is unfortunate since
419 * allowing applications to make a non-local bind solves
420 * several problems with systems using dynamic addressing.
421 * (ie. your servers still start up even if your ISDN link
422 * is temporarily down)
423 */
424 err = -EADDRNOTAVAIL;
425 if (!sysctl_ip_nonlocal_bind && //没有启用动态绑定
426 !inet->freebind &&
427 addr->sin_addr.s_addr != INADDR_ANY && //不是绑定任意本地local地址
428 chk_addr_ret != RTN_LOCAL &&
429 chk_addr_ret != RTN_MULTICAST &&
430 chk_addr_ret != RTN_BROADCAST)
431 goto out;
/* 获取协议端口号 */
433 snum = ntohs(addr->sin_port); //用户空间指定的端口
434 err = -EACCES;
/* 校验当前进程有没有使用低于 1024 端口的能力 */
435 if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
436 goto out;
437
438 /* We keep a pair of addresses. rcv_saddr is the one
439 * used by hash lookups, and saddr is used for transmit.
440 *
441 * In the BSD API these are the same except where it
442 * would be illegal to use them (multicast/broadcast) in
443 * which case the sending device address is used.
444 */
445 lock_sock(sk);
446
447 /* Check these errors (active socket, double bind). */
448 err = -EINVAL;
/* 检查socket是否已经被绑定过了: 用了两个检查项, 一个是 sk 状态, 另一个是是否已经绑定过端口了
当然地址本来就可以为0,所以不能做为检查项 */
449 if (sk->sk_state != TCP_CLOSE || inet->num)
450 goto out_release_sock;
451 /* 绑定inet的接收地址(地址服务绑定地址)和来源地址为用户态指定地址 */
452 inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr;
/* 若地址类型为广播或多播,则将地址置 0,表示直接使用网络设备 */
453 if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
454 inet->saddr = 0; /* Use device */
//例如对于组播而言,其saddr由setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr))来设置
455
/* 调用协议的 get_port 函数,确认是否可绑定端口.
* 若可以, 则绑定在 inet->num 之上, 注意这里虽然没有把inet传过去,但是第一个参数sk
* 它本身和 inet是可以互相转化的 */
456 /* Make sure we are allowed to bind here. */
457 if (sk->sk_prot->get_port(sk, snum)) {
458 inet->saddr = inet->rcv_saddr = 0;
459 err = -EADDRINUSE;
460 goto out_release_sock;
461 }
462 /* 如果端口和地址可以绑定,置标志位 */
463 if (inet->rcv_saddr)
464 sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
465 if (snum)
466 sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
/* inet的 sport(来源端口)成员也置为绑定端口 */
467 inet->sport = htons(inet->num);
468 inet->daddr = 0;
469 inet->dport = 0;
470 sk_dst_reset(sk);
471 err = 0;
472 out_release_sock:
473 release_sock(sk);
474 out:
475 return err;
476 }
上述分析中忽略的第一个细节是capable()函数调用,它是 Linux 安全模块(LSM)的一部份简单地讲其用来对权限做出检查
检查是否有权对指定的资源进行操作。这里它的参数是CAP_NET_BIND_SERVICE表示的含义是:
/* Allows binding to TCP/UDP sockets below 1024 */
/* Allows binding to ATM VCIs below 32 */
#define CAP_NET_BIND_SERVICE 10[/code]
另一个就是协议的端口绑定,调用了协议的get_port函数,如果是SOCK_STREAM的TCP协议,那么它
就是tcp_v4_get_port()函数.
到这里,可以为这部份下一个小结了,所谓绑定,就是:
1. 设置内核中 inet 相关变量成员的值,以待后用;
2. 协议中,如TCP协议,记录绑定的协议端口的信息,采用 hash 链表存储,sk 中也同时维护了这么一个链表。
两者的区别应该是前者给协议用, 后者给socket 用。
浙公网安备 33010602011771号