connect端口抉择

在socket编程中, 客户端应用connect向服务端发动申请时,如果不指定本地端口(个别都不指定), 内核会主动为连贯调配一个可用的端口. connect是如何进行端口抉择的呢?
connect端口抉择逻辑如下:

  • 如果sock曾经指定了端口, 应用指定的端口
  • 如果sock没有指定端口, 调配一个端口

    1. 先获取内核参数设置的本地可用端口范畴,默认为low: 32768 - high: 60999
    2. 依据hint和三元组的hash失去一个随机的偏移量offset
    3. 从low+offset开始, 在可用端口范畴内遍历判断端口是否可用, 每次端口值+2

      1. 不容许应用用户设置的保留端口
      2. 如果端口曾经应用

        1. 不容许应用bind绑定的端口
        2. 查看端口是否可重用

          1. ehash表中没有四元组匹配的sock时端口可重用2. 有四元组匹配的sock时进行TIME_WAIT判断  1. 合乎以下条件可重用    1. 匹配的连贯处于TIME_WAIT状态    2. 满足TIME_WAIT端口复用条件
      1. 如果端口没有被应用,应用此端口

内核容许同一个端口向两个不同的服务端发动连贯申请

源码剖析:

connect端口抉择外围函数inet_hash_connect剖析:

/* * Bind a port for a connect operation and hash it. */int inet_hash_connect(struct inet_timewait_death_row *death_row,              struct sock *sk){    u32 port_offset = 0;    /* 如果sk的本地源端口设置为0 */    if (!inet_sk(sk)->inet_num)        /* 依据源IP、目标IP、目标端口,用hash函数计算出一个随机数,作为端口的初始偏移值 */        port_offset = inet_sk_port_offset(sk);    return __inet_hash_connect(death_row, sk, port_offset,                   __inet_check_established);                   /* __inet_check_established为查看端口是否可用的回调函数 */}

__inet_hash_connect剖析:

int __inet_hash_connect(struct inet_timewait_death_row *death_row,        struct sock *sk, u32 port_offset,        int (*check_established)(struct inet_timewait_death_row *,            struct sock *, __u16, struct inet_timewait_sock **)){    struct inet_hashinfo *hinfo = death_row->hashinfo;    struct inet_timewait_sock *tw = NULL;    struct inet_bind_hashbucket *head;    int port = inet_sk(sk)->inet_num;    struct net *net = sock_net(sk);    struct inet_bind_bucket *tb;    u32 remaining, offset;    int ret, i, low, high;    static u32 hint;    int l3mdev;    /* sock曾经设置了端口 */    if (port) {        head = &hinfo->bhash[inet_bhashfn(net, port,                          hinfo->bhash_size)];        tb = inet_csk(sk)->icsk_bind_hash;        spin_lock_bh(&head->lock);        if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) {            inet_ehash_nolisten(sk, NULL);            spin_unlock_bh(&head->lock);            return 0;        }        spin_unlock(&head->lock);        /* No definite answer... Walk to established hash table */        ret = check_established(death_row, sk, port, NULL);        local_bh_enable();        return ret;    }    /*     * sock没有设置端口时会走到这里,以下是内核主动抉择端口的过程    * connect主动抉择端口会保障和ip_local_port_range的low的奇偶性保持一致    * bind主动抉择端口会保障和low奇偶性相同 */    /*     * 绑定的VRF(Virtual Routing and Forwarding)设施    * 内核默认不开启tcp_l3mdev_accept,间接返回0 */    l3mdev = inet_sk_bound_l3mdev(sk);    /* 获取本地可用端口范畴,ip_local_port_range是能够设置的内核参数,默认是32768-60999 */    inet_get_local_port_range(net, &low, &high);    high++; /* [32768, 60999] -> [32768, 61000[ */    /* 计算端口范畴差值 */    remaining = high - low;    if (likely(remaining > 1))        /* 确保remaining为偶数,保障和low的奇偶性保持一致 */        remaining &= ~1U;    /*     * 依据hint和port_offset计算出一个remaining范畴内的偏移量    * hint是一个动态变量,每次+(i+2),i为上次的可用端口-初始抉择端口    * hint尽量使每次抉择的端口递增,进步端口命中率    * port_offset是之前依据源地址,目标地址,目标端口hash进去的一个随机数 */    offset = (hint + port_offset) % remaining;    /* In first pass we try ports of @low parity.     * inet_csk_get_port() does the opposite choice.     */    /* 确保offset为偶数,保障和low的奇偶性保持一致 */    offset &= ~1U;other_parity_scan:    /* 抉择第一个port的值 */    port = low + offset;    /* 从第一个port开始判断端口是否可用,每次port+2,保障和low的奇偶性保持一致 */    for (i = 0; i < remaining; i += 2, port += 2) {        /* port超范围则返回low持续查找 */        if (unlikely(port >= high))            port -= remaining;        /*         * 排除ip_local_reserved_ports内核参数中设置的保留端口        * 参数默认为空,能够本人配置想要为某些服务保留的端口 */        if (inet_is_local_reserved_port(net, port))            continue;                /* 依据端口号和命名空间的哈希失去哈希表头 */        head = &hinfo->bhash[inet_bhashfn(net, port,                          hinfo->bhash_size)];        /* 锁住此表头 */        spin_lock_bh(&head->lock);        /* Does not bother with rcv_saddr checks, because         * the established check is already unique enough.         */        /* 从哈希失去的链表中查找对应的命名空间和端口号的bind_bucket */        inet_bind_bucket_for_each(tb, &head->chain) {            /* 比照命名空间,端口号和VRF设施(默认不开启) */            if (net_eq(ib_net(tb), net) && tb->l3mdev == l3mdev &&                tb->port == port) {                /* 不容许应用bind创立或者应用的端口                 * bind创立构造体时,会使得fastreuse和fastreuseport>=0                 * connect创立构造体时,两个值为-1 */                if (tb->fastreuse >= 0 ||                    tb->fastreuseport >= 0)                    goto next_port;                WARN_ON(hlist_empty(&tb->owners));                /* 查看端口是否可重用                 * 1.ehash表中没有四元组,命名空间匹配的sock时可重用                 * 2.有四元组命名空间匹配的连贯时进行TIME_WAIT判断                 *     合乎以下条件可重用                 *     - 匹配的连贯处于TIME_WAIT状态                 *     - 满足TIME_WAIT端口复用条件 */                if (!check_established(death_row, sk,                               port, &tw))                    goto ok;                goto next_port;            }        }        /* 没有找到对应的bind_bucket时会走到这里         * 阐明还没有创立端口的inet_bind_bucket构造,端口肯定可用 */        /* 为端口创立inet_bind_bucket构造 */        tb = inet_bind_bucket_create(hinfo->bind_bucket_cachep,                         net, head, port, l3mdev);        if (!tb) {            spin_unlock_bh(&head->lock);            return -ENOMEM;        }        /* fastreuse和fastreuseport设置为-1 */        tb->fastreuse = -1;        tb->fastreuseport = -1;        goto ok;next_port:        spin_unlock_bh(&head->lock);        cond_resched();    }    /* 走到这里阐明没有适合端口,扭转奇偶性再选一次 */    offset++;    if ((offset & 1) && remaining > 1)        goto other_parity_scan;    /* 扭转奇偶性仍然没有适合端口,返回谬误Cannot assign requested address */    return -EADDRNOTAVAIL;ok:    /* 保留动态变量的值,下个雷同三元组会应用新的hint,缩小反复判断 */    hint += i + 2;    /* Head lock still held and bh's disabled */    /* 将sock增加到inet_bind_bucket构造的owner链表中 */    inet_bind_hash(sk, tb, port);    /* 如果sokc没有增加到ehash表,将sock增加到ehash表中 */    if (sk_unhashed(sk)) {        inet_sk(sk)->inet_sport = htons(port);        inet_ehash_nolisten(sk, (struct sock *)tw);    }    /* 提前结束time_wait状态 */    if (tw)        inet_twsk_bind_unhash(tw, hinfo);    spin_unlock(&head->lock);    if (tw)        inet_twsk_deschedule_put(tw);    local_bh_enable();    return 0;}

__inet_check_established剖析:

/* called with local bh disabled */static int __inet_check_established(struct inet_timewait_death_row *death_row,                    struct sock *sk, __u16 lport,                    struct inet_timewait_sock **twp){    struct inet_hashinfo *hinfo = death_row->hashinfo;    struct inet_sock *inet = inet_sk(sk);    __be32 daddr = inet->inet_rcv_saddr;    __be32 saddr = inet->inet_daddr;    int dif = sk->sk_bound_dev_if;    struct net *net = sock_net(sk);    int sdif = l3mdev_master_ifindex_by_index(net, dif);    INET_ADDR_COOKIE(acookie, saddr, daddr);    const __portpair ports = INET_COMBINED_PORTS(inet->inet_dport, lport);    /* 依据四元组和命名空间失去ehash表的哈希值 */    unsigned int hash = inet_ehashfn(net, daddr, lport,                     saddr, inet->inet_dport);    /* 获取指定哈希值的哈希桶 */    struct inet_ehash_bucket *head = inet_ehash_bucket(hinfo, hash);    /* 指定哈希桶的锁 */    spinlock_t *lock = inet_ehash_lockp(hinfo, hash);    struct sock *sk2;    const struct hlist_nulls_node *node;    struct inet_timewait_sock *tw = NULL;    /* 锁住哈希桶 */    spin_lock(lock);    /* 遍历哈希桶匹配四元组,命名空间,绑定设施雷同的sock */    sk_nulls_for_each(sk2, node, &head->chain) {        /* 比拟hash值 */        if (sk2->sk_hash != hash)            continue;        /* 有四元组,命名空间,绑定设施齐全匹配的连贯 */        if (likely(INET_MATCH(sk2, net, acookie,                     saddr, daddr, ports, dif, sdif))) {            /* 连贯处于TIME_WAIT状态 */            if (sk2->sk_state == TCP_TIME_WAIT) {                tw = inet_twsk(sk2);                /* 判断是否满足TIME_WAIT端口复用条件 */                if (twsk_unique(sk, sk2, twp))                    break;            }            /* 如果有齐全匹配的连贯,且不可TIME_WAIT复用,会走到这里,返回不可用 */            goto not_unique;        }    }    /* 没有匹配到雷同的连贯,或者time_wait重用会走到这里 */    /* Must record num and sport now. Otherwise we will see     * in hash table socket with a funny identity.     */    inet->inet_num = lport;    inet->inet_sport = htons(lport);    sk->sk_hash = hash;    WARN_ON(!sk_unhashed(sk));    /* 增加sock到ehash哈希桶中 */    __sk_nulls_add_node_rcu(sk, &head->chain);    /* 从ehash哈希桶中删除TIME_WAIT连贯 */    if (tw) {        sk_nulls_del_node_init_rcu((struct sock *)tw);        __NET_INC_STATS(net, LINUX_MIB_TIMEWAITRECYCLED);    }    spin_unlock(lock);    /* 减少端口应用计数 */    sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);    /* 提前终止time_wait */    if (twp) {        *twp = tw;    } else if (tw) {        /* Silly. Should hash-dance instead... */        inet_twsk_deschedule_put(tw);    }    /* 返回可用 */    return 0;not_unique:    spin_unlock(lock);    return -EADDRNOTAVAIL;}