Ipvlan should return an error when an address is already in use.
authorKrister Johansen <kjlx@templeofstupid.com>
Thu, 8 Jun 2017 20:12:14 +0000 (13:12 -0700)
committerDavid S. Miller <davem@davemloft.net>
Fri, 9 Jun 2017 16:26:07 +0000 (12:26 -0400)
The ipvlan code already knows how to detect when a duplicate address is
about to be assigned to an ipvlan device.  However, that failure is not
propogated outward and leads to a silent failure.

Introduce a validation step at ip address creation time and allow device
drivers to register to validate the incoming ip addresses.  The ipvlan
code is the first consumer.  If it detects an address in use, we can
return an error to the user before beginning to commit the new ifa in
the networking code.

This can be especially useful if it is necessary to provision many
ipvlans in containers.  The provisioning software (or operator) can use
this to detect situations where an ip address is unexpectedly in use.

Signed-off-by: Krister Johansen <kjlx@templeofstupid.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ipvlan/ipvlan_main.c
include/linux/inetdevice.h
include/net/addrconf.h
net/ipv4/devinet.c
net/ipv6/addrconf.c
net/ipv6/addrconf_core.c

index 618ed88fad0fc1d4e227f0e84fde74462b2bc496..e4141d62b5c34b0104c7d205c4038230766b6f5e 100644 (file)
@@ -824,6 +824,33 @@ static int ipvlan_addr6_event(struct notifier_block *unused,
        return NOTIFY_OK;
 }
 
+static int ipvlan_addr6_validator_event(struct notifier_block *unused,
+                                       unsigned long event, void *ptr)
+{
+       struct in6_validator_info *i6vi = (struct in6_validator_info *)ptr;
+       struct net_device *dev = (struct net_device *)i6vi->i6vi_dev->dev;
+       struct ipvl_dev *ipvlan = netdev_priv(dev);
+
+       /* FIXME IPv6 autoconf calls us from bh without RTNL */
+       if (in_softirq())
+               return NOTIFY_DONE;
+
+       if (!netif_is_ipvlan(dev))
+               return NOTIFY_DONE;
+
+       if (!ipvlan || !ipvlan->port)
+               return NOTIFY_DONE;
+
+       switch (event) {
+       case NETDEV_UP:
+               if (ipvlan_addr_busy(ipvlan->port, &i6vi->i6vi_addr, true))
+                       return notifier_from_errno(-EADDRINUSE);
+               break;
+       }
+
+       return NOTIFY_OK;
+}
+
 static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr)
 {
        if (ipvlan_addr_busy(ipvlan->port, ip4_addr, false)) {
@@ -871,10 +898,37 @@ static int ipvlan_addr4_event(struct notifier_block *unused,
        return NOTIFY_OK;
 }
 
+static int ipvlan_addr4_validator_event(struct notifier_block *unused,
+                                       unsigned long event, void *ptr)
+{
+       struct in_validator_info *ivi = (struct in_validator_info *)ptr;
+       struct net_device *dev = (struct net_device *)ivi->ivi_dev->dev;
+       struct ipvl_dev *ipvlan = netdev_priv(dev);
+
+       if (!netif_is_ipvlan(dev))
+               return NOTIFY_DONE;
+
+       if (!ipvlan || !ipvlan->port)
+               return NOTIFY_DONE;
+
+       switch (event) {
+       case NETDEV_UP:
+               if (ipvlan_addr_busy(ipvlan->port, &ivi->ivi_addr, false))
+                       return notifier_from_errno(-EADDRINUSE);
+               break;
+       }
+
+       return NOTIFY_OK;
+}
+
 static struct notifier_block ipvlan_addr4_notifier_block __read_mostly = {
        .notifier_call = ipvlan_addr4_event,
 };
 
+static struct notifier_block ipvlan_addr4_vtor_notifier_block __read_mostly = {
+       .notifier_call = ipvlan_addr4_validator_event,
+};
+
 static struct notifier_block ipvlan_notifier_block __read_mostly = {
        .notifier_call = ipvlan_device_event,
 };
@@ -883,6 +937,10 @@ static struct notifier_block ipvlan_addr6_notifier_block __read_mostly = {
        .notifier_call = ipvlan_addr6_event,
 };
 
+static struct notifier_block ipvlan_addr6_vtor_notifier_block __read_mostly = {
+       .notifier_call = ipvlan_addr6_validator_event,
+};
+
 static void ipvlan_ns_exit(struct net *net)
 {
        struct ipvlan_netns *vnet = net_generic(net, ipvlan_netid);
@@ -907,7 +965,10 @@ static int __init ipvlan_init_module(void)
        ipvlan_init_secret();
        register_netdevice_notifier(&ipvlan_notifier_block);
        register_inet6addr_notifier(&ipvlan_addr6_notifier_block);
+       register_inet6addr_validator_notifier(
+           &ipvlan_addr6_vtor_notifier_block);
        register_inetaddr_notifier(&ipvlan_addr4_notifier_block);
+       register_inetaddr_validator_notifier(&ipvlan_addr4_vtor_notifier_block);
 
        err = register_pernet_subsys(&ipvlan_net_ops);
        if (err < 0)
@@ -922,7 +983,11 @@ static int __init ipvlan_init_module(void)
        return 0;
 error:
        unregister_inetaddr_notifier(&ipvlan_addr4_notifier_block);
+       unregister_inetaddr_validator_notifier(
+           &ipvlan_addr4_vtor_notifier_block);
        unregister_inet6addr_notifier(&ipvlan_addr6_notifier_block);
+       unregister_inet6addr_validator_notifier(
+           &ipvlan_addr6_vtor_notifier_block);
        unregister_netdevice_notifier(&ipvlan_notifier_block);
        return err;
 }
@@ -933,7 +998,11 @@ static void __exit ipvlan_cleanup_module(void)
        unregister_pernet_subsys(&ipvlan_net_ops);
        unregister_netdevice_notifier(&ipvlan_notifier_block);
        unregister_inetaddr_notifier(&ipvlan_addr4_notifier_block);
+       unregister_inetaddr_validator_notifier(
+           &ipvlan_addr4_vtor_notifier_block);
        unregister_inet6addr_notifier(&ipvlan_addr6_notifier_block);
+       unregister_inet6addr_validator_notifier(
+           &ipvlan_addr6_vtor_notifier_block);
 }
 
 module_init(ipvlan_init_module);
index a2e9d6ea1349fb85418a9ebafc55dd08d16ca6b0..e7c04c4e4bcd3e538b365b138a848df9ed96ede6 100644 (file)
@@ -150,8 +150,15 @@ struct in_ifaddr {
        unsigned long           ifa_tstamp; /* updated timestamp */
 };
 
+struct in_validator_info {
+       __be32                  ivi_addr;
+       struct in_device        *ivi_dev;
+};
+
 int register_inetaddr_notifier(struct notifier_block *nb);
 int unregister_inetaddr_notifier(struct notifier_block *nb);
+int register_inetaddr_validator_notifier(struct notifier_block *nb);
+int unregister_inetaddr_validator_notifier(struct notifier_block *nb);
 
 void inet_netconf_notify_devconf(struct net *net, int event, int type,
                                 int ifindex, struct ipv4_devconf *devconf);
index b43a4eec3ceca4f798bf7513ecd4aa999535c6e7..d0889cb501726685e56a2d6cf333ab55c2d0d48a 100644 (file)
@@ -48,11 +48,15 @@ struct prefix_info {
        struct in6_addr         prefix;
 };
 
-
 #include <linux/netdevice.h>
 #include <net/if_inet6.h>
 #include <net/ipv6.h>
 
+struct in6_validator_info {
+       struct in6_addr         i6vi_addr;
+       struct inet6_dev        *i6vi_dev;
+};
+
 #define IN6_ADDR_HSIZE_SHIFT   4
 #define IN6_ADDR_HSIZE         (1 << IN6_ADDR_HSIZE_SHIFT)
 
@@ -278,6 +282,10 @@ int register_inet6addr_notifier(struct notifier_block *nb);
 int unregister_inet6addr_notifier(struct notifier_block *nb);
 int inet6addr_notifier_call_chain(unsigned long val, void *v);
 
+int register_inet6addr_validator_notifier(struct notifier_block *nb);
+int unregister_inet6addr_validator_notifier(struct notifier_block *nb);
+int inet6addr_validator_notifier_call_chain(unsigned long val, void *v);
+
 void inet6_netconf_notify_devconf(struct net *net, int event, int type,
                                  int ifindex, struct ipv6_devconf *devconf);
 
index df14815a3b8ce74aeb613458ffaf5f6eee4a263d..a7dd088d5fc96da259813cae48c374527c9e251e 100644 (file)
@@ -176,6 +176,7 @@ EXPORT_SYMBOL(__ip_dev_find);
 static void rtmsg_ifa(int event, struct in_ifaddr *, struct nlmsghdr *, u32);
 
 static BLOCKING_NOTIFIER_HEAD(inetaddr_chain);
+static BLOCKING_NOTIFIER_HEAD(inetaddr_validator_chain);
 static void inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap,
                         int destroy);
 #ifdef CONFIG_SYSCTL
@@ -441,6 +442,8 @@ static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh,
 {
        struct in_device *in_dev = ifa->ifa_dev;
        struct in_ifaddr *ifa1, **ifap, **last_primary;
+       struct in_validator_info ivi;
+       int ret;
 
        ASSERT_RTNL();
 
@@ -471,6 +474,23 @@ static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh,
                }
        }
 
+       /* Allow any devices that wish to register ifaddr validtors to weigh
+        * in now, before changes are committed.  The rntl lock is serializing
+        * access here, so the state should not change between a validator call
+        * and a final notify on commit.  This isn't invoked on promotion under
+        * the assumption that validators are checking the address itself, and
+        * not the flags.
+        */
+       ivi.ivi_addr = ifa->ifa_address;
+       ivi.ivi_dev = ifa->ifa_dev;
+       ret = blocking_notifier_call_chain(&inetaddr_validator_chain,
+                                          NETDEV_UP, &ivi);
+       ret = notifier_to_errno(ret);
+       if (ret) {
+               inet_free_ifa(ifa);
+               return ret;
+       }
+
        if (!(ifa->ifa_flags & IFA_F_SECONDARY)) {
                prandom_seed((__force u32) ifa->ifa_local);
                ifap = last_primary;
@@ -1356,6 +1376,19 @@ int unregister_inetaddr_notifier(struct notifier_block *nb)
 }
 EXPORT_SYMBOL(unregister_inetaddr_notifier);
 
+int register_inetaddr_validator_notifier(struct notifier_block *nb)
+{
+       return blocking_notifier_chain_register(&inetaddr_validator_chain, nb);
+}
+EXPORT_SYMBOL(register_inetaddr_validator_notifier);
+
+int unregister_inetaddr_validator_notifier(struct notifier_block *nb)
+{
+       return blocking_notifier_chain_unregister(&inetaddr_validator_chain,
+           nb);
+}
+EXPORT_SYMBOL(unregister_inetaddr_validator_notifier);
+
 /* Rename ifa_labels for a device name change. Make some effort to preserve
  * existing alias numbering and to create unique labels if possible.
 */
index 25443fd946a80f16e627e8ad60f466e60a6ccadf..0aa36b09301305a41daea121b52f0e050d8e9cd5 100644 (file)
@@ -963,6 +963,7 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
        struct net *net = dev_net(idev->dev);
        struct inet6_ifaddr *ifa = NULL;
        struct rt6_info *rt;
+       struct in6_validator_info i6vi;
        unsigned int hash;
        int err = 0;
        int addr_type = ipv6_addr_type(addr);
@@ -974,6 +975,9 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
                return ERR_PTR(-EADDRNOTAVAIL);
 
        rcu_read_lock_bh();
+
+       in6_dev_hold(idev);
+
        if (idev->dead) {
                err = -ENODEV;                  /*XXX*/
                goto out2;
@@ -984,6 +988,17 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
                goto out2;
        }
 
+       i6vi.i6vi_addr = *addr;
+       i6vi.i6vi_dev = idev;
+       rcu_read_unlock_bh();
+
+       err = inet6addr_validator_notifier_call_chain(NETDEV_UP, &i6vi);
+
+       rcu_read_lock_bh();
+       err = notifier_to_errno(err);
+       if (err)
+               goto out2;
+
        spin_lock(&addrconf_hash_lock);
 
        /* Ignore adding duplicate addresses on an interface */
@@ -1034,7 +1049,6 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
        ifa->rt = rt;
 
        ifa->idev = idev;
-       in6_dev_hold(idev);
        /* For caller */
        in6_ifa_hold(ifa);
 
@@ -1062,6 +1076,7 @@ out2:
                inet6addr_notifier_call_chain(NETDEV_UP, ifa);
        else {
                kfree(ifa);
+               in6_dev_put(idev);
                ifa = ERR_PTR(err);
        }
 
index bfa941fc1165002903b5a0364e5584b075469e2e..9e3488d50b157eb8ee7f9be44370176c157a1c62 100644 (file)
@@ -88,6 +88,7 @@ int __ipv6_addr_type(const struct in6_addr *addr)
 EXPORT_SYMBOL(__ipv6_addr_type);
 
 static ATOMIC_NOTIFIER_HEAD(inet6addr_chain);
+static ATOMIC_NOTIFIER_HEAD(inet6addr_validator_chain);
 
 int register_inet6addr_notifier(struct notifier_block *nb)
 {
@@ -107,6 +108,24 @@ int inet6addr_notifier_call_chain(unsigned long val, void *v)
 }
 EXPORT_SYMBOL(inet6addr_notifier_call_chain);
 
+int register_inet6addr_validator_notifier(struct notifier_block *nb)
+{
+       return atomic_notifier_chain_register(&inet6addr_validator_chain, nb);
+}
+EXPORT_SYMBOL(register_inet6addr_validator_notifier);
+
+int unregister_inet6addr_validator_notifier(struct notifier_block *nb)
+{
+       return atomic_notifier_chain_unregister(&inet6addr_validator_chain, nb);
+}
+EXPORT_SYMBOL(unregister_inet6addr_validator_notifier);
+
+int inet6addr_validator_notifier_call_chain(unsigned long val, void *v)
+{
+       return atomic_notifier_call_chain(&inet6addr_validator_chain, val, v);
+}
+EXPORT_SYMBOL(inet6addr_validator_notifier_call_chain);
+
 static int eafnosupport_ipv6_dst_lookup(struct net *net, struct sock *u1,
                                        struct dst_entry **u2,
                                        struct flowi6 *u3)