ipv4: introduce address lifetime
authorJiri Pirko <jiri@resnulli.us>
Thu, 24 Jan 2013 09:41:41 +0000 (09:41 +0000)
committerDavid S. Miller <davem@davemloft.net>
Tue, 29 Jan 2013 18:59:57 +0000 (13:59 -0500)
There are some usecase when lifetime of ipv4 addresses might be helpful.
For example:
1) initramfs networkmanager uses a DHCP daemon to learn network
configuration parameters
2) initramfs networkmanager addresses, routes and DNS configuration
3) initramfs networkmanager is requested to stop
4) initramfs networkmanager stops all daemons including dhclient
5) there are addresses and routes configured but no daemon running. If
the system doesn't start networkmanager for some reason, addresses and
routes will be used forever, which violates RFC 2131.

This patch is essentially a backport of ivp6 address lifetime mechanism
for ipv4 addresses.

Current "ip" tool supports this without any patch (since it does not
distinguish between ipv4 and ipv6 addresses in this perspective.

Also, this should be back-compatible with all current netlink users.

Reported-by: Pavel Šimerda <psimerda@redhat.com>
Signed-off-by: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/inetdevice.h
include/net/addrconf.h
net/ipv4/devinet.c
net/ipv6/addrconf.c

index a9d828976a77a56b400ffd3f4e7a56915f3762a5..ea1e3b8638900a5d7f5cf46e49eec37c3274f9a9 100644 (file)
@@ -166,6 +166,12 @@ struct in_ifaddr {
        unsigned char           ifa_flags;
        unsigned char           ifa_prefixlen;
        char                    ifa_label[IFNAMSIZ];
+
+       /* In seconds, relative to tstamp. Expiry is at tstamp + HZ * lft. */
+       __u32                   ifa_valid_lft;
+       __u32                   ifa_preferred_lft;
+       unsigned long           ifa_cstamp; /* created timestamp */
+       unsigned long           ifa_tstamp; /* updated timestamp */
 };
 
 extern int register_inetaddr_notifier(struct notifier_block *nb);
index 6c58d507123fbf1370942143aaf151fcf17e4394..40be2a0d8ae1bef7270b06a8a04cc9b9ab30b630 100644 (file)
 
 #define IPV6_MAX_ADDRESSES             16
 
+#define ADDRCONF_TIMER_FUZZ_MINUS      (HZ > 50 ? HZ / 50 : 1)
+#define ADDRCONF_TIMER_FUZZ            (HZ / 4)
+#define ADDRCONF_TIMER_FUZZ_MAX                (HZ)
+
 #include <linux/in.h>
 #include <linux/in6.h>
 
index a8e4f2665d5e6687c28438de69586673be9b3484..5281314886c1fc3d0bb0488b9338a5a2124158e9 100644 (file)
@@ -63,6 +63,7 @@
 #include <net/ip_fib.h>
 #include <net/rtnetlink.h>
 #include <net/net_namespace.h>
+#include <net/addrconf.h>
 
 #include "fib_lookup.h"
 
@@ -93,6 +94,7 @@ static const struct nla_policy ifa_ipv4_policy[IFA_MAX+1] = {
        [IFA_ADDRESS]           = { .type = NLA_U32 },
        [IFA_BROADCAST]         = { .type = NLA_U32 },
        [IFA_LABEL]             = { .type = NLA_STRING, .len = IFNAMSIZ - 1 },
+       [IFA_CACHEINFO]         = { .len = sizeof(struct ifa_cacheinfo) },
 };
 
 #define IN4_ADDR_HSIZE_SHIFT   8
@@ -417,6 +419,10 @@ static void inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap,
        __inet_del_ifa(in_dev, ifap, destroy, NULL, 0);
 }
 
+static void check_lifetime(struct work_struct *work);
+
+static DECLARE_DELAYED_WORK(check_lifetime_work, check_lifetime);
+
 static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh,
                             u32 portid)
 {
@@ -462,6 +468,9 @@ static int __inet_insert_ifa(struct in_ifaddr *ifa, struct nlmsghdr *nlh,
 
        inet_hash_insert(dev_net(in_dev->dev), ifa);
 
+       cancel_delayed_work(&check_lifetime_work);
+       schedule_delayed_work(&check_lifetime_work, 0);
+
        /* Send message first, then call notifier.
           Notifier will trigger FIB update, so that
           listeners of netlink will know about new ifaddr */
@@ -573,7 +582,107 @@ errout:
        return err;
 }
 
-static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh)
+#define INFINITY_LIFE_TIME     0xFFFFFFFF
+
+static void check_lifetime(struct work_struct *work)
+{
+       unsigned long now, next, next_sec, next_sched;
+       struct in_ifaddr *ifa;
+       struct hlist_node *node;
+       int i;
+
+       now = jiffies;
+       next = round_jiffies_up(now + ADDR_CHECK_FREQUENCY);
+
+       rcu_read_lock();
+       for (i = 0; i < IN4_ADDR_HSIZE; i++) {
+               hlist_for_each_entry_rcu(ifa, node,
+                                        &inet_addr_lst[i], hash) {
+                       unsigned long age;
+
+                       if (ifa->ifa_flags & IFA_F_PERMANENT)
+                               continue;
+
+                       /* We try to batch several events at once. */
+                       age = (now - ifa->ifa_tstamp +
+                              ADDRCONF_TIMER_FUZZ_MINUS) / HZ;
+
+                       if (ifa->ifa_valid_lft != INFINITY_LIFE_TIME &&
+                           age >= ifa->ifa_valid_lft) {
+                               struct in_ifaddr **ifap ;
+
+                               rtnl_lock();
+                               for (ifap = &ifa->ifa_dev->ifa_list;
+                                    *ifap != NULL; ifap = &ifa->ifa_next) {
+                                       if (*ifap == ifa)
+                                               inet_del_ifa(ifa->ifa_dev,
+                                                            ifap, 1);
+                               }
+                               rtnl_unlock();
+                       } else if (ifa->ifa_preferred_lft ==
+                                  INFINITY_LIFE_TIME) {
+                               continue;
+                       } else if (age >= ifa->ifa_preferred_lft) {
+                               if (time_before(ifa->ifa_tstamp +
+                                               ifa->ifa_valid_lft * HZ, next))
+                                       next = ifa->ifa_tstamp +
+                                              ifa->ifa_valid_lft * HZ;
+
+                               if (!(ifa->ifa_flags & IFA_F_DEPRECATED)) {
+                                       ifa->ifa_flags |= IFA_F_DEPRECATED;
+                                       rtmsg_ifa(RTM_NEWADDR, ifa, NULL, 0);
+                               }
+                       } else if (time_before(ifa->ifa_tstamp +
+                                              ifa->ifa_preferred_lft * HZ,
+                                              next)) {
+                               next = ifa->ifa_tstamp +
+                                      ifa->ifa_preferred_lft * HZ;
+                       }
+               }
+       }
+       rcu_read_unlock();
+
+       next_sec = round_jiffies_up(next);
+       next_sched = next;
+
+       /* If rounded timeout is accurate enough, accept it. */
+       if (time_before(next_sec, next + ADDRCONF_TIMER_FUZZ))
+               next_sched = next_sec;
+
+       now = jiffies;
+       /* And minimum interval is ADDRCONF_TIMER_FUZZ_MAX. */
+       if (time_before(next_sched, now + ADDRCONF_TIMER_FUZZ_MAX))
+               next_sched = now + ADDRCONF_TIMER_FUZZ_MAX;
+
+       schedule_delayed_work(&check_lifetime_work, next_sched - now);
+}
+
+static void set_ifa_lifetime(struct in_ifaddr *ifa, __u32 valid_lft,
+                            __u32 prefered_lft)
+{
+       unsigned long timeout;
+
+       ifa->ifa_flags &= ~(IFA_F_PERMANENT | IFA_F_DEPRECATED);
+
+       timeout = addrconf_timeout_fixup(valid_lft, HZ);
+       if (addrconf_finite_timeout(timeout))
+               ifa->ifa_valid_lft = timeout;
+       else
+               ifa->ifa_flags |= IFA_F_PERMANENT;
+
+       timeout = addrconf_timeout_fixup(prefered_lft, HZ);
+       if (addrconf_finite_timeout(timeout)) {
+               if (timeout == 0)
+                       ifa->ifa_flags |= IFA_F_DEPRECATED;
+               ifa->ifa_preferred_lft = timeout;
+       }
+       ifa->ifa_tstamp = jiffies;
+       if (!ifa->ifa_cstamp)
+               ifa->ifa_cstamp = ifa->ifa_tstamp;
+}
+
+static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh,
+                                      __u32 *pvalid_lft, __u32 *pprefered_lft)
 {
        struct nlattr *tb[IFA_MAX+1];
        struct in_ifaddr *ifa;
@@ -633,24 +742,73 @@ static struct in_ifaddr *rtm_to_ifaddr(struct net *net, struct nlmsghdr *nlh)
        else
                memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
 
+       if (tb[IFA_CACHEINFO]) {
+               struct ifa_cacheinfo *ci;
+
+               ci = nla_data(tb[IFA_CACHEINFO]);
+               if (!ci->ifa_valid || ci->ifa_prefered > ci->ifa_valid) {
+                       err = -EINVAL;
+                       goto errout;
+               }
+               *pvalid_lft = ci->ifa_valid;
+               *pprefered_lft = ci->ifa_prefered;
+       }
+
        return ifa;
 
 errout:
        return ERR_PTR(err);
 }
 
+static struct in_ifaddr *find_matching_ifa(struct in_ifaddr *ifa)
+{
+       struct in_device *in_dev = ifa->ifa_dev;
+       struct in_ifaddr *ifa1, **ifap;
+
+       if (!ifa->ifa_local)
+               return NULL;
+
+       for (ifap = &in_dev->ifa_list; (ifa1 = *ifap) != NULL;
+            ifap = &ifa1->ifa_next) {
+               if (ifa1->ifa_mask == ifa->ifa_mask &&
+                   inet_ifa_match(ifa1->ifa_address, ifa) &&
+                   ifa1->ifa_local == ifa->ifa_local)
+                       return ifa1;
+       }
+       return NULL;
+}
+
 static int inet_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
 {
        struct net *net = sock_net(skb->sk);
        struct in_ifaddr *ifa;
+       struct in_ifaddr *ifa_existing;
+       __u32 valid_lft = INFINITY_LIFE_TIME;
+       __u32 prefered_lft = INFINITY_LIFE_TIME;
 
        ASSERT_RTNL();
 
-       ifa = rtm_to_ifaddr(net, nlh);
+       ifa = rtm_to_ifaddr(net, nlh, &valid_lft, &prefered_lft);
        if (IS_ERR(ifa))
                return PTR_ERR(ifa);
 
-       return __inet_insert_ifa(ifa, nlh, NETLINK_CB(skb).portid);
+       ifa_existing = find_matching_ifa(ifa);
+       if (!ifa_existing) {
+               /* It would be best to check for !NLM_F_CREATE here but
+                * userspace alreay relies on not having to provide this.
+                */
+               set_ifa_lifetime(ifa, valid_lft, prefered_lft);
+               return __inet_insert_ifa(ifa, nlh, NETLINK_CB(skb).portid);
+       } else {
+               inet_free_ifa(ifa);
+
+               if (nlh->nlmsg_flags & NLM_F_EXCL ||
+                   !(nlh->nlmsg_flags & NLM_F_REPLACE))
+                       return -EEXIST;
+
+               set_ifa_lifetime(ifa_existing, valid_lft, prefered_lft);
+       }
+       return 0;
 }
 
 /*
@@ -852,6 +1010,7 @@ int devinet_ioctl(struct net *net, unsigned int cmd, void __user *arg)
                        ifa->ifa_prefixlen = 32;
                        ifa->ifa_mask = inet_make_mask(32);
                }
+               set_ifa_lifetime(ifa, INFINITY_LIFE_TIME, INFINITY_LIFE_TIME);
                ret = inet_set_ifa(dev, ifa);
                break;
 
@@ -1190,6 +1349,8 @@ static int inetdev_event(struct notifier_block *this, unsigned long event,
                                ifa->ifa_dev = in_dev;
                                ifa->ifa_scope = RT_SCOPE_HOST;
                                memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+                               set_ifa_lifetime(ifa, INFINITY_LIFE_TIME,
+                                                INFINITY_LIFE_TIME);
                                inet_insert_ifa(ifa);
                        }
                }
@@ -1246,11 +1407,30 @@ static size_t inet_nlmsg_size(void)
               + nla_total_size(IFNAMSIZ); /* IFA_LABEL */
 }
 
+static inline u32 cstamp_delta(unsigned long cstamp)
+{
+       return (cstamp - INITIAL_JIFFIES) * 100UL / HZ;
+}
+
+static int put_cacheinfo(struct sk_buff *skb, unsigned long cstamp,
+                        unsigned long tstamp, u32 preferred, u32 valid)
+{
+       struct ifa_cacheinfo ci;
+
+       ci.cstamp = cstamp_delta(cstamp);
+       ci.tstamp = cstamp_delta(tstamp);
+       ci.ifa_prefered = preferred;
+       ci.ifa_valid = valid;
+
+       return nla_put(skb, IFA_CACHEINFO, sizeof(ci), &ci);
+}
+
 static int inet_fill_ifaddr(struct sk_buff *skb, struct in_ifaddr *ifa,
                            u32 portid, u32 seq, int event, unsigned int flags)
 {
        struct ifaddrmsg *ifm;
        struct nlmsghdr  *nlh;
+       u32 preferred, valid;
 
        nlh = nlmsg_put(skb, portid, seq, event, sizeof(*ifm), flags);
        if (nlh == NULL)
@@ -1259,10 +1439,31 @@ static int inet_fill_ifaddr(struct sk_buff *skb, struct in_ifaddr *ifa,
        ifm = nlmsg_data(nlh);
        ifm->ifa_family = AF_INET;
        ifm->ifa_prefixlen = ifa->ifa_prefixlen;
-       ifm->ifa_flags = ifa->ifa_flags|IFA_F_PERMANENT;
+       ifm->ifa_flags = ifa->ifa_flags;
        ifm->ifa_scope = ifa->ifa_scope;
        ifm->ifa_index = ifa->ifa_dev->dev->ifindex;
 
+       if (!(ifm->ifa_flags & IFA_F_PERMANENT)) {
+               preferred = ifa->ifa_preferred_lft;
+               valid = ifa->ifa_valid_lft;
+               if (preferred != INFINITY_LIFE_TIME) {
+                       long tval = (jiffies - ifa->ifa_tstamp) / HZ;
+
+                       if (preferred > tval)
+                               preferred -= tval;
+                       else
+                               preferred = 0;
+                       if (valid != INFINITY_LIFE_TIME) {
+                               if (valid > tval)
+                                       valid -= tval;
+                               else
+                                       valid = 0;
+                       }
+               }
+       } else {
+               preferred = INFINITY_LIFE_TIME;
+               valid = INFINITY_LIFE_TIME;
+       }
        if ((ifa->ifa_address &&
             nla_put_be32(skb, IFA_ADDRESS, ifa->ifa_address)) ||
            (ifa->ifa_local &&
@@ -1270,7 +1471,9 @@ static int inet_fill_ifaddr(struct sk_buff *skb, struct in_ifaddr *ifa,
            (ifa->ifa_broadcast &&
             nla_put_be32(skb, IFA_BROADCAST, ifa->ifa_broadcast)) ||
            (ifa->ifa_label[0] &&
-            nla_put_string(skb, IFA_LABEL, ifa->ifa_label)))
+            nla_put_string(skb, IFA_LABEL, ifa->ifa_label)) ||
+           put_cacheinfo(skb, ifa->ifa_cstamp, ifa->ifa_tstamp,
+                         preferred, valid))
                goto nla_put_failure;
 
        return nlmsg_end(skb, nlh);
@@ -1988,6 +2191,8 @@ void __init devinet_init(void)
        register_gifconf(PF_INET, inet_gifconf);
        register_netdevice_notifier(&ip_netdev_notifier);
 
+       schedule_delayed_work(&check_lifetime_work, 0);
+
        rtnl_af_register(&inet_af_ops);
 
        rtnl_register(PF_INET, RTM_NEWADDR, inet_rtm_newaddr, NULL, NULL);
index 80d59802d964abc9b7a2f6d2bdefa86161bd1249..7f7332b446992d6f01dc873eebe976e7ac433f12 100644 (file)
@@ -110,10 +110,6 @@ static inline u32 cstamp_delta(unsigned long cstamp)
        return (cstamp - INITIAL_JIFFIES) * 100UL / HZ;
 }
 
-#define ADDRCONF_TIMER_FUZZ_MINUS      (HZ > 50 ? HZ/50 : 1)
-#define ADDRCONF_TIMER_FUZZ            (HZ / 4)
-#define ADDRCONF_TIMER_FUZZ_MAX                (HZ)
-
 #ifdef CONFIG_SYSCTL
 static void addrconf_sysctl_register(struct inet6_dev *idev);
 static void addrconf_sysctl_unregister(struct inet6_dev *idev);