162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
262306a36Sopenharmony_ci/* Copyright (C) 2019 Netronome Systems, Inc. */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/if_arp.h>
562306a36Sopenharmony_ci#include <linux/init.h>
662306a36Sopenharmony_ci#include <linux/kernel.h>
762306a36Sopenharmony_ci#include <linux/module.h>
862306a36Sopenharmony_ci#include <linux/mpls.h>
962306a36Sopenharmony_ci#include <linux/rtnetlink.h>
1062306a36Sopenharmony_ci#include <linux/skbuff.h>
1162306a36Sopenharmony_ci#include <linux/tc_act/tc_mpls.h>
1262306a36Sopenharmony_ci#include <net/mpls.h>
1362306a36Sopenharmony_ci#include <net/netlink.h>
1462306a36Sopenharmony_ci#include <net/pkt_sched.h>
1562306a36Sopenharmony_ci#include <net/pkt_cls.h>
1662306a36Sopenharmony_ci#include <net/tc_act/tc_mpls.h>
1762306a36Sopenharmony_ci#include <net/tc_wrapper.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic struct tc_action_ops act_mpls_ops;
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define ACT_MPLS_TTL_DEFAULT	255
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic __be32 tcf_mpls_get_lse(struct mpls_shim_hdr *lse,
2462306a36Sopenharmony_ci			       struct tcf_mpls_params *p, bool set_bos)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	u32 new_lse = 0;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci	if (lse)
2962306a36Sopenharmony_ci		new_lse = be32_to_cpu(lse->label_stack_entry);
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	if (p->tcfm_label != ACT_MPLS_LABEL_NOT_SET) {
3262306a36Sopenharmony_ci		new_lse &= ~MPLS_LS_LABEL_MASK;
3362306a36Sopenharmony_ci		new_lse |= p->tcfm_label << MPLS_LS_LABEL_SHIFT;
3462306a36Sopenharmony_ci	}
3562306a36Sopenharmony_ci	if (p->tcfm_ttl) {
3662306a36Sopenharmony_ci		new_lse &= ~MPLS_LS_TTL_MASK;
3762306a36Sopenharmony_ci		new_lse |= p->tcfm_ttl << MPLS_LS_TTL_SHIFT;
3862306a36Sopenharmony_ci	}
3962306a36Sopenharmony_ci	if (p->tcfm_tc != ACT_MPLS_TC_NOT_SET) {
4062306a36Sopenharmony_ci		new_lse &= ~MPLS_LS_TC_MASK;
4162306a36Sopenharmony_ci		new_lse |= p->tcfm_tc << MPLS_LS_TC_SHIFT;
4262306a36Sopenharmony_ci	}
4362306a36Sopenharmony_ci	if (p->tcfm_bos != ACT_MPLS_BOS_NOT_SET) {
4462306a36Sopenharmony_ci		new_lse &= ~MPLS_LS_S_MASK;
4562306a36Sopenharmony_ci		new_lse |= p->tcfm_bos << MPLS_LS_S_SHIFT;
4662306a36Sopenharmony_ci	} else if (set_bos) {
4762306a36Sopenharmony_ci		new_lse |= 1 << MPLS_LS_S_SHIFT;
4862306a36Sopenharmony_ci	}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	return cpu_to_be32(new_lse);
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ciTC_INDIRECT_SCOPE int tcf_mpls_act(struct sk_buff *skb,
5462306a36Sopenharmony_ci				   const struct tc_action *a,
5562306a36Sopenharmony_ci				   struct tcf_result *res)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct tcf_mpls *m = to_mpls(a);
5862306a36Sopenharmony_ci	struct tcf_mpls_params *p;
5962306a36Sopenharmony_ci	__be32 new_lse;
6062306a36Sopenharmony_ci	int ret, mac_len;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	tcf_lastuse_update(&m->tcf_tm);
6362306a36Sopenharmony_ci	bstats_update(this_cpu_ptr(m->common.cpu_bstats), skb);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	/* Ensure 'data' points at mac_header prior calling mpls manipulating
6662306a36Sopenharmony_ci	 * functions.
6762306a36Sopenharmony_ci	 */
6862306a36Sopenharmony_ci	if (skb_at_tc_ingress(skb)) {
6962306a36Sopenharmony_ci		skb_push_rcsum(skb, skb->mac_len);
7062306a36Sopenharmony_ci		mac_len = skb->mac_len;
7162306a36Sopenharmony_ci	} else {
7262306a36Sopenharmony_ci		mac_len = skb_network_offset(skb);
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	ret = READ_ONCE(m->tcf_action);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	p = rcu_dereference_bh(m->mpls_p);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	switch (p->tcfm_action) {
8062306a36Sopenharmony_ci	case TCA_MPLS_ACT_POP:
8162306a36Sopenharmony_ci		if (skb_mpls_pop(skb, p->tcfm_proto, mac_len,
8262306a36Sopenharmony_ci				 skb->dev && skb->dev->type == ARPHRD_ETHER))
8362306a36Sopenharmony_ci			goto drop;
8462306a36Sopenharmony_ci		break;
8562306a36Sopenharmony_ci	case TCA_MPLS_ACT_PUSH:
8662306a36Sopenharmony_ci		new_lse = tcf_mpls_get_lse(NULL, p, !eth_p_mpls(skb_protocol(skb, true)));
8762306a36Sopenharmony_ci		if (skb_mpls_push(skb, new_lse, p->tcfm_proto, mac_len,
8862306a36Sopenharmony_ci				  skb->dev && skb->dev->type == ARPHRD_ETHER))
8962306a36Sopenharmony_ci			goto drop;
9062306a36Sopenharmony_ci		break;
9162306a36Sopenharmony_ci	case TCA_MPLS_ACT_MAC_PUSH:
9262306a36Sopenharmony_ci		if (skb_vlan_tag_present(skb)) {
9362306a36Sopenharmony_ci			if (__vlan_insert_inner_tag(skb, skb->vlan_proto,
9462306a36Sopenharmony_ci						    skb_vlan_tag_get(skb),
9562306a36Sopenharmony_ci						    ETH_HLEN) < 0)
9662306a36Sopenharmony_ci				goto drop;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci			skb->protocol = skb->vlan_proto;
9962306a36Sopenharmony_ci			__vlan_hwaccel_clear_tag(skb);
10062306a36Sopenharmony_ci		}
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		new_lse = tcf_mpls_get_lse(NULL, p, mac_len ||
10362306a36Sopenharmony_ci					   !eth_p_mpls(skb->protocol));
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci		if (skb_mpls_push(skb, new_lse, p->tcfm_proto, 0, false))
10662306a36Sopenharmony_ci			goto drop;
10762306a36Sopenharmony_ci		break;
10862306a36Sopenharmony_ci	case TCA_MPLS_ACT_MODIFY:
10962306a36Sopenharmony_ci		if (!pskb_may_pull(skb,
11062306a36Sopenharmony_ci				   skb_network_offset(skb) + MPLS_HLEN))
11162306a36Sopenharmony_ci			goto drop;
11262306a36Sopenharmony_ci		new_lse = tcf_mpls_get_lse(mpls_hdr(skb), p, false);
11362306a36Sopenharmony_ci		if (skb_mpls_update_lse(skb, new_lse))
11462306a36Sopenharmony_ci			goto drop;
11562306a36Sopenharmony_ci		break;
11662306a36Sopenharmony_ci	case TCA_MPLS_ACT_DEC_TTL:
11762306a36Sopenharmony_ci		if (skb_mpls_dec_ttl(skb))
11862306a36Sopenharmony_ci			goto drop;
11962306a36Sopenharmony_ci		break;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	if (skb_at_tc_ingress(skb))
12362306a36Sopenharmony_ci		skb_pull_rcsum(skb, skb->mac_len);
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	return ret;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cidrop:
12862306a36Sopenharmony_ci	qstats_drop_inc(this_cpu_ptr(m->common.cpu_qstats));
12962306a36Sopenharmony_ci	return TC_ACT_SHOT;
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic int valid_label(const struct nlattr *attr,
13362306a36Sopenharmony_ci		       struct netlink_ext_ack *extack)
13462306a36Sopenharmony_ci{
13562306a36Sopenharmony_ci	const u32 *label = nla_data(attr);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	if (nla_len(attr) != sizeof(*label)) {
13862306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Invalid MPLS label length");
13962306a36Sopenharmony_ci		return -EINVAL;
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (*label & ~MPLS_LABEL_MASK || *label == MPLS_LABEL_IMPLNULL) {
14362306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "MPLS label out of range");
14462306a36Sopenharmony_ci		return -EINVAL;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	return 0;
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic const struct nla_policy mpls_policy[TCA_MPLS_MAX + 1] = {
15162306a36Sopenharmony_ci	[TCA_MPLS_PARMS]	= NLA_POLICY_EXACT_LEN(sizeof(struct tc_mpls)),
15262306a36Sopenharmony_ci	[TCA_MPLS_PROTO]	= { .type = NLA_U16 },
15362306a36Sopenharmony_ci	[TCA_MPLS_LABEL]	= NLA_POLICY_VALIDATE_FN(NLA_BINARY,
15462306a36Sopenharmony_ci							 valid_label),
15562306a36Sopenharmony_ci	[TCA_MPLS_TC]		= NLA_POLICY_RANGE(NLA_U8, 0, 7),
15662306a36Sopenharmony_ci	[TCA_MPLS_TTL]		= NLA_POLICY_MIN(NLA_U8, 1),
15762306a36Sopenharmony_ci	[TCA_MPLS_BOS]		= NLA_POLICY_RANGE(NLA_U8, 0, 1),
15862306a36Sopenharmony_ci};
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic int tcf_mpls_init(struct net *net, struct nlattr *nla,
16162306a36Sopenharmony_ci			 struct nlattr *est, struct tc_action **a,
16262306a36Sopenharmony_ci			 struct tcf_proto *tp, u32 flags,
16362306a36Sopenharmony_ci			 struct netlink_ext_ack *extack)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	struct tc_action_net *tn = net_generic(net, act_mpls_ops.net_id);
16662306a36Sopenharmony_ci	bool bind = flags & TCA_ACT_FLAGS_BIND;
16762306a36Sopenharmony_ci	struct nlattr *tb[TCA_MPLS_MAX + 1];
16862306a36Sopenharmony_ci	struct tcf_chain *goto_ch = NULL;
16962306a36Sopenharmony_ci	struct tcf_mpls_params *p;
17062306a36Sopenharmony_ci	struct tc_mpls *parm;
17162306a36Sopenharmony_ci	bool exists = false;
17262306a36Sopenharmony_ci	struct tcf_mpls *m;
17362306a36Sopenharmony_ci	int ret = 0, err;
17462306a36Sopenharmony_ci	u8 mpls_ttl = 0;
17562306a36Sopenharmony_ci	u32 index;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if (!nla) {
17862306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Missing netlink attributes");
17962306a36Sopenharmony_ci		return -EINVAL;
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	err = nla_parse_nested(tb, TCA_MPLS_MAX, nla, mpls_policy, extack);
18362306a36Sopenharmony_ci	if (err < 0)
18462306a36Sopenharmony_ci		return err;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (!tb[TCA_MPLS_PARMS]) {
18762306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "No MPLS params");
18862306a36Sopenharmony_ci		return -EINVAL;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci	parm = nla_data(tb[TCA_MPLS_PARMS]);
19162306a36Sopenharmony_ci	index = parm->index;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	err = tcf_idr_check_alloc(tn, &index, a, bind);
19462306a36Sopenharmony_ci	if (err < 0)
19562306a36Sopenharmony_ci		return err;
19662306a36Sopenharmony_ci	exists = err;
19762306a36Sopenharmony_ci	if (exists && bind)
19862306a36Sopenharmony_ci		return 0;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	if (!exists) {
20162306a36Sopenharmony_ci		ret = tcf_idr_create(tn, index, est, a, &act_mpls_ops, bind,
20262306a36Sopenharmony_ci				     true, flags);
20362306a36Sopenharmony_ci		if (ret) {
20462306a36Sopenharmony_ci			tcf_idr_cleanup(tn, index);
20562306a36Sopenharmony_ci			return ret;
20662306a36Sopenharmony_ci		}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci		ret = ACT_P_CREATED;
20962306a36Sopenharmony_ci	} else if (!(flags & TCA_ACT_FLAGS_REPLACE)) {
21062306a36Sopenharmony_ci		tcf_idr_release(*a, bind);
21162306a36Sopenharmony_ci		return -EEXIST;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	/* Verify parameters against action type. */
21562306a36Sopenharmony_ci	switch (parm->m_action) {
21662306a36Sopenharmony_ci	case TCA_MPLS_ACT_POP:
21762306a36Sopenharmony_ci		if (!tb[TCA_MPLS_PROTO]) {
21862306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Protocol must be set for MPLS pop");
21962306a36Sopenharmony_ci			err = -EINVAL;
22062306a36Sopenharmony_ci			goto release_idr;
22162306a36Sopenharmony_ci		}
22262306a36Sopenharmony_ci		if (!eth_proto_is_802_3(nla_get_be16(tb[TCA_MPLS_PROTO]))) {
22362306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Invalid protocol type for MPLS pop");
22462306a36Sopenharmony_ci			err = -EINVAL;
22562306a36Sopenharmony_ci			goto release_idr;
22662306a36Sopenharmony_ci		}
22762306a36Sopenharmony_ci		if (tb[TCA_MPLS_LABEL] || tb[TCA_MPLS_TTL] || tb[TCA_MPLS_TC] ||
22862306a36Sopenharmony_ci		    tb[TCA_MPLS_BOS]) {
22962306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Label, TTL, TC or BOS cannot be used with MPLS pop");
23062306a36Sopenharmony_ci			err = -EINVAL;
23162306a36Sopenharmony_ci			goto release_idr;
23262306a36Sopenharmony_ci		}
23362306a36Sopenharmony_ci		break;
23462306a36Sopenharmony_ci	case TCA_MPLS_ACT_DEC_TTL:
23562306a36Sopenharmony_ci		if (tb[TCA_MPLS_PROTO] || tb[TCA_MPLS_LABEL] ||
23662306a36Sopenharmony_ci		    tb[TCA_MPLS_TTL] || tb[TCA_MPLS_TC] || tb[TCA_MPLS_BOS]) {
23762306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Label, TTL, TC, BOS or protocol cannot be used with MPLS dec_ttl");
23862306a36Sopenharmony_ci			err = -EINVAL;
23962306a36Sopenharmony_ci			goto release_idr;
24062306a36Sopenharmony_ci		}
24162306a36Sopenharmony_ci		break;
24262306a36Sopenharmony_ci	case TCA_MPLS_ACT_PUSH:
24362306a36Sopenharmony_ci	case TCA_MPLS_ACT_MAC_PUSH:
24462306a36Sopenharmony_ci		if (!tb[TCA_MPLS_LABEL]) {
24562306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Label is required for MPLS push");
24662306a36Sopenharmony_ci			err = -EINVAL;
24762306a36Sopenharmony_ci			goto release_idr;
24862306a36Sopenharmony_ci		}
24962306a36Sopenharmony_ci		if (tb[TCA_MPLS_PROTO] &&
25062306a36Sopenharmony_ci		    !eth_p_mpls(nla_get_be16(tb[TCA_MPLS_PROTO]))) {
25162306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Protocol must be an MPLS type for MPLS push");
25262306a36Sopenharmony_ci			err = -EPROTONOSUPPORT;
25362306a36Sopenharmony_ci			goto release_idr;
25462306a36Sopenharmony_ci		}
25562306a36Sopenharmony_ci		/* Push needs a TTL - if not specified, set a default value. */
25662306a36Sopenharmony_ci		if (!tb[TCA_MPLS_TTL]) {
25762306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_MPLS)
25862306a36Sopenharmony_ci			mpls_ttl = net->mpls.default_ttl ?
25962306a36Sopenharmony_ci				   net->mpls.default_ttl : ACT_MPLS_TTL_DEFAULT;
26062306a36Sopenharmony_ci#else
26162306a36Sopenharmony_ci			mpls_ttl = ACT_MPLS_TTL_DEFAULT;
26262306a36Sopenharmony_ci#endif
26362306a36Sopenharmony_ci		}
26462306a36Sopenharmony_ci		break;
26562306a36Sopenharmony_ci	case TCA_MPLS_ACT_MODIFY:
26662306a36Sopenharmony_ci		if (tb[TCA_MPLS_PROTO]) {
26762306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Protocol cannot be used with MPLS modify");
26862306a36Sopenharmony_ci			err = -EINVAL;
26962306a36Sopenharmony_ci			goto release_idr;
27062306a36Sopenharmony_ci		}
27162306a36Sopenharmony_ci		break;
27262306a36Sopenharmony_ci	default:
27362306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Unknown MPLS action");
27462306a36Sopenharmony_ci		err = -EINVAL;
27562306a36Sopenharmony_ci		goto release_idr;
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
27962306a36Sopenharmony_ci	if (err < 0)
28062306a36Sopenharmony_ci		goto release_idr;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	m = to_mpls(*a);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	p = kzalloc(sizeof(*p), GFP_KERNEL);
28562306a36Sopenharmony_ci	if (!p) {
28662306a36Sopenharmony_ci		err = -ENOMEM;
28762306a36Sopenharmony_ci		goto put_chain;
28862306a36Sopenharmony_ci	}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	p->tcfm_action = parm->m_action;
29162306a36Sopenharmony_ci	p->tcfm_label = tb[TCA_MPLS_LABEL] ? nla_get_u32(tb[TCA_MPLS_LABEL]) :
29262306a36Sopenharmony_ci					     ACT_MPLS_LABEL_NOT_SET;
29362306a36Sopenharmony_ci	p->tcfm_tc = tb[TCA_MPLS_TC] ? nla_get_u8(tb[TCA_MPLS_TC]) :
29462306a36Sopenharmony_ci				       ACT_MPLS_TC_NOT_SET;
29562306a36Sopenharmony_ci	p->tcfm_ttl = tb[TCA_MPLS_TTL] ? nla_get_u8(tb[TCA_MPLS_TTL]) :
29662306a36Sopenharmony_ci					 mpls_ttl;
29762306a36Sopenharmony_ci	p->tcfm_bos = tb[TCA_MPLS_BOS] ? nla_get_u8(tb[TCA_MPLS_BOS]) :
29862306a36Sopenharmony_ci					 ACT_MPLS_BOS_NOT_SET;
29962306a36Sopenharmony_ci	p->tcfm_proto = tb[TCA_MPLS_PROTO] ? nla_get_be16(tb[TCA_MPLS_PROTO]) :
30062306a36Sopenharmony_ci					     htons(ETH_P_MPLS_UC);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	spin_lock_bh(&m->tcf_lock);
30362306a36Sopenharmony_ci	goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
30462306a36Sopenharmony_ci	p = rcu_replace_pointer(m->mpls_p, p, lockdep_is_held(&m->tcf_lock));
30562306a36Sopenharmony_ci	spin_unlock_bh(&m->tcf_lock);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	if (goto_ch)
30862306a36Sopenharmony_ci		tcf_chain_put_by_act(goto_ch);
30962306a36Sopenharmony_ci	if (p)
31062306a36Sopenharmony_ci		kfree_rcu(p, rcu);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	return ret;
31362306a36Sopenharmony_ciput_chain:
31462306a36Sopenharmony_ci	if (goto_ch)
31562306a36Sopenharmony_ci		tcf_chain_put_by_act(goto_ch);
31662306a36Sopenharmony_cirelease_idr:
31762306a36Sopenharmony_ci	tcf_idr_release(*a, bind);
31862306a36Sopenharmony_ci	return err;
31962306a36Sopenharmony_ci}
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_cistatic void tcf_mpls_cleanup(struct tc_action *a)
32262306a36Sopenharmony_ci{
32362306a36Sopenharmony_ci	struct tcf_mpls *m = to_mpls(a);
32462306a36Sopenharmony_ci	struct tcf_mpls_params *p;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	p = rcu_dereference_protected(m->mpls_p, 1);
32762306a36Sopenharmony_ci	if (p)
32862306a36Sopenharmony_ci		kfree_rcu(p, rcu);
32962306a36Sopenharmony_ci}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_cistatic int tcf_mpls_dump(struct sk_buff *skb, struct tc_action *a,
33262306a36Sopenharmony_ci			 int bind, int ref)
33362306a36Sopenharmony_ci{
33462306a36Sopenharmony_ci	unsigned char *b = skb_tail_pointer(skb);
33562306a36Sopenharmony_ci	struct tcf_mpls *m = to_mpls(a);
33662306a36Sopenharmony_ci	struct tcf_mpls_params *p;
33762306a36Sopenharmony_ci	struct tc_mpls opt = {
33862306a36Sopenharmony_ci		.index    = m->tcf_index,
33962306a36Sopenharmony_ci		.refcnt   = refcount_read(&m->tcf_refcnt) - ref,
34062306a36Sopenharmony_ci		.bindcnt  = atomic_read(&m->tcf_bindcnt) - bind,
34162306a36Sopenharmony_ci	};
34262306a36Sopenharmony_ci	struct tcf_t t;
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	spin_lock_bh(&m->tcf_lock);
34562306a36Sopenharmony_ci	opt.action = m->tcf_action;
34662306a36Sopenharmony_ci	p = rcu_dereference_protected(m->mpls_p, lockdep_is_held(&m->tcf_lock));
34762306a36Sopenharmony_ci	opt.m_action = p->tcfm_action;
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	if (nla_put(skb, TCA_MPLS_PARMS, sizeof(opt), &opt))
35062306a36Sopenharmony_ci		goto nla_put_failure;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	if (p->tcfm_label != ACT_MPLS_LABEL_NOT_SET &&
35362306a36Sopenharmony_ci	    nla_put_u32(skb, TCA_MPLS_LABEL, p->tcfm_label))
35462306a36Sopenharmony_ci		goto nla_put_failure;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	if (p->tcfm_tc != ACT_MPLS_TC_NOT_SET &&
35762306a36Sopenharmony_ci	    nla_put_u8(skb, TCA_MPLS_TC, p->tcfm_tc))
35862306a36Sopenharmony_ci		goto nla_put_failure;
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	if (p->tcfm_ttl && nla_put_u8(skb, TCA_MPLS_TTL, p->tcfm_ttl))
36162306a36Sopenharmony_ci		goto nla_put_failure;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	if (p->tcfm_bos != ACT_MPLS_BOS_NOT_SET &&
36462306a36Sopenharmony_ci	    nla_put_u8(skb, TCA_MPLS_BOS, p->tcfm_bos))
36562306a36Sopenharmony_ci		goto nla_put_failure;
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	if (nla_put_be16(skb, TCA_MPLS_PROTO, p->tcfm_proto))
36862306a36Sopenharmony_ci		goto nla_put_failure;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	tcf_tm_dump(&t, &m->tcf_tm);
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	if (nla_put_64bit(skb, TCA_MPLS_TM, sizeof(t), &t, TCA_MPLS_PAD))
37362306a36Sopenharmony_ci		goto nla_put_failure;
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	spin_unlock_bh(&m->tcf_lock);
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci	return skb->len;
37862306a36Sopenharmony_ci
37962306a36Sopenharmony_cinla_put_failure:
38062306a36Sopenharmony_ci	spin_unlock_bh(&m->tcf_lock);
38162306a36Sopenharmony_ci	nlmsg_trim(skb, b);
38262306a36Sopenharmony_ci	return -EMSGSIZE;
38362306a36Sopenharmony_ci}
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_cistatic int tcf_mpls_offload_act_setup(struct tc_action *act, void *entry_data,
38662306a36Sopenharmony_ci				      u32 *index_inc, bool bind,
38762306a36Sopenharmony_ci				      struct netlink_ext_ack *extack)
38862306a36Sopenharmony_ci{
38962306a36Sopenharmony_ci	if (bind) {
39062306a36Sopenharmony_ci		struct flow_action_entry *entry = entry_data;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci		switch (tcf_mpls_action(act)) {
39362306a36Sopenharmony_ci		case TCA_MPLS_ACT_PUSH:
39462306a36Sopenharmony_ci			entry->id = FLOW_ACTION_MPLS_PUSH;
39562306a36Sopenharmony_ci			entry->mpls_push.proto = tcf_mpls_proto(act);
39662306a36Sopenharmony_ci			entry->mpls_push.label = tcf_mpls_label(act);
39762306a36Sopenharmony_ci			entry->mpls_push.tc = tcf_mpls_tc(act);
39862306a36Sopenharmony_ci			entry->mpls_push.bos = tcf_mpls_bos(act);
39962306a36Sopenharmony_ci			entry->mpls_push.ttl = tcf_mpls_ttl(act);
40062306a36Sopenharmony_ci			break;
40162306a36Sopenharmony_ci		case TCA_MPLS_ACT_POP:
40262306a36Sopenharmony_ci			entry->id = FLOW_ACTION_MPLS_POP;
40362306a36Sopenharmony_ci			entry->mpls_pop.proto = tcf_mpls_proto(act);
40462306a36Sopenharmony_ci			break;
40562306a36Sopenharmony_ci		case TCA_MPLS_ACT_MODIFY:
40662306a36Sopenharmony_ci			entry->id = FLOW_ACTION_MPLS_MANGLE;
40762306a36Sopenharmony_ci			entry->mpls_mangle.label = tcf_mpls_label(act);
40862306a36Sopenharmony_ci			entry->mpls_mangle.tc = tcf_mpls_tc(act);
40962306a36Sopenharmony_ci			entry->mpls_mangle.bos = tcf_mpls_bos(act);
41062306a36Sopenharmony_ci			entry->mpls_mangle.ttl = tcf_mpls_ttl(act);
41162306a36Sopenharmony_ci			break;
41262306a36Sopenharmony_ci		case TCA_MPLS_ACT_DEC_TTL:
41362306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Offload not supported when \"dec_ttl\" option is used");
41462306a36Sopenharmony_ci			return -EOPNOTSUPP;
41562306a36Sopenharmony_ci		case TCA_MPLS_ACT_MAC_PUSH:
41662306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Offload not supported when \"mac_push\" option is used");
41762306a36Sopenharmony_ci			return -EOPNOTSUPP;
41862306a36Sopenharmony_ci		default:
41962306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Unsupported MPLS mode offload");
42062306a36Sopenharmony_ci			return -EOPNOTSUPP;
42162306a36Sopenharmony_ci		}
42262306a36Sopenharmony_ci		*index_inc = 1;
42362306a36Sopenharmony_ci	} else {
42462306a36Sopenharmony_ci		struct flow_offload_action *fl_action = entry_data;
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci		switch (tcf_mpls_action(act)) {
42762306a36Sopenharmony_ci		case TCA_MPLS_ACT_PUSH:
42862306a36Sopenharmony_ci			fl_action->id = FLOW_ACTION_MPLS_PUSH;
42962306a36Sopenharmony_ci			break;
43062306a36Sopenharmony_ci		case TCA_MPLS_ACT_POP:
43162306a36Sopenharmony_ci			fl_action->id = FLOW_ACTION_MPLS_POP;
43262306a36Sopenharmony_ci			break;
43362306a36Sopenharmony_ci		case TCA_MPLS_ACT_MODIFY:
43462306a36Sopenharmony_ci			fl_action->id = FLOW_ACTION_MPLS_MANGLE;
43562306a36Sopenharmony_ci			break;
43662306a36Sopenharmony_ci		default:
43762306a36Sopenharmony_ci			return -EOPNOTSUPP;
43862306a36Sopenharmony_ci		}
43962306a36Sopenharmony_ci	}
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	return 0;
44262306a36Sopenharmony_ci}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_cistatic struct tc_action_ops act_mpls_ops = {
44562306a36Sopenharmony_ci	.kind		=	"mpls",
44662306a36Sopenharmony_ci	.id		=	TCA_ID_MPLS,
44762306a36Sopenharmony_ci	.owner		=	THIS_MODULE,
44862306a36Sopenharmony_ci	.act		=	tcf_mpls_act,
44962306a36Sopenharmony_ci	.dump		=	tcf_mpls_dump,
45062306a36Sopenharmony_ci	.init		=	tcf_mpls_init,
45162306a36Sopenharmony_ci	.cleanup	=	tcf_mpls_cleanup,
45262306a36Sopenharmony_ci	.offload_act_setup =	tcf_mpls_offload_act_setup,
45362306a36Sopenharmony_ci	.size		=	sizeof(struct tcf_mpls),
45462306a36Sopenharmony_ci};
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_cistatic __net_init int mpls_init_net(struct net *net)
45762306a36Sopenharmony_ci{
45862306a36Sopenharmony_ci	struct tc_action_net *tn = net_generic(net, act_mpls_ops.net_id);
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	return tc_action_net_init(net, tn, &act_mpls_ops);
46162306a36Sopenharmony_ci}
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_cistatic void __net_exit mpls_exit_net(struct list_head *net_list)
46462306a36Sopenharmony_ci{
46562306a36Sopenharmony_ci	tc_action_net_exit(net_list, act_mpls_ops.net_id);
46662306a36Sopenharmony_ci}
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_cistatic struct pernet_operations mpls_net_ops = {
46962306a36Sopenharmony_ci	.init = mpls_init_net,
47062306a36Sopenharmony_ci	.exit_batch = mpls_exit_net,
47162306a36Sopenharmony_ci	.id   = &act_mpls_ops.net_id,
47262306a36Sopenharmony_ci	.size = sizeof(struct tc_action_net),
47362306a36Sopenharmony_ci};
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_cistatic int __init mpls_init_module(void)
47662306a36Sopenharmony_ci{
47762306a36Sopenharmony_ci	return tcf_register_action(&act_mpls_ops, &mpls_net_ops);
47862306a36Sopenharmony_ci}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_cistatic void __exit mpls_cleanup_module(void)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	tcf_unregister_action(&act_mpls_ops, &mpls_net_ops);
48362306a36Sopenharmony_ci}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_cimodule_init(mpls_init_module);
48662306a36Sopenharmony_cimodule_exit(mpls_cleanup_module);
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ciMODULE_SOFTDEP("post: mpls_gso");
48962306a36Sopenharmony_ciMODULE_AUTHOR("Netronome Systems <oss-drivers@netronome.com>");
49062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
49162306a36Sopenharmony_ciMODULE_DESCRIPTION("MPLS manipulation actions");
492