之前也聊过udp:udp dns 的思考
UDP 传输块的管理
1、udp并不是在hash 接口中将其控制块添加到udp_hash散列表中,而是在绑定端口后才将其添加到散列表中;
2、并不是所有的udp传输控制块都在散列表中管理,只有当套接字绑定了端口之后,此时可以接收发送数据,才会添加到散列表中管理
3、udp_hash为散列表。socket 一旦绑定port就回添加到散列表管理,知道关闭后才会从散列表中删除
udp的bind 调用:
1、udp/tcp socket 执行bind时 首先调用inet_bind系统调用,如果是raw socket 则会调用对应proto bind 接口函数,目前tcp/udp socket 都是通过get_port 函数来bind socket
分析get_port函数
int udp_v4_get_port(struct sock *sk, unsigned short snum)
{
unsigned int hash2_nulladdr =
udp4_portaddr_hash(sock_net(sk), htonl(INADDR_ANY), snum);
unsigned int hash2_partial =
udp4_portaddr_hash(sock_net(sk), inet_sk(sk)->inet_rcv_saddr, 0);
/*
哈希值hash2_nulladdr由[INADDR_ANY, snum]得到,hash2_partial由[inet_rcv_saddr, 0]得到,
即前者用本地端口作哈希,后者用本地地址作哈希*/
/* precompute partial secondary hash */
udp_sk(sk)->udp_portaddr_hash = hash2_partial;
//ipv4_rcv_saddr_equal()是比较地址是否相等的函数
return udp_lib_get_port(sk, snum, ipv4_rcv_saddr_equal, hash2_nulladdr);
}
/**
* udp_lib_get_port - UDP/-Lite port lookup for IPv4 and IPv6
*
* @sk: socket struct in question
* @snum: port number to look up
* @saddr_comp: AF-dependent comparison of bound local IP addresses
* @hash2_nulladdr: AF-dependent hash value in secondary hash chains,
* with NULL address
*/
int udp_lib_get_port(struct sock *sk, unsigned short snum,
int (*saddr_comp)(const struct sock *sk1,
const struct sock *sk2),
unsigned int hash2_nulladdr)
{
struct udp_hslot *hslot, *hslot2;
struct udp_table *udptable = sk->sk_prot->h.udp_table;
int error = 1;
struct net *net = sock_net(sk);
if (!snum) {//num为0则先选择一个可用端口号,再插入//没有绑定本地端口
int low, high, remaining;
unsigned int rand;
unsigned short first, last;
DECLARE_BITMAP(bitmap, PORTS_PER_CHAIN);
inet_get_local_port_range(net, &low, &high);
remaining = (high - low) + 1;
rand = prandom_u32();
first = reciprocal_scale(rand, remaining) + low;
/*
* force rand to be an odd multiple of UDP_HTABLE_SIZE
static inline u32 udp_hashfn(const struct net *net, u32 num, u32 mask)
{
return (num + net_hash_mix(net)) & mask;
}//net_hash_mix(net)返回一般为0,hash公式可简写为num&mask。即本地端口对udptable大小取模
*/
rand = (rand | 1) * (udptable->mask + 1);
last = first + udptable->mask + 1;
do {
hslot = udp_hashslot(udptable, net, first);
bitmap_zero(bitmap, PORTS_PER_CHAIN);
spin_lock_bh(&hslot->lock);
udp_lib_lport_inuse(net, snum, hslot, bitmap, sk,
saddr_comp, udptable->log);
snum = first;
/*
* Iterate on all possible values of snum for this hash.
* Using steps of an odd multiple of UDP_HTABLE_SIZE
* give us randomization and full range coverage.
*/
do {
if (low <= snum && snum <= high &&
!test_bit(snum >> udptable->log, bitmap) &&
!inet_is_local_reserved_port(net, snum))
goto found;
snum += rand;
} while (snum != first);
spin_unlock_bh(&hslot->lock);
} while (++first != last);
goto fail;
} else {//snum不为0则先确定之前没有存储相应sk,再插入。
//hslot是从udp_table中hash表取出的表项,键值是snum 端口号
hslot = udp_hashslot(udptable, net, snum);
spin_lock_bh(&hslot->lock);
if (hslot->count > 10) {
int exist;
/*hslot->count大于10,即在hash表中以snum为键值的项的数目在于10,
此时改用在hash2表中查找。
如果hslot->count不足10,那么直接在hash表中查找*/
// 在之前udp_portaddr_hash 是rcv_addr xor过的数值
unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum;
slot2 &= udptable->mask;
hash2_nulladdr &= udptable->mask;
// hslot2是udptable中hash2表取出的表项,键值是[inet_rcv_addr, snum]
hslot2 = udp_hashslot2(udptable, slot2);
if (hslot->count < hslot2->count)
goto scan_primary_hash;//hslot2项的数目比hslot还多,那么查找hash2表是不划算的 返回直接查找hash表
/*hslot2更少(这也是设计hash2的目的),使用udp_lib_lport_inuse2()查找是否有匹配项;
如果没有找到,则使用新的键值hash2_nulladdr,即[INADDR_ANY, snum]从hash2中取出表项,
再使用udp_lib_lport_inuse2()查找是否有匹配项。
如果有,表明要插入的sk已经存在于内核表中,直接返回;
如果没有,则执行sk的插入操作 */
exist = udp_lib_lport_inuse2(net, snum, hslot2,
sk, saddr_comp);
if (!exist && (hash2_nulladdr != slot2)) {
hslot2 = udp_hashslot2(udptable, hash2_nulladdr);
exist = udp_lib_lport_inuse2(net, snum, hslot2,
sk, saddr_comp);
}
if (exist)
goto fail_unlock;
else
goto found;
}
scan_primary_hash:
if (udp_lib_lport_inuse(net, snum, hslot, NULL, sk,
saddr_comp, 0))
goto fail_unlock;
}
found:
// 当没有在当前内核udp_table中找到匹配项时,执行插入新sk的操作
//给sk参数赋值:inet_num, udp_port_hash, udp_portaddr_hash。然后将sk加入到hash表和hash2表中,并增加相应计数
inet_sk(sk)->inet_num = snum;
udp_sk(sk)->udp_port_hash = snum;
udp_sk(sk)->udp_portaddr_hash ^= snum;
if (sk_unhashed(sk)) {
sk_nulls_add_node_rcu(sk, &hslot->head);
hslot->count++;
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash);
spin_lock(&hslot2->lock);
hlist_nulls_add_head_rcu(&udp_sk(sk)->udp_portaddr_node,
&hslot2->head);
hslot2->count++;
spin_unlock(&hslot2->lock);
}
error = 0;
fail_unlock:
spin_unlock_bh(&hslot->lock);
fail:
return error;
}
怎样判断bind 的这个port 是否可用?
看下这个函数便可知:主要是判断 net_namespace 是否设置 reuseaddr reuseport bind_dev_if 等标志位, notice只要有一个为false 就行,
/*
* Note: we still hold spinlock of primary hash chain, so no other writer
* can insert/delete a socket with local_port == num
*/
static int udp_lib_lport_inuse2(struct net *net, __u16 num,
struct udp_hslot *hslot2,
struct sock *sk,
int (*saddr_comp)(const struct sock *sk1,
const struct sock *sk2))
{
struct sock *sk2;
struct hlist_nulls_node *node;
kuid_t uid = sock_i_uid(sk);
int res = 0;
spin_lock(&hslot2->lock);
udp_portaddr_for_each_entry(sk2, node, &hslot2->head) {
if (net_eq(sock_net(sk2), net) &&
sk2 != sk &&
(udp_sk(sk2)->udp_port_hash == num) &&
(!sk2->sk_reuse || !sk->sk_reuse) &&
(!sk2->sk_bound_dev_if || !sk->sk_bound_dev_if ||
sk2->sk_bound_dev_if == sk->sk_bound_dev_if) &&
(!sk2->sk_reuseport || !sk->sk_reuseport ||
!uid_eq(uid, sock_i_uid(sk2))) &&
saddr_comp(sk, sk2)) {
res = 1;
break;
}
}
spin_unlock(&hslot2->lock);
return res;
}
sock如何被访问
创建的udp socket成功后,当使用该socket与外部通信时,协议栈会收到发往该socket的udp报文。 udp_rcv() -> __udp4_lib_rcv() -> __udp4_lib_lookup() 在该函数中有关于udp socket的查找代码段,它以[saddr, sport, daddr, dport, iif]为键值在udptable中查找相应的sk。
__udp4_lib_lookup() sock在udptable中查找
查找的过程与插入sock的过程很相似,先以hnum作哈希得到hslot,daddr, hnum作哈希得到hslot2,如果hslot数目不足10或hslot的表项数少于hslot2的,则在hslot中查找(begin代码段)。否则,在hslot2中查找。查找时使用udp4_lib_lookup2()函数,它返回与收到报文相匹配的sock。
在hslot2中没有查找结果,则用INADDR_ANY, hnum作哈希得到重新得到hslot2,因为服务器端的udp socket只绑定了本地端口,没有绑定本地地址,所以查找时需要先使用[saddr, sport]查找,没有时再使用[INADDR_ANY, sport]查找。如果hslot2->count比hslot->count要多,或者在hslot2中没有查找到,则在hslot中查找(begin代码段)
UDP 输入输出 函数调用:
http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!!
但行好事 莫问前程
--身高体重180的胖子