linux/net/sched/em_ipt.c
<<
>>
Prefs
   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
  21struct em_ipt_match {
  22        const struct xt_match *match;
  23        u32 hook;
  24        u8 nfproto;
  25        u8 match_data[] __aligned(8);
  26};
  27
  28struct em_ipt_xt_match {
  29        char *match_name;
  30        int (*validate_match_data)(struct nlattr **tb, u8 mrev);
  31};
  32
  33static 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
  42static 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
  60static 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
  75static 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
  85static 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
  97static 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
 126static 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
 179err:
 180        kfree(im);
 181        module_put(match->me);
 182        return ret;
 183}
 184
 185static 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
 205static 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 (skb_protocol(skb, true)) {
 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
 250static 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
 270static 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
 280static int __init init_em_ipt(void)
 281{
 282        return tcf_em_register(&em_ipt_ops);
 283}
 284
 285static void __exit exit_em_ipt(void)
 286{
 287        tcf_em_unregister(&em_ipt_ops);
 288}
 289
 290MODULE_LICENSE("GPL");
 291MODULE_AUTHOR("Eyal Birger <eyal.birger@gmail.com>");
 292MODULE_DESCRIPTION("TC extended match for IPtables matches");
 293
 294module_init(init_em_ipt);
 295module_exit(exit_em_ipt);
 296
 297MODULE_ALIAS_TCF_EMATCH(TCF_EM_IPT);
 298