Merge tag 'locks-v4.4-1' of git://git.samba.org/jlayton/linux
[firefly-linux-kernel-4.4.55.git] / net / mpls / af_mpls.c
index cc972e30355b600769051dc3841ba7fa582b42a5..c70d750148b66759ce47525c6f6b348c2e69efaa 100644 (file)
@@ -57,6 +57,20 @@ bool mpls_output_possible(const struct net_device *dev)
 }
 EXPORT_SYMBOL_GPL(mpls_output_possible);
 
+static u8 *__mpls_nh_via(struct mpls_route *rt, struct mpls_nh *nh)
+{
+       u8 *nh0_via = PTR_ALIGN((u8 *)&rt->rt_nh[rt->rt_nhn], VIA_ALEN_ALIGN);
+       int nh_index = nh - rt->rt_nh;
+
+       return nh0_via + rt->rt_max_alen * nh_index;
+}
+
+static const u8 *mpls_nh_via(const struct mpls_route *rt,
+                            const struct mpls_nh *nh)
+{
+       return __mpls_nh_via((struct mpls_route *)rt, (struct mpls_nh *)nh);
+}
+
 static unsigned int mpls_nh_header_size(const struct mpls_nh *nh)
 {
        /* The size of the layer 2.5 labels to be added for this route */
@@ -303,7 +317,7 @@ static int mpls_forward(struct sk_buff *skb, struct net_device *dev,
                }
        }
 
-       err = neigh_xmit(nh->nh_via_table, out_dev, nh->nh_via, skb);
+       err = neigh_xmit(nh->nh_via_table, out_dev, mpls_nh_via(rt, nh), skb);
        if (err)
                net_dbg_ratelimited("%s: packet transmission failed: %d\n",
                                    __func__, err);
@@ -340,14 +354,19 @@ struct mpls_route_config {
        int                     rc_mp_len;
 };
 
-static struct mpls_route *mpls_rt_alloc(int num_nh)
+static struct mpls_route *mpls_rt_alloc(int num_nh, u8 max_alen)
 {
+       u8 max_alen_aligned = ALIGN(max_alen, VIA_ALEN_ALIGN);
        struct mpls_route *rt;
 
-       rt = kzalloc(sizeof(*rt) + (num_nh * sizeof(struct mpls_nh)),
+       rt = kzalloc(ALIGN(sizeof(*rt) + num_nh * sizeof(*rt->rt_nh),
+                          VIA_ALEN_ALIGN) +
+                    num_nh * max_alen_aligned,
                     GFP_KERNEL);
-       if (rt)
+       if (rt) {
                rt->rt_nhn = num_nh;
+               rt->rt_max_alen = max_alen_aligned;
+       }
 
        return rt;
 }
@@ -408,7 +427,8 @@ static unsigned find_free_label(struct net *net)
 }
 
 #if IS_ENABLED(CONFIG_INET)
-static struct net_device *inet_fib_lookup_dev(struct net *net, void *addr)
+static struct net_device *inet_fib_lookup_dev(struct net *net,
+                                             const void *addr)
 {
        struct net_device *dev;
        struct rtable *rt;
@@ -427,14 +447,16 @@ static struct net_device *inet_fib_lookup_dev(struct net *net, void *addr)
        return dev;
 }
 #else
-static struct net_device *inet_fib_lookup_dev(struct net *net, void *addr)
+static struct net_device *inet_fib_lookup_dev(struct net *net,
+                                             const void *addr)
 {
        return ERR_PTR(-EAFNOSUPPORT);
 }
 #endif
 
 #if IS_ENABLED(CONFIG_IPV6)
-static struct net_device *inet6_fib_lookup_dev(struct net *net, void *addr)
+static struct net_device *inet6_fib_lookup_dev(struct net *net,
+                                              const void *addr)
 {
        struct net_device *dev;
        struct dst_entry *dst;
@@ -457,13 +479,15 @@ static struct net_device *inet6_fib_lookup_dev(struct net *net, void *addr)
        return dev;
 }
 #else
-static struct net_device *inet6_fib_lookup_dev(struct net *net, void *addr)
+static struct net_device *inet6_fib_lookup_dev(struct net *net,
+                                              const void *addr)
 {
        return ERR_PTR(-EAFNOSUPPORT);
 }
 #endif
 
 static struct net_device *find_outdev(struct net *net,
+                                     struct mpls_route *rt,
                                      struct mpls_nh *nh, int oif)
 {
        struct net_device *dev = NULL;
@@ -471,10 +495,10 @@ static struct net_device *find_outdev(struct net *net,
        if (!oif) {
                switch (nh->nh_via_table) {
                case NEIGH_ARP_TABLE:
-                       dev = inet_fib_lookup_dev(net, nh->nh_via);
+                       dev = inet_fib_lookup_dev(net, mpls_nh_via(rt, nh));
                        break;
                case NEIGH_ND_TABLE:
-                       dev = inet6_fib_lookup_dev(net, nh->nh_via);
+                       dev = inet6_fib_lookup_dev(net, mpls_nh_via(rt, nh));
                        break;
                case NEIGH_LINK_TABLE:
                        break;
@@ -492,12 +516,13 @@ static struct net_device *find_outdev(struct net *net,
        return dev;
 }
 
-static int mpls_nh_assign_dev(struct net *net, struct mpls_nh *nh, int oif)
+static int mpls_nh_assign_dev(struct net *net, struct mpls_route *rt,
+                             struct mpls_nh *nh, int oif)
 {
        struct net_device *dev = NULL;
        int err = -ENODEV;
 
-       dev = find_outdev(net, nh, oif);
+       dev = find_outdev(net, rt, nh, oif);
        if (IS_ERR(dev)) {
                err = PTR_ERR(dev);
                dev = NULL;
@@ -538,10 +563,10 @@ static int mpls_nh_build_from_cfg(struct mpls_route_config *cfg,
                nh->nh_label[i] = cfg->rc_output_label[i];
 
        nh->nh_via_table = cfg->rc_via_table;
-       memcpy(nh->nh_via, cfg->rc_via, cfg->rc_via_alen);
+       memcpy(__mpls_nh_via(rt, nh), cfg->rc_via, cfg->rc_via_alen);
        nh->nh_via_alen = cfg->rc_via_alen;
 
-       err = mpls_nh_assign_dev(net, nh, cfg->rc_ifindex);
+       err = mpls_nh_assign_dev(net, rt, nh, cfg->rc_ifindex);
        if (err)
                goto errout;
 
@@ -551,8 +576,9 @@ errout:
        return err;
 }
 
-static int mpls_nh_build(struct net *net, struct mpls_nh *nh,
-                        int oif, struct nlattr *via, struct nlattr *newdst)
+static int mpls_nh_build(struct net *net, struct mpls_route *rt,
+                        struct mpls_nh *nh, int oif,
+                        struct nlattr *via, struct nlattr *newdst)
 {
        int err = -ENOMEM;
 
@@ -567,11 +593,11 @@ static int mpls_nh_build(struct net *net, struct mpls_nh *nh,
        }
 
        err = nla_get_via(via, &nh->nh_via_alen, &nh->nh_via_table,
-                         nh->nh_via);
+                         __mpls_nh_via(rt, nh));
        if (err)
                goto errout;
 
-       err = mpls_nh_assign_dev(net, nh, oif);
+       err = mpls_nh_assign_dev(net, rt, nh, oif);
        if (err)
                goto errout;
 
@@ -581,12 +607,35 @@ errout:
        return err;
 }
 
-static int mpls_count_nexthops(struct rtnexthop *rtnh, int len)
+static int mpls_count_nexthops(struct rtnexthop *rtnh, int len,
+                              u8 cfg_via_alen, u8 *max_via_alen)
 {
        int nhs = 0;
        int remaining = len;
 
+       if (!rtnh) {
+               *max_via_alen = cfg_via_alen;
+               return 1;
+       }
+
+       *max_via_alen = 0;
+
        while (rtnh_ok(rtnh, remaining)) {
+               struct nlattr *nla, *attrs = rtnh_attrs(rtnh);
+               int attrlen;
+
+               attrlen = rtnh_attrlen(rtnh);
+               nla = nla_find(attrs, attrlen, RTA_VIA);
+               if (nla && nla_len(nla) >=
+                   offsetof(struct rtvia, rtvia_addr)) {
+                       int via_alen = nla_len(nla) -
+                               offsetof(struct rtvia, rtvia_addr);
+
+                       if (via_alen <= MAX_VIA_ALEN)
+                               *max_via_alen = max_t(u16, *max_via_alen,
+                                                     via_alen);
+               }
+
                nhs++;
                rtnh = rtnh_next(rtnh, &remaining);
        }
@@ -631,7 +680,7 @@ static int mpls_nh_build_multi(struct mpls_route_config *cfg,
                if (!nla_via)
                        goto errout;
 
-               err = mpls_nh_build(cfg->rc_nlinfo.nl_net, nh,
+               err = mpls_nh_build(cfg->rc_nlinfo.nl_net, rt, nh,
                                    rtnh->rtnh_ifindex, nla_via,
                                    nla_newdst);
                if (err)
@@ -655,8 +704,9 @@ static int mpls_route_add(struct mpls_route_config *cfg)
        struct net *net = cfg->rc_nlinfo.nl_net;
        struct mpls_route *rt, *old;
        int err = -EINVAL;
+       u8 max_via_alen;
        unsigned index;
-       int nhs = 1; /* default to one nexthop */
+       int nhs;
 
        index = cfg->rc_label;
 
@@ -693,15 +743,14 @@ static int mpls_route_add(struct mpls_route_config *cfg)
        if (!(cfg->rc_nlflags & NLM_F_CREATE) && !old)
                goto errout;
 
-       if (cfg->rc_mp) {
-               err = -EINVAL;
-               nhs = mpls_count_nexthops(cfg->rc_mp, cfg->rc_mp_len);
-               if (nhs == 0)
-                       goto errout;
-       }
+       err = -EINVAL;
+       nhs = mpls_count_nexthops(cfg->rc_mp, cfg->rc_mp_len,
+                                 cfg->rc_via_alen, &max_via_alen);
+       if (nhs == 0)
+               goto errout;
 
        err = -ENOMEM;
-       rt = mpls_rt_alloc(nhs);
+       rt = mpls_rt_alloc(nhs, max_via_alen);
        if (!rt)
                goto errout;
 
@@ -1176,13 +1225,13 @@ static int mpls_dump_route(struct sk_buff *skb, u32 portid, u32 seq, int event,
        if (nla_put_labels(skb, RTA_DST, 1, &label))
                goto nla_put_failure;
        if (rt->rt_nhn == 1) {
-               struct mpls_nh *nh = rt->rt_nh;
+               const struct mpls_nh *nh = rt->rt_nh;
 
                if (nh->nh_labels &&
                    nla_put_labels(skb, RTA_NEWDST, nh->nh_labels,
                                   nh->nh_label))
                        goto nla_put_failure;
-               if (nla_put_via(skb, nh->nh_via_table, nh->nh_via,
+               if (nla_put_via(skb, nh->nh_via_table, mpls_nh_via(rt, nh),
                                nh->nh_via_alen))
                        goto nla_put_failure;
                dev = rtnl_dereference(nh->nh_dev);
@@ -1209,7 +1258,7 @@ static int mpls_dump_route(struct sk_buff *skb, u32 portid, u32 seq, int event,
                                                            nh->nh_label))
                                goto nla_put_failure;
                        if (nla_put_via(skb, nh->nh_via_table,
-                                       nh->nh_via,
+                                       mpls_nh_via(rt, nh),
                                        nh->nh_via_alen))
                                goto nla_put_failure;
 
@@ -1338,25 +1387,29 @@ static int resize_platform_label_table(struct net *net, size_t limit)
        /* In case the predefined labels need to be populated */
        if (limit > MPLS_LABEL_IPV4NULL) {
                struct net_device *lo = net->loopback_dev;
-               rt0 = mpls_rt_alloc(1);
+               rt0 = mpls_rt_alloc(1, lo->addr_len);
                if (!rt0)
                        goto nort0;
                RCU_INIT_POINTER(rt0->rt_nh->nh_dev, lo);
                rt0->rt_protocol = RTPROT_KERNEL;
                rt0->rt_payload_type = MPT_IPV4;
                rt0->rt_nh->nh_via_table = NEIGH_LINK_TABLE;
-               memcpy(rt0->rt_nh->nh_via, lo->dev_addr, lo->addr_len);
+               rt0->rt_nh->nh_via_alen = lo->addr_len;
+               memcpy(__mpls_nh_via(rt0, rt0->rt_nh), lo->dev_addr,
+                      lo->addr_len);
        }
        if (limit > MPLS_LABEL_IPV6NULL) {
                struct net_device *lo = net->loopback_dev;
-               rt2 = mpls_rt_alloc(1);
+               rt2 = mpls_rt_alloc(1, lo->addr_len);
                if (!rt2)
                        goto nort2;
                RCU_INIT_POINTER(rt2->rt_nh->nh_dev, lo);
                rt2->rt_protocol = RTPROT_KERNEL;
                rt2->rt_payload_type = MPT_IPV6;
                rt2->rt_nh->nh_via_table = NEIGH_LINK_TABLE;
-               memcpy(rt2->rt_nh->nh_via, lo->dev_addr, lo->addr_len);
+               rt2->rt_nh->nh_via_alen = lo->addr_len;
+               memcpy(__mpls_nh_via(rt2, rt2->rt_nh), lo->dev_addr,
+                      lo->addr_len);
        }
 
        rtnl_lock();