summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorLorenzo Colitti <lorenzo@google.com>2011-12-02 13:52:45 +0800
committerAndy Green <andy.green@linaro.org>2011-12-02 13:52:45 +0800
commitb6f282d84bbc9cc8931b919099e69bc6c0959344 (patch)
treed05f025d335890f23070447b3e3d1ce615e70a97 /net
parent4594340381d27c809c7daf7f70ceecdbc29ec92c (diff)
net: Support nuking IPv6 sockets as well as IPv4.
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>
Diffstat (limited to 'net')
-rw-r--r--net/ipv4/devinet.c3
-rw-r--r--net/ipv4/tcp.c97
-rw-r--r--net/ipv4/tcp_ipv4.c43
-rw-r--r--net/ipv6/af_inet6.c17
4 files changed, 115 insertions, 45 deletions
diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c
index d1dc83caa2f..e9b81e69f78 100644
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -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:
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index b9693c1512b..953316169b1 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -272,6 +272,7 @@
#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>
@@ -3317,3 +3318,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;
+}
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index a4430ed3440..a9db4b1a221 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1976,49 +1976,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. */
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index 4eb4d74d0a9..4252b3cc183 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -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;