From 2df5996350da51bcdee798c00ae1f2415fd9ad20 Mon Sep 17 00:00:00 2001
From: Robert Love <rlove@google.com>
Date: Mon, 12 May 2008 17:08:29 -0400
Subject: [PATCH 081/134] 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: Brian Swetland <swetland@google.com>

net: fix tcp_v4_nuke_addr

Signed-off-by: Dima Zavin <dima@android.com>
---
 include/linux/sockios.h |    1 +
 include/net/tcp.h       |    2 ++
 net/ipv4/af_inet.c      |    1 +
 net/ipv4/devinet.c      |    9 ++++++++-
 net/ipv4/tcp_ipv4.c     |   31 +++++++++++++++++++++++++++++++
 5 files changed, 43 insertions(+), 1 deletions(-)

--- a/include/linux/sockios.h
+++ b/include/linux/sockios.h
@@ -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 	*/
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1381,6 +1381,8 @@ extern struct sk_buff **tcp4_gro_receive
 extern int tcp_gro_complete(struct sk_buff *skb);
 extern int tcp4_gro_complete(struct sk_buff *skb);
 
+extern void tcp_v4_nuke_addr(__u32 saddr);
+
 #ifdef CONFIG_PROC_FS
 extern int  tcp4_proc_init(void);
 extern void tcp4_proc_exit(void);
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -825,6 +825,7 @@ int inet_ioctl(struct socket *sock, unsi
 		case SIOCSIFPFLAGS:
 		case SIOCGIFPFLAGS:
 		case SIOCSIFFLAGS:
+		case SIOCKILLADDR:
 			err = devinet_ioctl(net, cmd, (void __user *)arg);
 			break;
 		default:
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -57,6 +57,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>
@@ -631,6 +632,7 @@ int devinet_ioctl(struct net *net, unsig
 	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 = -EACCES;
 		if (!capable(CAP_NET_ADMIN))
 			goto out;
@@ -680,7 +682,8 @@ int devinet_ioctl(struct net *net, unsig
 	}
 
 	ret = -EADDRNOTAVAIL;
-	if (!ifa && cmd != SIOCSIFADDR && cmd != SIOCSIFFLAGS)
+	if (!ifa && cmd != SIOCSIFADDR && cmd != SIOCSIFFLAGS
+	    && cmd != SIOCKILLADDR)
 		goto done;
 
 	switch (cmd) {
@@ -804,6 +807,10 @@ int devinet_ioctl(struct net *net, unsig
 			inet_insert_ifa(ifa);
 		}
 		break;
+	case SIOCKILLADDR:	/* Nuke all connections on this address */
+		ret = 0;
+		tcp_v4_nuke_addr(sin->sin_addr.s_addr);
+		break;
 	}
 done:
 	rtnl_unlock();
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1845,6 +1845,37 @@ void tcp_v4_destroy_sock(struct sock *sk
 
 EXPORT_SYMBOL(tcp_v4_destroy_sock);
 
+/*
+ * tcp_v4_nuke_addr - destroy all sockets on the given local address
+ */
+void tcp_v4_nuke_addr(__u32 saddr)
+{
+	unsigned int bucket;
+
+	for (bucket = 0; bucket < tcp_hashinfo.ehash_size; bucket++) {
+		struct hlist_nulls_node *node;
+		struct sock *sk;
+		spinlock_t *lock = inet_ehash_lockp(&tcp_hashinfo, bucket);
+
+		spin_lock_bh(lock);
+		sk_nulls_for_each(sk, node, &tcp_hashinfo.ehash[bucket].chain) {
+			struct inet_sock *inet = inet_sk(sk);
+
+			if (inet->rcv_saddr != saddr)
+				continue;
+			if (sysctl_ip_dynaddr && sk->sk_state == TCP_SYN_SENT)
+				continue;
+			if (sock_flag(sk, SOCK_DEAD))
+				continue;
+
+			sk->sk_err = ETIMEDOUT;
+			sk->sk_error_report(sk);
+			tcp_done(sk);
+		}
+		spin_unlock_bh(lock);
+	}
+}
+
 #ifdef CONFIG_PROC_FS
 /* Proc filesystem TCP sock list dumping. */
 
