162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * net/sched/act_pedit.c Generic packet editor 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Authors: Jamal Hadi Salim (2002-4) 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/types.h> 962306a36Sopenharmony_ci#include <linux/kernel.h> 1062306a36Sopenharmony_ci#include <linux/string.h> 1162306a36Sopenharmony_ci#include <linux/errno.h> 1262306a36Sopenharmony_ci#include <linux/skbuff.h> 1362306a36Sopenharmony_ci#include <linux/rtnetlink.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/init.h> 1662306a36Sopenharmony_ci#include <linux/ip.h> 1762306a36Sopenharmony_ci#include <linux/ipv6.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <net/ipv6.h> 2062306a36Sopenharmony_ci#include <net/netlink.h> 2162306a36Sopenharmony_ci#include <net/pkt_sched.h> 2262306a36Sopenharmony_ci#include <linux/tc_act/tc_pedit.h> 2362306a36Sopenharmony_ci#include <net/tc_act/tc_pedit.h> 2462306a36Sopenharmony_ci#include <uapi/linux/tc_act/tc_pedit.h> 2562306a36Sopenharmony_ci#include <net/pkt_cls.h> 2662306a36Sopenharmony_ci#include <net/tc_wrapper.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistatic struct tc_action_ops act_pedit_ops; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic const struct nla_policy pedit_policy[TCA_PEDIT_MAX + 1] = { 3162306a36Sopenharmony_ci [TCA_PEDIT_PARMS] = { .len = sizeof(struct tc_pedit) }, 3262306a36Sopenharmony_ci [TCA_PEDIT_PARMS_EX] = { .len = sizeof(struct tc_pedit) }, 3362306a36Sopenharmony_ci [TCA_PEDIT_KEYS_EX] = { .type = NLA_NESTED }, 3462306a36Sopenharmony_ci}; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic const struct nla_policy pedit_key_ex_policy[TCA_PEDIT_KEY_EX_MAX + 1] = { 3762306a36Sopenharmony_ci [TCA_PEDIT_KEY_EX_HTYPE] = 3862306a36Sopenharmony_ci NLA_POLICY_MAX(NLA_U16, TCA_PEDIT_HDR_TYPE_MAX), 3962306a36Sopenharmony_ci [TCA_PEDIT_KEY_EX_CMD] = NLA_POLICY_MAX(NLA_U16, TCA_PEDIT_CMD_MAX), 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic struct tcf_pedit_key_ex *tcf_pedit_keys_ex_parse(struct nlattr *nla, 4362306a36Sopenharmony_ci u8 n, struct netlink_ext_ack *extack) 4462306a36Sopenharmony_ci{ 4562306a36Sopenharmony_ci struct tcf_pedit_key_ex *keys_ex; 4662306a36Sopenharmony_ci struct tcf_pedit_key_ex *k; 4762306a36Sopenharmony_ci const struct nlattr *ka; 4862306a36Sopenharmony_ci int err = -EINVAL; 4962306a36Sopenharmony_ci int rem; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci if (!nla) 5262306a36Sopenharmony_ci return NULL; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci keys_ex = kcalloc(n, sizeof(*k), GFP_KERNEL); 5562306a36Sopenharmony_ci if (!keys_ex) 5662306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci k = keys_ex; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci nla_for_each_nested(ka, nla, rem) { 6162306a36Sopenharmony_ci struct nlattr *tb[TCA_PEDIT_KEY_EX_MAX + 1]; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci if (!n) { 6462306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Can't parse more extended keys than requested"); 6562306a36Sopenharmony_ci err = -EINVAL; 6662306a36Sopenharmony_ci goto err_out; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci n--; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (nla_type(ka) != TCA_PEDIT_KEY_EX) { 7162306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, ka, "Unknown attribute, expected extended key"); 7262306a36Sopenharmony_ci err = -EINVAL; 7362306a36Sopenharmony_ci goto err_out; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_PEDIT_KEY_EX_MAX, 7762306a36Sopenharmony_ci ka, pedit_key_ex_policy, 7862306a36Sopenharmony_ci NULL); 7962306a36Sopenharmony_ci if (err) 8062306a36Sopenharmony_ci goto err_out; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (NL_REQ_ATTR_CHECK(extack, nla, tb, TCA_PEDIT_KEY_EX_HTYPE)) { 8362306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Missing required attribute"); 8462306a36Sopenharmony_ci err = -EINVAL; 8562306a36Sopenharmony_ci goto err_out; 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci if (NL_REQ_ATTR_CHECK(extack, nla, tb, TCA_PEDIT_KEY_EX_CMD)) { 8962306a36Sopenharmony_ci NL_SET_ERR_MSG(extack, "Missing required attribute"); 9062306a36Sopenharmony_ci err = -EINVAL; 9162306a36Sopenharmony_ci goto err_out; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci k->htype = nla_get_u16(tb[TCA_PEDIT_KEY_EX_HTYPE]); 9562306a36Sopenharmony_ci k->cmd = nla_get_u16(tb[TCA_PEDIT_KEY_EX_CMD]); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci k++; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci if (n) { 10162306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Not enough extended keys to parse"); 10262306a36Sopenharmony_ci err = -EINVAL; 10362306a36Sopenharmony_ci goto err_out; 10462306a36Sopenharmony_ci } 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci return keys_ex; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cierr_out: 10962306a36Sopenharmony_ci kfree(keys_ex); 11062306a36Sopenharmony_ci return ERR_PTR(err); 11162306a36Sopenharmony_ci} 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic int tcf_pedit_key_ex_dump(struct sk_buff *skb, 11462306a36Sopenharmony_ci struct tcf_pedit_key_ex *keys_ex, int n) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci struct nlattr *keys_start = nla_nest_start_noflag(skb, 11762306a36Sopenharmony_ci TCA_PEDIT_KEYS_EX); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (!keys_start) 12062306a36Sopenharmony_ci goto nla_failure; 12162306a36Sopenharmony_ci for (; n > 0; n--) { 12262306a36Sopenharmony_ci struct nlattr *key_start; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci key_start = nla_nest_start_noflag(skb, TCA_PEDIT_KEY_EX); 12562306a36Sopenharmony_ci if (!key_start) 12662306a36Sopenharmony_ci goto nla_failure; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (nla_put_u16(skb, TCA_PEDIT_KEY_EX_HTYPE, keys_ex->htype) || 12962306a36Sopenharmony_ci nla_put_u16(skb, TCA_PEDIT_KEY_EX_CMD, keys_ex->cmd)) 13062306a36Sopenharmony_ci goto nla_failure; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci nla_nest_end(skb, key_start); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci keys_ex++; 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci nla_nest_end(skb, keys_start); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci return 0; 14062306a36Sopenharmony_cinla_failure: 14162306a36Sopenharmony_ci nla_nest_cancel(skb, keys_start); 14262306a36Sopenharmony_ci return -EINVAL; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic void tcf_pedit_cleanup_rcu(struct rcu_head *head) 14662306a36Sopenharmony_ci{ 14762306a36Sopenharmony_ci struct tcf_pedit_parms *parms = 14862306a36Sopenharmony_ci container_of(head, struct tcf_pedit_parms, rcu); 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci kfree(parms->tcfp_keys_ex); 15162306a36Sopenharmony_ci kfree(parms->tcfp_keys); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci kfree(parms); 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic int tcf_pedit_init(struct net *net, struct nlattr *nla, 15762306a36Sopenharmony_ci struct nlattr *est, struct tc_action **a, 15862306a36Sopenharmony_ci struct tcf_proto *tp, u32 flags, 15962306a36Sopenharmony_ci struct netlink_ext_ack *extack) 16062306a36Sopenharmony_ci{ 16162306a36Sopenharmony_ci struct tc_action_net *tn = net_generic(net, act_pedit_ops.net_id); 16262306a36Sopenharmony_ci bool bind = flags & TCA_ACT_FLAGS_BIND; 16362306a36Sopenharmony_ci struct tcf_chain *goto_ch = NULL; 16462306a36Sopenharmony_ci struct tcf_pedit_parms *oparms, *nparms; 16562306a36Sopenharmony_ci struct nlattr *tb[TCA_PEDIT_MAX + 1]; 16662306a36Sopenharmony_ci struct tc_pedit *parm; 16762306a36Sopenharmony_ci struct nlattr *pattr; 16862306a36Sopenharmony_ci struct tcf_pedit *p; 16962306a36Sopenharmony_ci int ret = 0, err; 17062306a36Sopenharmony_ci int i, ksize; 17162306a36Sopenharmony_ci u32 index; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (!nla) { 17462306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Pedit requires attributes to be passed"); 17562306a36Sopenharmony_ci return -EINVAL; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci err = nla_parse_nested_deprecated(tb, TCA_PEDIT_MAX, nla, 17962306a36Sopenharmony_ci pedit_policy, NULL); 18062306a36Sopenharmony_ci if (err < 0) 18162306a36Sopenharmony_ci return err; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci pattr = tb[TCA_PEDIT_PARMS]; 18462306a36Sopenharmony_ci if (!pattr) 18562306a36Sopenharmony_ci pattr = tb[TCA_PEDIT_PARMS_EX]; 18662306a36Sopenharmony_ci if (!pattr) { 18762306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Missing required TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute"); 18862306a36Sopenharmony_ci return -EINVAL; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci parm = nla_data(pattr); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci index = parm->index; 19462306a36Sopenharmony_ci err = tcf_idr_check_alloc(tn, &index, a, bind); 19562306a36Sopenharmony_ci if (!err) { 19662306a36Sopenharmony_ci ret = tcf_idr_create_from_flags(tn, index, est, a, 19762306a36Sopenharmony_ci &act_pedit_ops, bind, flags); 19862306a36Sopenharmony_ci if (ret) { 19962306a36Sopenharmony_ci tcf_idr_cleanup(tn, index); 20062306a36Sopenharmony_ci return ret; 20162306a36Sopenharmony_ci } 20262306a36Sopenharmony_ci ret = ACT_P_CREATED; 20362306a36Sopenharmony_ci } else if (err > 0) { 20462306a36Sopenharmony_ci if (bind) 20562306a36Sopenharmony_ci return 0; 20662306a36Sopenharmony_ci if (!(flags & TCA_ACT_FLAGS_REPLACE)) { 20762306a36Sopenharmony_ci ret = -EEXIST; 20862306a36Sopenharmony_ci goto out_release; 20962306a36Sopenharmony_ci } 21062306a36Sopenharmony_ci } else { 21162306a36Sopenharmony_ci return err; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci if (!parm->nkeys) { 21562306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Pedit requires keys to be passed"); 21662306a36Sopenharmony_ci ret = -EINVAL; 21762306a36Sopenharmony_ci goto out_release; 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci ksize = parm->nkeys * sizeof(struct tc_pedit_key); 22062306a36Sopenharmony_ci if (nla_len(pattr) < sizeof(*parm) + ksize) { 22162306a36Sopenharmony_ci NL_SET_ERR_MSG_ATTR(extack, pattr, "Length of TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute is invalid"); 22262306a36Sopenharmony_ci ret = -EINVAL; 22362306a36Sopenharmony_ci goto out_release; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci nparms = kzalloc(sizeof(*nparms), GFP_KERNEL); 22762306a36Sopenharmony_ci if (!nparms) { 22862306a36Sopenharmony_ci ret = -ENOMEM; 22962306a36Sopenharmony_ci goto out_release; 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci nparms->tcfp_keys_ex = 23362306a36Sopenharmony_ci tcf_pedit_keys_ex_parse(tb[TCA_PEDIT_KEYS_EX], parm->nkeys, extack); 23462306a36Sopenharmony_ci if (IS_ERR(nparms->tcfp_keys_ex)) { 23562306a36Sopenharmony_ci ret = PTR_ERR(nparms->tcfp_keys_ex); 23662306a36Sopenharmony_ci goto out_free; 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); 24062306a36Sopenharmony_ci if (err < 0) { 24162306a36Sopenharmony_ci ret = err; 24262306a36Sopenharmony_ci goto out_free_ex; 24362306a36Sopenharmony_ci } 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci nparms->tcfp_off_max_hint = 0; 24662306a36Sopenharmony_ci nparms->tcfp_flags = parm->flags; 24762306a36Sopenharmony_ci nparms->tcfp_nkeys = parm->nkeys; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci nparms->tcfp_keys = kmemdup(parm->keys, ksize, GFP_KERNEL); 25062306a36Sopenharmony_ci if (!nparms->tcfp_keys) { 25162306a36Sopenharmony_ci ret = -ENOMEM; 25262306a36Sopenharmony_ci goto put_chain; 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci for (i = 0; i < nparms->tcfp_nkeys; ++i) { 25662306a36Sopenharmony_ci u32 offmask = nparms->tcfp_keys[i].offmask; 25762306a36Sopenharmony_ci u32 cur = nparms->tcfp_keys[i].off; 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci /* The AT option can be added to static offsets in the datapath */ 26062306a36Sopenharmony_ci if (!offmask && cur % 4) { 26162306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Offsets must be on 32bit boundaries"); 26262306a36Sopenharmony_ci ret = -EINVAL; 26362306a36Sopenharmony_ci goto out_free_keys; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci /* sanitize the shift value for any later use */ 26762306a36Sopenharmony_ci nparms->tcfp_keys[i].shift = min_t(size_t, 26862306a36Sopenharmony_ci BITS_PER_TYPE(int) - 1, 26962306a36Sopenharmony_ci nparms->tcfp_keys[i].shift); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci /* The AT option can read a single byte, we can bound the actual 27262306a36Sopenharmony_ci * value with uchar max. 27362306a36Sopenharmony_ci */ 27462306a36Sopenharmony_ci cur += (0xff & offmask) >> nparms->tcfp_keys[i].shift; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci /* Each key touches 4 bytes starting from the computed offset */ 27762306a36Sopenharmony_ci nparms->tcfp_off_max_hint = 27862306a36Sopenharmony_ci max(nparms->tcfp_off_max_hint, cur + 4); 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci p = to_pedit(*a); 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci spin_lock_bh(&p->tcf_lock); 28462306a36Sopenharmony_ci goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch); 28562306a36Sopenharmony_ci oparms = rcu_replace_pointer(p->parms, nparms, 1); 28662306a36Sopenharmony_ci spin_unlock_bh(&p->tcf_lock); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci if (oparms) 28962306a36Sopenharmony_ci call_rcu(&oparms->rcu, tcf_pedit_cleanup_rcu); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci if (goto_ch) 29262306a36Sopenharmony_ci tcf_chain_put_by_act(goto_ch); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci return ret; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ciout_free_keys: 29762306a36Sopenharmony_ci kfree(nparms->tcfp_keys); 29862306a36Sopenharmony_ciput_chain: 29962306a36Sopenharmony_ci if (goto_ch) 30062306a36Sopenharmony_ci tcf_chain_put_by_act(goto_ch); 30162306a36Sopenharmony_ciout_free_ex: 30262306a36Sopenharmony_ci kfree(nparms->tcfp_keys_ex); 30362306a36Sopenharmony_ciout_free: 30462306a36Sopenharmony_ci kfree(nparms); 30562306a36Sopenharmony_ciout_release: 30662306a36Sopenharmony_ci tcf_idr_release(*a, bind); 30762306a36Sopenharmony_ci return ret; 30862306a36Sopenharmony_ci} 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_cistatic void tcf_pedit_cleanup(struct tc_action *a) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci struct tcf_pedit *p = to_pedit(a); 31362306a36Sopenharmony_ci struct tcf_pedit_parms *parms; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci parms = rcu_dereference_protected(p->parms, 1); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci if (parms) 31862306a36Sopenharmony_ci call_rcu(&parms->rcu, tcf_pedit_cleanup_rcu); 31962306a36Sopenharmony_ci} 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cistatic bool offset_valid(struct sk_buff *skb, int offset) 32262306a36Sopenharmony_ci{ 32362306a36Sopenharmony_ci if (offset > 0 && offset > skb->len) 32462306a36Sopenharmony_ci return false; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci if (offset < 0 && -offset > skb_headroom(skb)) 32762306a36Sopenharmony_ci return false; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci return true; 33062306a36Sopenharmony_ci} 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_cistatic int pedit_l4_skb_offset(struct sk_buff *skb, int *hoffset, const int header_type) 33362306a36Sopenharmony_ci{ 33462306a36Sopenharmony_ci const int noff = skb_network_offset(skb); 33562306a36Sopenharmony_ci int ret = -EINVAL; 33662306a36Sopenharmony_ci struct iphdr _iph; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci switch (skb->protocol) { 33962306a36Sopenharmony_ci case htons(ETH_P_IP): { 34062306a36Sopenharmony_ci const struct iphdr *iph = skb_header_pointer(skb, noff, sizeof(_iph), &_iph); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci if (!iph) 34362306a36Sopenharmony_ci goto out; 34462306a36Sopenharmony_ci *hoffset = noff + iph->ihl * 4; 34562306a36Sopenharmony_ci ret = 0; 34662306a36Sopenharmony_ci break; 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci case htons(ETH_P_IPV6): 34962306a36Sopenharmony_ci ret = ipv6_find_hdr(skb, hoffset, header_type, NULL, NULL) == header_type ? 0 : -EINVAL; 35062306a36Sopenharmony_ci break; 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ciout: 35362306a36Sopenharmony_ci return ret; 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_cistatic int pedit_skb_hdr_offset(struct sk_buff *skb, 35762306a36Sopenharmony_ci enum pedit_header_type htype, int *hoffset) 35862306a36Sopenharmony_ci{ 35962306a36Sopenharmony_ci int ret = -EINVAL; 36062306a36Sopenharmony_ci /* 'htype' is validated in the netlink parsing */ 36162306a36Sopenharmony_ci switch (htype) { 36262306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_HDR_TYPE_ETH: 36362306a36Sopenharmony_ci if (skb_mac_header_was_set(skb)) { 36462306a36Sopenharmony_ci *hoffset = skb_mac_offset(skb); 36562306a36Sopenharmony_ci ret = 0; 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci break; 36862306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK: 36962306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_HDR_TYPE_IP4: 37062306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_HDR_TYPE_IP6: 37162306a36Sopenharmony_ci *hoffset = skb_network_offset(skb); 37262306a36Sopenharmony_ci ret = 0; 37362306a36Sopenharmony_ci break; 37462306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_HDR_TYPE_TCP: 37562306a36Sopenharmony_ci ret = pedit_l4_skb_offset(skb, hoffset, IPPROTO_TCP); 37662306a36Sopenharmony_ci break; 37762306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_HDR_TYPE_UDP: 37862306a36Sopenharmony_ci ret = pedit_l4_skb_offset(skb, hoffset, IPPROTO_UDP); 37962306a36Sopenharmony_ci break; 38062306a36Sopenharmony_ci default: 38162306a36Sopenharmony_ci break; 38262306a36Sopenharmony_ci } 38362306a36Sopenharmony_ci return ret; 38462306a36Sopenharmony_ci} 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ciTC_INDIRECT_SCOPE int tcf_pedit_act(struct sk_buff *skb, 38762306a36Sopenharmony_ci const struct tc_action *a, 38862306a36Sopenharmony_ci struct tcf_result *res) 38962306a36Sopenharmony_ci{ 39062306a36Sopenharmony_ci enum pedit_header_type htype = TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK; 39162306a36Sopenharmony_ci enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET; 39262306a36Sopenharmony_ci struct tcf_pedit *p = to_pedit(a); 39362306a36Sopenharmony_ci struct tcf_pedit_key_ex *tkey_ex; 39462306a36Sopenharmony_ci struct tcf_pedit_parms *parms; 39562306a36Sopenharmony_ci struct tc_pedit_key *tkey; 39662306a36Sopenharmony_ci u32 max_offset; 39762306a36Sopenharmony_ci int i; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci parms = rcu_dereference_bh(p->parms); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci max_offset = (skb_transport_header_was_set(skb) ? 40262306a36Sopenharmony_ci skb_transport_offset(skb) : 40362306a36Sopenharmony_ci skb_network_offset(skb)) + 40462306a36Sopenharmony_ci parms->tcfp_off_max_hint; 40562306a36Sopenharmony_ci if (skb_ensure_writable(skb, min(skb->len, max_offset))) 40662306a36Sopenharmony_ci goto done; 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci tcf_lastuse_update(&p->tcf_tm); 40962306a36Sopenharmony_ci tcf_action_update_bstats(&p->common, skb); 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci tkey = parms->tcfp_keys; 41262306a36Sopenharmony_ci tkey_ex = parms->tcfp_keys_ex; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci for (i = parms->tcfp_nkeys; i > 0; i--, tkey++) { 41562306a36Sopenharmony_ci int offset = tkey->off; 41662306a36Sopenharmony_ci int hoffset = 0; 41762306a36Sopenharmony_ci u32 *ptr, hdata; 41862306a36Sopenharmony_ci u32 val; 41962306a36Sopenharmony_ci int rc; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci if (tkey_ex) { 42262306a36Sopenharmony_ci htype = tkey_ex->htype; 42362306a36Sopenharmony_ci cmd = tkey_ex->cmd; 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci tkey_ex++; 42662306a36Sopenharmony_ci } 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci rc = pedit_skb_hdr_offset(skb, htype, &hoffset); 42962306a36Sopenharmony_ci if (rc) { 43062306a36Sopenharmony_ci pr_info_ratelimited("tc action pedit unable to extract header offset for header type (0x%x)\n", htype); 43162306a36Sopenharmony_ci goto bad; 43262306a36Sopenharmony_ci } 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci if (tkey->offmask) { 43562306a36Sopenharmony_ci u8 *d, _d; 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci if (!offset_valid(skb, hoffset + tkey->at)) { 43862306a36Sopenharmony_ci pr_info_ratelimited("tc action pedit 'at' offset %d out of bounds\n", 43962306a36Sopenharmony_ci hoffset + tkey->at); 44062306a36Sopenharmony_ci goto bad; 44162306a36Sopenharmony_ci } 44262306a36Sopenharmony_ci d = skb_header_pointer(skb, hoffset + tkey->at, 44362306a36Sopenharmony_ci sizeof(_d), &_d); 44462306a36Sopenharmony_ci if (!d) 44562306a36Sopenharmony_ci goto bad; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci offset += (*d & tkey->offmask) >> tkey->shift; 44862306a36Sopenharmony_ci if (offset % 4) { 44962306a36Sopenharmony_ci pr_info_ratelimited("tc action pedit offset must be on 32 bit boundaries\n"); 45062306a36Sopenharmony_ci goto bad; 45162306a36Sopenharmony_ci } 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci if (!offset_valid(skb, hoffset + offset)) { 45562306a36Sopenharmony_ci pr_info_ratelimited("tc action pedit offset %d out of bounds\n", hoffset + offset); 45662306a36Sopenharmony_ci goto bad; 45762306a36Sopenharmony_ci } 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci ptr = skb_header_pointer(skb, hoffset + offset, 46062306a36Sopenharmony_ci sizeof(hdata), &hdata); 46162306a36Sopenharmony_ci if (!ptr) 46262306a36Sopenharmony_ci goto bad; 46362306a36Sopenharmony_ci /* just do it, baby */ 46462306a36Sopenharmony_ci switch (cmd) { 46562306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_CMD_SET: 46662306a36Sopenharmony_ci val = tkey->val; 46762306a36Sopenharmony_ci break; 46862306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_CMD_ADD: 46962306a36Sopenharmony_ci val = (*ptr + tkey->val) & ~tkey->mask; 47062306a36Sopenharmony_ci break; 47162306a36Sopenharmony_ci default: 47262306a36Sopenharmony_ci pr_info_ratelimited("tc action pedit bad command (%d)\n", cmd); 47362306a36Sopenharmony_ci goto bad; 47462306a36Sopenharmony_ci } 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci *ptr = ((*ptr & tkey->mask) ^ val); 47762306a36Sopenharmony_ci if (ptr == &hdata) 47862306a36Sopenharmony_ci skb_store_bits(skb, hoffset + offset, ptr, 4); 47962306a36Sopenharmony_ci } 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci goto done; 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_cibad: 48462306a36Sopenharmony_ci tcf_action_inc_overlimit_qstats(&p->common); 48562306a36Sopenharmony_cidone: 48662306a36Sopenharmony_ci return p->tcf_action; 48762306a36Sopenharmony_ci} 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_cistatic void tcf_pedit_stats_update(struct tc_action *a, u64 bytes, u64 packets, 49062306a36Sopenharmony_ci u64 drops, u64 lastuse, bool hw) 49162306a36Sopenharmony_ci{ 49262306a36Sopenharmony_ci struct tcf_pedit *d = to_pedit(a); 49362306a36Sopenharmony_ci struct tcf_t *tm = &d->tcf_tm; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci tcf_action_update_stats(a, bytes, packets, drops, hw); 49662306a36Sopenharmony_ci tm->lastuse = max_t(u64, tm->lastuse, lastuse); 49762306a36Sopenharmony_ci} 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_cistatic int tcf_pedit_dump(struct sk_buff *skb, struct tc_action *a, 50062306a36Sopenharmony_ci int bind, int ref) 50162306a36Sopenharmony_ci{ 50262306a36Sopenharmony_ci unsigned char *b = skb_tail_pointer(skb); 50362306a36Sopenharmony_ci struct tcf_pedit *p = to_pedit(a); 50462306a36Sopenharmony_ci struct tcf_pedit_parms *parms; 50562306a36Sopenharmony_ci struct tc_pedit *opt; 50662306a36Sopenharmony_ci struct tcf_t t; 50762306a36Sopenharmony_ci int s; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci spin_lock_bh(&p->tcf_lock); 51062306a36Sopenharmony_ci parms = rcu_dereference_protected(p->parms, 1); 51162306a36Sopenharmony_ci s = struct_size(opt, keys, parms->tcfp_nkeys); 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci opt = kzalloc(s, GFP_ATOMIC); 51462306a36Sopenharmony_ci if (unlikely(!opt)) { 51562306a36Sopenharmony_ci spin_unlock_bh(&p->tcf_lock); 51662306a36Sopenharmony_ci return -ENOBUFS; 51762306a36Sopenharmony_ci } 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci memcpy(opt->keys, parms->tcfp_keys, 52062306a36Sopenharmony_ci flex_array_size(opt, keys, parms->tcfp_nkeys)); 52162306a36Sopenharmony_ci opt->index = p->tcf_index; 52262306a36Sopenharmony_ci opt->nkeys = parms->tcfp_nkeys; 52362306a36Sopenharmony_ci opt->flags = parms->tcfp_flags; 52462306a36Sopenharmony_ci opt->action = p->tcf_action; 52562306a36Sopenharmony_ci opt->refcnt = refcount_read(&p->tcf_refcnt) - ref; 52662306a36Sopenharmony_ci opt->bindcnt = atomic_read(&p->tcf_bindcnt) - bind; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci if (parms->tcfp_keys_ex) { 52962306a36Sopenharmony_ci if (tcf_pedit_key_ex_dump(skb, parms->tcfp_keys_ex, 53062306a36Sopenharmony_ci parms->tcfp_nkeys)) 53162306a36Sopenharmony_ci goto nla_put_failure; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci if (nla_put(skb, TCA_PEDIT_PARMS_EX, s, opt)) 53462306a36Sopenharmony_ci goto nla_put_failure; 53562306a36Sopenharmony_ci } else { 53662306a36Sopenharmony_ci if (nla_put(skb, TCA_PEDIT_PARMS, s, opt)) 53762306a36Sopenharmony_ci goto nla_put_failure; 53862306a36Sopenharmony_ci } 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci tcf_tm_dump(&t, &p->tcf_tm); 54162306a36Sopenharmony_ci if (nla_put_64bit(skb, TCA_PEDIT_TM, sizeof(t), &t, TCA_PEDIT_PAD)) 54262306a36Sopenharmony_ci goto nla_put_failure; 54362306a36Sopenharmony_ci spin_unlock_bh(&p->tcf_lock); 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci kfree(opt); 54662306a36Sopenharmony_ci return skb->len; 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_cinla_put_failure: 54962306a36Sopenharmony_ci spin_unlock_bh(&p->tcf_lock); 55062306a36Sopenharmony_ci nlmsg_trim(skb, b); 55162306a36Sopenharmony_ci kfree(opt); 55262306a36Sopenharmony_ci return -1; 55362306a36Sopenharmony_ci} 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_cistatic int tcf_pedit_offload_act_setup(struct tc_action *act, void *entry_data, 55662306a36Sopenharmony_ci u32 *index_inc, bool bind, 55762306a36Sopenharmony_ci struct netlink_ext_ack *extack) 55862306a36Sopenharmony_ci{ 55962306a36Sopenharmony_ci if (bind) { 56062306a36Sopenharmony_ci struct flow_action_entry *entry = entry_data; 56162306a36Sopenharmony_ci int k; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci for (k = 0; k < tcf_pedit_nkeys(act); k++) { 56462306a36Sopenharmony_ci switch (tcf_pedit_cmd(act, k)) { 56562306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_CMD_SET: 56662306a36Sopenharmony_ci entry->id = FLOW_ACTION_MANGLE; 56762306a36Sopenharmony_ci break; 56862306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_CMD_ADD: 56962306a36Sopenharmony_ci entry->id = FLOW_ACTION_ADD; 57062306a36Sopenharmony_ci break; 57162306a36Sopenharmony_ci default: 57262306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload"); 57362306a36Sopenharmony_ci return -EOPNOTSUPP; 57462306a36Sopenharmony_ci } 57562306a36Sopenharmony_ci entry->mangle.htype = tcf_pedit_htype(act, k); 57662306a36Sopenharmony_ci entry->mangle.mask = tcf_pedit_mask(act, k); 57762306a36Sopenharmony_ci entry->mangle.val = tcf_pedit_val(act, k); 57862306a36Sopenharmony_ci entry->mangle.offset = tcf_pedit_offset(act, k); 57962306a36Sopenharmony_ci entry->hw_stats = tc_act_hw_stats(act->hw_stats); 58062306a36Sopenharmony_ci entry++; 58162306a36Sopenharmony_ci } 58262306a36Sopenharmony_ci *index_inc = k; 58362306a36Sopenharmony_ci } else { 58462306a36Sopenharmony_ci struct flow_offload_action *fl_action = entry_data; 58562306a36Sopenharmony_ci u32 cmd = tcf_pedit_cmd(act, 0); 58662306a36Sopenharmony_ci int k; 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci switch (cmd) { 58962306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_CMD_SET: 59062306a36Sopenharmony_ci fl_action->id = FLOW_ACTION_MANGLE; 59162306a36Sopenharmony_ci break; 59262306a36Sopenharmony_ci case TCA_PEDIT_KEY_EX_CMD_ADD: 59362306a36Sopenharmony_ci fl_action->id = FLOW_ACTION_ADD; 59462306a36Sopenharmony_ci break; 59562306a36Sopenharmony_ci default: 59662306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload"); 59762306a36Sopenharmony_ci return -EOPNOTSUPP; 59862306a36Sopenharmony_ci } 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci for (k = 1; k < tcf_pedit_nkeys(act); k++) { 60162306a36Sopenharmony_ci if (cmd != tcf_pedit_cmd(act, k)) { 60262306a36Sopenharmony_ci NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload"); 60362306a36Sopenharmony_ci return -EOPNOTSUPP; 60462306a36Sopenharmony_ci } 60562306a36Sopenharmony_ci } 60662306a36Sopenharmony_ci } 60762306a36Sopenharmony_ci 60862306a36Sopenharmony_ci return 0; 60962306a36Sopenharmony_ci} 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_cistatic struct tc_action_ops act_pedit_ops = { 61262306a36Sopenharmony_ci .kind = "pedit", 61362306a36Sopenharmony_ci .id = TCA_ID_PEDIT, 61462306a36Sopenharmony_ci .owner = THIS_MODULE, 61562306a36Sopenharmony_ci .act = tcf_pedit_act, 61662306a36Sopenharmony_ci .stats_update = tcf_pedit_stats_update, 61762306a36Sopenharmony_ci .dump = tcf_pedit_dump, 61862306a36Sopenharmony_ci .cleanup = tcf_pedit_cleanup, 61962306a36Sopenharmony_ci .init = tcf_pedit_init, 62062306a36Sopenharmony_ci .offload_act_setup = tcf_pedit_offload_act_setup, 62162306a36Sopenharmony_ci .size = sizeof(struct tcf_pedit), 62262306a36Sopenharmony_ci}; 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_cistatic __net_init int pedit_init_net(struct net *net) 62562306a36Sopenharmony_ci{ 62662306a36Sopenharmony_ci struct tc_action_net *tn = net_generic(net, act_pedit_ops.net_id); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci return tc_action_net_init(net, tn, &act_pedit_ops); 62962306a36Sopenharmony_ci} 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_cistatic void __net_exit pedit_exit_net(struct list_head *net_list) 63262306a36Sopenharmony_ci{ 63362306a36Sopenharmony_ci tc_action_net_exit(net_list, act_pedit_ops.net_id); 63462306a36Sopenharmony_ci} 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_cistatic struct pernet_operations pedit_net_ops = { 63762306a36Sopenharmony_ci .init = pedit_init_net, 63862306a36Sopenharmony_ci .exit_batch = pedit_exit_net, 63962306a36Sopenharmony_ci .id = &act_pedit_ops.net_id, 64062306a36Sopenharmony_ci .size = sizeof(struct tc_action_net), 64162306a36Sopenharmony_ci}; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ciMODULE_AUTHOR("Jamal Hadi Salim(2002-4)"); 64462306a36Sopenharmony_ciMODULE_DESCRIPTION("Generic Packet Editor actions"); 64562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_cistatic int __init pedit_init_module(void) 64862306a36Sopenharmony_ci{ 64962306a36Sopenharmony_ci return tcf_register_action(&act_pedit_ops, &pedit_net_ops); 65062306a36Sopenharmony_ci} 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_cistatic void __exit pedit_cleanup_module(void) 65362306a36Sopenharmony_ci{ 65462306a36Sopenharmony_ci tcf_unregister_action(&act_pedit_ops, &pedit_net_ops); 65562306a36Sopenharmony_ci} 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_cimodule_init(pedit_init_module); 65862306a36Sopenharmony_cimodule_exit(pedit_cleanup_module); 659