18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * net/sched/em_ipt.c IPtables matches Ematch 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * (c) 2018 Eyal Birger <eyal.birger@gmail.com> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/gfp.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/types.h> 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/string.h> 138c2ecf20Sopenharmony_ci#include <linux/skbuff.h> 148c2ecf20Sopenharmony_ci#include <linux/tc_ematch/tc_em_ipt.h> 158c2ecf20Sopenharmony_ci#include <linux/netfilter.h> 168c2ecf20Sopenharmony_ci#include <linux/netfilter/x_tables.h> 178c2ecf20Sopenharmony_ci#include <linux/netfilter_ipv4/ip_tables.h> 188c2ecf20Sopenharmony_ci#include <linux/netfilter_ipv6/ip6_tables.h> 198c2ecf20Sopenharmony_ci#include <net/pkt_cls.h> 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistruct em_ipt_match { 228c2ecf20Sopenharmony_ci const struct xt_match *match; 238c2ecf20Sopenharmony_ci u32 hook; 248c2ecf20Sopenharmony_ci u8 nfproto; 258c2ecf20Sopenharmony_ci u8 match_data[] __aligned(8); 268c2ecf20Sopenharmony_ci}; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistruct em_ipt_xt_match { 298c2ecf20Sopenharmony_ci char *match_name; 308c2ecf20Sopenharmony_ci int (*validate_match_data)(struct nlattr **tb, u8 mrev); 318c2ecf20Sopenharmony_ci}; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic const struct nla_policy em_ipt_policy[TCA_EM_IPT_MAX + 1] = { 348c2ecf20Sopenharmony_ci [TCA_EM_IPT_MATCH_NAME] = { .type = NLA_STRING, 358c2ecf20Sopenharmony_ci .len = XT_EXTENSION_MAXNAMELEN }, 368c2ecf20Sopenharmony_ci [TCA_EM_IPT_MATCH_REVISION] = { .type = NLA_U8 }, 378c2ecf20Sopenharmony_ci [TCA_EM_IPT_HOOK] = { .type = NLA_U32 }, 388c2ecf20Sopenharmony_ci [TCA_EM_IPT_NFPROTO] = { .type = NLA_U8 }, 398c2ecf20Sopenharmony_ci [TCA_EM_IPT_MATCH_DATA] = { .type = NLA_UNSPEC }, 408c2ecf20Sopenharmony_ci}; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic int check_match(struct net *net, struct em_ipt_match *im, int mdata_len) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci struct xt_mtchk_param mtpar = {}; 458c2ecf20Sopenharmony_ci union { 468c2ecf20Sopenharmony_ci struct ipt_entry e4; 478c2ecf20Sopenharmony_ci struct ip6t_entry e6; 488c2ecf20Sopenharmony_ci } e = {}; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci mtpar.net = net; 518c2ecf20Sopenharmony_ci mtpar.table = "filter"; 528c2ecf20Sopenharmony_ci mtpar.hook_mask = 1 << im->hook; 538c2ecf20Sopenharmony_ci mtpar.family = im->match->family; 548c2ecf20Sopenharmony_ci mtpar.match = im->match; 558c2ecf20Sopenharmony_ci mtpar.entryinfo = &e; 568c2ecf20Sopenharmony_ci mtpar.matchinfo = (void *)im->match_data; 578c2ecf20Sopenharmony_ci return xt_check_match(&mtpar, mdata_len, 0, 0); 588c2ecf20Sopenharmony_ci} 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic int policy_validate_match_data(struct nlattr **tb, u8 mrev) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci if (mrev != 0) { 638c2ecf20Sopenharmony_ci pr_err("only policy match revision 0 supported"); 648c2ecf20Sopenharmony_ci return -EINVAL; 658c2ecf20Sopenharmony_ci } 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci if (nla_get_u32(tb[TCA_EM_IPT_HOOK]) != NF_INET_PRE_ROUTING) { 688c2ecf20Sopenharmony_ci pr_err("policy can only be matched on NF_INET_PRE_ROUTING"); 698c2ecf20Sopenharmony_ci return -EINVAL; 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci return 0; 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic int addrtype_validate_match_data(struct nlattr **tb, u8 mrev) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci if (mrev != 1) { 788c2ecf20Sopenharmony_ci pr_err("only addrtype match revision 1 supported"); 798c2ecf20Sopenharmony_ci return -EINVAL; 808c2ecf20Sopenharmony_ci } 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci return 0; 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_cistatic const struct em_ipt_xt_match em_ipt_xt_matches[] = { 868c2ecf20Sopenharmony_ci { 878c2ecf20Sopenharmony_ci .match_name = "policy", 888c2ecf20Sopenharmony_ci .validate_match_data = policy_validate_match_data 898c2ecf20Sopenharmony_ci }, 908c2ecf20Sopenharmony_ci { 918c2ecf20Sopenharmony_ci .match_name = "addrtype", 928c2ecf20Sopenharmony_ci .validate_match_data = addrtype_validate_match_data 938c2ecf20Sopenharmony_ci }, 948c2ecf20Sopenharmony_ci {} 958c2ecf20Sopenharmony_ci}; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic struct xt_match *get_xt_match(struct nlattr **tb) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci const struct em_ipt_xt_match *m; 1008c2ecf20Sopenharmony_ci struct nlattr *mname_attr; 1018c2ecf20Sopenharmony_ci u8 nfproto, mrev = 0; 1028c2ecf20Sopenharmony_ci int ret; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci mname_attr = tb[TCA_EM_IPT_MATCH_NAME]; 1058c2ecf20Sopenharmony_ci for (m = em_ipt_xt_matches; m->match_name; m++) { 1068c2ecf20Sopenharmony_ci if (!nla_strcmp(mname_attr, m->match_name)) 1078c2ecf20Sopenharmony_ci break; 1088c2ecf20Sopenharmony_ci } 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci if (!m->match_name) { 1118c2ecf20Sopenharmony_ci pr_err("Unsupported xt match"); 1128c2ecf20Sopenharmony_ci return ERR_PTR(-EINVAL); 1138c2ecf20Sopenharmony_ci } 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if (tb[TCA_EM_IPT_MATCH_REVISION]) 1168c2ecf20Sopenharmony_ci mrev = nla_get_u8(tb[TCA_EM_IPT_MATCH_REVISION]); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci ret = m->validate_match_data(tb, mrev); 1198c2ecf20Sopenharmony_ci if (ret < 0) 1208c2ecf20Sopenharmony_ci return ERR_PTR(ret); 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]); 1238c2ecf20Sopenharmony_ci return xt_request_find_match(nfproto, m->match_name, mrev); 1248c2ecf20Sopenharmony_ci} 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_cistatic int em_ipt_change(struct net *net, void *data, int data_len, 1278c2ecf20Sopenharmony_ci struct tcf_ematch *em) 1288c2ecf20Sopenharmony_ci{ 1298c2ecf20Sopenharmony_ci struct nlattr *tb[TCA_EM_IPT_MAX + 1]; 1308c2ecf20Sopenharmony_ci struct em_ipt_match *im = NULL; 1318c2ecf20Sopenharmony_ci struct xt_match *match; 1328c2ecf20Sopenharmony_ci int mdata_len, ret; 1338c2ecf20Sopenharmony_ci u8 nfproto; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci ret = nla_parse_deprecated(tb, TCA_EM_IPT_MAX, data, data_len, 1368c2ecf20Sopenharmony_ci em_ipt_policy, NULL); 1378c2ecf20Sopenharmony_ci if (ret < 0) 1388c2ecf20Sopenharmony_ci return ret; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci if (!tb[TCA_EM_IPT_HOOK] || !tb[TCA_EM_IPT_MATCH_NAME] || 1418c2ecf20Sopenharmony_ci !tb[TCA_EM_IPT_MATCH_DATA] || !tb[TCA_EM_IPT_NFPROTO]) 1428c2ecf20Sopenharmony_ci return -EINVAL; 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]); 1458c2ecf20Sopenharmony_ci switch (nfproto) { 1468c2ecf20Sopenharmony_ci case NFPROTO_IPV4: 1478c2ecf20Sopenharmony_ci case NFPROTO_IPV6: 1488c2ecf20Sopenharmony_ci break; 1498c2ecf20Sopenharmony_ci default: 1508c2ecf20Sopenharmony_ci return -EINVAL; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci match = get_xt_match(tb); 1548c2ecf20Sopenharmony_ci if (IS_ERR(match)) { 1558c2ecf20Sopenharmony_ci pr_err("unable to load match\n"); 1568c2ecf20Sopenharmony_ci return PTR_ERR(match); 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci mdata_len = XT_ALIGN(nla_len(tb[TCA_EM_IPT_MATCH_DATA])); 1608c2ecf20Sopenharmony_ci im = kzalloc(sizeof(*im) + mdata_len, GFP_KERNEL); 1618c2ecf20Sopenharmony_ci if (!im) { 1628c2ecf20Sopenharmony_ci ret = -ENOMEM; 1638c2ecf20Sopenharmony_ci goto err; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci im->match = match; 1678c2ecf20Sopenharmony_ci im->hook = nla_get_u32(tb[TCA_EM_IPT_HOOK]); 1688c2ecf20Sopenharmony_ci im->nfproto = nfproto; 1698c2ecf20Sopenharmony_ci nla_memcpy(im->match_data, tb[TCA_EM_IPT_MATCH_DATA], mdata_len); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci ret = check_match(net, im, mdata_len); 1728c2ecf20Sopenharmony_ci if (ret) 1738c2ecf20Sopenharmony_ci goto err; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci em->datalen = sizeof(*im) + mdata_len; 1768c2ecf20Sopenharmony_ci em->data = (unsigned long)im; 1778c2ecf20Sopenharmony_ci return 0; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cierr: 1808c2ecf20Sopenharmony_ci kfree(im); 1818c2ecf20Sopenharmony_ci module_put(match->me); 1828c2ecf20Sopenharmony_ci return ret; 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cistatic void em_ipt_destroy(struct tcf_ematch *em) 1868c2ecf20Sopenharmony_ci{ 1878c2ecf20Sopenharmony_ci struct em_ipt_match *im = (void *)em->data; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci if (!im) 1908c2ecf20Sopenharmony_ci return; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci if (im->match->destroy) { 1938c2ecf20Sopenharmony_ci struct xt_mtdtor_param par = { 1948c2ecf20Sopenharmony_ci .net = em->net, 1958c2ecf20Sopenharmony_ci .match = im->match, 1968c2ecf20Sopenharmony_ci .matchinfo = im->match_data, 1978c2ecf20Sopenharmony_ci .family = im->match->family 1988c2ecf20Sopenharmony_ci }; 1998c2ecf20Sopenharmony_ci im->match->destroy(&par); 2008c2ecf20Sopenharmony_ci } 2018c2ecf20Sopenharmony_ci module_put(im->match->me); 2028c2ecf20Sopenharmony_ci kfree(im); 2038c2ecf20Sopenharmony_ci} 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic int em_ipt_match(struct sk_buff *skb, struct tcf_ematch *em, 2068c2ecf20Sopenharmony_ci struct tcf_pkt_info *info) 2078c2ecf20Sopenharmony_ci{ 2088c2ecf20Sopenharmony_ci const struct em_ipt_match *im = (const void *)em->data; 2098c2ecf20Sopenharmony_ci struct xt_action_param acpar = {}; 2108c2ecf20Sopenharmony_ci struct net_device *indev = NULL; 2118c2ecf20Sopenharmony_ci u8 nfproto = im->match->family; 2128c2ecf20Sopenharmony_ci struct nf_hook_state state; 2138c2ecf20Sopenharmony_ci int ret; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci switch (skb_protocol(skb, true)) { 2168c2ecf20Sopenharmony_ci case htons(ETH_P_IP): 2178c2ecf20Sopenharmony_ci if (!pskb_network_may_pull(skb, sizeof(struct iphdr))) 2188c2ecf20Sopenharmony_ci return 0; 2198c2ecf20Sopenharmony_ci if (nfproto == NFPROTO_UNSPEC) 2208c2ecf20Sopenharmony_ci nfproto = NFPROTO_IPV4; 2218c2ecf20Sopenharmony_ci break; 2228c2ecf20Sopenharmony_ci case htons(ETH_P_IPV6): 2238c2ecf20Sopenharmony_ci if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr))) 2248c2ecf20Sopenharmony_ci return 0; 2258c2ecf20Sopenharmony_ci if (nfproto == NFPROTO_UNSPEC) 2268c2ecf20Sopenharmony_ci nfproto = NFPROTO_IPV6; 2278c2ecf20Sopenharmony_ci break; 2288c2ecf20Sopenharmony_ci default: 2298c2ecf20Sopenharmony_ci return 0; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci rcu_read_lock(); 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci if (skb->skb_iif) 2358c2ecf20Sopenharmony_ci indev = dev_get_by_index_rcu(em->net, skb->skb_iif); 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci nf_hook_state_init(&state, im->hook, nfproto, 2388c2ecf20Sopenharmony_ci indev ?: skb->dev, skb->dev, NULL, em->net, NULL); 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci acpar.match = im->match; 2418c2ecf20Sopenharmony_ci acpar.matchinfo = im->match_data; 2428c2ecf20Sopenharmony_ci acpar.state = &state; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci ret = im->match->match(skb, &acpar); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci rcu_read_unlock(); 2478c2ecf20Sopenharmony_ci return ret; 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_cistatic int em_ipt_dump(struct sk_buff *skb, struct tcf_ematch *em) 2518c2ecf20Sopenharmony_ci{ 2528c2ecf20Sopenharmony_ci struct em_ipt_match *im = (void *)em->data; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci if (nla_put_string(skb, TCA_EM_IPT_MATCH_NAME, im->match->name) < 0) 2558c2ecf20Sopenharmony_ci return -EMSGSIZE; 2568c2ecf20Sopenharmony_ci if (nla_put_u32(skb, TCA_EM_IPT_HOOK, im->hook) < 0) 2578c2ecf20Sopenharmony_ci return -EMSGSIZE; 2588c2ecf20Sopenharmony_ci if (nla_put_u8(skb, TCA_EM_IPT_MATCH_REVISION, im->match->revision) < 0) 2598c2ecf20Sopenharmony_ci return -EMSGSIZE; 2608c2ecf20Sopenharmony_ci if (nla_put_u8(skb, TCA_EM_IPT_NFPROTO, im->nfproto) < 0) 2618c2ecf20Sopenharmony_ci return -EMSGSIZE; 2628c2ecf20Sopenharmony_ci if (nla_put(skb, TCA_EM_IPT_MATCH_DATA, 2638c2ecf20Sopenharmony_ci im->match->usersize ?: im->match->matchsize, 2648c2ecf20Sopenharmony_ci im->match_data) < 0) 2658c2ecf20Sopenharmony_ci return -EMSGSIZE; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci return 0; 2688c2ecf20Sopenharmony_ci} 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cistatic struct tcf_ematch_ops em_ipt_ops = { 2718c2ecf20Sopenharmony_ci .kind = TCF_EM_IPT, 2728c2ecf20Sopenharmony_ci .change = em_ipt_change, 2738c2ecf20Sopenharmony_ci .destroy = em_ipt_destroy, 2748c2ecf20Sopenharmony_ci .match = em_ipt_match, 2758c2ecf20Sopenharmony_ci .dump = em_ipt_dump, 2768c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2778c2ecf20Sopenharmony_ci .link = LIST_HEAD_INIT(em_ipt_ops.link) 2788c2ecf20Sopenharmony_ci}; 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_cistatic int __init init_em_ipt(void) 2818c2ecf20Sopenharmony_ci{ 2828c2ecf20Sopenharmony_ci return tcf_em_register(&em_ipt_ops); 2838c2ecf20Sopenharmony_ci} 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_cistatic void __exit exit_em_ipt(void) 2868c2ecf20Sopenharmony_ci{ 2878c2ecf20Sopenharmony_ci tcf_em_unregister(&em_ipt_ops); 2888c2ecf20Sopenharmony_ci} 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2918c2ecf20Sopenharmony_ciMODULE_AUTHOR("Eyal Birger <eyal.birger@gmail.com>"); 2928c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TC extended match for IPtables matches"); 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_cimodule_init(init_em_ipt); 2958c2ecf20Sopenharmony_cimodule_exit(exit_em_ipt); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ciMODULE_ALIAS_TCF_EMATCH(TCF_EM_IPT); 298