]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net/sched: act_vlan: Add {POP,PUSH}_ETH actions
authorGuillaume Nault <gnault@redhat.com>
Fri, 2 Oct 2020 22:44:28 +0000 (00:44 +0200)
committerDavid S. Miller <davem@davemloft.net>
Sun, 4 Oct 2020 00:28:45 +0000 (17:28 -0700)
Implement TCA_VLAN_ACT_POP_ETH and TCA_VLAN_ACT_PUSH_ETH, to
respectively pop and push a base Ethernet header at the beginning of a
frame.

POP_ETH is just a matter of pulling ETH_HLEN bytes. VLAN tags, if any,
must be stripped before calling POP_ETH.

PUSH_ETH is restricted to skbs with no mac_header, and only the MAC
addresses can be configured. The Ethertype is automatically set from
skb->protocol. These restrictions ensure that all skb's fields remain
consistent, so that this action can't confuse other part of the
networking stack (like GSO).

Since openvswitch already had these actions, consolidate the code in
skbuff.c (like for vlan and mpls push/pop).

Signed-off-by: Guillaume Nault <gnault@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/skbuff.h
include/net/tc_act/tc_vlan.h
include/uapi/linux/tc_act/tc_vlan.h
net/core/skbuff.c
net/openvswitch/actions.c
net/sched/act_vlan.c

index 3d0cf3722bb4db793adbca565146b5d21e223db4..42131e325e2702ae276f312759700c275c6f331e 100644 (file)
@@ -3573,6 +3573,9 @@ int skb_ensure_writable(struct sk_buff *skb, int write_len);
 int __skb_vlan_pop(struct sk_buff *skb, u16 *vlan_tci);
 int skb_vlan_pop(struct sk_buff *skb);
 int skb_vlan_push(struct sk_buff *skb, __be16 vlan_proto, u16 vlan_tci);
+int skb_eth_pop(struct sk_buff *skb);
+int skb_eth_push(struct sk_buff *skb, const unsigned char *dst,
+                const unsigned char *src);
 int skb_mpls_push(struct sk_buff *skb, __be32 mpls_lse, __be16 mpls_proto,
                  int mac_len, bool ethernet);
 int skb_mpls_pop(struct sk_buff *skb, __be16 next_proto, int mac_len,
index 4e2502408c316d679b8e766bc7e67fd2f48f2693..f051046ba034420a45188df0ba8bc7e5b257163c 100644 (file)
@@ -11,6 +11,8 @@
 
 struct tcf_vlan_params {
        int               tcfv_action;
+       unsigned char     tcfv_push_dst[ETH_ALEN];
+       unsigned char     tcfv_push_src[ETH_ALEN];
        u16               tcfv_push_vid;
        __be16            tcfv_push_proto;
        u8                tcfv_push_prio;
index 168995b54a70330081fdc6d835cc6d27c822a8f7..5b306fe815ccb0a13a4a9763ed728964d390661c 100644 (file)
@@ -16,6 +16,8 @@
 #define TCA_VLAN_ACT_POP       1
 #define TCA_VLAN_ACT_PUSH      2
 #define TCA_VLAN_ACT_MODIFY    3
+#define TCA_VLAN_ACT_POP_ETH   4
+#define TCA_VLAN_ACT_PUSH_ETH  5
 
 struct tc_vlan {
        tc_gen;
@@ -30,6 +32,8 @@ enum {
        TCA_VLAN_PUSH_VLAN_PROTOCOL,
        TCA_VLAN_PAD,
        TCA_VLAN_PUSH_VLAN_PRIORITY,
+       TCA_VLAN_PUSH_ETH_DST,
+       TCA_VLAN_PUSH_ETH_SRC,
        __TCA_VLAN_MAX,
 };
 #define TCA_VLAN_MAX (__TCA_VLAN_MAX - 1)
index e0774471f56d5f8095e446cc3b45098865e6dae2..75b043accddbc454dcfcf04131164e154e597557 100644 (file)
@@ -5558,6 +5558,73 @@ int skb_vlan_push(struct sk_buff *skb, __be16 vlan_proto, u16 vlan_tci)
 }
 EXPORT_SYMBOL(skb_vlan_push);
 
+/**
+ * skb_eth_pop() - Drop the Ethernet header at the head of a packet
+ *
+ * @skb: Socket buffer to modify
+ *
+ * Drop the Ethernet header of @skb.
+ *
+ * Expects that skb->data points to the mac header and that no VLAN tags are
+ * present.
+ *
+ * Returns 0 on success, -errno otherwise.
+ */
+int skb_eth_pop(struct sk_buff *skb)
+{
+       if (!pskb_may_pull(skb, ETH_HLEN) || skb_vlan_tagged(skb) ||
+           skb_network_offset(skb) < ETH_HLEN)
+               return -EPROTO;
+
+       skb_pull_rcsum(skb, ETH_HLEN);
+       skb_reset_mac_header(skb);
+       skb_reset_mac_len(skb);
+
+       return 0;
+}
+EXPORT_SYMBOL(skb_eth_pop);
+
+/**
+ * skb_eth_push() - Add a new Ethernet header at the head of a packet
+ *
+ * @skb: Socket buffer to modify
+ * @dst: Destination MAC address of the new header
+ * @src: Source MAC address of the new header
+ *
+ * Prepend @skb with a new Ethernet header.
+ *
+ * Expects that skb->data points to the mac header, which must be empty.
+ *
+ * Returns 0 on success, -errno otherwise.
+ */
+int skb_eth_push(struct sk_buff *skb, const unsigned char *dst,
+                const unsigned char *src)
+{
+       struct ethhdr *eth;
+       int err;
+
+       if (skb_network_offset(skb) || skb_vlan_tag_present(skb))
+               return -EPROTO;
+
+       err = skb_cow_head(skb, sizeof(*eth));
+       if (err < 0)
+               return err;
+
+       skb_push(skb, sizeof(*eth));
+       skb_reset_mac_header(skb);
+       skb_reset_mac_len(skb);
+
+       eth = eth_hdr(skb);
+       ether_addr_copy(eth->h_dest, dst);
+       ether_addr_copy(eth->h_source, src);
+       eth->h_proto = skb->protocol;
+
+       skb_postpush_rcsum(skb, eth, sizeof(*eth));
+
+       return 0;
+}
+EXPORT_SYMBOL(skb_eth_push);
+
 /* Update the ethertype of hdr and the skb csum value if required. */
 static void skb_mod_eth_type(struct sk_buff *skb, struct ethhdr *hdr,
                             __be16 ethertype)
index 855f2c155956b04e2f54f7c7745f2f00224f1e85..b87bfc82f44f02e6b53b8afba309b220e9607d9a 100644 (file)
@@ -277,9 +277,11 @@ static int set_eth_addr(struct sk_buff *skb, struct sw_flow_key *flow_key,
  */
 static int pop_eth(struct sk_buff *skb, struct sw_flow_key *key)
 {
-       skb_pull_rcsum(skb, ETH_HLEN);
-       skb_reset_mac_header(skb);
-       skb_reset_mac_len(skb);
+       int err;
+
+       err = skb_eth_pop(skb);
+       if (err)
+               return err;
 
        /* safe right before invalidate_flow_key */
        key->mac_proto = MAC_PROTO_NONE;
@@ -290,22 +292,12 @@ static int pop_eth(struct sk_buff *skb, struct sw_flow_key *key)
 static int push_eth(struct sk_buff *skb, struct sw_flow_key *key,
                    const struct ovs_action_push_eth *ethh)
 {
-       struct ethhdr *hdr;
-
-       /* Add the new Ethernet header */
-       if (skb_cow_head(skb, ETH_HLEN) < 0)
-               return -ENOMEM;
-
-       skb_push(skb, ETH_HLEN);
-       skb_reset_mac_header(skb);
-       skb_reset_mac_len(skb);
-
-       hdr = eth_hdr(skb);
-       ether_addr_copy(hdr->h_source, ethh->addresses.eth_src);
-       ether_addr_copy(hdr->h_dest, ethh->addresses.eth_dst);
-       hdr->h_proto = skb->protocol;
+       int err;
 
-       skb_postpush_rcsum(skb, hdr, ETH_HLEN);
+       err = skb_eth_push(skb, ethh->addresses.eth_dst,
+                          ethh->addresses.eth_src);
+       if (err)
+               return err;
 
        /* safe right before invalidate_flow_key */
        key->mac_proto = MAC_PROTO_ETHERNET;
index a5ff9f68ab023a4a3cba5ef25c43fe695ef1eee6..8758bd2a78fa4ef5485697a2dc468b7a83dad77f 100644 (file)
@@ -77,6 +77,16 @@ static int tcf_vlan_act(struct sk_buff *skb, const struct tc_action *a,
                /* put updated tci as hwaccel tag */
                __vlan_hwaccel_put_tag(skb, p->tcfv_push_proto, tci);
                break;
+       case TCA_VLAN_ACT_POP_ETH:
+               err = skb_eth_pop(skb);
+               if (err)
+                       goto drop;
+               break;
+       case TCA_VLAN_ACT_PUSH_ETH:
+               err = skb_eth_push(skb, p->tcfv_push_dst, p->tcfv_push_src);
+               if (err)
+                       goto drop;
+               break;
        default:
                BUG();
        }
@@ -93,10 +103,13 @@ drop:
 }
 
 static const struct nla_policy vlan_policy[TCA_VLAN_MAX + 1] = {
+       [TCA_VLAN_UNSPEC]               = { .strict_start_type = TCA_VLAN_PUSH_ETH_DST },
        [TCA_VLAN_PARMS]                = { .len = sizeof(struct tc_vlan) },
        [TCA_VLAN_PUSH_VLAN_ID]         = { .type = NLA_U16 },
        [TCA_VLAN_PUSH_VLAN_PROTOCOL]   = { .type = NLA_U16 },
        [TCA_VLAN_PUSH_VLAN_PRIORITY]   = { .type = NLA_U8 },
+       [TCA_VLAN_PUSH_ETH_DST]         = NLA_POLICY_ETH_ADDR,
+       [TCA_VLAN_PUSH_ETH_SRC]         = NLA_POLICY_ETH_ADDR,
 };
 
 static int tcf_vlan_init(struct net *net, struct nlattr *nla,
@@ -179,6 +192,17 @@ static int tcf_vlan_init(struct net *net, struct nlattr *nla,
                if (tb[TCA_VLAN_PUSH_VLAN_PRIORITY])
                        push_prio = nla_get_u8(tb[TCA_VLAN_PUSH_VLAN_PRIORITY]);
                break;
+       case TCA_VLAN_ACT_POP_ETH:
+               break;
+       case TCA_VLAN_ACT_PUSH_ETH:
+               if (!tb[TCA_VLAN_PUSH_ETH_DST] || !tb[TCA_VLAN_PUSH_ETH_SRC]) {
+                       if (exists)
+                               tcf_idr_release(*a, bind);
+                       else
+                               tcf_idr_cleanup(tn, index);
+                       return -EINVAL;
+               }
+               break;
        default:
                if (exists)
                        tcf_idr_release(*a, bind);
@@ -219,6 +243,13 @@ static int tcf_vlan_init(struct net *net, struct nlattr *nla,
        p->tcfv_push_prio = push_prio;
        p->tcfv_push_proto = push_proto;
 
+       if (action == TCA_VLAN_ACT_PUSH_ETH) {
+               nla_memcpy(&p->tcfv_push_dst, tb[TCA_VLAN_PUSH_ETH_DST],
+                          ETH_ALEN);
+               nla_memcpy(&p->tcfv_push_src, tb[TCA_VLAN_PUSH_ETH_SRC],
+                          ETH_ALEN);
+       }
+
        spin_lock_bh(&v->tcf_lock);
        goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
        p = rcu_replace_pointer(v->vlan_p, p, lockdep_is_held(&v->tcf_lock));
@@ -279,6 +310,15 @@ static int tcf_vlan_dump(struct sk_buff *skb, struct tc_action *a,
                                              p->tcfv_push_prio))))
                goto nla_put_failure;
 
+       if (p->tcfv_action == TCA_VLAN_ACT_PUSH_ETH) {
+               if (nla_put(skb, TCA_VLAN_PUSH_ETH_DST, ETH_ALEN,
+                           p->tcfv_push_dst))
+                       goto nla_put_failure;
+               if (nla_put(skb, TCA_VLAN_PUSH_ETH_SRC, ETH_ALEN,
+                           p->tcfv_push_src))
+                       goto nla_put_failure;
+       }
+
        tcf_tm_dump(&t, &v->tcf_tm);
        if (nla_put_64bit(skb, TCA_VLAN_TM, sizeof(t), &t, TCA_VLAN_PAD))
                goto nla_put_failure;