linux/net/sched/act_connmark.c
<<
>>
Prefs
   1/*
   2 * net/sched/act_connmark.c  netfilter connmark retriever action
   3 * skb mark is over-written
   4 *
   5 * Copyright (c) 2011 Felix Fietkau <nbd@openwrt.org>
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License as published by
   9 * the Free Software Foundation; either version 2 of the License, or
  10 * (at your option) any later version.
  11*/
  12
  13#include <linux/module.h>
  14#include <linux/init.h>
  15#include <linux/kernel.h>
  16#include <linux/skbuff.h>
  17#include <linux/rtnetlink.h>
  18#include <linux/pkt_cls.h>
  19#include <linux/ip.h>
  20#include <linux/ipv6.h>
  21#include <net/netlink.h>
  22#include <net/pkt_sched.h>
  23#include <net/act_api.h>
  24#include <uapi/linux/tc_act/tc_connmark.h>
  25#include <net/tc_act/tc_connmark.h>
  26
  27#include <net/netfilter/nf_conntrack.h>
  28#include <net/netfilter/nf_conntrack_core.h>
  29#include <net/netfilter/nf_conntrack_zones.h>
  30
  31#define CONNMARK_TAB_MASK     3
  32
  33static unsigned int connmark_net_id;
  34static struct tc_action_ops act_connmark_ops;
  35
  36static int tcf_connmark(struct sk_buff *skb, const struct tc_action *a,
  37                        struct tcf_result *res)
  38{
  39        const struct nf_conntrack_tuple_hash *thash;
  40        struct nf_conntrack_tuple tuple;
  41        enum ip_conntrack_info ctinfo;
  42        struct tcf_connmark_info *ca = to_connmark(a);
  43        struct nf_conntrack_zone zone;
  44        struct nf_conn *c;
  45        int proto;
  46
  47        spin_lock(&ca->tcf_lock);
  48        tcf_lastuse_update(&ca->tcf_tm);
  49        bstats_update(&ca->tcf_bstats, skb);
  50
  51        if (skb->protocol == htons(ETH_P_IP)) {
  52                if (skb->len < sizeof(struct iphdr))
  53                        goto out;
  54
  55                proto = NFPROTO_IPV4;
  56        } else if (skb->protocol == htons(ETH_P_IPV6)) {
  57                if (skb->len < sizeof(struct ipv6hdr))
  58                        goto out;
  59
  60                proto = NFPROTO_IPV6;
  61        } else {
  62                goto out;
  63        }
  64
  65        c = nf_ct_get(skb, &ctinfo);
  66        if (c) {
  67                skb->mark = c->mark;
  68                /* using overlimits stats to count how many packets marked */
  69                ca->tcf_qstats.overlimits++;
  70                goto out;
  71        }
  72
  73        if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb),
  74                               proto, ca->net, &tuple))
  75                goto out;
  76
  77        zone.id = ca->zone;
  78        zone.dir = NF_CT_DEFAULT_ZONE_DIR;
  79
  80        thash = nf_conntrack_find_get(ca->net, &zone, &tuple);
  81        if (!thash)
  82                goto out;
  83
  84        c = nf_ct_tuplehash_to_ctrack(thash);
  85        /* using overlimits stats to count how many packets marked */
  86        ca->tcf_qstats.overlimits++;
  87        skb->mark = c->mark;
  88        nf_ct_put(c);
  89
  90out:
  91        spin_unlock(&ca->tcf_lock);
  92        return ca->tcf_action;
  93}
  94
  95static const struct nla_policy connmark_policy[TCA_CONNMARK_MAX + 1] = {
  96        [TCA_CONNMARK_PARMS] = { .len = sizeof(struct tc_connmark) },
  97};
  98
  99static int tcf_connmark_init(struct net *net, struct nlattr *nla,
 100                             struct nlattr *est, struct tc_action **a,
 101                             int ovr, int bind)
 102{
 103        struct tc_action_net *tn = net_generic(net, connmark_net_id);
 104        struct nlattr *tb[TCA_CONNMARK_MAX + 1];
 105        struct tcf_connmark_info *ci;
 106        struct tc_connmark *parm;
 107        int ret = 0;
 108
 109        if (!nla)
 110                return -EINVAL;
 111
 112        ret = nla_parse_nested(tb, TCA_CONNMARK_MAX, nla, connmark_policy,
 113                               NULL);
 114        if (ret < 0)
 115                return ret;
 116
 117        if (!tb[TCA_CONNMARK_PARMS])
 118                return -EINVAL;
 119
 120        parm = nla_data(tb[TCA_CONNMARK_PARMS]);
 121
 122        if (!tcf_hash_check(tn, parm->index, a, bind)) {
 123                ret = tcf_hash_create(tn, parm->index, est, a,
 124                                      &act_connmark_ops, bind, false);
 125                if (ret)
 126                        return ret;
 127
 128                ci = to_connmark(*a);
 129                ci->tcf_action = parm->action;
 130                ci->net = net;
 131                ci->zone = parm->zone;
 132
 133                tcf_hash_insert(tn, *a);
 134                ret = ACT_P_CREATED;
 135        } else {
 136                ci = to_connmark(*a);
 137                if (bind)
 138                        return 0;
 139                tcf_hash_release(*a, bind);
 140                if (!ovr)
 141                        return -EEXIST;
 142                /* replacing action and zone */
 143                ci->tcf_action = parm->action;
 144                ci->zone = parm->zone;
 145        }
 146
 147        return ret;
 148}
 149
 150static inline int tcf_connmark_dump(struct sk_buff *skb, struct tc_action *a,
 151                                    int bind, int ref)
 152{
 153        unsigned char *b = skb_tail_pointer(skb);
 154        struct tcf_connmark_info *ci = to_connmark(a);
 155
 156        struct tc_connmark opt = {
 157                .index   = ci->tcf_index,
 158                .refcnt  = ci->tcf_refcnt - ref,
 159                .bindcnt = ci->tcf_bindcnt - bind,
 160                .action  = ci->tcf_action,
 161                .zone   = ci->zone,
 162        };
 163        struct tcf_t t;
 164
 165        if (nla_put(skb, TCA_CONNMARK_PARMS, sizeof(opt), &opt))
 166                goto nla_put_failure;
 167
 168        tcf_tm_dump(&t, &ci->tcf_tm);
 169        if (nla_put_64bit(skb, TCA_CONNMARK_TM, sizeof(t), &t,
 170                          TCA_CONNMARK_PAD))
 171                goto nla_put_failure;
 172
 173        return skb->len;
 174nla_put_failure:
 175        nlmsg_trim(skb, b);
 176        return -1;
 177}
 178
 179static int tcf_connmark_walker(struct net *net, struct sk_buff *skb,
 180                               struct netlink_callback *cb, int type,
 181                               const struct tc_action_ops *ops)
 182{
 183        struct tc_action_net *tn = net_generic(net, connmark_net_id);
 184
 185        return tcf_generic_walker(tn, skb, cb, type, ops);
 186}
 187
 188static int tcf_connmark_search(struct net *net, struct tc_action **a, u32 index)
 189{
 190        struct tc_action_net *tn = net_generic(net, connmark_net_id);
 191
 192        return tcf_hash_search(tn, a, index);
 193}
 194
 195static struct tc_action_ops act_connmark_ops = {
 196        .kind           =       "connmark",
 197        .type           =       TCA_ACT_CONNMARK,
 198        .owner          =       THIS_MODULE,
 199        .act            =       tcf_connmark,
 200        .dump           =       tcf_connmark_dump,
 201        .init           =       tcf_connmark_init,
 202        .walk           =       tcf_connmark_walker,
 203        .lookup         =       tcf_connmark_search,
 204        .size           =       sizeof(struct tcf_connmark_info),
 205};
 206
 207static __net_init int connmark_init_net(struct net *net)
 208{
 209        struct tc_action_net *tn = net_generic(net, connmark_net_id);
 210
 211        return tc_action_net_init(tn, &act_connmark_ops, CONNMARK_TAB_MASK);
 212}
 213
 214static void __net_exit connmark_exit_net(struct net *net)
 215{
 216        struct tc_action_net *tn = net_generic(net, connmark_net_id);
 217
 218        tc_action_net_exit(tn);
 219}
 220
 221static struct pernet_operations connmark_net_ops = {
 222        .init = connmark_init_net,
 223        .exit = connmark_exit_net,
 224        .id   = &connmark_net_id,
 225        .size = sizeof(struct tc_action_net),
 226};
 227
 228static int __init connmark_init_module(void)
 229{
 230        return tcf_register_action(&act_connmark_ops, &connmark_net_ops);
 231}
 232
 233static void __exit connmark_cleanup_module(void)
 234{
 235        tcf_unregister_action(&act_connmark_ops, &connmark_net_ops);
 236}
 237
 238module_init(connmark_init_module);
 239module_exit(connmark_cleanup_module);
 240MODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>");
 241MODULE_DESCRIPTION("Connection tracking mark restoring");
 242MODULE_LICENSE("GPL");
 243
 244