at master 7.8 kB view raw
1/* SPDX-License-Identifier: GPL-2.0 */ 2#include <linux/kernel.h> 3#include <linux/init.h> 4#include <linux/module.h> 5#include <linux/spinlock.h> 6#include <linux/netlink.h> 7#include <linux/netfilter.h> 8#include <linux/netfilter/nf_tables.h> 9#include <net/netfilter/nf_tables.h> 10#include <net/netfilter/nf_conntrack.h> 11#include <net/netfilter/nf_conntrack_count.h> 12#include <net/netfilter/nf_conntrack_core.h> 13#include <net/netfilter/nf_conntrack_tuple.h> 14#include <net/netfilter/nf_conntrack_zones.h> 15 16struct nft_connlimit { 17 struct nf_conncount_list *list; 18 u32 limit; 19 bool invert; 20}; 21 22static inline void nft_connlimit_do_eval(struct nft_connlimit *priv, 23 struct nft_regs *regs, 24 const struct nft_pktinfo *pkt, 25 const struct nft_set_ext *ext) 26{ 27 unsigned int count; 28 int err; 29 30 err = nf_conncount_add_skb(nft_net(pkt), pkt->skb, nft_pf(pkt), priv->list); 31 if (err) { 32 if (err == -EEXIST) { 33 /* Call gc to update the list count if any connection has 34 * been closed already. This is useful for softlimit 35 * connections like limiting bandwidth based on a number 36 * of open connections. 37 */ 38 nf_conncount_gc_list(nft_net(pkt), priv->list); 39 } else { 40 regs->verdict.code = NF_DROP; 41 return; 42 } 43 } 44 45 count = READ_ONCE(priv->list->count); 46 47 if ((count > READ_ONCE(priv->limit)) ^ READ_ONCE(priv->invert)) { 48 regs->verdict.code = NFT_BREAK; 49 return; 50 } 51} 52 53static int nft_connlimit_do_init(const struct nft_ctx *ctx, 54 const struct nlattr * const tb[], 55 struct nft_connlimit *priv) 56{ 57 bool invert = false; 58 u32 flags, limit; 59 int err; 60 61 if (!tb[NFTA_CONNLIMIT_COUNT]) 62 return -EINVAL; 63 64 limit = ntohl(nla_get_be32(tb[NFTA_CONNLIMIT_COUNT])); 65 66 if (tb[NFTA_CONNLIMIT_FLAGS]) { 67 flags = ntohl(nla_get_be32(tb[NFTA_CONNLIMIT_FLAGS])); 68 if (flags & ~NFT_CONNLIMIT_F_INV) 69 return -EOPNOTSUPP; 70 if (flags & NFT_CONNLIMIT_F_INV) 71 invert = true; 72 } 73 74 priv->list = kmalloc(sizeof(*priv->list), GFP_KERNEL_ACCOUNT); 75 if (!priv->list) 76 return -ENOMEM; 77 78 nf_conncount_list_init(priv->list); 79 priv->limit = limit; 80 priv->invert = invert; 81 82 err = nf_ct_netns_get(ctx->net, ctx->family); 83 if (err < 0) 84 goto err_netns; 85 86 return 0; 87err_netns: 88 kfree(priv->list); 89 90 return err; 91} 92 93static void nft_connlimit_do_destroy(const struct nft_ctx *ctx, 94 struct nft_connlimit *priv) 95{ 96 nf_ct_netns_put(ctx->net, ctx->family); 97 nf_conncount_cache_free(priv->list); 98 kfree(priv->list); 99} 100 101static int nft_connlimit_do_dump(struct sk_buff *skb, 102 struct nft_connlimit *priv) 103{ 104 if (nla_put_be32(skb, NFTA_CONNLIMIT_COUNT, htonl(priv->limit))) 105 goto nla_put_failure; 106 if (priv->invert && 107 nla_put_be32(skb, NFTA_CONNLIMIT_FLAGS, htonl(NFT_CONNLIMIT_F_INV))) 108 goto nla_put_failure; 109 110 return 0; 111 112nla_put_failure: 113 return -1; 114} 115 116static inline void nft_connlimit_obj_eval(struct nft_object *obj, 117 struct nft_regs *regs, 118 const struct nft_pktinfo *pkt) 119{ 120 struct nft_connlimit *priv = nft_obj_data(obj); 121 122 nft_connlimit_do_eval(priv, regs, pkt, NULL); 123} 124 125static int nft_connlimit_obj_init(const struct nft_ctx *ctx, 126 const struct nlattr * const tb[], 127 struct nft_object *obj) 128{ 129 struct nft_connlimit *priv = nft_obj_data(obj); 130 131 return nft_connlimit_do_init(ctx, tb, priv); 132} 133 134static void nft_connlimit_obj_update(struct nft_object *obj, 135 struct nft_object *newobj) 136{ 137 struct nft_connlimit *newpriv = nft_obj_data(newobj); 138 struct nft_connlimit *priv = nft_obj_data(obj); 139 140 WRITE_ONCE(priv->limit, newpriv->limit); 141 WRITE_ONCE(priv->invert, newpriv->invert); 142} 143 144static void nft_connlimit_obj_destroy(const struct nft_ctx *ctx, 145 struct nft_object *obj) 146{ 147 struct nft_connlimit *priv = nft_obj_data(obj); 148 149 nft_connlimit_do_destroy(ctx, priv); 150} 151 152static int nft_connlimit_obj_dump(struct sk_buff *skb, 153 struct nft_object *obj, bool reset) 154{ 155 struct nft_connlimit *priv = nft_obj_data(obj); 156 157 return nft_connlimit_do_dump(skb, priv); 158} 159 160static const struct nla_policy nft_connlimit_policy[NFTA_CONNLIMIT_MAX + 1] = { 161 [NFTA_CONNLIMIT_COUNT] = { .type = NLA_U32 }, 162 [NFTA_CONNLIMIT_FLAGS] = { .type = NLA_U32 }, 163}; 164 165static struct nft_object_type nft_connlimit_obj_type; 166static const struct nft_object_ops nft_connlimit_obj_ops = { 167 .type = &nft_connlimit_obj_type, 168 .size = sizeof(struct nft_connlimit), 169 .eval = nft_connlimit_obj_eval, 170 .init = nft_connlimit_obj_init, 171 .destroy = nft_connlimit_obj_destroy, 172 .dump = nft_connlimit_obj_dump, 173 .update = nft_connlimit_obj_update, 174}; 175 176static struct nft_object_type nft_connlimit_obj_type __read_mostly = { 177 .type = NFT_OBJECT_CONNLIMIT, 178 .ops = &nft_connlimit_obj_ops, 179 .maxattr = NFTA_CONNLIMIT_MAX, 180 .policy = nft_connlimit_policy, 181 .owner = THIS_MODULE, 182}; 183 184static void nft_connlimit_eval(const struct nft_expr *expr, 185 struct nft_regs *regs, 186 const struct nft_pktinfo *pkt) 187{ 188 struct nft_connlimit *priv = nft_expr_priv(expr); 189 190 nft_connlimit_do_eval(priv, regs, pkt, NULL); 191} 192 193static int nft_connlimit_dump(struct sk_buff *skb, 194 const struct nft_expr *expr, bool reset) 195{ 196 struct nft_connlimit *priv = nft_expr_priv(expr); 197 198 return nft_connlimit_do_dump(skb, priv); 199} 200 201static int nft_connlimit_init(const struct nft_ctx *ctx, 202 const struct nft_expr *expr, 203 const struct nlattr * const tb[]) 204{ 205 struct nft_connlimit *priv = nft_expr_priv(expr); 206 207 return nft_connlimit_do_init(ctx, tb, priv); 208} 209 210static void nft_connlimit_destroy(const struct nft_ctx *ctx, 211 const struct nft_expr *expr) 212{ 213 struct nft_connlimit *priv = nft_expr_priv(expr); 214 215 nft_connlimit_do_destroy(ctx, priv); 216} 217 218static int nft_connlimit_clone(struct nft_expr *dst, const struct nft_expr *src, gfp_t gfp) 219{ 220 struct nft_connlimit *priv_dst = nft_expr_priv(dst); 221 struct nft_connlimit *priv_src = nft_expr_priv(src); 222 223 priv_dst->list = kmalloc(sizeof(*priv_dst->list), gfp); 224 if (!priv_dst->list) 225 return -ENOMEM; 226 227 nf_conncount_list_init(priv_dst->list); 228 priv_dst->limit = priv_src->limit; 229 priv_dst->invert = priv_src->invert; 230 231 return 0; 232} 233 234static void nft_connlimit_destroy_clone(const struct nft_ctx *ctx, 235 const struct nft_expr *expr) 236{ 237 struct nft_connlimit *priv = nft_expr_priv(expr); 238 239 nf_conncount_cache_free(priv->list); 240 kfree(priv->list); 241} 242 243static bool nft_connlimit_gc(struct net *net, const struct nft_expr *expr) 244{ 245 struct nft_connlimit *priv = nft_expr_priv(expr); 246 247 return nf_conncount_gc_list(net, priv->list); 248} 249 250static struct nft_expr_type nft_connlimit_type; 251static const struct nft_expr_ops nft_connlimit_ops = { 252 .type = &nft_connlimit_type, 253 .size = NFT_EXPR_SIZE(sizeof(struct nft_connlimit)), 254 .eval = nft_connlimit_eval, 255 .init = nft_connlimit_init, 256 .destroy = nft_connlimit_destroy, 257 .clone = nft_connlimit_clone, 258 .destroy_clone = nft_connlimit_destroy_clone, 259 .dump = nft_connlimit_dump, 260 .gc = nft_connlimit_gc, 261 .reduce = NFT_REDUCE_READONLY, 262}; 263 264static struct nft_expr_type nft_connlimit_type __read_mostly = { 265 .name = "connlimit", 266 .ops = &nft_connlimit_ops, 267 .policy = nft_connlimit_policy, 268 .maxattr = NFTA_CONNLIMIT_MAX, 269 .flags = NFT_EXPR_STATEFUL | NFT_EXPR_GC, 270 .owner = THIS_MODULE, 271}; 272 273static int __init nft_connlimit_module_init(void) 274{ 275 int err; 276 277 err = nft_register_obj(&nft_connlimit_obj_type); 278 if (err < 0) 279 return err; 280 281 err = nft_register_expr(&nft_connlimit_type); 282 if (err < 0) 283 goto err1; 284 285 return 0; 286err1: 287 nft_unregister_obj(&nft_connlimit_obj_type); 288 return err; 289} 290 291static void __exit nft_connlimit_module_exit(void) 292{ 293 nft_unregister_expr(&nft_connlimit_type); 294 nft_unregister_obj(&nft_connlimit_obj_type); 295} 296 297module_init(nft_connlimit_module_init); 298module_exit(nft_connlimit_module_exit); 299 300MODULE_LICENSE("GPL"); 301MODULE_AUTHOR("Pablo Neira Ayuso"); 302MODULE_ALIAS_NFT_EXPR("connlimit"); 303MODULE_ALIAS_NFT_OBJ(NFT_OBJECT_CONNLIMIT); 304MODULE_DESCRIPTION("nftables connlimit rule support");