1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * net/sched/em_ipt.c IPtables matches Ematch 4 * 5 * (c) 2018 Eyal Birger <eyal.birger@gmail.com> 6 */ 7 8 #include <linux/gfp.h> 9 #include <linux/module.h> 10 #include <linux/types.h> 11 #include <linux/kernel.h> 12 #include <linux/string.h> 13 #include <linux/skbuff.h> 14 #include <linux/tc_ematch/tc_em_ipt.h> 15 #include <linux/netfilter.h> 16 #include <linux/netfilter/x_tables.h> 17 #include <linux/netfilter_ipv4/ip_tables.h> 18 #include <linux/netfilter_ipv6/ip6_tables.h> 19 #include <net/pkt_cls.h> 20 21 struct em_ipt_match { 22 const struct xt_match *match; 23 u32 hook; 24 u8 nfproto; 25 u8 match_data[0] __aligned(8); 26 }; 27 28 struct em_ipt_xt_match { 29 char *match_name; 30 int (*validate_match_data)(struct nlattr **tb, u8 mrev); 31 }; 32 33 static const struct nla_policy em_ipt_policy[TCA_EM_IPT_MAX + 1] = { 34 [TCA_EM_IPT_MATCH_NAME] = { .type = NLA_STRING, 35 .len = XT_EXTENSION_MAXNAMELEN }, 36 [TCA_EM_IPT_MATCH_REVISION] = { .type = NLA_U8 }, 37 [TCA_EM_IPT_HOOK] = { .type = NLA_U32 }, 38 [TCA_EM_IPT_NFPROTO] = { .type = NLA_U8 }, 39 [TCA_EM_IPT_MATCH_DATA] = { .type = NLA_UNSPEC }, 40 }; 41 42 static int check_match(struct net *net, struct em_ipt_match *im, int mdata_len) 43 { 44 struct xt_mtchk_param mtpar = {}; 45 union { 46 struct ipt_entry e4; 47 struct ip6t_entry e6; 48 } e = {}; 49 50 mtpar.net = net; 51 mtpar.table = "filter"; 52 mtpar.hook_mask = 1 << im->hook; 53 mtpar.family = im->match->family; 54 mtpar.match = im->match; 55 mtpar.entryinfo = &e; 56 mtpar.matchinfo = (void *)im->match_data; 57 return xt_check_match(&mtpar, mdata_len, 0, 0); 58 } 59 60 static int policy_validate_match_data(struct nlattr **tb, u8 mrev) 61 { 62 if (mrev != 0) { 63 pr_err("only policy match revision 0 supported"); 64 return -EINVAL; 65 } 66 67 if (nla_get_u32(tb[TCA_EM_IPT_HOOK]) != NF_INET_PRE_ROUTING) { 68 pr_err("policy can only be matched on NF_INET_PRE_ROUTING"); 69 return -EINVAL; 70 } 71 72 return 0; 73 } 74 75 static const struct em_ipt_xt_match em_ipt_xt_matches[] = { 76 { 77 .match_name = "policy", 78 .validate_match_data = policy_validate_match_data 79 }, 80 {} 81 }; 82 83 static struct xt_match *get_xt_match(struct nlattr **tb) 84 { 85 const struct em_ipt_xt_match *m; 86 struct nlattr *mname_attr; 87 u8 nfproto, mrev = 0; 88 int ret; 89 90 mname_attr = tb[TCA_EM_IPT_MATCH_NAME]; 91 for (m = em_ipt_xt_matches; m->match_name; m++) { 92 if (!nla_strcmp(mname_attr, m->match_name)) 93 break; 94 } 95 96 if (!m->match_name) { 97 pr_err("Unsupported xt match"); 98 return ERR_PTR(-EINVAL); 99 } 100 101 if (tb[TCA_EM_IPT_MATCH_REVISION]) 102 mrev = nla_get_u8(tb[TCA_EM_IPT_MATCH_REVISION]); 103 104 ret = m->validate_match_data(tb, mrev); 105 if (ret < 0) 106 return ERR_PTR(ret); 107 108 nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]); 109 return xt_request_find_match(nfproto, m->match_name, mrev); 110 } 111 112 static int em_ipt_change(struct net *net, void *data, int data_len, 113 struct tcf_ematch *em) 114 { 115 struct nlattr *tb[TCA_EM_IPT_MAX + 1]; 116 struct em_ipt_match *im = NULL; 117 struct xt_match *match; 118 int mdata_len, ret; 119 u8 nfproto; 120 121 ret = nla_parse_deprecated(tb, TCA_EM_IPT_MAX, data, data_len, 122 em_ipt_policy, NULL); 123 if (ret < 0) 124 return ret; 125 126 if (!tb[TCA_EM_IPT_HOOK] || !tb[TCA_EM_IPT_MATCH_NAME] || 127 !tb[TCA_EM_IPT_MATCH_DATA] || !tb[TCA_EM_IPT_NFPROTO]) 128 return -EINVAL; 129 130 nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]); 131 switch (nfproto) { 132 case NFPROTO_IPV4: 133 case NFPROTO_IPV6: 134 break; 135 default: 136 return -EINVAL; 137 } 138 139 match = get_xt_match(tb); 140 if (IS_ERR(match)) { 141 pr_err("unable to load match\n"); 142 return PTR_ERR(match); 143 } 144 145 mdata_len = XT_ALIGN(nla_len(tb[TCA_EM_IPT_MATCH_DATA])); 146 im = kzalloc(sizeof(*im) + mdata_len, GFP_KERNEL); 147 if (!im) { 148 ret = -ENOMEM; 149 goto err; 150 } 151 152 im->match = match; 153 im->hook = nla_get_u32(tb[TCA_EM_IPT_HOOK]); 154 im->nfproto = nfproto; 155 nla_memcpy(im->match_data, tb[TCA_EM_IPT_MATCH_DATA], mdata_len); 156 157 ret = check_match(net, im, mdata_len); 158 if (ret) 159 goto err; 160 161 em->datalen = sizeof(*im) + mdata_len; 162 em->data = (unsigned long)im; 163 return 0; 164 165 err: 166 kfree(im); 167 module_put(match->me); 168 return ret; 169 } 170 171 static void em_ipt_destroy(struct tcf_ematch *em) 172 { 173 struct em_ipt_match *im = (void *)em->data; 174 175 if (!im) 176 return; 177 178 if (im->match->destroy) { 179 struct xt_mtdtor_param par = { 180 .net = em->net, 181 .match = im->match, 182 .matchinfo = im->match_data, 183 .family = im->match->family 184 }; 185 im->match->destroy(&par); 186 } 187 module_put(im->match->me); 188 kfree((void *)im); 189 } 190 191 static int em_ipt_match(struct sk_buff *skb, struct tcf_ematch *em, 192 struct tcf_pkt_info *info) 193 { 194 const struct em_ipt_match *im = (const void *)em->data; 195 struct xt_action_param acpar = {}; 196 struct net_device *indev = NULL; 197 u8 nfproto = im->match->family; 198 struct nf_hook_state state; 199 int ret; 200 201 switch (tc_skb_protocol(skb)) { 202 case htons(ETH_P_IP): 203 if (!pskb_network_may_pull(skb, sizeof(struct iphdr))) 204 return 0; 205 if (nfproto == NFPROTO_UNSPEC) 206 nfproto = NFPROTO_IPV4; 207 break; 208 case htons(ETH_P_IPV6): 209 if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr))) 210 return 0; 211 if (nfproto == NFPROTO_UNSPEC) 212 nfproto = NFPROTO_IPV6; 213 break; 214 default: 215 return 0; 216 } 217 218 rcu_read_lock(); 219 220 if (skb->skb_iif) 221 indev = dev_get_by_index_rcu(em->net, skb->skb_iif); 222 223 nf_hook_state_init(&state, im->hook, nfproto, 224 indev ?: skb->dev, skb->dev, NULL, em->net, NULL); 225 226 acpar.match = im->match; 227 acpar.matchinfo = im->match_data; 228 acpar.state = &state; 229 230 ret = im->match->match(skb, &acpar); 231 232 rcu_read_unlock(); 233 return ret; 234 } 235 236 static int em_ipt_dump(struct sk_buff *skb, struct tcf_ematch *em) 237 { 238 struct em_ipt_match *im = (void *)em->data; 239 240 if (nla_put_string(skb, TCA_EM_IPT_MATCH_NAME, im->match->name) < 0) 241 return -EMSGSIZE; 242 if (nla_put_u32(skb, TCA_EM_IPT_HOOK, im->hook) < 0) 243 return -EMSGSIZE; 244 if (nla_put_u8(skb, TCA_EM_IPT_MATCH_REVISION, im->match->revision) < 0) 245 return -EMSGSIZE; 246 if (nla_put_u8(skb, TCA_EM_IPT_NFPROTO, im->nfproto) < 0) 247 return -EMSGSIZE; 248 if (nla_put(skb, TCA_EM_IPT_MATCH_DATA, 249 im->match->usersize ?: im->match->matchsize, 250 im->match_data) < 0) 251 return -EMSGSIZE; 252 253 return 0; 254 } 255 256 static struct tcf_ematch_ops em_ipt_ops = { 257 .kind = TCF_EM_IPT, 258 .change = em_ipt_change, 259 .destroy = em_ipt_destroy, 260 .match = em_ipt_match, 261 .dump = em_ipt_dump, 262 .owner = THIS_MODULE, 263 .link = LIST_HEAD_INIT(em_ipt_ops.link) 264 }; 265 266 static int __init init_em_ipt(void) 267 { 268 return tcf_em_register(&em_ipt_ops); 269 } 270 271 static void __exit exit_em_ipt(void) 272 { 273 tcf_em_unregister(&em_ipt_ops); 274 } 275 276 MODULE_LICENSE("GPL"); 277 MODULE_AUTHOR("Eyal Birger <eyal.birger@gmail.com>"); 278 MODULE_DESCRIPTION("TC extended match for IPtables matches"); 279 280 module_init(init_em_ipt); 281 module_exit(exit_em_ipt); 282 283 MODULE_ALIAS_TCF_EMATCH(TCF_EM_IPT); 284