Skip to content

Commit 719f835

Browse files
Eric Dumazetdavem330
authored andcommitted
udp: add rehash on connect()
commit 30fff92 introduced in linux-2.6.33 (udp: bind() optimisation) added a secondary hash on UDP, hashed on (local addr, local port). Problem is that following sequence : fd = socket(...) connect(fd, &remote, ...) not only selects remote end point (address and port), but also sets local address, while UDP stack stored in secondary hash table the socket while its local address was INADDR_ANY (or ipv6 equivalent) Sequence is : - autobind() : choose a random local port, insert socket in hash tables [while local address is INADDR_ANY] - connect() : set remote address and port, change local address to IP given by a route lookup. When an incoming UDP frame comes, if more than 10 sockets are found in primary hash table, we switch to secondary table, and fail to find socket because its local address changed. One solution to this problem is to rehash datagram socket if needed. We add a new rehash(struct socket *) method in "struct proto", and implement this method for UDP v4 & v6, using a common helper. This rehashing only takes care of secondary hash table, since primary hash (based on local port only) is not changed. Reported-by: Krzysztof Piotr Oledzki <ole@ans.pl> Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com> Tested-by: Krzysztof Piotr Oledzki <ole@ans.pl> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent ae2688d commit 719f835

File tree

6 files changed

+66
-2
lines changed

6 files changed

+66
-2
lines changed

include/net/sock.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,7 @@ struct proto {
752752
/* Keeping track of sk's, looking them up, and port selection methods. */
753753
void (*hash)(struct sock *sk);
754754
void (*unhash)(struct sock *sk);
755+
void (*rehash)(struct sock *sk);
755756
int (*get_port)(struct sock *sk, unsigned short snum);
756757

757758
/* Keeping track of sockets in use */

include/net/udp.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ static inline void udp_lib_hash(struct sock *sk)
151151
}
152152

153153
extern void udp_lib_unhash(struct sock *sk);
154+
extern void udp_lib_rehash(struct sock *sk, u16 new_hash);
154155

155156
static inline void udp_lib_close(struct sock *sk, long timeout)
156157
{

net/ipv4/datagram.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,11 @@ int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
6262
}
6363
if (!inet->inet_saddr)
6464
inet->inet_saddr = rt->rt_src; /* Update source address */
65-
if (!inet->inet_rcv_saddr)
65+
if (!inet->inet_rcv_saddr) {
6666
inet->inet_rcv_saddr = rt->rt_src;
67+
if (sk->sk_prot->rehash)
68+
sk->sk_prot->rehash(sk);
69+
}
6770
inet->inet_daddr = rt->rt_dst;
6871
inet->inet_dport = usin->sin_port;
6972
sk->sk_state = TCP_ESTABLISHED;

net/ipv4/udp.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1260,6 +1260,49 @@ void udp_lib_unhash(struct sock *sk)
12601260
}
12611261
EXPORT_SYMBOL(udp_lib_unhash);
12621262

1263+
/*
1264+
* inet_rcv_saddr was changed, we must rehash secondary hash
1265+
*/
1266+
void udp_lib_rehash(struct sock *sk, u16 newhash)
1267+
{
1268+
if (sk_hashed(sk)) {
1269+
struct udp_table *udptable = sk->sk_prot->h.udp_table;
1270+
struct udp_hslot *hslot, *hslot2, *nhslot2;
1271+
1272+
hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash);
1273+
nhslot2 = udp_hashslot2(udptable, newhash);
1274+
udp_sk(sk)->udp_portaddr_hash = newhash;
1275+
if (hslot2 != nhslot2) {
1276+
hslot = udp_hashslot(udptable, sock_net(sk),
1277+
udp_sk(sk)->udp_port_hash);
1278+
/* we must lock primary chain too */
1279+
spin_lock_bh(&hslot->lock);
1280+
1281+
spin_lock(&hslot2->lock);
1282+
hlist_nulls_del_init_rcu(&udp_sk(sk)->udp_portaddr_node);
1283+
hslot2->count--;
1284+
spin_unlock(&hslot2->lock);
1285+
1286+
spin_lock(&nhslot2->lock);
1287+
hlist_nulls_add_head_rcu(&udp_sk(sk)->udp_portaddr_node,
1288+
&nhslot2->head);
1289+
nhslot2->count++;
1290+
spin_unlock(&nhslot2->lock);
1291+
1292+
spin_unlock_bh(&hslot->lock);
1293+
}
1294+
}
1295+
}
1296+
EXPORT_SYMBOL(udp_lib_rehash);
1297+
1298+
static void udp_v4_rehash(struct sock *sk)
1299+
{
1300+
u16 new_hash = udp4_portaddr_hash(sock_net(sk),
1301+
inet_sk(sk)->inet_rcv_saddr,
1302+
inet_sk(sk)->inet_num);
1303+
udp_lib_rehash(sk, new_hash);
1304+
}
1305+
12631306
static int __udp_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
12641307
{
12651308
int rc;
@@ -1843,6 +1886,7 @@ struct proto udp_prot = {
18431886
.backlog_rcv = __udp_queue_rcv_skb,
18441887
.hash = udp_lib_hash,
18451888
.unhash = udp_lib_unhash,
1889+
.rehash = udp_v4_rehash,
18461890
.get_port = udp_v4_get_port,
18471891
.memory_allocated = &udp_memory_allocated,
18481892
.sysctl_mem = sysctl_udp_mem,

net/ipv6/datagram.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,12 @@ int ip6_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
105105
if (ipv6_addr_any(&np->saddr))
106106
ipv6_addr_set_v4mapped(inet->inet_saddr, &np->saddr);
107107

108-
if (ipv6_addr_any(&np->rcv_saddr))
108+
if (ipv6_addr_any(&np->rcv_saddr)) {
109109
ipv6_addr_set_v4mapped(inet->inet_rcv_saddr,
110110
&np->rcv_saddr);
111+
if (sk->sk_prot->rehash)
112+
sk->sk_prot->rehash(sk);
113+
}
111114

112115
goto out;
113116
}
@@ -181,6 +184,8 @@ int ip6_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
181184
if (ipv6_addr_any(&np->rcv_saddr)) {
182185
ipv6_addr_copy(&np->rcv_saddr, &fl.fl6_src);
183186
inet->inet_rcv_saddr = LOOPBACK4_IPV6;
187+
if (sk->sk_prot->rehash)
188+
sk->sk_prot->rehash(sk);
184189
}
185190

186191
ip6_dst_store(sk, dst,

net/ipv6/udp.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,15 @@ int udp_v6_get_port(struct sock *sk, unsigned short snum)
111111
return udp_lib_get_port(sk, snum, ipv6_rcv_saddr_equal, hash2_nulladdr);
112112
}
113113

114+
static void udp_v6_rehash(struct sock *sk)
115+
{
116+
u16 new_hash = udp6_portaddr_hash(sock_net(sk),
117+
&inet6_sk(sk)->rcv_saddr,
118+
inet_sk(sk)->inet_num);
119+
120+
udp_lib_rehash(sk, new_hash);
121+
}
122+
114123
static inline int compute_score(struct sock *sk, struct net *net,
115124
unsigned short hnum,
116125
struct in6_addr *saddr, __be16 sport,
@@ -1447,6 +1456,7 @@ struct proto udpv6_prot = {
14471456
.backlog_rcv = udpv6_queue_rcv_skb,
14481457
.hash = udp_lib_hash,
14491458
.unhash = udp_lib_unhash,
1459+
.rehash = udp_v6_rehash,
14501460
.get_port = udp_v6_get_port,
14511461
.memory_allocated = &udp_memory_allocated,
14521462
.sysctl_mem = sysctl_udp_mem,

0 commit comments

Comments
 (0)