162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * net/sched/em_ipt.c IPtables matches Ematch 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (c) 2018 Eyal Birger <eyal.birger@gmail.com> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/gfp.h> 962306a36Sopenharmony_ci#include <linux/module.h> 1062306a36Sopenharmony_ci#include <linux/types.h> 1162306a36Sopenharmony_ci#include <linux/kernel.h> 1262306a36Sopenharmony_ci#include <linux/string.h> 1362306a36Sopenharmony_ci#include <linux/skbuff.h> 1462306a36Sopenharmony_ci#include <linux/tc_ematch/tc_em_ipt.h> 1562306a36Sopenharmony_ci#include <linux/netfilter.h> 1662306a36Sopenharmony_ci#include <linux/netfilter/x_tables.h> 1762306a36Sopenharmony_ci#include <linux/netfilter_ipv4/ip_tables.h> 1862306a36Sopenharmony_ci#include <linux/netfilter_ipv6/ip6_tables.h> 1962306a36Sopenharmony_ci#include <net/pkt_cls.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cistruct em_ipt_match { 2262306a36Sopenharmony_ci const struct xt_match *match; 2362306a36Sopenharmony_ci u32 hook; 2462306a36Sopenharmony_ci u8 nfproto; 2562306a36Sopenharmony_ci u8 match_data[] __aligned(8); 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistruct em_ipt_xt_match { 2962306a36Sopenharmony_ci char *match_name; 3062306a36Sopenharmony_ci int (*validate_match_data)(struct nlattr **tb, u8 mrev); 3162306a36Sopenharmony_ci}; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_cistatic const struct nla_policy em_ipt_policy[TCA_EM_IPT_MAX + 1] = { 3462306a36Sopenharmony_ci [TCA_EM_IPT_MATCH_NAME] = { .type = NLA_STRING, 3562306a36Sopenharmony_ci .len = XT_EXTENSION_MAXNAMELEN }, 3662306a36Sopenharmony_ci [TCA_EM_IPT_MATCH_REVISION] = { .type = NLA_U8 }, 3762306a36Sopenharmony_ci [TCA_EM_IPT_HOOK] = { .type = NLA_U32 }, 3862306a36Sopenharmony_ci [TCA_EM_IPT_NFPROTO] = { .type = NLA_U8 }, 3962306a36Sopenharmony_ci [TCA_EM_IPT_MATCH_DATA] = { .type = NLA_UNSPEC }, 4062306a36Sopenharmony_ci}; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic int check_match(struct net *net, struct em_ipt_match *im, int mdata_len) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci struct xt_mtchk_param mtpar = {}; 4562306a36Sopenharmony_ci union { 4662306a36Sopenharmony_ci struct ipt_entry e4; 4762306a36Sopenharmony_ci struct ip6t_entry e6; 4862306a36Sopenharmony_ci } e = {}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci mtpar.net = net; 5162306a36Sopenharmony_ci mtpar.table = "filter"; 5262306a36Sopenharmony_ci mtpar.hook_mask = 1 << im->hook; 5362306a36Sopenharmony_ci mtpar.family = im->match->family; 5462306a36Sopenharmony_ci mtpar.match = im->match; 5562306a36Sopenharmony_ci mtpar.entryinfo = &e; 5662306a36Sopenharmony_ci mtpar.matchinfo = (void *)im->match_data; 5762306a36Sopenharmony_ci return xt_check_match(&mtpar, mdata_len, 0, 0); 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_cistatic int policy_validate_match_data(struct nlattr **tb, u8 mrev) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci if (mrev != 0) { 6362306a36Sopenharmony_ci pr_err("only policy match revision 0 supported"); 6462306a36Sopenharmony_ci return -EINVAL; 6562306a36Sopenharmony_ci } 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci if (nla_get_u32(tb[TCA_EM_IPT_HOOK]) != NF_INET_PRE_ROUTING) { 6862306a36Sopenharmony_ci pr_err("policy can only be matched on NF_INET_PRE_ROUTING"); 6962306a36Sopenharmony_ci return -EINVAL; 7062306a36Sopenharmony_ci } 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci return 0; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_cistatic int addrtype_validate_match_data(struct nlattr **tb, u8 mrev) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci if (mrev != 1) { 7862306a36Sopenharmony_ci pr_err("only addrtype match revision 1 supported"); 7962306a36Sopenharmony_ci return -EINVAL; 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci return 0; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic const struct em_ipt_xt_match em_ipt_xt_matches[] = { 8662306a36Sopenharmony_ci { 8762306a36Sopenharmony_ci .match_name = "policy", 8862306a36Sopenharmony_ci .validate_match_data = policy_validate_match_data 8962306a36Sopenharmony_ci }, 9062306a36Sopenharmony_ci { 9162306a36Sopenharmony_ci .match_name = "addrtype", 9262306a36Sopenharmony_ci .validate_match_data = addrtype_validate_match_data 9362306a36Sopenharmony_ci }, 9462306a36Sopenharmony_ci {} 9562306a36Sopenharmony_ci}; 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_cistatic struct xt_match *get_xt_match(struct nlattr **tb) 9862306a36Sopenharmony_ci{ 9962306a36Sopenharmony_ci const struct em_ipt_xt_match *m; 10062306a36Sopenharmony_ci struct nlattr *mname_attr; 10162306a36Sopenharmony_ci u8 nfproto, mrev = 0; 10262306a36Sopenharmony_ci int ret; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci mname_attr = tb[TCA_EM_IPT_MATCH_NAME]; 10562306a36Sopenharmony_ci for (m = em_ipt_xt_matches; m->match_name; m++) { 10662306a36Sopenharmony_ci if (!nla_strcmp(mname_attr, m->match_name)) 10762306a36Sopenharmony_ci break; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (!m->match_name) { 11162306a36Sopenharmony_ci pr_err("Unsupported xt match"); 11262306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 11362306a36Sopenharmony_ci } 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (tb[TCA_EM_IPT_MATCH_REVISION]) 11662306a36Sopenharmony_ci mrev = nla_get_u8(tb[TCA_EM_IPT_MATCH_REVISION]); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci ret = m->validate_match_data(tb, mrev); 11962306a36Sopenharmony_ci if (ret < 0) 12062306a36Sopenharmony_ci return ERR_PTR(ret); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]); 12362306a36Sopenharmony_ci return xt_request_find_match(nfproto, m->match_name, mrev); 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cistatic int em_ipt_change(struct net *net, void *data, int data_len, 12762306a36Sopenharmony_ci struct tcf_ematch *em) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci struct nlattr *tb[TCA_EM_IPT_MAX + 1]; 13062306a36Sopenharmony_ci struct em_ipt_match *im = NULL; 13162306a36Sopenharmony_ci struct xt_match *match; 13262306a36Sopenharmony_ci int mdata_len, ret; 13362306a36Sopenharmony_ci u8 nfproto; 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci ret = nla_parse_deprecated(tb, TCA_EM_IPT_MAX, data, data_len, 13662306a36Sopenharmony_ci em_ipt_policy, NULL); 13762306a36Sopenharmony_ci if (ret < 0) 13862306a36Sopenharmony_ci return ret; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (!tb[TCA_EM_IPT_HOOK] || !tb[TCA_EM_IPT_MATCH_NAME] || 14162306a36Sopenharmony_ci !tb[TCA_EM_IPT_MATCH_DATA] || !tb[TCA_EM_IPT_NFPROTO]) 14262306a36Sopenharmony_ci return -EINVAL; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]); 14562306a36Sopenharmony_ci switch (nfproto) { 14662306a36Sopenharmony_ci case NFPROTO_IPV4: 14762306a36Sopenharmony_ci case NFPROTO_IPV6: 14862306a36Sopenharmony_ci break; 14962306a36Sopenharmony_ci default: 15062306a36Sopenharmony_ci return -EINVAL; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci match = get_xt_match(tb); 15462306a36Sopenharmony_ci if (IS_ERR(match)) { 15562306a36Sopenharmony_ci pr_err("unable to load match\n"); 15662306a36Sopenharmony_ci return PTR_ERR(match); 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci mdata_len = XT_ALIGN(nla_len(tb[TCA_EM_IPT_MATCH_DATA])); 16062306a36Sopenharmony_ci im = kzalloc(sizeof(*im) + mdata_len, GFP_KERNEL); 16162306a36Sopenharmony_ci if (!im) { 16262306a36Sopenharmony_ci ret = -ENOMEM; 16362306a36Sopenharmony_ci goto err; 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci im->match = match; 16762306a36Sopenharmony_ci im->hook = nla_get_u32(tb[TCA_EM_IPT_HOOK]); 16862306a36Sopenharmony_ci im->nfproto = nfproto; 16962306a36Sopenharmony_ci nla_memcpy(im->match_data, tb[TCA_EM_IPT_MATCH_DATA], mdata_len); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci ret = check_match(net, im, mdata_len); 17262306a36Sopenharmony_ci if (ret) 17362306a36Sopenharmony_ci goto err; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci em->datalen = sizeof(*im) + mdata_len; 17662306a36Sopenharmony_ci em->data = (unsigned long)im; 17762306a36Sopenharmony_ci return 0; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cierr: 18062306a36Sopenharmony_ci kfree(im); 18162306a36Sopenharmony_ci module_put(match->me); 18262306a36Sopenharmony_ci return ret; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_cistatic void em_ipt_destroy(struct tcf_ematch *em) 18662306a36Sopenharmony_ci{ 18762306a36Sopenharmony_ci struct em_ipt_match *im = (void *)em->data; 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci if (!im) 19062306a36Sopenharmony_ci return; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci if (im->match->destroy) { 19362306a36Sopenharmony_ci struct xt_mtdtor_param par = { 19462306a36Sopenharmony_ci .net = em->net, 19562306a36Sopenharmony_ci .match = im->match, 19662306a36Sopenharmony_ci .matchinfo = im->match_data, 19762306a36Sopenharmony_ci .family = im->match->family 19862306a36Sopenharmony_ci }; 19962306a36Sopenharmony_ci im->match->destroy(&par); 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci module_put(im->match->me); 20262306a36Sopenharmony_ci kfree(im); 20362306a36Sopenharmony_ci} 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_cistatic int em_ipt_match(struct sk_buff *skb, struct tcf_ematch *em, 20662306a36Sopenharmony_ci struct tcf_pkt_info *info) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci const struct em_ipt_match *im = (const void *)em->data; 20962306a36Sopenharmony_ci struct xt_action_param acpar = {}; 21062306a36Sopenharmony_ci struct net_device *indev = NULL; 21162306a36Sopenharmony_ci u8 nfproto = im->match->family; 21262306a36Sopenharmony_ci struct nf_hook_state state; 21362306a36Sopenharmony_ci int ret; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci switch (skb_protocol(skb, true)) { 21662306a36Sopenharmony_ci case htons(ETH_P_IP): 21762306a36Sopenharmony_ci if (!pskb_network_may_pull(skb, sizeof(struct iphdr))) 21862306a36Sopenharmony_ci return 0; 21962306a36Sopenharmony_ci if (nfproto == NFPROTO_UNSPEC) 22062306a36Sopenharmony_ci nfproto = NFPROTO_IPV4; 22162306a36Sopenharmony_ci break; 22262306a36Sopenharmony_ci case htons(ETH_P_IPV6): 22362306a36Sopenharmony_ci if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr))) 22462306a36Sopenharmony_ci return 0; 22562306a36Sopenharmony_ci if (nfproto == NFPROTO_UNSPEC) 22662306a36Sopenharmony_ci nfproto = NFPROTO_IPV6; 22762306a36Sopenharmony_ci break; 22862306a36Sopenharmony_ci default: 22962306a36Sopenharmony_ci return 0; 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci rcu_read_lock(); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci if (skb->skb_iif) 23562306a36Sopenharmony_ci indev = dev_get_by_index_rcu(em->net, skb->skb_iif); 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci nf_hook_state_init(&state, im->hook, nfproto, 23862306a36Sopenharmony_ci indev ?: skb->dev, skb->dev, NULL, em->net, NULL); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci acpar.match = im->match; 24162306a36Sopenharmony_ci acpar.matchinfo = im->match_data; 24262306a36Sopenharmony_ci acpar.state = &state; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci ret = im->match->match(skb, &acpar); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci rcu_read_unlock(); 24762306a36Sopenharmony_ci return ret; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic int em_ipt_dump(struct sk_buff *skb, struct tcf_ematch *em) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct em_ipt_match *im = (void *)em->data; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (nla_put_string(skb, TCA_EM_IPT_MATCH_NAME, im->match->name) < 0) 25562306a36Sopenharmony_ci return -EMSGSIZE; 25662306a36Sopenharmony_ci if (nla_put_u32(skb, TCA_EM_IPT_HOOK, im->hook) < 0) 25762306a36Sopenharmony_ci return -EMSGSIZE; 25862306a36Sopenharmony_ci if (nla_put_u8(skb, TCA_EM_IPT_MATCH_REVISION, im->match->revision) < 0) 25962306a36Sopenharmony_ci return -EMSGSIZE; 26062306a36Sopenharmony_ci if (nla_put_u8(skb, TCA_EM_IPT_NFPROTO, im->nfproto) < 0) 26162306a36Sopenharmony_ci return -EMSGSIZE; 26262306a36Sopenharmony_ci if (nla_put(skb, TCA_EM_IPT_MATCH_DATA, 26362306a36Sopenharmony_ci im->match->usersize ?: im->match->matchsize, 26462306a36Sopenharmony_ci im->match_data) < 0) 26562306a36Sopenharmony_ci return -EMSGSIZE; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci return 0; 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistatic struct tcf_ematch_ops em_ipt_ops = { 27162306a36Sopenharmony_ci .kind = TCF_EM_IPT, 27262306a36Sopenharmony_ci .change = em_ipt_change, 27362306a36Sopenharmony_ci .destroy = em_ipt_destroy, 27462306a36Sopenharmony_ci .match = em_ipt_match, 27562306a36Sopenharmony_ci .dump = em_ipt_dump, 27662306a36Sopenharmony_ci .owner = THIS_MODULE, 27762306a36Sopenharmony_ci .link = LIST_HEAD_INIT(em_ipt_ops.link) 27862306a36Sopenharmony_ci}; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_cistatic int __init init_em_ipt(void) 28162306a36Sopenharmony_ci{ 28262306a36Sopenharmony_ci return tcf_em_register(&em_ipt_ops); 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic void __exit exit_em_ipt(void) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci tcf_em_unregister(&em_ipt_ops); 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 29162306a36Sopenharmony_ciMODULE_AUTHOR("Eyal Birger <eyal.birger@gmail.com>"); 29262306a36Sopenharmony_ciMODULE_DESCRIPTION("TC extended match for IPtables matches"); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cimodule_init(init_em_ipt); 29562306a36Sopenharmony_cimodule_exit(exit_em_ipt); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ciMODULE_ALIAS_TCF_EMATCH(TCF_EM_IPT); 298