Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c7c3ec49 authored by Robert Love's avatar Robert Love Committed by Ruchi Kandoi
Browse files

net: socket ioctl to reset connections matching local address



Introduce a new socket ioctl, SIOCKILLADDR, that nukes all sockets
bound to the same local address. This is useful in situations with
dynamic IPs, to kill stuck connections.

Signed-off-by: default avatarBrian Swetland <swetland@google.com>

net: fix tcp_v4_nuke_addr

Signed-off-by: default avatarDima Zavin <dima@android.com>

net: ipv4: Fix a spinlock recursion bug in tcp_v4_nuke.

We can't hold the lock while calling to tcp_done(), so we drop
it before calling. We then have to start at the top of the chain again.

Signed-off-by: default avatarDima Zavin <dima@android.com>

net: ipv4: Fix race in tcp_v4_nuke_addr().

To fix a recursive deadlock in 2.6.29, we stopped holding the hash table lock
across tcp_done() calls. This fixed the deadlock, but introduced a race where
the socket could die or change state.

Fix: Before unlocking the hash table, we grab a reference to the socket. We
can then unlock the hash table without risk of the socket going away. We then
lock the socket, which is safe because it is pinned. We can then call
tcp_done() without recursive deadlock and without race. Upon return, we unlock
the socket and then unpin it, killing it.

Change-Id: Idcdae072b48238b01bdbc8823b60310f1976e045
Signed-off-by: default avatarRobert Love <rlove@google.com>
Acked-by: default avatarDima Zavin <dima@android.com>

ipv4: disable bottom halves around call to tcp_done().

Signed-off-by: default avatarRobert Love <rlove@google.com>
Signed-off-by: default avatarColin Cross <ccross@android.com>

ipv4: Move sk_error_report inside bh_lock_sock in tcp_v4_nuke_addr

When sk_error_report is called, it wakes up the user-space thread, which then
calls tcp_close.  When the tcp_close is interrupted by the tcp_v4_nuke_addr
ioctl thread running tcp_done, it leaks 392 bytes and triggers a WARN_ON.

This patch moves the call to sk_error_report inside the bh_lock_sock, which
matches the locking used in tcp_v4_err.

Signed-off-by: default avatarColin Cross <ccross@android.com>
parent b23a0e30
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1601,6 +1601,8 @@ static inline bool tcp_stream_memory_free(const struct sock *sk)
	return notsent_bytes < tcp_notsent_lowat(tp);
}

extern int tcp_nuke_addr(struct net *net, struct sockaddr *addr);

#ifdef CONFIG_PROC_FS
int tcp4_proc_init(void);
void tcp4_proc_exit(void);
+1 −0
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@
#define SIOCDIFADDR	0x8936		/* delete PA address		*/
#define	SIOCSIFHWBROADCAST	0x8937	/* set hardware broadcast addr	*/
#define SIOCGIFCOUNT	0x8938		/* get number of devices */
#define SIOCKILLADDR	0x8939		/* kill sockets with this local addr */

#define SIOCGIFBR	0x8940		/* Bridging support		*/
#define SIOCSIFBR	0x8941		/* Set bridging options 	*/
+1 −0
Original line number Diff line number Diff line
@@ -897,6 +897,7 @@ int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
	case SIOCSIFPFLAGS:
	case SIOCGIFPFLAGS:
	case SIOCSIFFLAGS:
	case SIOCKILLADDR:
		err = devinet_ioctl(net, cmd, (void __user *)arg);
		break;
	default:
+7 −1
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@

#include <net/arp.h>
#include <net/ip.h>
#include <net/tcp.h>
#include <net/route.h>
#include <net/ip_fib.h>
#include <net/rtnetlink.h>
@@ -934,6 +935,7 @@ int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)
	case SIOCSIFBRDADDR:	/* Set the broadcast address */
	case SIOCSIFDSTADDR:	/* Set the destination address */
	case SIOCSIFNETMASK: 	/* Set the netmask for the interface */
	case SIOCKILLADDR:	/* Nuke all sockets on this address */
		ret = -EPERM;
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
			goto out;
@@ -985,7 +987,8 @@ int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)
	}

	ret = -EADDRNOTAVAIL;
	if (!ifa && cmd != SIOCSIFADDR && cmd != SIOCSIFFLAGS)
	if (!ifa && cmd != SIOCSIFADDR && cmd != SIOCSIFFLAGS
	    && cmd != SIOCKILLADDR)
		goto done;

	switch (cmd) {
@@ -1112,6 +1115,9 @@ int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)
			inet_insert_ifa(ifa);
		}
		break;
	case SIOCKILLADDR:	/* Nuke all connections on this address */
		ret = tcp_nuke_addr(net, (struct sockaddr *) sin);
		break;
	}
done:
	rtnl_unlock();
+107 −0
Original line number Diff line number Diff line
@@ -274,6 +274,9 @@
#include <net/tcp.h>
#include <net/xfrm.h>
#include <net/ip.h>
#include <net/ip6_route.h>
#include <net/ipv6.h>
#include <net/transp_v6.h>
#include <net/sock.h>

#include <asm/uaccess.h>
@@ -3123,3 +3126,107 @@ void __init tcp_init(void)
	BUG_ON(tcp_register_congestion_control(&tcp_reno) != 0);
	tcp_tasklet_init();
}

static int tcp_is_local(struct net *net, __be32 addr) {
	struct rtable *rt;
	struct flowi4 fl4 = { .daddr = addr };
	rt = ip_route_output_key(net, &fl4);
	if (IS_ERR_OR_NULL(rt))
		return 0;
	return rt->dst.dev && (rt->dst.dev->flags & IFF_LOOPBACK);
}

#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
static int tcp_is_local6(struct net *net, struct in6_addr *addr) {
	struct rt6_info *rt6 = rt6_lookup(net, addr, addr, 0, 0);
	return rt6 && rt6->dst.dev && (rt6->dst.dev->flags & IFF_LOOPBACK);
}
#endif

/*
 * tcp_nuke_addr - destroy all sockets on the given local address
 * if local address is the unspecified address (0.0.0.0 or ::), destroy all
 * sockets with local addresses that are not configured.
 */
int tcp_nuke_addr(struct net *net, struct sockaddr *addr)
{
	int family = addr->sa_family;
	unsigned int bucket;

	struct in_addr *in;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	struct in6_addr *in6;
#endif
	if (family == AF_INET) {
		in = &((struct sockaddr_in *)addr)->sin_addr;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	} else if (family == AF_INET6) {
		in6 = &((struct sockaddr_in6 *)addr)->sin6_addr;
#endif
	} else {
		return -EAFNOSUPPORT;
	}

	for (bucket = 0; bucket < tcp_hashinfo.ehash_mask; bucket++) {
		struct hlist_nulls_node *node;
		struct sock *sk;
		spinlock_t *lock = inet_ehash_lockp(&tcp_hashinfo, bucket);

restart:
		spin_lock_bh(lock);
		sk_nulls_for_each(sk, node, &tcp_hashinfo.ehash[bucket].chain) {
			struct inet_sock *inet = inet_sk(sk);

			if (sysctl_ip_dynaddr && sk->sk_state == TCP_SYN_SENT)
				continue;
			if (sock_flag(sk, SOCK_DEAD))
				continue;

			if (family == AF_INET) {
				__be32 s4 = inet->inet_rcv_saddr;
				if (s4 == LOOPBACK4_IPV6)
					continue;

				if (in->s_addr != s4 &&
				    !(in->s_addr == INADDR_ANY &&
				      !tcp_is_local(net, s4)))
					continue;
			}

#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
			if (family == AF_INET6) {
				struct in6_addr *s6;
				if (!inet->pinet6)
					continue;

				s6 = &inet->pinet6->rcv_saddr;
				if (ipv6_addr_type(s6) == IPV6_ADDR_MAPPED)
					continue;

				if (!ipv6_addr_equal(in6, s6) &&
				    !(ipv6_addr_equal(in6, &in6addr_any) &&
				      !tcp_is_local6(net, s6)))
				continue;
			}
#endif

			sock_hold(sk);
			spin_unlock_bh(lock);

			local_bh_disable();
			bh_lock_sock(sk);
			sk->sk_err = ETIMEDOUT;
			sk->sk_error_report(sk);

			tcp_done(sk);
			bh_unlock_sock(sk);
			local_bh_enable();
			sock_put(sk);

			goto restart;
		}
		spin_unlock_bh(lock);
	}

	return 0;
}
Loading