Merge tag 'regmap-v4.3' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie...
[firefly-linux-kernel-4.4.55.git] / drivers / net / vrf.c
index b3d9c5546c795768a7982092b23063a36b7749d9..e7094fbd75685998bb5ee492367e686c133a2e15 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/hashtable.h>
 
 #include <linux/inetdevice.h>
+#include <net/arp.h>
 #include <net/ip.h>
 #include <net/ip_fib.h>
 #include <net/ip6_route.h>
@@ -219,6 +220,9 @@ err:
 
 static netdev_tx_t is_ip_tx_frame(struct sk_buff *skb, struct net_device *dev)
 {
+       /* strip the ethernet header added for pass through VRF device */
+       __skb_pull(skb, skb_network_offset(skb));
+
        switch (skb->protocol) {
        case htons(ETH_P_IP):
                return vrf_process_v4_outbound(skb, dev);
@@ -248,9 +252,47 @@ static netdev_tx_t vrf_xmit(struct sk_buff *skb, struct net_device *dev)
        return ret;
 }
 
-static netdev_tx_t vrf_finish(struct sock *sk, struct sk_buff *skb)
+/* modelled after ip_finish_output2 */
+static int vrf_finish_output(struct sock *sk, struct sk_buff *skb)
 {
-       return dev_queue_xmit(skb);
+       struct dst_entry *dst = skb_dst(skb);
+       struct rtable *rt = (struct rtable *)dst;
+       struct net_device *dev = dst->dev;
+       unsigned int hh_len = LL_RESERVED_SPACE(dev);
+       struct neighbour *neigh;
+       u32 nexthop;
+       int ret = -EINVAL;
+
+       /* Be paranoid, rather than too clever. */
+       if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
+               struct sk_buff *skb2;
+
+               skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
+               if (!skb2) {
+                       ret = -ENOMEM;
+                       goto err;
+               }
+               if (skb->sk)
+                       skb_set_owner_w(skb2, skb->sk);
+
+               consume_skb(skb);
+               skb = skb2;
+       }
+
+       rcu_read_lock_bh();
+
+       nexthop = (__force u32)rt_nexthop(rt, ip_hdr(skb)->daddr);
+       neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
+       if (unlikely(!neigh))
+               neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);
+       if (!IS_ERR(neigh))
+               ret = dst_neigh_output(dst, neigh, skb);
+
+       rcu_read_unlock_bh();
+err:
+       if (unlikely(ret < 0))
+               vrf_tx_error(skb->dev, skb);
+       return ret;
 }
 
 static int vrf_output(struct sock *sk, struct sk_buff *skb)
@@ -264,7 +306,7 @@ static int vrf_output(struct sock *sk, struct sk_buff *skb)
 
        return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING, sk, skb,
                            NULL, dev,
-                           vrf_finish,
+                           vrf_finish_output,
                            !(IPCB(skb)->flags & IPSKB_REROUTED));
 }