ipv6 addrconf: introduce IFA_F_MANAGETEMPADDR to tell kernel to manage temporary...
authorJiri Pirko <jiri@resnulli.us>
Fri, 6 Dec 2013 08:45:22 +0000 (09:45 +0100)
committerDavid S. Miller <davem@davemloft.net>
Fri, 6 Dec 2013 21:34:43 +0000 (16:34 -0500)
Creating an address with this flag set will result in kernel taking care
of temporary addresses in the same way as if the address was created by
kernel itself (after RA receive). This allows userspace applications
implementing the autoconfiguration (NetworkManager for example) to
implement ipv6 addresses privacy.

Signed-off-by: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: Thomas Haller <thaller@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/uapi/linux/if_addr.h
net/ipv6/addrconf.c

index 8ab0c2cc9b7307d9434a9b546cee49e4c017d315..cfed10be7529c5f540220789eb0f9025165157f3 100644 (file)
@@ -48,6 +48,7 @@ enum {
 #define IFA_F_DEPRECATED       0x20
 #define IFA_F_TENTATIVE                0x40
 #define IFA_F_PERMANENT                0x80
+#define IFA_F_MANAGETEMPADDR   0x100
 
 struct ifa_cacheinfo {
        __u32   ifa_prefered;
index 334a7e114e1dbf397ab35b3f5c0d4d1d5bce3767..5087cc5cb810f5363c6f603cb63e52c49136b2bc 100644 (file)
@@ -1024,7 +1024,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, struct inet6_ifaddr *i
        u32 addr_flags;
        unsigned long now = jiffies;
 
-       write_lock(&idev->lock);
+       write_lock_bh(&idev->lock);
        if (ift) {
                spin_lock_bh(&ift->lock);
                memcpy(&addr.s6_addr[8], &ift->addr.s6_addr[8], 8);
@@ -1036,7 +1036,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, struct inet6_ifaddr *i
 retry:
        in6_dev_hold(idev);
        if (idev->cnf.use_tempaddr <= 0) {
-               write_unlock(&idev->lock);
+               write_unlock_bh(&idev->lock);
                pr_info("%s: use_tempaddr is disabled\n", __func__);
                in6_dev_put(idev);
                ret = -1;
@@ -1046,7 +1046,7 @@ retry:
        if (ifp->regen_count++ >= idev->cnf.regen_max_retry) {
                idev->cnf.use_tempaddr = -1;    /*XXX*/
                spin_unlock_bh(&ifp->lock);
-               write_unlock(&idev->lock);
+               write_unlock_bh(&idev->lock);
                pr_warn("%s: regeneration time exceeded - disabled temporary address support\n",
                        __func__);
                in6_dev_put(idev);
@@ -1072,7 +1072,7 @@ retry:
        regen_advance = idev->cnf.regen_max_retry *
                        idev->cnf.dad_transmits *
                        idev->nd_parms->retrans_time / HZ;
-       write_unlock(&idev->lock);
+       write_unlock_bh(&idev->lock);
 
        /* A temporary address is created only if this calculated Preferred
         * Lifetime is greater than REGEN_ADVANCE time units.  In particular,
@@ -1099,7 +1099,7 @@ retry:
                in6_dev_put(idev);
                pr_info("%s: retry temporary address regeneration\n", __func__);
                tmpaddr = &addr;
-               write_lock(&idev->lock);
+               write_lock_bh(&idev->lock);
                goto retry;
        }
 
@@ -2016,6 +2016,73 @@ static struct inet6_dev *addrconf_add_dev(struct net_device *dev)
        return idev;
 }
 
+static void manage_tempaddrs(struct inet6_dev *idev,
+                            struct inet6_ifaddr *ifp,
+                            __u32 valid_lft, __u32 prefered_lft,
+                            bool create, unsigned long now)
+{
+       u32 flags;
+       struct inet6_ifaddr *ift;
+
+       read_lock_bh(&idev->lock);
+       /* update all temporary addresses in the list */
+       list_for_each_entry(ift, &idev->tempaddr_list, tmp_list) {
+               int age, max_valid, max_prefered;
+
+               if (ifp != ift->ifpub)
+                       continue;
+
+               /* RFC 4941 section 3.3:
+                * If a received option will extend the lifetime of a public
+                * address, the lifetimes of temporary addresses should
+                * be extended, subject to the overall constraint that no
+                * temporary addresses should ever remain "valid" or "preferred"
+                * for a time longer than (TEMP_VALID_LIFETIME) or
+                * (TEMP_PREFERRED_LIFETIME - DESYNC_FACTOR), respectively.
+                */
+               age = (now - ift->cstamp) / HZ;
+               max_valid = idev->cnf.temp_valid_lft - age;
+               if (max_valid < 0)
+                       max_valid = 0;
+
+               max_prefered = idev->cnf.temp_prefered_lft -
+                              idev->cnf.max_desync_factor - age;
+               if (max_prefered < 0)
+                       max_prefered = 0;
+
+               if (valid_lft > max_valid)
+                       valid_lft = max_valid;
+
+               if (prefered_lft > max_prefered)
+                       prefered_lft = max_prefered;
+
+               spin_lock(&ift->lock);
+               flags = ift->flags;
+               ift->valid_lft = valid_lft;
+               ift->prefered_lft = prefered_lft;
+               ift->tstamp = now;
+               if (prefered_lft > 0)
+                       ift->flags &= ~IFA_F_DEPRECATED;
+
+               spin_unlock(&ift->lock);
+               if (!(flags&IFA_F_TENTATIVE))
+                       ipv6_ifa_notify(0, ift);
+       }
+
+       if ((create || list_empty(&idev->tempaddr_list)) &&
+           idev->cnf.use_tempaddr > 0) {
+               /* When a new public address is created as described
+                * in [ADDRCONF], also create a new temporary address.
+                * Also create a temporary address if it's enabled but
+                * no temporary address currently exists.
+                */
+               read_unlock_bh(&idev->lock);
+               ipv6_create_tempaddr(ifp, NULL);
+       } else {
+               read_unlock_bh(&idev->lock);
+       }
+}
+
 void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
 {
        struct prefix_info *pinfo;
@@ -2170,6 +2237,7 @@ ok:
                                return;
                        }
 
+                       ifp->flags |= IFA_F_MANAGETEMPADDR;
                        update_lft = 0;
                        create = 1;
                        ifp->cstamp = jiffies;
@@ -2180,7 +2248,6 @@ ok:
                if (ifp) {
                        u32 flags;
                        unsigned long now;
-                       struct inet6_ifaddr *ift;
                        u32 stored_lft;
 
                        /* update lifetime (RFC2462 5.5.3 e) */
@@ -2221,70 +2288,8 @@ ok:
                        } else
                                spin_unlock(&ifp->lock);
 
-                       read_lock_bh(&in6_dev->lock);
-                       /* update all temporary addresses in the list */
-                       list_for_each_entry(ift, &in6_dev->tempaddr_list,
-                                           tmp_list) {
-                               int age, max_valid, max_prefered;
-
-                               if (ifp != ift->ifpub)
-                                       continue;
-
-                               /*
-                                * RFC 4941 section 3.3:
-                                * If a received option will extend the lifetime
-                                * of a public address, the lifetimes of
-                                * temporary addresses should be extended,
-                                * subject to the overall constraint that no
-                                * temporary addresses should ever remain
-                                * "valid" or "preferred" for a time longer than
-                                * (TEMP_VALID_LIFETIME) or
-                                * (TEMP_PREFERRED_LIFETIME - DESYNC_FACTOR),
-                                * respectively.
-                                */
-                               age = (now - ift->cstamp) / HZ;
-                               max_valid = in6_dev->cnf.temp_valid_lft - age;
-                               if (max_valid < 0)
-                                       max_valid = 0;
-
-                               max_prefered = in6_dev->cnf.temp_prefered_lft -
-                                              in6_dev->cnf.max_desync_factor -
-                                              age;
-                               if (max_prefered < 0)
-                                       max_prefered = 0;
-
-                               if (valid_lft > max_valid)
-                                       valid_lft = max_valid;
-
-                               if (prefered_lft > max_prefered)
-                                       prefered_lft = max_prefered;
-
-                               spin_lock(&ift->lock);
-                               flags = ift->flags;
-                               ift->valid_lft = valid_lft;
-                               ift->prefered_lft = prefered_lft;
-                               ift->tstamp = now;
-                               if (prefered_lft > 0)
-                                       ift->flags &= ~IFA_F_DEPRECATED;
-
-                               spin_unlock(&ift->lock);
-                               if (!(flags&IFA_F_TENTATIVE))
-                                       ipv6_ifa_notify(0, ift);
-                       }
-
-                       if ((create || list_empty(&in6_dev->tempaddr_list)) && in6_dev->cnf.use_tempaddr > 0) {
-                               /*
-                                * When a new public address is created as
-                                * described in [ADDRCONF], also create a new
-                                * temporary address. Also create a temporary
-                                * address if it's enabled but no temporary
-                                * address currently exists.
-                                */
-                               read_unlock_bh(&in6_dev->lock);
-                               ipv6_create_tempaddr(ifp, NULL);
-                       } else {
-                               read_unlock_bh(&in6_dev->lock);
-                       }
+                       manage_tempaddrs(in6_dev, ifp, valid_lft, prefered_lft,
+                                        create, now);
 
                        in6_ifa_put(ifp);
                        addrconf_verify(0);
@@ -2386,6 +2391,9 @@ static int inet6_addr_add(struct net *net, int ifindex,
        if (!valid_lft || prefered_lft > valid_lft)
                return -EINVAL;
 
+       if (ifa_flags & IFA_F_MANAGETEMPADDR && plen != 64)
+               return -EINVAL;
+
        dev = __dev_get_by_index(net, ifindex);
        if (!dev)
                return -ENODEV;
@@ -2426,6 +2434,9 @@ static int inet6_addr_add(struct net *net, int ifindex,
                 * manually configured addresses
                 */
                addrconf_dad_start(ifp);
+               if (ifa_flags & IFA_F_MANAGETEMPADDR)
+                       manage_tempaddrs(idev, ifp, valid_lft, prefered_lft,
+                                        true, jiffies);
                in6_ifa_put(ifp);
                addrconf_verify(0);
                return 0;
@@ -3603,10 +3614,15 @@ static int inet6_addr_modify(struct inet6_ifaddr *ifp, u32 ifa_flags,
        u32 flags;
        clock_t expires;
        unsigned long timeout;
+       bool was_managetempaddr;
 
        if (!valid_lft || (prefered_lft > valid_lft))
                return -EINVAL;
 
+       if (ifa_flags & IFA_F_MANAGETEMPADDR &&
+           (ifp->flags & IFA_F_TEMPORARY || ifp->prefix_len != 64))
+               return -EINVAL;
+
        timeout = addrconf_timeout_fixup(valid_lft, HZ);
        if (addrconf_finite_timeout(timeout)) {
                expires = jiffies_to_clock_t(timeout * HZ);
@@ -3626,7 +3642,10 @@ static int inet6_addr_modify(struct inet6_ifaddr *ifp, u32 ifa_flags,
        }
 
        spin_lock_bh(&ifp->lock);
-       ifp->flags = (ifp->flags & ~(IFA_F_DEPRECATED | IFA_F_PERMANENT | IFA_F_NODAD | IFA_F_HOMEADDRESS)) | ifa_flags;
+       was_managetempaddr = ifp->flags & IFA_F_MANAGETEMPADDR;
+       ifp->flags &= ~(IFA_F_DEPRECATED | IFA_F_PERMANENT | IFA_F_NODAD |
+                       IFA_F_HOMEADDRESS | IFA_F_MANAGETEMPADDR);
+       ifp->flags |= ifa_flags;
        ifp->tstamp = jiffies;
        ifp->valid_lft = valid_lft;
        ifp->prefered_lft = prefered_lft;
@@ -3637,6 +3656,14 @@ static int inet6_addr_modify(struct inet6_ifaddr *ifp, u32 ifa_flags,
 
        addrconf_prefix_route(&ifp->addr, ifp->prefix_len, ifp->idev->dev,
                              expires, flags);
+
+       if (was_managetempaddr || ifp->flags & IFA_F_MANAGETEMPADDR) {
+               if (was_managetempaddr && !(ifp->flags & IFA_F_MANAGETEMPADDR))
+                       valid_lft = prefered_lft = 0;
+               manage_tempaddrs(ifp->idev, ifp, valid_lft, prefered_lft,
+                                !was_managetempaddr, jiffies);
+       }
+
        addrconf_verify(0);
 
        return 0;
@@ -3682,7 +3709,7 @@ 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_flags &= IFA_F_NODAD | IFA_F_HOMEADDRESS | IFA_F_MANAGETEMPADDR;
 
        ifa = ipv6_get_ifaddr(net, pfx, dev, 1);
        if (ifa == NULL) {