• linux网络协议栈源码分析 - 传输层(TCP连接的建立)


    1、bind系统调用

    1.1、地址端口及状态检查(inet_bind)

            通过路由表查找绑定地址的路由类型,对于非本地IP检查是否允许绑定非本地IP地址;检查公认端口绑定权限,是否允许绑定0~1024端口;检查socket是否已经绑定了或者已经激活了;然后调用inet_csk_get_port绑定指定端口或者绑定动态分配的端口。

            inet_bind函数实现如下:

    1. int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
    2. {
    3. struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
    4. struct sock *sk = sock->sk;
    5. struct inet_sock *inet = inet_sk(sk);
    6. struct net *net = sock_net(sk);
    7. unsigned short snum;
    8. int chk_addr_ret;
    9. u32 tb_id = RT_TABLE_LOCAL;
    10. int err;
    11. /* If the socket has its own bind function then use it. (RAW) */
    12. if (sk->sk_prot->bind) { // socket有自己的绑定函数
    13. err = sk->sk_prot->bind(sk, uaddr, addr_len); // 调用socket自己的绑定函数
    14. goto out;
    15. }
    16. err = -EINVAL;
    17. if (addr_len < sizeof(struct sockaddr_in))
    18. goto out; // 地址长度小于sockaddr_in结构体长度,地址错误,跳转到out
    19. if (addr->sin_family != AF_INET) { // 协议类型不是AF_INET
    20. /* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
    21. * only if s_addr is INADDR_ANY.
    22. */
    23. err = -EAFNOSUPPORT; // 设置错误返回值不支持EAFNOSUPPORT
    24. if (addr->sin_family != AF_UNSPEC ||
    25. addr->sin_addr.s_addr != htonl(INADDR_ANY))
    26. goto out;
    27. }
    28. tb_id = l3mdev_fib_table_by_index(net, sk->sk_bound_dev_if) ? : tb_id;
    29. chk_addr_ret = inet_addr_type_table(net, addr->sin_addr.s_addr, tb_id); // 从路由表查找地址的路由类型RTN_LOCAL/RTN_MULTICAST/RTN_BROADCAST...
    30. /* Not specified by any standard per-se, however it breaks too
    31. * many applications when removed. It is unfortunate since
    32. * allowing applications to make a non-local bind solves
    33. * several problems with systems using dynamic addressing.
    34. * (ie. your servers still start up even if your ISDN link
    35. * is temporarily down)
    36. */
    37. err = -EADDRNOTAVAIL; // 设置错误返回值地址不可用EADDRNOTAVAIL
    38. if (!net->ipv4.sysctl_ip_nonlocal_bind && // 不允许bind绑定非本地IP地址
    39. !(inet->freebind || inet->transparent) && // 参考https://www.man7.org/linux/man-pages/man7/ip.7.html,IP_TRANSPARENT/IP_FREEBIND
    40. addr->sin_addr.s_addr != htonl(INADDR_ANY) && // 非INADDR_ANY地址(0.0.0.0),0.0.0.0地址可以绑定,不需要检查0.0.0.0地址作用域
    41. chk_addr_ret != RTN_LOCAL && // 可以绑定RTN_LOCAL、RTN_MULTICAST、RTN_BROADCAST路由类型的地址
    42. chk_addr_ret != RTN_MULTICAST &&
    43. chk_addr_ret != RTN_BROADCAST)
    44. goto out; // RTN_LOCAL、RTN_MULTICAST、RTN_BROADCAST之外的路由类型地址不能绑定,跳转到out
    45. snum = ntohs(addr->sin_port);
    46. err = -EACCES;
    47. if (snum && snum < PROT_SOCK && // 端口号不为0并且端口号小于PROT_SOCK(1024),绑定的端口号为公认端口
    48. !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE)) // namespace相关capability权限检查
    49. goto out;
    50. /* We keep a pair of addresses. rcv_saddr is the one
    51. * used by hash lookups, and saddr is used for transmit.
    52. *
    53. * In the BSD API these are the same except where it
    54. * would be illegal to use them (multicast/broadcast) in
    55. * which case the sending device address is used.
    56. */
    57. lock_sock(sk);
    58. /* Check these errors (active socket, double bind). */
    59. err = -EINVAL;
    60. if (sk->sk_state != TCP_CLOSE || inet->inet_num) // 已经激活或者已经绑定的socket,不允许绑定
    61. goto out_release_sock; // 跳转到out_release_sock
    62. inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; // 设置已经绑定的本地IP地址。接收数据时,作为条件的一部分查找数据所属的传输控制块。机械工业出版社《Linux内核源码剖析:TCP/IP实现(下册)》
    63. if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
    64. inet->inet_saddr = 0; /* Use device */ // 本地IP地址,发送时使用
    65. /* Make sure we are allowed to bind here. */
    66. if ((snum || !inet->bind_address_no_port) && // 绑定的端口不为0需要检查端口是否被占用,不允许在connect时候绑定端口(https://www.man7.org/linux/man-pages/man7/ip.7.html IP_BIND_ADDRESS_NO_PORT)
    67. sk->sk_prot->get_port(sk, snum)) { // 调用inet_csk_get_port绑定端口,如果端口为0,则自动分配一个可用端口进行绑定
    68. inet->inet_saddr = inet->inet_rcv_saddr = 0;
    69. err = -EADDRINUSE; // 端口被占用
    70. goto out_release_sock; // 跳转到out_release_sock
    71. }
    72. if (inet->inet_rcv_saddr)
    73. sk->sk_userlocks |= SOCK_BINDADDR_LOCK; // 已经绑定了本地地址
    74. if (snum)
    75. sk->sk_userlocks |= SOCK_BINDPORT_LOCK; // 已经绑定了本地端口
    76. inet->inet_sport = htons(inet->inet_num); // 源端口
    77. inet->inet_daddr = 0;
    78. inet->inet_dport = 0;
    79. sk_dst_reset(sk); // 重置socket路由缓存
    80. err = 0;
    81. out_release_sock:
    82. release_sock(sk);
    83. out:
    84. return err;
    85. }

    1.2、端口获取(inet_csk_get_port)

            如果没有指定端口,inet_csk_get_port动态分配一个端口, 并检测端口冲突,如果有指定端口,inet_csk_get_port检测端口冲突;端口地址复用参考https://www.man7.org/linux/man-pages/man7/socket.7.html​​​​​​​中的SO_REUSEADDR、SO_REUSEPORT以及人民邮电出版社《UNIX网络编程 卷1:套接字联网API(第3版)》"7.5.11 SO_REUSEADDR 和 SO_REUSEPORT 套接字选项"。

    • SO_REUSEADDR

            Indicates that the rules used in validating addresses supplied in a bind(2) call should allow reuse of local addresses.  For AF_INET sockets this means that a socket may bind, except when there is an active listening socket bound to the address.  When the listening socket is bound to INADDR_ANY with a specific port then it is not possible to bind to this port for any local address.  Argument is an integer boolean flag.

    • SO_REUSEPORT (since Linux 3.9)

    Permits multiple AF_INET or AF_INET6 sockets to be bound to an identical socket address.  This option must be set on each socket (including the first socket) prior to calling bind(2) on the socket.  To prevent port hijacking, all of the processes binding to the same address must have the same effective UID.  This option can be employed with both TCP and UDP sockets.

    For TCP sockets, this option allows accept(2) load distribution in a multi-threaded server to be improved by using a distinct listener socket for each thread.  This provides improved load distribution as compared to traditional techniques such using a single accept(2)ing thread that distributes connections, or having multiple threads that compete to accept(2) from the same socket.

    For UDP sockets, the use of this option can provide better distribution of incoming datagrams to multiple processes(or threads) as compared to the traditional technique of having multiple processes compete to receive datagrams on the same socket.


            动态分配的本地端口范围比较大,可以用如下命令缩小本地端口范围,然后动态绑定两个端口就可以占用所有本地端口,写代码就容易构造各种条件的冲突场景,使用gdb在内核相关函数设置断点即可跟踪各种状态的处理流程,场景比较多,所以构造各种场景,使用gdb调试代码更容易理解观察各种处理流程:

    echo 32768 32768 >/proc/sys/net/ipv4/ip_local_port_range

            inet_csk_get_port函数实现如下:

    1. int inet_csk_get_port(struct sock *sk, unsigned short snum)
    2. {
    3. struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo;
    4. struct inet_bind_hashbucket *head;
    5. struct inet_bind_bucket *tb;
    6. int ret, attempts = 5; // 分配动态端口尝试次数
    7. struct net *net = sock_net(sk);
    8. int smallest_size = -1, smallest_rover;
    9. kuid_t uid = sock_i_uid(sk);
    10. int attempt_half = (sk->sk_reuse == SK_CAN_REUSE) ? 1 : 0; // 地址复用SO_REUSEADDR,从一半端口范围内尝试分配可用端口(空闲或者可复用的端口)
    11. local_bh_disable();
    12. if (!snum) { // 端口号为0,动态分配一个端口
    13. int remaining, rover, low, high;
    14. again:
    15. inet_get_local_port_range(net, &low, &high); // 获取本地端口的范围
    16. if (attempt_half) { // attempt_half不为0,从一半端口范围内尝试分配可用端口(空闲或者可复用的端口)
    17. int half = low + ((high - low) >> 1); // 计算[low, high]区间的中间端口
    18. if (attempt_half == 1) // 第1次尝试分配动态端口,先从[low, half]查找可用端口
    19. high = half;
    20. else // 第1次之后分配动态端口(attempts减到为0的时候,不再尝试分配动态端口),从[half, high]查找可用端口
    21. low = half;
    22. }
    23. remaining = (high - low) + 1; // 剩余端口数[low: high],查找过程不是从low到high查找,而是从[low: high]中间的一个随机端口查找,查找到high之后再从low开始查找,remaining就用于记录查找剩余的次数,也就是还有多少端口没有查找
    24. smallest_rover = rover = prandom_u32() % remaining + low; // 生成一个[low: high]之间的随机端口,从rover开始往大的端口开始查找,查找[rover: high]区间,查找到high之后,再查找[low: rover)区间
    25. smallest_size = -1; // 记录最少owners的可以复用的端口(地址/端口复用),也就是在复用端口/地址的情况下,优先复用owners最少的地址/端口,避免有的端口复用多有的端口复用少的情况
    26. do {
    27. if (inet_is_local_reserved_port(net, rover)) // 是否是被预留的端口? sysctl_local_reserved_ports
    28. goto next_nolock; // 不能使用/复用被预留的端口,跳转到next_nolock,检查下一个端口
    29. head = &hashinfo->bhash[inet_bhashfn(net, rover,
    30. hashinfo->bhash_size)]; // 调用inet_bhashfn计算rover端口的哈希值,根据哈希值找到rover端口所在的哈希链表表头(哈希值相同的端口存在一个链表里面,只需要查找对于端口哈希值所在链表即可)
    31. spin_lock(&head->lock);
    32. inet_bind_bucket_for_each(tb, &head->chain) // 遍历哈希链表,检查已绑定的rover端口,绑定的端口是否可以复用,是否存在冲突
    33. if (net_eq(ib_net(tb), net) && tb->port == rover) { // 在一个网络命名空间里面(不同网络命名空间,端口号不存在冲突),并且端口号与绑定的端口相同
    34. if (((tb->fastreuse > 0 && // 地址可以被复用(非TCP_LISTEN状态会设置fastreuse为1,因此可以快速判断绑定的端口不处于监听状态)
    35. sk->sk_reuse && // 地址可以被复用
    36. sk->sk_state != TCP_LISTEN) || // sk处于非监听状态(占用端口及非占用端口的socket都处于非监听状态,可以复用地址)
    37. (tb->fastreuseport > 0 && // 端口可以被复用(只有一个socket占用端口并且可以复用端口的情况下,会设置fastreuseport为1并记录占用端口的socket的fastuid,一般情况,uid相同即可复用端口)
    38. sk->sk_reuseport && // 端口可以被复用
    39. uid_eq(tb->fastuid, uid))) && // uid相同,相同的用户复用端口
    40. (tb->num_owners < smallest_size || smallest_size == -1)) { // 当前地址/端口可以复用的端口的owners数量小于之前记录的地址/端口可以复用的端口的owners数量,当前地址/端口可以复用的端口是第一个可以复用的,更新记录owners最少的地址/端口可以复用的端口及端口的owners数量,尽量分配owners最少的可用端口
    41. smallest_size = tb->num_owners; // 记录smallest_rover端口的owners数量
    42. smallest_rover = rover; // 记录复用最少owners的端口smallest_rover
    43. }
    44. if (!inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, false)) { // 调用inet_csk_bind_conflict检查冲突,检测SO_REUSEADDR/SO_REUSEPORT相关选项及绑定状态是否可以地址/端口复用,relax为false情况,非监听状态socket也不允许地址相同(尽可能不使用相同的端口地址???)
    45. snum = rover; // 不冲突,使用rover作为绑定端口
    46. goto tb_found; // 跳转到tb_found(锁还没释放,rover绑定状态不会被改变)
    47. }
    48. goto next; // 地址冲突,跳转到next
    49. }
    50. break; // inet_bind_bucket_for_each循环结束,绑定端口里面没有找到rover端口
    51. next:
    52. spin_unlock(&head->lock); // 遍历所有端口时间太长,影响其他进程/线程获取端口,这里释放了自旋锁(遍历过了的绑定端口的状态可能会被其他线程/进程改变,可能变成不冲突,一次遍历没找到合适端口情况下,绑定的端口状态可能变化,需要再次遍历;smallest_rover端口绑定状态也可能被改变)
    53. next_nolock:
    54. if (++rover > high) // 查找下一个端口,下一个端口大于最大的端口,那么从最小的端口重新开始查找
    55. rover = low; // 从low开始查找,查找[low: rover)区间 (这里的rover是随机初始化的rover,remaining控制查找次数,减去[rover: high]区间的查找次数就剩下[low: rover)区间的次数)
    56. } while (--remaining > 0);
    57. /* Exhausted local port range during search? It is not
    58. * p
  • 相关阅读:
    15.cuBLAS开发指南中文版--cuBLAS中的Level-1函数rotg()
    Python数据分析与机器学习31-SVM案例:人脸识别
    数学建模学习(105):五种正态检验方法的实践,Python实现
    Mybatis入门与数据库连接池以及lombok插件
    mac pro M1(ARM)安装:centos8.0虚拟机
    Postman的使用
    GitHub/R3D3项目环境配置踩坑记录
    [附源码]JAVA毕业设计论文管理系统(系统+LW)
    Java面试题汇总(持续更新.....)
    Jmeter面试题
  • 原文地址:https://blog.csdn.net/arm7star/article/details/126393427