12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2c7e2b968SJiri Pirko /* 3c7e2b968SJiri Pirko * Copyright (c) 2014 Jiri Pirko <jiri@resnulli.us> 4c7e2b968SJiri Pirko */ 5c7e2b968SJiri Pirko 6c7e2b968SJiri Pirko #include <linux/module.h> 7c7e2b968SJiri Pirko #include <linux/init.h> 8c7e2b968SJiri Pirko #include <linux/kernel.h> 9c7e2b968SJiri Pirko #include <linux/skbuff.h> 10c7e2b968SJiri Pirko #include <linux/rtnetlink.h> 11c7e2b968SJiri Pirko #include <linux/if_vlan.h> 12c7e2b968SJiri Pirko #include <net/netlink.h> 13c7e2b968SJiri Pirko #include <net/pkt_sched.h> 147e0c8892SDavide Caratti #include <net/pkt_cls.h> 15*871cf386SPedro Tammela #include <net/tc_wrapper.h> 16c7e2b968SJiri Pirko 17c7e2b968SJiri Pirko #include <linux/tc_act/tc_vlan.h> 18c7e2b968SJiri Pirko #include <net/tc_act/tc_vlan.h> 19c7e2b968SJiri Pirko 20a85a970aSWANG Cong static struct tc_action_ops act_vlan_ops; 21ddf97ccdSWANG Cong 22*871cf386SPedro Tammela TC_INDIRECT_SCOPE int tcf_vlan_act(struct sk_buff *skb, 23*871cf386SPedro Tammela const struct tc_action *a, 24c7e2b968SJiri Pirko struct tcf_result *res) 25c7e2b968SJiri Pirko { 26a85a970aSWANG Cong struct tcf_vlan *v = to_vlan(a); 274c5b9d96SManish Kurup struct tcf_vlan_params *p; 28c7e2b968SJiri Pirko int action; 29c7e2b968SJiri Pirko int err; 3045a497f2SShmulik Ladkani u16 tci; 31c7e2b968SJiri Pirko 329c4a4e48SJamal Hadi Salim tcf_lastuse_update(&v->tcf_tm); 335e1ad95bSVlad Buslov tcf_action_update_bstats(&v->common, skb); 34e0496cbbSManish Kurup 35f39acc84SShmulik Ladkani /* Ensure 'data' points at mac_header prior calling vlan manipulating 36f39acc84SShmulik Ladkani * functions. 37f39acc84SShmulik Ladkani */ 38f39acc84SShmulik Ladkani if (skb_at_tc_ingress(skb)) 39f39acc84SShmulik Ladkani skb_push_rcsum(skb, skb->mac_len); 40f39acc84SShmulik Ladkani 414c5b9d96SManish Kurup action = READ_ONCE(v->tcf_action); 424c5b9d96SManish Kurup 437fd4b288SPaolo Abeni p = rcu_dereference_bh(v->vlan_p); 444c5b9d96SManish Kurup 454c5b9d96SManish Kurup switch (p->tcfv_action) { 46c7e2b968SJiri Pirko case TCA_VLAN_ACT_POP: 47c7e2b968SJiri Pirko err = skb_vlan_pop(skb); 48c7e2b968SJiri Pirko if (err) 49c7e2b968SJiri Pirko goto drop; 50c7e2b968SJiri Pirko break; 51c7e2b968SJiri Pirko case TCA_VLAN_ACT_PUSH: 524c5b9d96SManish Kurup err = skb_vlan_push(skb, p->tcfv_push_proto, p->tcfv_push_vid | 534c5b9d96SManish Kurup (p->tcfv_push_prio << VLAN_PRIO_SHIFT)); 54c7e2b968SJiri Pirko if (err) 55c7e2b968SJiri Pirko goto drop; 56c7e2b968SJiri Pirko break; 5745a497f2SShmulik Ladkani case TCA_VLAN_ACT_MODIFY: 5845a497f2SShmulik Ladkani /* No-op if no vlan tag (either hw-accel or in-payload) */ 5945a497f2SShmulik Ladkani if (!skb_vlan_tagged(skb)) 607fd4b288SPaolo Abeni goto out; 6145a497f2SShmulik Ladkani /* extract existing tag (and guarantee no hw-accel tag) */ 6245a497f2SShmulik Ladkani if (skb_vlan_tag_present(skb)) { 6345a497f2SShmulik Ladkani tci = skb_vlan_tag_get(skb); 64b1817524SMichał Mirosław __vlan_hwaccel_clear_tag(skb); 6545a497f2SShmulik Ladkani } else { 6645a497f2SShmulik Ladkani /* in-payload vlan tag, pop it */ 6745a497f2SShmulik Ladkani err = __skb_vlan_pop(skb, &tci); 6845a497f2SShmulik Ladkani if (err) 6945a497f2SShmulik Ladkani goto drop; 7045a497f2SShmulik Ladkani } 7145a497f2SShmulik Ladkani /* replace the vid */ 724c5b9d96SManish Kurup tci = (tci & ~VLAN_VID_MASK) | p->tcfv_push_vid; 7345a497f2SShmulik Ladkani /* replace prio bits, if tcfv_push_prio specified */ 749c5eee0aSBoris Sukholitko if (p->tcfv_push_prio_exists) { 7545a497f2SShmulik Ladkani tci &= ~VLAN_PRIO_MASK; 764c5b9d96SManish Kurup tci |= p->tcfv_push_prio << VLAN_PRIO_SHIFT; 7745a497f2SShmulik Ladkani } 7845a497f2SShmulik Ladkani /* put updated tci as hwaccel tag */ 794c5b9d96SManish Kurup __vlan_hwaccel_put_tag(skb, p->tcfv_push_proto, tci); 8045a497f2SShmulik Ladkani break; 8119fbcb36SGuillaume Nault case TCA_VLAN_ACT_POP_ETH: 8219fbcb36SGuillaume Nault err = skb_eth_pop(skb); 8319fbcb36SGuillaume Nault if (err) 8419fbcb36SGuillaume Nault goto drop; 8519fbcb36SGuillaume Nault break; 8619fbcb36SGuillaume Nault case TCA_VLAN_ACT_PUSH_ETH: 8719fbcb36SGuillaume Nault err = skb_eth_push(skb, p->tcfv_push_dst, p->tcfv_push_src); 8819fbcb36SGuillaume Nault if (err) 8919fbcb36SGuillaume Nault goto drop; 9019fbcb36SGuillaume Nault break; 91c7e2b968SJiri Pirko default: 92c7e2b968SJiri Pirko BUG(); 93c7e2b968SJiri Pirko } 94c7e2b968SJiri Pirko 957fd4b288SPaolo Abeni out: 96f39acc84SShmulik Ladkani if (skb_at_tc_ingress(skb)) 97f39acc84SShmulik Ladkani skb_pull_rcsum(skb, skb->mac_len); 98f39acc84SShmulik Ladkani 99c7e2b968SJiri Pirko return action; 1007fd4b288SPaolo Abeni 1017fd4b288SPaolo Abeni drop: 10226b537a8SVlad Buslov tcf_action_inc_drop_qstats(&v->common); 1037fd4b288SPaolo Abeni return TC_ACT_SHOT; 104c7e2b968SJiri Pirko } 105c7e2b968SJiri Pirko 106c7e2b968SJiri Pirko static const struct nla_policy vlan_policy[TCA_VLAN_MAX + 1] = { 10719fbcb36SGuillaume Nault [TCA_VLAN_UNSPEC] = { .strict_start_type = TCA_VLAN_PUSH_ETH_DST }, 108c7e2b968SJiri Pirko [TCA_VLAN_PARMS] = { .len = sizeof(struct tc_vlan) }, 109c7e2b968SJiri Pirko [TCA_VLAN_PUSH_VLAN_ID] = { .type = NLA_U16 }, 110c7e2b968SJiri Pirko [TCA_VLAN_PUSH_VLAN_PROTOCOL] = { .type = NLA_U16 }, 111956af371SHadar Hen Zion [TCA_VLAN_PUSH_VLAN_PRIORITY] = { .type = NLA_U8 }, 11219fbcb36SGuillaume Nault [TCA_VLAN_PUSH_ETH_DST] = NLA_POLICY_ETH_ADDR, 11319fbcb36SGuillaume Nault [TCA_VLAN_PUSH_ETH_SRC] = NLA_POLICY_ETH_ADDR, 114c7e2b968SJiri Pirko }; 115c7e2b968SJiri Pirko 116c7e2b968SJiri Pirko static int tcf_vlan_init(struct net *net, struct nlattr *nla, 117a85a970aSWANG Cong struct nlattr *est, struct tc_action **a, 118abbb0d33SVlad Buslov struct tcf_proto *tp, u32 flags, 119abbb0d33SVlad Buslov struct netlink_ext_ack *extack) 120c7e2b968SJiri Pirko { 121acd0a7abSZhengchao Shao struct tc_action_net *tn = net_generic(net, act_vlan_ops.net_id); 122695176bfSCong Wang bool bind = flags & TCA_ACT_FLAGS_BIND; 123c7e2b968SJiri Pirko struct nlattr *tb[TCA_VLAN_MAX + 1]; 1247e0c8892SDavide Caratti struct tcf_chain *goto_ch = NULL; 1259c5eee0aSBoris Sukholitko bool push_prio_exists = false; 126764e9a24SVlad Buslov struct tcf_vlan_params *p; 127c7e2b968SJiri Pirko struct tc_vlan *parm; 128c7e2b968SJiri Pirko struct tcf_vlan *v; 129c7e2b968SJiri Pirko int action; 13094cb5492SDavide Caratti u16 push_vid = 0; 131c7e2b968SJiri Pirko __be16 push_proto = 0; 132956af371SHadar Hen Zion u8 push_prio = 0; 133b2313077SWANG Cong bool exists = false; 134b2313077SWANG Cong int ret = 0, err; 1357be8ef2cSDmytro Linkin u32 index; 136c7e2b968SJiri Pirko 137c7e2b968SJiri Pirko if (!nla) 138c7e2b968SJiri Pirko return -EINVAL; 139c7e2b968SJiri Pirko 1408cb08174SJohannes Berg err = nla_parse_nested_deprecated(tb, TCA_VLAN_MAX, nla, vlan_policy, 1418cb08174SJohannes Berg NULL); 142c7e2b968SJiri Pirko if (err < 0) 143c7e2b968SJiri Pirko return err; 144c7e2b968SJiri Pirko 145c7e2b968SJiri Pirko if (!tb[TCA_VLAN_PARMS]) 146c7e2b968SJiri Pirko return -EINVAL; 147c7e2b968SJiri Pirko parm = nla_data(tb[TCA_VLAN_PARMS]); 1487be8ef2cSDmytro Linkin index = parm->index; 1497be8ef2cSDmytro Linkin err = tcf_idr_check_alloc(tn, &index, a, bind); 1500190c1d4SVlad Buslov if (err < 0) 1510190c1d4SVlad Buslov return err; 1520190c1d4SVlad Buslov exists = err; 1535026c9b1SJamal Hadi Salim if (exists && bind) 1545026c9b1SJamal Hadi Salim return 0; 1555026c9b1SJamal Hadi Salim 156c7e2b968SJiri Pirko switch (parm->v_action) { 157c7e2b968SJiri Pirko case TCA_VLAN_ACT_POP: 158c7e2b968SJiri Pirko break; 159c7e2b968SJiri Pirko case TCA_VLAN_ACT_PUSH: 16045a497f2SShmulik Ladkani case TCA_VLAN_ACT_MODIFY: 1615026c9b1SJamal Hadi Salim if (!tb[TCA_VLAN_PUSH_VLAN_ID]) { 1625026c9b1SJamal Hadi Salim if (exists) 16365a206c0SChris Mi tcf_idr_release(*a, bind); 1640190c1d4SVlad Buslov else 1657be8ef2cSDmytro Linkin tcf_idr_cleanup(tn, index); 166c7e2b968SJiri Pirko return -EINVAL; 1675026c9b1SJamal Hadi Salim } 168c7e2b968SJiri Pirko push_vid = nla_get_u16(tb[TCA_VLAN_PUSH_VLAN_ID]); 1695026c9b1SJamal Hadi Salim if (push_vid >= VLAN_VID_MASK) { 1705026c9b1SJamal Hadi Salim if (exists) 17165a206c0SChris Mi tcf_idr_release(*a, bind); 1720190c1d4SVlad Buslov else 1737be8ef2cSDmytro Linkin tcf_idr_cleanup(tn, index); 174c7e2b968SJiri Pirko return -ERANGE; 1755026c9b1SJamal Hadi Salim } 176c7e2b968SJiri Pirko 177c7e2b968SJiri Pirko if (tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]) { 178c7e2b968SJiri Pirko push_proto = nla_get_be16(tb[TCA_VLAN_PUSH_VLAN_PROTOCOL]); 179c7e2b968SJiri Pirko switch (push_proto) { 180c7e2b968SJiri Pirko case htons(ETH_P_8021Q): 181c7e2b968SJiri Pirko case htons(ETH_P_8021AD): 182c7e2b968SJiri Pirko break; 183c7e2b968SJiri Pirko default: 1845a4931aeSDavide Caratti if (exists) 1855a4931aeSDavide Caratti tcf_idr_release(*a, bind); 1860190c1d4SVlad Buslov else 1877be8ef2cSDmytro Linkin tcf_idr_cleanup(tn, index); 188c7e2b968SJiri Pirko return -EPROTONOSUPPORT; 189c7e2b968SJiri Pirko } 190c7e2b968SJiri Pirko } else { 191c7e2b968SJiri Pirko push_proto = htons(ETH_P_8021Q); 192c7e2b968SJiri Pirko } 193956af371SHadar Hen Zion 1949c5eee0aSBoris Sukholitko push_prio_exists = !!tb[TCA_VLAN_PUSH_VLAN_PRIORITY]; 1959c5eee0aSBoris Sukholitko if (push_prio_exists) 196956af371SHadar Hen Zion push_prio = nla_get_u8(tb[TCA_VLAN_PUSH_VLAN_PRIORITY]); 197c7e2b968SJiri Pirko break; 19819fbcb36SGuillaume Nault case TCA_VLAN_ACT_POP_ETH: 19919fbcb36SGuillaume Nault break; 20019fbcb36SGuillaume Nault case TCA_VLAN_ACT_PUSH_ETH: 20119fbcb36SGuillaume Nault if (!tb[TCA_VLAN_PUSH_ETH_DST] || !tb[TCA_VLAN_PUSH_ETH_SRC]) { 20219fbcb36SGuillaume Nault if (exists) 20319fbcb36SGuillaume Nault tcf_idr_release(*a, bind); 20419fbcb36SGuillaume Nault else 20519fbcb36SGuillaume Nault tcf_idr_cleanup(tn, index); 20619fbcb36SGuillaume Nault return -EINVAL; 20719fbcb36SGuillaume Nault } 20819fbcb36SGuillaume Nault break; 209c7e2b968SJiri Pirko default: 2105026c9b1SJamal Hadi Salim if (exists) 21165a206c0SChris Mi tcf_idr_release(*a, bind); 2120190c1d4SVlad Buslov else 2137be8ef2cSDmytro Linkin tcf_idr_cleanup(tn, index); 214c7e2b968SJiri Pirko return -EINVAL; 215c7e2b968SJiri Pirko } 216c7e2b968SJiri Pirko action = parm->v_action; 217c7e2b968SJiri Pirko 2185026c9b1SJamal Hadi Salim if (!exists) { 219e3822678SVlad Buslov ret = tcf_idr_create_from_flags(tn, index, est, a, 220e3822678SVlad Buslov &act_vlan_ops, bind, flags); 2210190c1d4SVlad Buslov if (ret) { 2227be8ef2cSDmytro Linkin tcf_idr_cleanup(tn, index); 223c7e2b968SJiri Pirko return ret; 2240190c1d4SVlad Buslov } 225c7e2b968SJiri Pirko 226c7e2b968SJiri Pirko ret = ACT_P_CREATED; 227695176bfSCong Wang } else if (!(flags & TCA_ACT_FLAGS_REPLACE)) { 22865a206c0SChris Mi tcf_idr_release(*a, bind); 229c7e2b968SJiri Pirko return -EEXIST; 230c7e2b968SJiri Pirko } 231c7e2b968SJiri Pirko 2327e0c8892SDavide Caratti err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); 2337e0c8892SDavide Caratti if (err < 0) 2347e0c8892SDavide Caratti goto release_idr; 2357e0c8892SDavide Caratti 236a85a970aSWANG Cong v = to_vlan(*a); 237c7e2b968SJiri Pirko 2384c5b9d96SManish Kurup p = kzalloc(sizeof(*p), GFP_KERNEL); 2394c5b9d96SManish Kurup if (!p) { 2407e0c8892SDavide Caratti err = -ENOMEM; 2417e0c8892SDavide Caratti goto put_chain; 2424c5b9d96SManish Kurup } 243c7e2b968SJiri Pirko 2444c5b9d96SManish Kurup p->tcfv_action = action; 2454c5b9d96SManish Kurup p->tcfv_push_vid = push_vid; 2464c5b9d96SManish Kurup p->tcfv_push_prio = push_prio; 2479c5eee0aSBoris Sukholitko p->tcfv_push_prio_exists = push_prio_exists || action == TCA_VLAN_ACT_PUSH; 2484c5b9d96SManish Kurup p->tcfv_push_proto = push_proto; 2494c5b9d96SManish Kurup 25019fbcb36SGuillaume Nault if (action == TCA_VLAN_ACT_PUSH_ETH) { 25119fbcb36SGuillaume Nault nla_memcpy(&p->tcfv_push_dst, tb[TCA_VLAN_PUSH_ETH_DST], 25219fbcb36SGuillaume Nault ETH_ALEN); 25319fbcb36SGuillaume Nault nla_memcpy(&p->tcfv_push_src, tb[TCA_VLAN_PUSH_ETH_SRC], 25419fbcb36SGuillaume Nault ETH_ALEN); 25519fbcb36SGuillaume Nault } 25619fbcb36SGuillaume Nault 257653cd284SVlad Buslov spin_lock_bh(&v->tcf_lock); 2587e0c8892SDavide Caratti goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch); 259445d3749SPaul E. McKenney p = rcu_replace_pointer(v->vlan_p, p, lockdep_is_held(&v->tcf_lock)); 260653cd284SVlad Buslov spin_unlock_bh(&v->tcf_lock); 2614c5b9d96SManish Kurup 2627e0c8892SDavide Caratti if (goto_ch) 2637e0c8892SDavide Caratti tcf_chain_put_by_act(goto_ch); 264764e9a24SVlad Buslov if (p) 265764e9a24SVlad Buslov kfree_rcu(p, rcu); 266c7e2b968SJiri Pirko 267c7e2b968SJiri Pirko return ret; 2687e0c8892SDavide Caratti put_chain: 2697e0c8892SDavide Caratti if (goto_ch) 2707e0c8892SDavide Caratti tcf_chain_put_by_act(goto_ch); 2717e0c8892SDavide Caratti release_idr: 2727e0c8892SDavide Caratti tcf_idr_release(*a, bind); 2737e0c8892SDavide Caratti return err; 274c7e2b968SJiri Pirko } 275c7e2b968SJiri Pirko 2769a63b255SCong Wang static void tcf_vlan_cleanup(struct tc_action *a) 2774c5b9d96SManish Kurup { 2784c5b9d96SManish Kurup struct tcf_vlan *v = to_vlan(a); 2794c5b9d96SManish Kurup struct tcf_vlan_params *p; 2804c5b9d96SManish Kurup 2814c5b9d96SManish Kurup p = rcu_dereference_protected(v->vlan_p, 1); 2821edf8abeSDavide Caratti if (p) 2834c5b9d96SManish Kurup kfree_rcu(p, rcu); 2844c5b9d96SManish Kurup } 2854c5b9d96SManish Kurup 286c7e2b968SJiri Pirko static int tcf_vlan_dump(struct sk_buff *skb, struct tc_action *a, 287c7e2b968SJiri Pirko int bind, int ref) 288c7e2b968SJiri Pirko { 289c7e2b968SJiri Pirko unsigned char *b = skb_tail_pointer(skb); 290a85a970aSWANG Cong struct tcf_vlan *v = to_vlan(a); 291764e9a24SVlad Buslov struct tcf_vlan_params *p; 292c7e2b968SJiri Pirko struct tc_vlan opt = { 293c7e2b968SJiri Pirko .index = v->tcf_index, 294036bb443SVlad Buslov .refcnt = refcount_read(&v->tcf_refcnt) - ref, 295036bb443SVlad Buslov .bindcnt = atomic_read(&v->tcf_bindcnt) - bind, 296c7e2b968SJiri Pirko }; 297c7e2b968SJiri Pirko struct tcf_t t; 298c7e2b968SJiri Pirko 299653cd284SVlad Buslov spin_lock_bh(&v->tcf_lock); 300764e9a24SVlad Buslov opt.action = v->tcf_action; 301764e9a24SVlad Buslov p = rcu_dereference_protected(v->vlan_p, lockdep_is_held(&v->tcf_lock)); 302764e9a24SVlad Buslov opt.v_action = p->tcfv_action; 303c7e2b968SJiri Pirko if (nla_put(skb, TCA_VLAN_PARMS, sizeof(opt), &opt)) 304c7e2b968SJiri Pirko goto nla_put_failure; 305c7e2b968SJiri Pirko 3064c5b9d96SManish Kurup if ((p->tcfv_action == TCA_VLAN_ACT_PUSH || 3074c5b9d96SManish Kurup p->tcfv_action == TCA_VLAN_ACT_MODIFY) && 3084c5b9d96SManish Kurup (nla_put_u16(skb, TCA_VLAN_PUSH_VLAN_ID, p->tcfv_push_vid) || 3090b0f43feSJamal Hadi Salim nla_put_be16(skb, TCA_VLAN_PUSH_VLAN_PROTOCOL, 3104c5b9d96SManish Kurup p->tcfv_push_proto) || 3118323b20fSBoris Sukholitko (p->tcfv_push_prio_exists && 3128323b20fSBoris Sukholitko nla_put_u8(skb, TCA_VLAN_PUSH_VLAN_PRIORITY, p->tcfv_push_prio)))) 313c7e2b968SJiri Pirko goto nla_put_failure; 314c7e2b968SJiri Pirko 31519fbcb36SGuillaume Nault if (p->tcfv_action == TCA_VLAN_ACT_PUSH_ETH) { 31619fbcb36SGuillaume Nault if (nla_put(skb, TCA_VLAN_PUSH_ETH_DST, ETH_ALEN, 31719fbcb36SGuillaume Nault p->tcfv_push_dst)) 31819fbcb36SGuillaume Nault goto nla_put_failure; 31919fbcb36SGuillaume Nault if (nla_put(skb, TCA_VLAN_PUSH_ETH_SRC, ETH_ALEN, 32019fbcb36SGuillaume Nault p->tcfv_push_src)) 32119fbcb36SGuillaume Nault goto nla_put_failure; 32219fbcb36SGuillaume Nault } 32319fbcb36SGuillaume Nault 32448d8ee16SJamal Hadi Salim tcf_tm_dump(&t, &v->tcf_tm); 3259854518eSNicolas Dichtel if (nla_put_64bit(skb, TCA_VLAN_TM, sizeof(t), &t, TCA_VLAN_PAD)) 326c7e2b968SJiri Pirko goto nla_put_failure; 327653cd284SVlad Buslov spin_unlock_bh(&v->tcf_lock); 328764e9a24SVlad Buslov 329c7e2b968SJiri Pirko return skb->len; 330c7e2b968SJiri Pirko 331c7e2b968SJiri Pirko nla_put_failure: 332653cd284SVlad Buslov spin_unlock_bh(&v->tcf_lock); 333c7e2b968SJiri Pirko nlmsg_trim(skb, b); 334c7e2b968SJiri Pirko return -1; 335c7e2b968SJiri Pirko } 336c7e2b968SJiri Pirko 3374b61d3e8SPo Liu static void tcf_vlan_stats_update(struct tc_action *a, u64 bytes, u64 packets, 3384b61d3e8SPo Liu u64 drops, u64 lastuse, bool hw) 339fa730a3bSJiri Pirko { 340fa730a3bSJiri Pirko struct tcf_vlan *v = to_vlan(a); 341fa730a3bSJiri Pirko struct tcf_t *tm = &v->tcf_tm; 342fa730a3bSJiri Pirko 3434b61d3e8SPo Liu tcf_action_update_stats(a, bytes, packets, drops, hw); 344fa730a3bSJiri Pirko tm->lastuse = max_t(u64, tm->lastuse, lastuse); 345fa730a3bSJiri Pirko } 346fa730a3bSJiri Pirko 347b35475c5SRoman Mashak static size_t tcf_vlan_get_fill_size(const struct tc_action *act) 348b35475c5SRoman Mashak { 349b35475c5SRoman Mashak return nla_total_size(sizeof(struct tc_vlan)) 350b35475c5SRoman Mashak + nla_total_size(sizeof(u16)) /* TCA_VLAN_PUSH_VLAN_ID */ 351b35475c5SRoman Mashak + nla_total_size(sizeof(u16)) /* TCA_VLAN_PUSH_VLAN_PROTOCOL */ 352b35475c5SRoman Mashak + nla_total_size(sizeof(u8)); /* TCA_VLAN_PUSH_VLAN_PRIORITY */ 353b35475c5SRoman Mashak } 354b35475c5SRoman Mashak 355c54e1d92SBaowen Zheng static int tcf_vlan_offload_act_setup(struct tc_action *act, void *entry_data, 356c2ccf84eSIdo Schimmel u32 *index_inc, bool bind, 357c2ccf84eSIdo Schimmel struct netlink_ext_ack *extack) 358c54e1d92SBaowen Zheng { 359c54e1d92SBaowen Zheng if (bind) { 360c54e1d92SBaowen Zheng struct flow_action_entry *entry = entry_data; 361c54e1d92SBaowen Zheng 362c54e1d92SBaowen Zheng switch (tcf_vlan_action(act)) { 363c54e1d92SBaowen Zheng case TCA_VLAN_ACT_PUSH: 364c54e1d92SBaowen Zheng entry->id = FLOW_ACTION_VLAN_PUSH; 365c54e1d92SBaowen Zheng entry->vlan.vid = tcf_vlan_push_vid(act); 366c54e1d92SBaowen Zheng entry->vlan.proto = tcf_vlan_push_proto(act); 367c54e1d92SBaowen Zheng entry->vlan.prio = tcf_vlan_push_prio(act); 368c54e1d92SBaowen Zheng break; 369c54e1d92SBaowen Zheng case TCA_VLAN_ACT_POP: 370c54e1d92SBaowen Zheng entry->id = FLOW_ACTION_VLAN_POP; 371c54e1d92SBaowen Zheng break; 372c54e1d92SBaowen Zheng case TCA_VLAN_ACT_MODIFY: 373c54e1d92SBaowen Zheng entry->id = FLOW_ACTION_VLAN_MANGLE; 374c54e1d92SBaowen Zheng entry->vlan.vid = tcf_vlan_push_vid(act); 375c54e1d92SBaowen Zheng entry->vlan.proto = tcf_vlan_push_proto(act); 376c54e1d92SBaowen Zheng entry->vlan.prio = tcf_vlan_push_prio(act); 377c54e1d92SBaowen Zheng break; 378ab95465cSMaor Dickman case TCA_VLAN_ACT_POP_ETH: 379ab95465cSMaor Dickman entry->id = FLOW_ACTION_VLAN_POP_ETH; 380ab95465cSMaor Dickman break; 381ab95465cSMaor Dickman case TCA_VLAN_ACT_PUSH_ETH: 382ab95465cSMaor Dickman entry->id = FLOW_ACTION_VLAN_PUSH_ETH; 383ab95465cSMaor Dickman tcf_vlan_push_eth(entry->vlan_push_eth.src, entry->vlan_push_eth.dst, act); 384ab95465cSMaor Dickman break; 385c54e1d92SBaowen Zheng default: 386f8fab316SIdo Schimmel NL_SET_ERR_MSG_MOD(extack, "Unsupported vlan action mode offload"); 387c54e1d92SBaowen Zheng return -EOPNOTSUPP; 388c54e1d92SBaowen Zheng } 389c54e1d92SBaowen Zheng *index_inc = 1; 390c54e1d92SBaowen Zheng } else { 3918cbfe939SBaowen Zheng struct flow_offload_action *fl_action = entry_data; 3928cbfe939SBaowen Zheng 3938cbfe939SBaowen Zheng switch (tcf_vlan_action(act)) { 3948cbfe939SBaowen Zheng case TCA_VLAN_ACT_PUSH: 3958cbfe939SBaowen Zheng fl_action->id = FLOW_ACTION_VLAN_PUSH; 3968cbfe939SBaowen Zheng break; 3978cbfe939SBaowen Zheng case TCA_VLAN_ACT_POP: 3988cbfe939SBaowen Zheng fl_action->id = FLOW_ACTION_VLAN_POP; 3998cbfe939SBaowen Zheng break; 4008cbfe939SBaowen Zheng case TCA_VLAN_ACT_MODIFY: 4018cbfe939SBaowen Zheng fl_action->id = FLOW_ACTION_VLAN_MANGLE; 4028cbfe939SBaowen Zheng break; 403ab95465cSMaor Dickman case TCA_VLAN_ACT_POP_ETH: 404ab95465cSMaor Dickman fl_action->id = FLOW_ACTION_VLAN_POP_ETH; 405ab95465cSMaor Dickman break; 406ab95465cSMaor Dickman case TCA_VLAN_ACT_PUSH_ETH: 407ab95465cSMaor Dickman fl_action->id = FLOW_ACTION_VLAN_PUSH_ETH; 408ab95465cSMaor Dickman break; 4098cbfe939SBaowen Zheng default: 410c54e1d92SBaowen Zheng return -EOPNOTSUPP; 411c54e1d92SBaowen Zheng } 4128cbfe939SBaowen Zheng } 413c54e1d92SBaowen Zheng 414c54e1d92SBaowen Zheng return 0; 415c54e1d92SBaowen Zheng } 416c54e1d92SBaowen Zheng 417c7e2b968SJiri Pirko static struct tc_action_ops act_vlan_ops = { 418c7e2b968SJiri Pirko .kind = "vlan", 419eddd2cf1SEli Cohen .id = TCA_ID_VLAN, 420c7e2b968SJiri Pirko .owner = THIS_MODULE, 4218aa7f22eSJamal Hadi Salim .act = tcf_vlan_act, 422c7e2b968SJiri Pirko .dump = tcf_vlan_dump, 423c7e2b968SJiri Pirko .init = tcf_vlan_init, 4244c5b9d96SManish Kurup .cleanup = tcf_vlan_cleanup, 425fa730a3bSJiri Pirko .stats_update = tcf_vlan_stats_update, 426b35475c5SRoman Mashak .get_fill_size = tcf_vlan_get_fill_size, 427c54e1d92SBaowen Zheng .offload_act_setup = tcf_vlan_offload_act_setup, 428a85a970aSWANG Cong .size = sizeof(struct tcf_vlan), 429ddf97ccdSWANG Cong }; 430ddf97ccdSWANG Cong 431ddf97ccdSWANG Cong static __net_init int vlan_init_net(struct net *net) 432ddf97ccdSWANG Cong { 433acd0a7abSZhengchao Shao struct tc_action_net *tn = net_generic(net, act_vlan_ops.net_id); 434ddf97ccdSWANG Cong 435981471bdSCong Wang return tc_action_net_init(net, tn, &act_vlan_ops); 436ddf97ccdSWANG Cong } 437ddf97ccdSWANG Cong 438039af9c6SCong Wang static void __net_exit vlan_exit_net(struct list_head *net_list) 439ddf97ccdSWANG Cong { 440acd0a7abSZhengchao Shao tc_action_net_exit(net_list, act_vlan_ops.net_id); 441ddf97ccdSWANG Cong } 442ddf97ccdSWANG Cong 443ddf97ccdSWANG Cong static struct pernet_operations vlan_net_ops = { 444ddf97ccdSWANG Cong .init = vlan_init_net, 445039af9c6SCong Wang .exit_batch = vlan_exit_net, 446acd0a7abSZhengchao Shao .id = &act_vlan_ops.net_id, 447ddf97ccdSWANG Cong .size = sizeof(struct tc_action_net), 448c7e2b968SJiri Pirko }; 449c7e2b968SJiri Pirko 450c7e2b968SJiri Pirko static int __init vlan_init_module(void) 451c7e2b968SJiri Pirko { 452ddf97ccdSWANG Cong return tcf_register_action(&act_vlan_ops, &vlan_net_ops); 453c7e2b968SJiri Pirko } 454c7e2b968SJiri Pirko 455c7e2b968SJiri Pirko static void __exit vlan_cleanup_module(void) 456c7e2b968SJiri Pirko { 457ddf97ccdSWANG Cong tcf_unregister_action(&act_vlan_ops, &vlan_net_ops); 458c7e2b968SJiri Pirko } 459c7e2b968SJiri Pirko 460c7e2b968SJiri Pirko module_init(vlan_init_module); 461c7e2b968SJiri Pirko module_exit(vlan_cleanup_module); 462c7e2b968SJiri Pirko 463c7e2b968SJiri Pirko MODULE_AUTHOR("Jiri Pirko <jiri@resnulli.us>"); 464c7e2b968SJiri Pirko MODULE_DESCRIPTION("vlan manipulation actions"); 465c7e2b968SJiri Pirko MODULE_LICENSE("GPL v2"); 466