xref: /openbmc/linux/net/sched/act_vlan.c (revision 871cf386)
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 
tcf_vlan_act(struct sk_buff * skb,const struct tc_action * a,struct tcf_result * res)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 
tcf_vlan_init(struct net * net,struct nlattr * nla,struct nlattr * est,struct tc_action ** a,struct tcf_proto * tp,u32 flags,struct netlink_ext_ack * extack)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 
tcf_vlan_cleanup(struct tc_action * a)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 
tcf_vlan_dump(struct sk_buff * skb,struct tc_action * a,int bind,int ref)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 
tcf_vlan_stats_update(struct tc_action * a,u64 bytes,u64 packets,u64 drops,u64 lastuse,bool hw)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 
tcf_vlan_get_fill_size(const struct tc_action * act)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 
tcf_vlan_offload_act_setup(struct tc_action * act,void * entry_data,u32 * index_inc,bool bind,struct netlink_ext_ack * extack)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 
vlan_init_net(struct net * net)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 
vlan_exit_net(struct list_head * net_list)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 
vlan_init_module(void)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 
vlan_cleanup_module(void)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