nfsd: Register callbacks on the inetaddr_chain and inet6addr_chain
authorScott Mayhew <smayhew@redhat.com>
Fri, 11 Dec 2015 21:45:59 +0000 (16:45 -0500)
committerJ. Bruce Fields <bfields@redhat.com>
Wed, 23 Dec 2015 15:08:15 +0000 (10:08 -0500)
Register callbacks on inetaddr_chain and inet6addr_chain to trigger
cleanup of nfsd transport sockets when an ip address is deleted.

Signed-off-by: Scott Mayhew <smayhew@redhat.com>
Signed-off-by: J. Bruce Fields <bfields@redhat.com>
fs/nfsd/nfssvc.c

index ad4e2377dd636cf893e7a02e61565730207045c5..3779a5fbeb424dafb6346aec30c5ceb7ec670538 100644 (file)
 
 #include <linux/sunrpc/stats.h>
 #include <linux/sunrpc/svcsock.h>
+#include <linux/sunrpc/svc_xprt.h>
 #include <linux/lockd/bind.h>
 #include <linux/nfsacl.h>
 #include <linux/seq_file.h>
+#include <linux/inetdevice.h>
+#include <net/addrconf.h>
+#include <net/ipv6.h>
 #include <net/net_namespace.h>
 #include "nfsd.h"
 #include "cache.h"
@@ -306,10 +310,70 @@ static void nfsd_shutdown_net(struct net *net)
        nfsd_shutdown_generic();
 }
 
+static int nfsd_inetaddr_event(struct notifier_block *this, unsigned long event,
+       void *ptr)
+{
+       struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
+       struct net_device *dev = ifa->ifa_dev->dev;
+       struct net *net = dev_net(dev);
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+       struct sockaddr_in sin;
+
+       if (event != NETDEV_DOWN)
+               goto out;
+
+       if (nn->nfsd_serv) {
+               dprintk("nfsd_inetaddr_event: removed %pI4\n", &ifa->ifa_local);
+               sin.sin_family = AF_INET;
+               sin.sin_addr.s_addr = ifa->ifa_local;
+               svc_age_temp_xprts_now(nn->nfsd_serv, (struct sockaddr *)&sin);
+       }
+
+out:
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block nfsd_inetaddr_notifier = {
+       .notifier_call = nfsd_inetaddr_event,
+};
+
+#if IS_ENABLED(CONFIG_IPV6)
+static int nfsd_inet6addr_event(struct notifier_block *this,
+       unsigned long event, void *ptr)
+{
+       struct inet6_ifaddr *ifa = (struct inet6_ifaddr *)ptr;
+       struct net_device *dev = ifa->idev->dev;
+       struct net *net = dev_net(dev);
+       struct nfsd_net *nn = net_generic(net, nfsd_net_id);
+       struct sockaddr_in6 sin6;
+
+       if (event != NETDEV_DOWN)
+               goto out;
+
+       if (nn->nfsd_serv) {
+               dprintk("nfsd_inet6addr_event: removed %pI6\n", &ifa->addr);
+               sin6.sin6_family = AF_INET6;
+               sin6.sin6_addr = ifa->addr;
+               svc_age_temp_xprts_now(nn->nfsd_serv, (struct sockaddr *)&sin6);
+       }
+
+out:
+       return NOTIFY_DONE;
+}
+
+static struct notifier_block nfsd_inet6addr_notifier = {
+       .notifier_call = nfsd_inet6addr_event,
+};
+#endif
+
 static void nfsd_last_thread(struct svc_serv *serv, struct net *net)
 {
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
+       unregister_inetaddr_notifier(&nfsd_inetaddr_notifier);
+#if IS_ENABLED(CONFIG_IPV6)
+       unregister_inet6addr_notifier(&nfsd_inet6addr_notifier);
+#endif
        /*
         * write_ports can create the server without actually starting
         * any threads--if we get shut down before any threads are
@@ -425,6 +489,10 @@ int nfsd_create_serv(struct net *net)
        }
 
        set_max_drc();
+       register_inetaddr_notifier(&nfsd_inetaddr_notifier);
+#if IS_ENABLED(CONFIG_IPV6)
+       register_inet6addr_notifier(&nfsd_inet6addr_notifier);
+#endif
        do_gettimeofday(&nn->nfssvc_boot);              /* record boot time */
        return 0;
 }