]> Pileus Git - ~andy/linux/commitdiff
Merge branch 'noprefixroute'
authorDavid S. Miller <davem@davemloft.net>
Thu, 16 Jan 2014 01:00:47 +0000 (17:00 -0800)
committerDavid S. Miller <davem@davemloft.net>
Thu, 16 Jan 2014 01:00:47 +0000 (17:00 -0800)
Thomas Haller says:

====================
ipv6 addrconf: add IFA_F_NOPREFIXROUTE flag to suppress creation of IP6 routes

v1 -> v2: add a second commit, handling NOPREFIXROUTE in ip6_del_addr.
v2 -> v3: reword commit messages, code comments and some refactoring.
v3 -> v4: refactor, rename variables, add enum
v4 -> v5: rebase, so that patch applies cleanly to current net-next/master
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
include/uapi/linux/if_addr.h
net/ipv6/addrconf.c

index cfed10be7529c5f540220789eb0f9025165157f3..dea10a87dfd10d961f9f63a527c729db69f613f7 100644 (file)
@@ -49,6 +49,7 @@ enum {
 #define IFA_F_TENTATIVE                0x40
 #define IFA_F_PERMANENT                0x80
 #define IFA_F_MANAGETEMPADDR   0x100
+#define IFA_F_NOPREFIXROUTE    0x200
 
 struct ifa_cacheinfo {
        __u32   ifa_prefered;
index 1b2e4ee3ce5efcffb9200cca8444baa2bfce3e3e..6913a82f4669d91491d92fe7bb46fa0fa8050a43 100644 (file)
@@ -900,15 +900,95 @@ out:
        goto out2;
 }
 
+enum cleanup_prefix_rt_t {
+       CLEANUP_PREFIX_RT_NOP,    /* no cleanup action for prefix route */
+       CLEANUP_PREFIX_RT_DEL,    /* delete the prefix route */
+       CLEANUP_PREFIX_RT_EXPIRE, /* update the lifetime of the prefix route */
+};
+
+/*
+ * Check, whether the prefix for ifp would still need a prefix route
+ * after deleting ifp. The function returns one of the CLEANUP_PREFIX_RT_*
+ * constants.
+ *
+ * 1) we don't purge prefix if address was not permanent.
+ *    prefix is managed by its own lifetime.
+ * 2) we also don't purge, if the address was IFA_F_NOPREFIXROUTE.
+ * 3) if there are no addresses, delete prefix.
+ * 4) if there are still other permanent address(es),
+ *    corresponding prefix is still permanent.
+ * 5) if there are still other addresses with IFA_F_NOPREFIXROUTE,
+ *    don't purge the prefix, assume user space is managing it.
+ * 6) otherwise, update prefix lifetime to the
+ *    longest valid lifetime among the corresponding
+ *    addresses on the device.
+ *    Note: subsequent RA will update lifetime.
+ **/
+static enum cleanup_prefix_rt_t
+check_cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long *expires)
+{
+       struct inet6_ifaddr *ifa;
+       struct inet6_dev *idev = ifp->idev;
+       unsigned long lifetime;
+       enum cleanup_prefix_rt_t action = CLEANUP_PREFIX_RT_DEL;
+
+       *expires = jiffies;
+
+       list_for_each_entry(ifa, &idev->addr_list, if_list) {
+               if (ifa == ifp)
+                       continue;
+               if (!ipv6_prefix_equal(&ifa->addr, &ifp->addr,
+                                      ifp->prefix_len))
+                       continue;
+               if (ifa->flags & (IFA_F_PERMANENT | IFA_F_NOPREFIXROUTE))
+                       return CLEANUP_PREFIX_RT_NOP;
+
+               action = CLEANUP_PREFIX_RT_EXPIRE;
+
+               spin_lock(&ifa->lock);
+
+               lifetime = addrconf_timeout_fixup(ifa->valid_lft, HZ);
+               /*
+                * Note: Because this address is
+                * not permanent, lifetime <
+                * LONG_MAX / HZ here.
+                */
+               if (time_before(*expires, ifa->tstamp + lifetime * HZ))
+                       *expires = ifa->tstamp + lifetime * HZ;
+               spin_unlock(&ifa->lock);
+       }
+
+       return action;
+}
+
+static void
+cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long expires, bool del_rt)
+{
+       struct rt6_info *rt;
+
+       rt = addrconf_get_prefix_route(&ifp->addr,
+                                      ifp->prefix_len,
+                                      ifp->idev->dev,
+                                      0, RTF_GATEWAY | RTF_DEFAULT);
+       if (rt) {
+               if (del_rt)
+                       ip6_del_rt(rt);
+               else {
+                       if (!(rt->rt6i_flags & RTF_EXPIRES))
+                               rt6_set_expires(rt, expires);
+                       ip6_rt_put(rt);
+               }
+       }
+}
+
+
 /* This function wants to get referenced ifp and releases it before return */
 
 static void ipv6_del_addr(struct inet6_ifaddr *ifp)
 {
-       struct inet6_ifaddr *ifa, *ifn;
-       struct inet6_dev *idev = ifp->idev;
        int state;
-       int deleted = 0, onlink = 0;
-       unsigned long expires = jiffies;
+       enum cleanup_prefix_rt_t action = CLEANUP_PREFIX_RT_NOP;
+       unsigned long expires;
 
        spin_lock_bh(&ifp->state_lock);
        state = ifp->state;
@@ -922,7 +1002,7 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp)
        hlist_del_init_rcu(&ifp->addr_lst);
        spin_unlock_bh(&addrconf_hash_lock);
 
-       write_lock_bh(&idev->lock);
+       write_lock_bh(&ifp->idev->lock);
 
        if (ifp->flags&IFA_F_TEMPORARY) {
                list_del(&ifp->tmp_list);
@@ -933,45 +1013,13 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp)
                __in6_ifa_put(ifp);
        }
 
-       list_for_each_entry_safe(ifa, ifn, &idev->addr_list, if_list) {
-               if (ifa == ifp) {
-                       list_del_init(&ifp->if_list);
-                       __in6_ifa_put(ifp);
+       if (ifp->flags & IFA_F_PERMANENT && !(ifp->flags & IFA_F_NOPREFIXROUTE))
+               action = check_cleanup_prefix_route(ifp, &expires);
 
-                       if (!(ifp->flags & IFA_F_PERMANENT) || onlink > 0)
-                               break;
-                       deleted = 1;
-                       continue;
-               } else if (ifp->flags & IFA_F_PERMANENT) {
-                       if (ipv6_prefix_equal(&ifa->addr, &ifp->addr,
-                                             ifp->prefix_len)) {
-                               if (ifa->flags & IFA_F_PERMANENT) {
-                                       onlink = 1;
-                                       if (deleted)
-                                               break;
-                               } else {
-                                       unsigned long lifetime;
-
-                                       if (!onlink)
-                                               onlink = -1;
-
-                                       spin_lock(&ifa->lock);
-
-                                       lifetime = addrconf_timeout_fixup(ifa->valid_lft, HZ);
-                                       /*
-                                        * Note: Because this address is
-                                        * not permanent, lifetime <
-                                        * LONG_MAX / HZ here.
-                                        */
-                                       if (time_before(expires,
-                                                       ifa->tstamp + lifetime * HZ))
-                                               expires = ifa->tstamp + lifetime * HZ;
-                                       spin_unlock(&ifa->lock);
-                               }
-                       }
-               }
-       }
-       write_unlock_bh(&idev->lock);
+       list_del_init(&ifp->if_list);
+       __in6_ifa_put(ifp);
+
+       write_unlock_bh(&ifp->idev->lock);
 
        addrconf_del_dad_timer(ifp);
 
@@ -979,38 +1027,9 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp)
 
        inet6addr_notifier_call_chain(NETDEV_DOWN, ifp);
 
-       /*
-        * Purge or update corresponding prefix
-        *
-        * 1) we don't purge prefix here if address was not permanent.
-        *    prefix is managed by its own lifetime.
-        * 2) if there are no addresses, delete prefix.
-        * 3) if there are still other permanent address(es),
-        *    corresponding prefix is still permanent.
-        * 4) otherwise, update prefix lifetime to the
-        *    longest valid lifetime among the corresponding
-        *    addresses on the device.
-        *    Note: subsequent RA will update lifetime.
-        *
-        * --yoshfuji
-        */
-       if ((ifp->flags & IFA_F_PERMANENT) && onlink < 1) {
-               struct rt6_info *rt;
-
-               rt = addrconf_get_prefix_route(&ifp->addr,
-                                              ifp->prefix_len,
-                                              ifp->idev->dev,
-                                              0, RTF_GATEWAY | RTF_DEFAULT);
-
-               if (rt) {
-                       if (onlink == 0) {
-                               ip6_del_rt(rt);
-                               rt = NULL;
-                       } else if (!(rt->rt6i_flags & RTF_EXPIRES)) {
-                               rt6_set_expires(rt, expires);
-                       }
-               }
-               ip6_rt_put(rt);
+       if (action != CLEANUP_PREFIX_RT_NOP) {
+               cleanup_prefix_route(ifp, expires,
+                       action == CLEANUP_PREFIX_RT_DEL);
        }
 
        /* clean up prefsrc entries */
@@ -2434,8 +2453,11 @@ static int inet6_addr_add(struct net *net, int ifindex,
                            valid_lft, prefered_lft);
 
        if (!IS_ERR(ifp)) {
-               addrconf_prefix_route(&ifp->addr, ifp->prefix_len, dev,
-                                     expires, flags);
+               if (!(ifa_flags & IFA_F_NOPREFIXROUTE)) {
+                       addrconf_prefix_route(&ifp->addr, ifp->prefix_len, dev,
+                                             expires, flags);
+               }
+
                /*
                 * Note that section 3.1 of RFC 4429 indicates
                 * that the Optimistic flag should not be set for
@@ -3633,6 +3655,7 @@ static int inet6_addr_modify(struct inet6_ifaddr *ifp, u32 ifa_flags,
        clock_t expires;
        unsigned long timeout;
        bool was_managetempaddr;
+       bool had_prefixroute;
 
        if (!valid_lft || (prefered_lft > valid_lft))
                return -EINVAL;
@@ -3661,8 +3684,11 @@ static int inet6_addr_modify(struct inet6_ifaddr *ifp, u32 ifa_flags,
 
        spin_lock_bh(&ifp->lock);
        was_managetempaddr = ifp->flags & IFA_F_MANAGETEMPADDR;
+       had_prefixroute = ifp->flags & IFA_F_PERMANENT &&
+                         !(ifp->flags & IFA_F_NOPREFIXROUTE);
        ifp->flags &= ~(IFA_F_DEPRECATED | IFA_F_PERMANENT | IFA_F_NODAD |
-                       IFA_F_HOMEADDRESS | IFA_F_MANAGETEMPADDR);
+                       IFA_F_HOMEADDRESS | IFA_F_MANAGETEMPADDR |
+                       IFA_F_NOPREFIXROUTE);
        ifp->flags |= ifa_flags;
        ifp->tstamp = jiffies;
        ifp->valid_lft = valid_lft;
@@ -3672,8 +3698,22 @@ static int inet6_addr_modify(struct inet6_ifaddr *ifp, u32 ifa_flags,
        if (!(ifp->flags&IFA_F_TENTATIVE))
                ipv6_ifa_notify(0, ifp);
 
-       addrconf_prefix_route(&ifp->addr, ifp->prefix_len, ifp->idev->dev,
-                             expires, flags);
+       if (!(ifa_flags & IFA_F_NOPREFIXROUTE)) {
+               addrconf_prefix_route(&ifp->addr, ifp->prefix_len, ifp->idev->dev,
+                                     expires, flags);
+       } else if (had_prefixroute) {
+               enum cleanup_prefix_rt_t action;
+               unsigned long rt_expires;
+
+               write_lock_bh(&ifp->idev->lock);
+               action = check_cleanup_prefix_route(ifp, &rt_expires);
+               write_unlock_bh(&ifp->idev->lock);
+
+               if (action != CLEANUP_PREFIX_RT_NOP) {
+                       cleanup_prefix_route(ifp, rt_expires,
+                               action == CLEANUP_PREFIX_RT_DEL);
+               }
+       }
 
        if (was_managetempaddr || ifp->flags & IFA_F_MANAGETEMPADDR) {
                if (was_managetempaddr && !(ifp->flags & IFA_F_MANAGETEMPADDR))
@@ -3727,7 +3767,8 @@ inet6_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh)
        ifa_flags = tb[IFA_FLAGS] ? nla_get_u32(tb[IFA_FLAGS]) : ifm->ifa_flags;
 
        /* We ignore other flags so far. */
-       ifa_flags &= IFA_F_NODAD | IFA_F_HOMEADDRESS | IFA_F_MANAGETEMPADDR;
+       ifa_flags &= IFA_F_NODAD | IFA_F_HOMEADDRESS | IFA_F_MANAGETEMPADDR |
+                    IFA_F_NOPREFIXROUTE;
 
        ifa = ipv6_get_ifaddr(net, pfx, dev, 1);
        if (ifa == NULL) {