net: Support nuking IPv6 sockets as well as IPv4.
authorLorenzo Colitti <lorenzo@google.com>
Fri, 11 Mar 2011 04:24:12 +0000 (20:24 -0800)
committerColin Cross <ccross@android.com>
Tue, 14 Jun 2011 16:09:56 +0000 (09:09 -0700)
On Linux, when an interface goes down all its IPv6
addresses are deleted, so relying on knowing the previous
IPv6 addresses on the interface is brittle. Instead,
support nuking all sockets that are bound to IP addresses
that are not configured and up on the system. This
behaviour is triggered by specifying the unspecified
address (:: or 0.0.0.0). If an IP address is specified, the
behaviour is unchanged, except the ioctl now supports IPv6
as well as IPv4.

Signed-off-by: Lorenzo Colitti <lorenzo@google.com>
include/net/tcp.h
net/ipv4/devinet.c
net/ipv4/tcp.c
net/ipv4/tcp_ipv4.c
net/ipv6/af_inet6.c

index d2b858d1f96743e40c318be430b25c1a9649c450..7377393bb2e293c17e427ef17b91a224a8c08b23 100644 (file)
@@ -1404,7 +1404,7 @@ 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);
+extern int tcp_nuke_addr(struct net *net, struct sockaddr *addr);
 
 #ifdef CONFIG_PROC_FS
 extern int tcp4_proc_init(void);
index b5aad2bd0445b309c46976d6e974c8f8eb507a5e..a8298246dab9d6d305ef5d47c7ef612c9677350d 100644 (file)
@@ -916,8 +916,7 @@ int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)
                }
                break;
        case SIOCKILLADDR:      /* Nuke all connections on this address */
-               ret = 0;
-               tcp_v4_nuke_addr(sin->sin_addr.s_addr);
+               ret = tcp_nuke_addr(net, (struct sockaddr *) sin);
                break;
        }
 done:
index 7764d4c73ee48fa6ce332f89c00aa1064a16d158..58da8e1e2aeef2a387d64c48600f4dd99239c904 100644 (file)
 #include <net/tcp.h>
 #include <net/xfrm.h>
 #include <net/ip.h>
+#include <net/ip6_route.h>
 #include <net/netdma.h>
 #include <net/sock.h>
 
@@ -3328,3 +3329,99 @@ void __init tcp_init(void)
        tcp_secret_retiring = &tcp_secret_two;
        tcp_secret_secondary = &tcp_secret_two;
 }
+
+static int tcp_is_local(struct net *net, __be32 addr) {
+       struct rtable *rt;
+       struct flowi fl = { .nl_u = { .ip4_u = { .daddr = addr } } };
+       if (ip_route_output_key(net, &rt, &fl) || !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->rt6i_dev && (rt6->rt6i_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 (family == AF_INET) {
+                               __be32 s4 = inet->inet_rcv_saddr;
+                               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_equal(in6, s6) &&
+                                   !(ipv6_addr_equal(in6, &in6addr_any) &&
+                                     !tcp_is_local6(net, s6)))
+                               continue;
+                       }
+#endif
+
+                       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);
+       }
+
+       return 0;
+}
index 4186c7613fb46922bb5667a8dd19d17f0452c12a..a7d6671e33b8a6e46aba0b352ad083736d1b421b 100644 (file)
@@ -1954,49 +1954,6 @@ 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. */
 
index cd436663f23f1207b7186df6adb6c162b3068278..8221871ff5cf9afdde65de371b3f45c320bcf9d7 100644 (file)
@@ -494,6 +494,21 @@ int inet6_getname(struct socket *sock, struct sockaddr *uaddr,
 
 EXPORT_SYMBOL(inet6_getname);
 
+int inet6_killaddr_ioctl(struct net *net, void __user *arg) {
+       struct in6_ifreq ireq;
+       struct sockaddr_in6 sin6;
+
+       if (!capable(CAP_NET_ADMIN))
+               return -EACCES;
+
+       if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
+               return -EFAULT;
+
+       sin6.sin6_family = AF_INET6;
+       ipv6_addr_copy(&sin6.sin6_addr, &ireq.ifr6_addr);
+       return tcp_nuke_addr(net, (struct sockaddr *) &sin6);
+}
+
 int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
 {
        struct sock *sk = sock->sk;
@@ -518,6 +533,8 @@ int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
                return addrconf_del_ifaddr(net, (void __user *) arg);
        case SIOCSIFDSTADDR:
                return addrconf_set_dstaddr(net, (void __user *) arg);
+       case SIOCKILLADDR:
+               return inet6_killaddr_ioctl(net, (void __user *) arg);
        default:
                if (!sk->sk_prot->ioctl)
                        return -ENOIOCTLCMD;