719f835853
commit 30fff923
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>
79 lines
1.9 KiB
C
79 lines
1.9 KiB
C
/*
|
|
* common UDP/RAW code
|
|
* Linux INET implementation
|
|
*
|
|
* Authors:
|
|
* Hideaki YOSHIFUJI <yoshfuji@linux-ipv6.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/module.h>
|
|
#include <linux/ip.h>
|
|
#include <linux/in.h>
|
|
#include <net/ip.h>
|
|
#include <net/sock.h>
|
|
#include <net/route.h>
|
|
#include <net/tcp_states.h>
|
|
|
|
int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
|
|
{
|
|
struct inet_sock *inet = inet_sk(sk);
|
|
struct sockaddr_in *usin = (struct sockaddr_in *) uaddr;
|
|
struct rtable *rt;
|
|
__be32 saddr;
|
|
int oif;
|
|
int err;
|
|
|
|
|
|
if (addr_len < sizeof(*usin))
|
|
return -EINVAL;
|
|
|
|
if (usin->sin_family != AF_INET)
|
|
return -EAFNOSUPPORT;
|
|
|
|
sk_dst_reset(sk);
|
|
|
|
oif = sk->sk_bound_dev_if;
|
|
saddr = inet->inet_saddr;
|
|
if (ipv4_is_multicast(usin->sin_addr.s_addr)) {
|
|
if (!oif)
|
|
oif = inet->mc_index;
|
|
if (!saddr)
|
|
saddr = inet->mc_addr;
|
|
}
|
|
err = ip_route_connect(&rt, usin->sin_addr.s_addr, saddr,
|
|
RT_CONN_FLAGS(sk), oif,
|
|
sk->sk_protocol,
|
|
inet->inet_sport, usin->sin_port, sk, 1);
|
|
if (err) {
|
|
if (err == -ENETUNREACH)
|
|
IP_INC_STATS_BH(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
|
|
return err;
|
|
}
|
|
|
|
if ((rt->rt_flags & RTCF_BROADCAST) && !sock_flag(sk, SOCK_BROADCAST)) {
|
|
ip_rt_put(rt);
|
|
return -EACCES;
|
|
}
|
|
if (!inet->inet_saddr)
|
|
inet->inet_saddr = rt->rt_src; /* Update source address */
|
|
if (!inet->inet_rcv_saddr) {
|
|
inet->inet_rcv_saddr = rt->rt_src;
|
|
if (sk->sk_prot->rehash)
|
|
sk->sk_prot->rehash(sk);
|
|
}
|
|
inet->inet_daddr = rt->rt_dst;
|
|
inet->inet_dport = usin->sin_port;
|
|
sk->sk_state = TCP_ESTABLISHED;
|
|
inet->inet_id = jiffies;
|
|
|
|
sk_dst_set(sk, &rt->dst);
|
|
return(0);
|
|
}
|
|
EXPORT_SYMBOL(ip4_datagram_connect);
|