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[] __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 int addrtype_validate_match_data(struct nlattr **tb, u8 mrev) 76 { 77 if (mrev != 1) { 78 pr_err("only addrtype match revision 1 supported"); 79 return -EINVAL; 80 } 81 82 return 0; 83 } 84 85 static const struct em_ipt_xt_match em_ipt_xt_matches[] = { 86 { 87 .match_name = "policy", 88 .validate_match_data = policy_validate_match_data 89 }, 90 { 91 .match_name = "addrtype", 92 .validate_match_data = addrtype_validate_match_data 93 }, 94 {} 95 }; 96 97 static struct xt_match *get_xt_match(struct nlattr **tb) 98 { 99 const struct em_ipt_xt_match *m; 100 struct nlattr *mname_attr; 101 u8 nfproto, mrev = 0; 102 int ret; 103 104 mname_attr = tb[TCA_EM_IPT_MATCH_NAME]; 105 for (m = em_ipt_xt_matches; m->match_name; m++) { 106 if (!nla_strcmp(mname_attr, m->match_name)) 107 break; 108 } 109 110 if (!m->match_name) { 111 pr_err("Unsupported xt match"); 112 return ERR_PTR(-EINVAL); 113 } 114 115 if (tb[TCA_EM_IPT_MATCH_REVISION]) 116 mrev = nla_get_u8(tb[TCA_EM_IPT_MATCH_REVISION]); 117 118 ret = m->validate_match_data(tb, mrev); 119 if (ret < 0) 120 return ERR_PTR(ret); 121 122 nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]); 123 return xt_request_find_match(nfproto, m->match_name, mrev); 124 } 125 126 static int em_ipt_change(struct net *net, void *data, int data_len, 127 struct tcf_ematch *em) 128 { 129 struct nlattr *tb[TCA_EM_IPT_MAX + 1]; 130 struct em_ipt_match *im = NULL; 131 struct xt_match *match; 132 int mdata_len, ret; 133 u8 nfproto; 134 135 ret = nla_parse_deprecated(tb, TCA_EM_IPT_MAX, data, data_len, 136 em_ipt_policy, NULL); 137 if (ret < 0) 138 return ret; 139 140 if (!tb[TCA_EM_IPT_HOOK] || !tb[TCA_EM_IPT_MATCH_NAME] || 141 !tb[TCA_EM_IPT_MATCH_DATA] || !tb[TCA_EM_IPT_NFPROTO]) 142 return -EINVAL; 143 144 nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]); 145 switch (nfproto) { 146 case NFPROTO_IPV4: 147 case NFPROTO_IPV6: 148 break; 149 default: 150 return -EINVAL; 151 } 152 153 match = get_xt_match(tb); 154 if (IS_ERR(match)) { 155 pr_err("unable to load match\n"); 156 return PTR_ERR(match); 157 } 158 159 mdata_len = XT_ALIGN(nla_len(tb[TCA_EM_IPT_MATCH_DATA])); 160 im = kzalloc(sizeof(*im) + mdata_len, GFP_KERNEL); 161 if (!im) { 162 ret = -ENOMEM; 163 goto err; 164 } 165 166 im->match = match; 167 im->hook = nla_get_u32(tb[TCA_EM_IPT_HOOK]); 168 im->nfproto = nfproto; 169 nla_memcpy(im->match_data, tb[TCA_EM_IPT_MATCH_DATA], mdata_len); 170 171 ret = check_match(net, im, mdata_len); 172 if (ret) 173 goto err; 174 175 em->datalen = sizeof(*im) + mdata_len; 176 em->data = (unsigned long)im; 177 return 0; 178 179 err: 180 kfree(im); 181 module_put(match->me); 182 return ret; 183 } 184 185 static void em_ipt_destroy(struct tcf_ematch *em) 186 { 187 struct em_ipt_match *im = (void *)em->data; 188 189 if (!im) 190 return; 191 192 if (im->match->destroy) { 193 struct xt_mtdtor_param par = { 194 .net = em->net, 195 .match = im->match, 196 .matchinfo = im->match_data, 197 .family = im->match->family 198 }; 199 im->match->destroy(&par); 200 } 201 module_put(im->match->me); 202 kfree(im); 203 } 204 205 static int em_ipt_match(struct sk_buff *skb, struct tcf_ematch *em, 206 struct tcf_pkt_info *info) 207 { 208 const struct em_ipt_match *im = (const void *)em->data; 209 struct xt_action_param acpar = {}; 210 struct net_device *indev = NULL; 211 u8 nfproto = im->match->family; 212 struct nf_hook_state state; 213 int ret; 214 215 switch (tc_skb_protocol(skb)) { 216 case htons(ETH_P_IP): 217 if (!pskb_network_may_pull(skb, sizeof(struct iphdr))) 218 return 0; 219 if (nfproto == NFPROTO_UNSPEC) 220 nfproto = NFPROTO_IPV4; 221 break; 222 case htons(ETH_P_IPV6): 223 if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr))) 224 return 0; 225 if (nfproto == NFPROTO_UNSPEC) 226 nfproto = NFPROTO_IPV6; 227 break; 228 default: 229 return 0; 230 } 231 232 rcu_read_lock(); 233 234 if (skb->skb_iif) 235 indev = dev_get_by_index_rcu(em->net, skb->skb_iif); 236 237 nf_hook_state_init(&state, im->hook, nfproto, 238 indev ?: skb->dev, skb->dev, NULL, em->net, NULL); 239 240 acpar.match = im->match; 241 acpar.matchinfo = im->match_data; 242 acpar.state = &state; 243 244 ret = im->match->match(skb, &acpar); 245 246 rcu_read_unlock(); 247 return ret; 248 } 249 250 static int em_ipt_dump(struct sk_buff *skb, struct tcf_ematch *em) 251 { 252 struct em_ipt_match *im = (void *)em->data; 253 254 if (nla_put_string(skb, TCA_EM_IPT_MATCH_NAME, im->match->name) < 0) 255 return -EMSGSIZE; 256 if (nla_put_u32(skb, TCA_EM_IPT_HOOK, im->hook) < 0) 257 return -EMSGSIZE; 258 if (nla_put_u8(skb, TCA_EM_IPT_MATCH_REVISION, im->match->revision) < 0) 259 return -EMSGSIZE; 260 if (nla_put_u8(skb, TCA_EM_IPT_NFPROTO, im->nfproto) < 0) 261 return -EMSGSIZE; 262 if (nla_put(skb, TCA_EM_IPT_MATCH_DATA, 263 im->match->usersize ?: im->match->matchsize, 264 im->match_data) < 0) 265 return -EMSGSIZE; 266 267 return 0; 268 } 269 270 static struct tcf_ematch_ops em_ipt_ops = { 271 .kind = TCF_EM_IPT, 272 .change = em_ipt_change, 273 .destroy = em_ipt_destroy, 274 .match = em_ipt_match, 275 .dump = em_ipt_dump, 276 .owner = THIS_MODULE, 277 .link = LIST_HEAD_INIT(em_ipt_ops.link) 278 }; 279 280 static int __init init_em_ipt(void) 281 { 282 return tcf_em_register(&em_ipt_ops); 283 } 284 285 static void __exit exit_em_ipt(void) 286 { 287 tcf_em_unregister(&em_ipt_ops); 288 } 289 290 MODULE_LICENSE("GPL"); 291 MODULE_AUTHOR("Eyal Birger <eyal.birger@gmail.com>"); 292 MODULE_DESCRIPTION("TC extended match for IPtables matches"); 293 294 module_init(init_em_ipt); 295 module_exit(exit_em_ipt); 296 297 MODULE_ALIAS_TCF_EMATCH(TCF_EM_IPT); 298