net: dsa: tag_ocelot: call only the relevant portion of __skb_vlan_pop() on TX
authorVladimir Oltean <vladimir.oltean@nxp.com>
Thu, 20 Apr 2023 22:56:01 +0000 (01:56 +0300)
committerDavid S. Miller <davem@davemloft.net>
Sun, 23 Apr 2023 13:16:45 +0000 (14:16 +0100)
ocelot_xmit_get_vlan_info() calls __skb_vlan_pop() as the most
appropriate helper I could find which strips away a VLAN header.
That's all I need it to do, but __skb_vlan_pop() has more logic, which
will become incompatible with the future revert of commit 6d1ccff62780
("net: reset mac header in dev_start_xmit()").

Namely, it performs a sanity check on skb_mac_header(), which will stop
being set after the above revert, so it will return an error instead of
removing the VLAN tag.

ocelot_xmit_get_vlan_info() gets called in 2 circumstances:

(1) the port is under a VLAN-aware bridge and the bridge sends
    VLAN-tagged packets

(2) the port is under a VLAN-aware bridge and somebody else (an 8021q
    upper) sends VLAN-tagged packets (using a VID that isn't in the
    bridge vlan tables)

In case (1), there is actually no bug to defend against, because
br_dev_xmit() calls skb_reset_mac_header() and things continue to work.

However, in case (2), illustrated using the commands below, it can be
seen that our intervention is needed, since __skb_vlan_pop() complains:

$ ip link add br0 type bridge vlan_filtering 1 && ip link set br0 up
$ ip link set $eth master br0 && ip link set $eth up
$ ip link add link $eth name $eth.100 type vlan id 100 && ip link set $eth.100 up
$ ip addr add 192.168.100.1/24 dev $eth.100

I could fend off the checks in __skb_vlan_pop() with some
skb_mac_header_was_set() calls, but seeing how few callers of
__skb_vlan_pop() there are from TX paths, that seems rather
unproductive.

As an alternative solution, extract the bare minimum logic to strip a
VLAN header, and move it to a new helper named vlan_remove_tag(), close
to the definition of vlan_insert_tag(). Document it appropriately and
make ocelot_xmit_get_vlan_info() call this smaller helper instead.

Seeing that it doesn't appear illegal to test skb->protocol in the TX
path, I guess it would be a good for vlan_remove_tag() to also absorb
the vlan_set_encap_proto() function call.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Simon Horman <simon.horman@corigine.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/if_vlan.h
net/core/skbuff.c
net/dsa/tag_ocelot.c

index 3698f2b..0f40f37 100644 (file)
@@ -686,6 +686,27 @@ static inline void vlan_set_encap_proto(struct sk_buff *skb,
 }
 
 /**
+ * vlan_remove_tag - remove outer VLAN tag from payload
+ * @skb: skbuff to remove tag from
+ * @vlan_tci: buffer to store value
+ *
+ * Expects the skb to contain a VLAN tag in the payload, and to have skb->data
+ * pointing at the MAC header.
+ *
+ * Returns a new pointer to skb->data, or NULL on failure to pull.
+ */
+static inline void *vlan_remove_tag(struct sk_buff *skb, u16 *vlan_tci)
+{
+       struct vlan_hdr *vhdr = (struct vlan_hdr *)(skb->data + ETH_HLEN);
+
+       *vlan_tci = ntohs(vhdr->h_vlan_TCI);
+
+       memmove(skb->data + VLAN_HLEN, skb->data, 2 * ETH_ALEN);
+       vlan_set_encap_proto(skb, vhdr);
+       return __skb_pull(skb, VLAN_HLEN);
+}
+
+/**
  * skb_vlan_tagged - check if skb is vlan tagged.
  * @skb: skbuff to query
  *
index 304a966..c7c141f 100644 (file)
@@ -5996,7 +5996,6 @@ EXPORT_SYMBOL(skb_ensure_writable);
  */
 int __skb_vlan_pop(struct sk_buff *skb, u16 *vlan_tci)
 {
-       struct vlan_hdr *vhdr;
        int offset = skb->data - skb_mac_header(skb);
        int err;
 
@@ -6012,13 +6011,8 @@ int __skb_vlan_pop(struct sk_buff *skb, u16 *vlan_tci)
 
        skb_postpull_rcsum(skb, skb->data + (2 * ETH_ALEN), VLAN_HLEN);
 
-       vhdr = (struct vlan_hdr *)(skb->data + ETH_HLEN);
-       *vlan_tci = ntohs(vhdr->h_vlan_TCI);
-
-       memmove(skb->data + VLAN_HLEN, skb->data, 2 * ETH_ALEN);
-       __skb_pull(skb, VLAN_HLEN);
+       vlan_remove_tag(skb, vlan_tci);
 
-       vlan_set_encap_proto(skb, vhdr);
        skb->mac_header += VLAN_HLEN;
 
        if (skb_network_offset(skb) < ETH_HLEN)
index 73ee09d..20bf707 100644 (file)
@@ -30,7 +30,7 @@ static void ocelot_xmit_get_vlan_info(struct sk_buff *skb, struct dsa_port *dp,
        br_vlan_get_proto(br, &proto);
 
        if (ntohs(hdr->h_vlan_proto) == proto) {
-               __skb_vlan_pop(skb, &tci);
+               vlan_remove_tag(skb, &tci);
                *vlan_tci = tci;
        } else {
                rcu_read_lock();