Merge branch 'exec-update-lock-for-v5.11' of git://git.kernel.org/pub/scm/linux/kerne...
[linux-2.6-microblaze.git] / drivers / net / vrf.c
index b9b7e00..6d91308 100644 (file)
@@ -9,6 +9,7 @@
  * Based on dummy, team and ipvlan drivers
  */
 
+#include <linux/ethtool.h>
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/netdevice.h>
@@ -1236,6 +1237,61 @@ static struct sk_buff *vrf_rcv_nfhook(u8 pf, unsigned int hook,
        return skb;
 }
 
+static int vrf_prepare_mac_header(struct sk_buff *skb,
+                                 struct net_device *vrf_dev, u16 proto)
+{
+       struct ethhdr *eth;
+       int err;
+
+       /* in general, we do not know if there is enough space in the head of
+        * the packet for hosting the mac header.
+        */
+       err = skb_cow_head(skb, LL_RESERVED_SPACE(vrf_dev));
+       if (unlikely(err))
+               /* no space in the skb head */
+               return -ENOBUFS;
+
+       __skb_push(skb, ETH_HLEN);
+       eth = (struct ethhdr *)skb->data;
+
+       skb_reset_mac_header(skb);
+
+       /* we set the ethernet destination and the source addresses to the
+        * address of the VRF device.
+        */
+       ether_addr_copy(eth->h_dest, vrf_dev->dev_addr);
+       ether_addr_copy(eth->h_source, vrf_dev->dev_addr);
+       eth->h_proto = htons(proto);
+
+       /* the destination address of the Ethernet frame corresponds to the
+        * address set on the VRF interface; therefore, the packet is intended
+        * to be processed locally.
+        */
+       skb->protocol = eth->h_proto;
+       skb->pkt_type = PACKET_HOST;
+
+       skb_postpush_rcsum(skb, skb->data, ETH_HLEN);
+
+       skb_pull_inline(skb, ETH_HLEN);
+
+       return 0;
+}
+
+/* prepare and add the mac header to the packet if it was not set previously.
+ * In this way, packet sniffers such as tcpdump can parse the packet correctly.
+ * If the mac header was already set, the original mac header is left
+ * untouched and the function returns immediately.
+ */
+static int vrf_add_mac_header_if_unset(struct sk_buff *skb,
+                                      struct net_device *vrf_dev,
+                                      u16 proto)
+{
+       if (skb_mac_header_was_set(skb))
+               return 0;
+
+       return vrf_prepare_mac_header(skb, vrf_dev, proto);
+}
+
 #if IS_ENABLED(CONFIG_IPV6)
 /* neighbor handling is done with actual device; do not want
  * to flip skb->dev for those ndisc packets. This really fails
@@ -1341,9 +1397,15 @@ static struct sk_buff *vrf_ip6_rcv(struct net_device *vrf_dev,
                skb->skb_iif = vrf_dev->ifindex;
 
                if (!list_empty(&vrf_dev->ptype_all)) {
-                       skb_push(skb, skb->mac_len);
-                       dev_queue_xmit_nit(skb, vrf_dev);
-                       skb_pull(skb, skb->mac_len);
+                       int err;
+
+                       err = vrf_add_mac_header_if_unset(skb, vrf_dev,
+                                                         ETH_P_IPV6);
+                       if (likely(!err)) {
+                               skb_push(skb, skb->mac_len);
+                               dev_queue_xmit_nit(skb, vrf_dev);
+                               skb_pull(skb, skb->mac_len);
+                       }
                }
 
                IP6CB(skb)->flags |= IP6SKB_L3SLAVE;
@@ -1386,9 +1448,14 @@ static struct sk_buff *vrf_ip_rcv(struct net_device *vrf_dev,
        vrf_rx_stats(vrf_dev, skb->len);
 
        if (!list_empty(&vrf_dev->ptype_all)) {
-               skb_push(skb, skb->mac_len);
-               dev_queue_xmit_nit(skb, vrf_dev);
-               skb_pull(skb, skb->mac_len);
+               int err;
+
+               err = vrf_add_mac_header_if_unset(skb, vrf_dev, ETH_P_IP);
+               if (likely(!err)) {
+                       skb_push(skb, skb->mac_len);
+                       dev_queue_xmit_nit(skb, vrf_dev);
+                       skb_pull(skb, skb->mac_len);
+               }
        }
 
        skb = vrf_rcv_nfhook(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, vrf_dev);