net: socket ioctl to reset connections matching local address
authorRobert Love <rlove@google.com>
Mon, 12 May 2008 21:08:29 +0000 (17:08 -0400)
committerColin Cross <ccross@android.com>
Tue, 14 Jun 2011 16:08:47 +0000 (09:08 -0700)
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>
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: Dima 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: Robert Love <rlove@google.com>
Acked-by: Dima Zavin <dima@android.com>
ipv4: disable bottom halves around call to tcp_done().

Signed-off-by: Robert Love <rlove@google.com>
Signed-off-by: Colin 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: Colin Cross <ccross@android.com>
include/linux/sockios.h
include/net/tcp.h
net/ipv4/af_inet.c
net/ipv4/devinet.c
net/ipv4/tcp_ipv4.c

index 7997a506ad4105fb145a9ddc120286a8431a1502..f7ffe36db03c4d8a1b9de218a8c402e24bd6e87f 100644 (file)
@@ -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         */
index cda30ea354a214072b634ee9c2fa9b7ff23cc216..d2b858d1f96743e40c318be430b25c1a9649c450 100644 (file)
@@ -1404,6 +1404,8 @@ extern struct sk_buff **tcp4_gro_receive(struct sk_buff **head,
 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);
index 5683c8f2bfa08756c70f39fac88965ccc7a167a9..49f024f62963e7de8dc3e652713232e77535ed6f 100644 (file)
@@ -900,6 +900,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:
index 0d4a184af16f901fc9d78d17ed1e5dc080446534..b5aad2bd0445b309c46976d6e974c8f8eb507a5e 100644 (file)
@@ -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>
@@ -735,6 +736,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 = -EACCES;
                if (!capable(CAP_NET_ADMIN))
                        goto out;
@@ -786,7 +788,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) {
@@ -912,6 +915,10 @@ 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 = 0;
+               tcp_v4_nuke_addr(sin->sin_addr.s_addr);
+               break;
        }
 done:
        rtnl_unlock();
index a7d6671e33b8a6e46aba0b352ad083736d1b421b..4186c7613fb46922bb5667a8dd19d17f0452c12a 100644 (file)
@@ -1954,6 +1954,49 @@ 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_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 (inet->inet_rcv_saddr != saddr)
+                               continue;
+                       if (sysctl_ip_dynaddr && sk->sk_state == TCP_SYN_SENT)
+                               continue;
+                       if (sock_flag(sk, SOCK_DEAD))
+                               continue;
+
+                       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);
+       }
+}
+
 #ifdef CONFIG_PROC_FS
 /* Proc filesystem TCP sock list dumping. */