博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Linux TCP/IP协议栈之Socket的BIND实现分析

Posted on 2016-11-21 15:09  bw_0927  阅读(605)  评论(0)    收藏  举报

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 用。