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