]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/mpls/af_mpls.c
Merge branch 'for-linus-4.5' of git://git.kernel.org/pub/scm/linux/kernel/git/mason...
[karo-tx-linux.git] / net / mpls / af_mpls.c
index c32fc411a911a414db2d0c1e141cb76093079d33..b18c5ed42d956bd7d9261caa7dd05278f72e2785 100644 (file)
@@ -98,22 +98,15 @@ bool mpls_pkt_too_big(const struct sk_buff *skb, unsigned int mtu)
 }
 EXPORT_SYMBOL_GPL(mpls_pkt_too_big);
 
-static struct mpls_nh *mpls_select_multipath(struct mpls_route *rt,
-                                            struct sk_buff *skb, bool bos)
+static u32 mpls_multipath_hash(struct mpls_route *rt,
+                              struct sk_buff *skb, bool bos)
 {
        struct mpls_entry_decoded dec;
        struct mpls_shim_hdr *hdr;
        bool eli_seen = false;
        int label_index;
-       int nh_index = 0;
        u32 hash = 0;
 
-       /* No need to look further into packet if there's only
-        * one path
-        */
-       if (rt->rt_nhn == 1)
-               goto out;
-
        for (label_index = 0; label_index < MAX_MP_SELECT_LABELS && !bos;
             label_index++) {
                if (!pskb_may_pull(skb, sizeof(*hdr) * label_index))
@@ -167,7 +160,38 @@ static struct mpls_nh *mpls_select_multipath(struct mpls_route *rt,
                }
        }
 
-       nh_index = hash % rt->rt_nhn;
+       return hash;
+}
+
+static struct mpls_nh *mpls_select_multipath(struct mpls_route *rt,
+                                            struct sk_buff *skb, bool bos)
+{
+       int alive = ACCESS_ONCE(rt->rt_nhn_alive);
+       u32 hash = 0;
+       int nh_index = 0;
+       int n = 0;
+
+       /* No need to look further into packet if there's only
+        * one path
+        */
+       if (rt->rt_nhn == 1)
+               goto out;
+
+       if (alive <= 0)
+               return NULL;
+
+       hash = mpls_multipath_hash(rt, skb, bos);
+       nh_index = hash % alive;
+       if (alive == rt->rt_nhn)
+               goto out;
+       for_nexthops(rt) {
+               if (nh->nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN))
+                       continue;
+               if (n == nh_index)
+                       return nh;
+               n++;
+       } endfor_nexthops(rt);
+
 out:
        return &rt->rt_nh[nh_index];
 }
@@ -373,6 +397,7 @@ static struct mpls_route *mpls_rt_alloc(int num_nh, u8 max_alen)
                     GFP_KERNEL);
        if (rt) {
                rt->rt_nhn = num_nh;
+               rt->rt_nhn_alive = num_nh;
                rt->rt_max_alen = max_alen_aligned;
        }
 
@@ -548,6 +573,16 @@ static int mpls_nh_assign_dev(struct net *net, struct mpls_route *rt,
 
        RCU_INIT_POINTER(nh->nh_dev, dev);
 
+       if (!(dev->flags & IFF_UP)) {
+               nh->nh_flags |= RTNH_F_DEAD;
+       } else {
+               unsigned int flags;
+
+               flags = dev_get_flags(dev);
+               if (!(flags & (IFF_RUNNING | IFF_LOWER_UP)))
+                       nh->nh_flags |= RTNH_F_LINKDOWN;
+       }
+
        return 0;
 
 errout:
@@ -582,6 +617,9 @@ static int mpls_nh_build_from_cfg(struct mpls_route_config *cfg,
        if (err)
                goto errout;
 
+       if (nh->nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN))
+               rt->rt_nhn_alive--;
+
        return 0;
 
 errout:
@@ -589,8 +627,8 @@ errout:
 }
 
 static int mpls_nh_build(struct net *net, struct mpls_route *rt,
-                        struct mpls_nh *nh, int oif,
-                        struct nlattr *via, struct nlattr *newdst)
+                        struct mpls_nh *nh, int oif, struct nlattr *via,
+                        struct nlattr *newdst)
 {
        int err = -ENOMEM;
 
@@ -694,11 +732,13 @@ static int mpls_nh_build_multi(struct mpls_route_config *cfg,
                }
 
                err = mpls_nh_build(cfg->rc_nlinfo.nl_net, rt, nh,
-                                   rtnh->rtnh_ifindex, nla_via,
-                                   nla_newdst);
+                                   rtnh->rtnh_ifindex, nla_via, nla_newdst);
                if (err)
                        goto errout;
 
+               if (nh->nh_flags & (RTNH_F_DEAD | RTNH_F_LINKDOWN))
+                       rt->rt_nhn_alive--;
+
                rtnh = rtnh_next(rtnh, &remaining);
                nhs++;
        } endfor_nexthops(rt);
@@ -888,34 +928,74 @@ free:
        return ERR_PTR(err);
 }
 
-static void mpls_ifdown(struct net_device *dev)
+static void mpls_ifdown(struct net_device *dev, int event)
 {
        struct mpls_route __rcu **platform_label;
        struct net *net = dev_net(dev);
-       struct mpls_dev *mdev;
        unsigned index;
 
        platform_label = rtnl_dereference(net->mpls.platform_label);
        for (index = 0; index < net->mpls.platform_labels; index++) {
                struct mpls_route *rt = rtnl_dereference(platform_label[index]);
+
                if (!rt)
                        continue;
-               for_nexthops(rt) {
+
+               change_nexthops(rt) {
                        if (rtnl_dereference(nh->nh_dev) != dev)
                                continue;
-                       nh->nh_dev = NULL;
+                       switch (event) {
+                       case NETDEV_DOWN:
+                       case NETDEV_UNREGISTER:
+                               nh->nh_flags |= RTNH_F_DEAD;
+                               /* fall through */
+                       case NETDEV_CHANGE:
+                               nh->nh_flags |= RTNH_F_LINKDOWN;
+                               ACCESS_ONCE(rt->rt_nhn_alive) = rt->rt_nhn_alive - 1;
+                               break;
+                       }
+                       if (event == NETDEV_UNREGISTER)
+                               RCU_INIT_POINTER(nh->nh_dev, NULL);
                } endfor_nexthops(rt);
        }
 
-       mdev = mpls_dev_get(dev);
-       if (!mdev)
-               return;
 
-       mpls_dev_sysctl_unregister(mdev);
+       return;
+}
+
+static void mpls_ifup(struct net_device *dev, unsigned int nh_flags)
+{
+       struct mpls_route __rcu **platform_label;
+       struct net *net = dev_net(dev);
+       unsigned index;
+       int alive;
+
+       platform_label = rtnl_dereference(net->mpls.platform_label);
+       for (index = 0; index < net->mpls.platform_labels; index++) {
+               struct mpls_route *rt = rtnl_dereference(platform_label[index]);
+
+               if (!rt)
+                       continue;
+
+               alive = 0;
+               change_nexthops(rt) {
+                       struct net_device *nh_dev =
+                               rtnl_dereference(nh->nh_dev);
+
+                       if (!(nh->nh_flags & nh_flags)) {
+                               alive++;
+                               continue;
+                       }
+                       if (nh_dev != dev)
+                               continue;
+                       alive++;
+                       nh->nh_flags &= ~nh_flags;
+               } endfor_nexthops(rt);
 
-       RCU_INIT_POINTER(dev->mpls_ptr, NULL);
+               ACCESS_ONCE(rt->rt_nhn_alive) = alive;
+       }
 
-       kfree_rcu(mdev, rcu);
+       return;
 }
 
 static int mpls_dev_notify(struct notifier_block *this, unsigned long event,
@@ -923,9 +1003,9 @@ static int mpls_dev_notify(struct notifier_block *this, unsigned long event,
 {
        struct net_device *dev = netdev_notifier_info_to_dev(ptr);
        struct mpls_dev *mdev;
+       unsigned int flags;
 
-       switch(event) {
-       case NETDEV_REGISTER:
+       if (event == NETDEV_REGISTER) {
                /* For now just support ethernet devices */
                if ((dev->type == ARPHRD_ETHER) ||
                    (dev->type == ARPHRD_LOOPBACK)) {
@@ -933,10 +1013,39 @@ static int mpls_dev_notify(struct notifier_block *this, unsigned long event,
                        if (IS_ERR(mdev))
                                return notifier_from_errno(PTR_ERR(mdev));
                }
-               break;
+               return NOTIFY_OK;
+       }
 
+       mdev = mpls_dev_get(dev);
+       if (!mdev)
+               return NOTIFY_OK;
+
+       switch (event) {
+       case NETDEV_DOWN:
+               mpls_ifdown(dev, event);
+               break;
+       case NETDEV_UP:
+               flags = dev_get_flags(dev);
+               if (flags & (IFF_RUNNING | IFF_LOWER_UP))
+                       mpls_ifup(dev, RTNH_F_DEAD | RTNH_F_LINKDOWN);
+               else
+                       mpls_ifup(dev, RTNH_F_DEAD);
+               break;
+       case NETDEV_CHANGE:
+               flags = dev_get_flags(dev);
+               if (flags & (IFF_RUNNING | IFF_LOWER_UP))
+                       mpls_ifup(dev, RTNH_F_DEAD | RTNH_F_LINKDOWN);
+               else
+                       mpls_ifdown(dev, event);
+               break;
        case NETDEV_UNREGISTER:
-               mpls_ifdown(dev);
+               mpls_ifdown(dev, event);
+               mdev = mpls_dev_get(dev);
+               if (mdev) {
+                       mpls_dev_sysctl_unregister(mdev);
+                       RCU_INIT_POINTER(dev->mpls_ptr, NULL);
+                       kfree_rcu(mdev, rcu);
+               }
                break;
        case NETDEV_CHANGENAME:
                mdev = mpls_dev_get(dev);
@@ -1252,9 +1361,15 @@ static int mpls_dump_route(struct sk_buff *skb, u32 portid, u32 seq, int event,
                dev = rtnl_dereference(nh->nh_dev);
                if (dev && nla_put_u32(skb, RTA_OIF, dev->ifindex))
                        goto nla_put_failure;
+               if (nh->nh_flags & RTNH_F_LINKDOWN)
+                       rtm->rtm_flags |= RTNH_F_LINKDOWN;
+               if (nh->nh_flags & RTNH_F_DEAD)
+                       rtm->rtm_flags |= RTNH_F_DEAD;
        } else {
                struct rtnexthop *rtnh;
                struct nlattr *mp;
+               int dead = 0;
+               int linkdown = 0;
 
                mp = nla_nest_start(skb, RTA_MULTIPATH);
                if (!mp)
@@ -1268,6 +1383,15 @@ static int mpls_dump_route(struct sk_buff *skb, u32 portid, u32 seq, int event,
                        dev = rtnl_dereference(nh->nh_dev);
                        if (dev)
                                rtnh->rtnh_ifindex = dev->ifindex;
+                       if (nh->nh_flags & RTNH_F_LINKDOWN) {
+                               rtnh->rtnh_flags |= RTNH_F_LINKDOWN;
+                               linkdown++;
+                       }
+                       if (nh->nh_flags & RTNH_F_DEAD) {
+                               rtnh->rtnh_flags |= RTNH_F_DEAD;
+                               dead++;
+                       }
+
                        if (nh->nh_labels && nla_put_labels(skb, RTA_NEWDST,
                                                            nh->nh_labels,
                                                            nh->nh_label))
@@ -1282,6 +1406,11 @@ static int mpls_dump_route(struct sk_buff *skb, u32 portid, u32 seq, int event,
                        rtnh->rtnh_len = nlmsg_get_pos(skb) - (void *)rtnh;
                } endfor_nexthops(rt);
 
+               if (linkdown == rt->rt_nhn)
+                       rtm->rtm_flags |= RTNH_F_LINKDOWN;
+               if (dead == rt->rt_nhn)
+                       rtm->rtm_flags |= RTNH_F_DEAD;
+
                nla_nest_end(skb, mp);
        }