Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: nf_tables: add support for dynamic set updates

Add a new "dynset" expression for dynamic set updates.

A new set op ->update() is added which, for non existant elements,
invokes an initialization callback and inserts the new element.
For both new or existing elements the extenstion pointer is returned
to the caller to optionally perform timer updates or other actions.

Element removal is not supported so far, however that seems to be a
rather exotic need and can be added later on.

Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

authored by

Patrick McHardy and committed by
Pablo Neira Ayuso
22fe54d5 11113e19

+315 -6
+17
include/net/netfilter/nf_tables.h
··· 196 196 }; 197 197 198 198 struct nft_set_ext; 199 + struct nft_expr; 199 200 200 201 /** 201 202 * struct nft_set_ops - nf_tables set operations ··· 219 218 bool (*lookup)(const struct nft_set *set, 220 219 const struct nft_data *key, 221 220 const struct nft_set_ext **ext); 221 + bool (*update)(struct nft_set *set, 222 + const struct nft_data *key, 223 + void *(*new)(struct nft_set *, 224 + const struct nft_expr *, 225 + struct nft_data []), 226 + const struct nft_expr *expr, 227 + struct nft_data data[], 228 + const struct nft_set_ext **ext); 229 + 222 230 int (*insert)(const struct nft_set *set, 223 231 const struct nft_set_elem *elem); 224 232 void (*activate)(const struct nft_set *set, ··· 476 466 return elem + set->ops->elemsize; 477 467 } 478 468 469 + void *nft_set_elem_init(const struct nft_set *set, 470 + const struct nft_set_ext_tmpl *tmpl, 471 + const struct nft_data *key, 472 + const struct nft_data *data, 473 + u64 timeout, gfp_t gfp); 479 474 void nft_set_elem_destroy(const struct nft_set *set, void *elem); 480 475 481 476 /** ··· 859 844 /* Use ACCESS_ONCE() to prevent refetching the value for atomicity */ 860 845 return 1 << ACCESS_ONCE(net->nft.gencursor); 861 846 } 847 + 848 + #define NFT_GENMASK_ANY ((1 << 0) | (1 << 1)) 862 849 863 850 /* 864 851 * Set element transaction helpers
+3
include/net/netfilter/nf_tables_core.h
··· 31 31 int nft_lookup_module_init(void); 32 32 void nft_lookup_module_exit(void); 33 33 34 + int nft_dynset_module_init(void); 35 + void nft_dynset_module_exit(void); 36 + 34 37 int nft_bitwise_module_init(void); 35 38 void nft_bitwise_module_exit(void); 36 39
+27
include/uapi/linux/netfilter/nf_tables.h
··· 515 515 }; 516 516 #define NFTA_LOOKUP_MAX (__NFTA_LOOKUP_MAX - 1) 517 517 518 + enum nft_dynset_ops { 519 + NFT_DYNSET_OP_ADD, 520 + NFT_DYNSET_OP_UPDATE, 521 + }; 522 + 523 + /** 524 + * enum nft_dynset_attributes - dynset expression attributes 525 + * 526 + * @NFTA_DYNSET_SET_NAME: name of set the to add data to (NLA_STRING) 527 + * @NFTA_DYNSET_SET_ID: uniquely identifier of the set in the transaction (NLA_U32) 528 + * @NFTA_DYNSET_OP: operation (NLA_U32) 529 + * @NFTA_DYNSET_SREG_KEY: source register of the key (NLA_U32) 530 + * @NFTA_DYNSET_SREG_DATA: source register of the data (NLA_U32) 531 + * @NFTA_DYNSET_TIMEOUT: timeout value for the new element (NLA_U64) 532 + */ 533 + enum nft_dynset_attributes { 534 + NFTA_DYNSET_UNSPEC, 535 + NFTA_DYNSET_SET_NAME, 536 + NFTA_DYNSET_SET_ID, 537 + NFTA_DYNSET_OP, 538 + NFTA_DYNSET_SREG_KEY, 539 + NFTA_DYNSET_SREG_DATA, 540 + NFTA_DYNSET_TIMEOUT, 541 + __NFTA_DYNSET_MAX, 542 + }; 543 + #define NFTA_DYNSET_MAX (__NFTA_DYNSET_MAX - 1) 544 + 518 545 /** 519 546 * enum nft_payload_bases - nf_tables payload expression offset bases 520 547 *
+1 -1
net/netfilter/Makefile
··· 70 70 71 71 # nf_tables 72 72 nf_tables-objs += nf_tables_core.o nf_tables_api.o 73 - nf_tables-objs += nft_immediate.o nft_cmp.o nft_lookup.o 73 + nf_tables-objs += nft_immediate.o nft_cmp.o nft_lookup.o nft_dynset.o 74 74 nf_tables-objs += nft_bitwise.o nft_byteorder.o nft_payload.o 75 75 76 76 obj-$(CONFIG_NF_TABLES) += nf_tables.o
+5 -5
net/netfilter/nf_tables_api.c
··· 3183 3183 return trans; 3184 3184 } 3185 3185 3186 - static void *nft_set_elem_init(const struct nft_set *set, 3187 - const struct nft_set_ext_tmpl *tmpl, 3188 - const struct nft_data *key, 3189 - const struct nft_data *data, 3190 - u64 timeout, gfp_t gfp) 3186 + void *nft_set_elem_init(const struct nft_set *set, 3187 + const struct nft_set_ext_tmpl *tmpl, 3188 + const struct nft_data *key, 3189 + const struct nft_data *data, 3190 + u64 timeout, gfp_t gfp) 3191 3191 { 3192 3192 struct nft_set_ext *ext; 3193 3193 void *elem;
+7
net/netfilter/nf_tables_core.c
··· 239 239 if (err < 0) 240 240 goto err6; 241 241 242 + err = nft_dynset_module_init(); 243 + if (err < 0) 244 + goto err7; 245 + 242 246 return 0; 243 247 248 + err7: 249 + nft_payload_module_exit(); 244 250 err6: 245 251 nft_byteorder_module_exit(); 246 252 err5: ··· 263 257 264 258 void nf_tables_core_module_exit(void) 265 259 { 260 + nft_dynset_module_exit(); 266 261 nft_payload_module_exit(); 267 262 nft_byteorder_module_exit(); 268 263 nft_bitwise_module_exit();
+218
net/netfilter/nft_dynset.c
··· 1 + /* 2 + * Copyright (c) 2015 Patrick McHardy <kaber@trash.net> 3 + * 4 + * This program is free software; you can redistribute it and/or modify 5 + * it under the terms of the GNU General Public License version 2 as 6 + * published by the Free Software Foundation. 7 + * 8 + */ 9 + 10 + #include <linux/kernel.h> 11 + #include <linux/module.h> 12 + #include <linux/init.h> 13 + #include <linux/netlink.h> 14 + #include <linux/netfilter.h> 15 + #include <linux/netfilter/nf_tables.h> 16 + #include <net/netfilter/nf_tables.h> 17 + #include <net/netfilter/nf_tables_core.h> 18 + 19 + struct nft_dynset { 20 + struct nft_set *set; 21 + struct nft_set_ext_tmpl tmpl; 22 + enum nft_dynset_ops op:8; 23 + enum nft_registers sreg_key:8; 24 + enum nft_registers sreg_data:8; 25 + u64 timeout; 26 + struct nft_set_binding binding; 27 + }; 28 + 29 + static void *nft_dynset_new(struct nft_set *set, const struct nft_expr *expr, 30 + struct nft_data data[NFT_REG_MAX + 1]) 31 + { 32 + const struct nft_dynset *priv = nft_expr_priv(expr); 33 + u64 timeout; 34 + void *elem; 35 + 36 + if (set->size && !atomic_add_unless(&set->nelems, 1, set->size)) 37 + return NULL; 38 + 39 + timeout = priv->timeout ? : set->timeout; 40 + elem = nft_set_elem_init(set, &priv->tmpl, 41 + &data[priv->sreg_key], &data[priv->sreg_data], 42 + timeout, GFP_ATOMIC); 43 + if (elem == NULL) { 44 + if (set->size) 45 + atomic_dec(&set->nelems); 46 + } 47 + return elem; 48 + } 49 + 50 + static void nft_dynset_eval(const struct nft_expr *expr, 51 + struct nft_data data[NFT_REG_MAX + 1], 52 + const struct nft_pktinfo *pkt) 53 + { 54 + const struct nft_dynset *priv = nft_expr_priv(expr); 55 + struct nft_set *set = priv->set; 56 + const struct nft_set_ext *ext; 57 + u64 timeout; 58 + 59 + if (set->ops->update(set, &data[priv->sreg_key], nft_dynset_new, 60 + expr, data, &ext)) { 61 + if (priv->op == NFT_DYNSET_OP_UPDATE && 62 + nft_set_ext_exists(ext, NFT_SET_EXT_EXPIRATION)) { 63 + timeout = priv->timeout ? : set->timeout; 64 + *nft_set_ext_expiration(ext) = jiffies + timeout; 65 + return; 66 + } 67 + } 68 + 69 + data[NFT_REG_VERDICT].verdict = NFT_BREAK; 70 + } 71 + 72 + static const struct nla_policy nft_dynset_policy[NFTA_DYNSET_MAX + 1] = { 73 + [NFTA_DYNSET_SET_NAME] = { .type = NLA_STRING }, 74 + [NFTA_DYNSET_SET_ID] = { .type = NLA_U32 }, 75 + [NFTA_DYNSET_OP] = { .type = NLA_U32 }, 76 + [NFTA_DYNSET_SREG_KEY] = { .type = NLA_U32 }, 77 + [NFTA_DYNSET_SREG_DATA] = { .type = NLA_U32 }, 78 + [NFTA_DYNSET_TIMEOUT] = { .type = NLA_U64 }, 79 + }; 80 + 81 + static int nft_dynset_init(const struct nft_ctx *ctx, 82 + const struct nft_expr *expr, 83 + const struct nlattr * const tb[]) 84 + { 85 + struct nft_dynset *priv = nft_expr_priv(expr); 86 + struct nft_set *set; 87 + u64 timeout; 88 + int err; 89 + 90 + if (tb[NFTA_DYNSET_SET_NAME] == NULL || 91 + tb[NFTA_DYNSET_OP] == NULL || 92 + tb[NFTA_DYNSET_SREG_KEY] == NULL) 93 + return -EINVAL; 94 + 95 + set = nf_tables_set_lookup(ctx->table, tb[NFTA_DYNSET_SET_NAME]); 96 + if (IS_ERR(set)) { 97 + if (tb[NFTA_DYNSET_SET_ID]) 98 + set = nf_tables_set_lookup_byid(ctx->net, 99 + tb[NFTA_DYNSET_SET_ID]); 100 + if (IS_ERR(set)) 101 + return PTR_ERR(set); 102 + } 103 + 104 + if (set->flags & NFT_SET_CONSTANT) 105 + return -EBUSY; 106 + 107 + priv->op = ntohl(nla_get_be32(tb[NFTA_DYNSET_OP])); 108 + switch (priv->op) { 109 + case NFT_DYNSET_OP_ADD: 110 + break; 111 + case NFT_DYNSET_OP_UPDATE: 112 + if (!(set->flags & NFT_SET_TIMEOUT)) 113 + return -EOPNOTSUPP; 114 + break; 115 + default: 116 + return -EOPNOTSUPP; 117 + } 118 + 119 + timeout = 0; 120 + if (tb[NFTA_DYNSET_TIMEOUT] != NULL) { 121 + if (!(set->flags & NFT_SET_TIMEOUT)) 122 + return -EINVAL; 123 + timeout = be64_to_cpu(nla_get_be64(tb[NFTA_DYNSET_TIMEOUT])); 124 + } 125 + 126 + priv->sreg_key = ntohl(nla_get_be32(tb[NFTA_DYNSET_SREG_KEY])); 127 + err = nft_validate_input_register(priv->sreg_key); 128 + if (err < 0) 129 + return err; 130 + 131 + if (tb[NFTA_DYNSET_SREG_DATA] != NULL) { 132 + if (!(set->flags & NFT_SET_MAP)) 133 + return -EINVAL; 134 + if (set->dtype == NFT_DATA_VERDICT) 135 + return -EOPNOTSUPP; 136 + 137 + priv->sreg_data = ntohl(nla_get_be32(tb[NFTA_DYNSET_SREG_DATA])); 138 + err = nft_validate_input_register(priv->sreg_data); 139 + if (err < 0) 140 + return err; 141 + } else if (set->flags & NFT_SET_MAP) 142 + return -EINVAL; 143 + 144 + nft_set_ext_prepare(&priv->tmpl); 145 + nft_set_ext_add_length(&priv->tmpl, NFT_SET_EXT_KEY, set->klen); 146 + if (set->flags & NFT_SET_MAP) 147 + nft_set_ext_add_length(&priv->tmpl, NFT_SET_EXT_DATA, set->dlen); 148 + if (set->flags & NFT_SET_TIMEOUT) { 149 + if (timeout || set->timeout) 150 + nft_set_ext_add(&priv->tmpl, NFT_SET_EXT_EXPIRATION); 151 + } 152 + 153 + priv->timeout = timeout; 154 + 155 + err = nf_tables_bind_set(ctx, set, &priv->binding); 156 + if (err < 0) 157 + return err; 158 + 159 + priv->set = set; 160 + return 0; 161 + } 162 + 163 + static void nft_dynset_destroy(const struct nft_ctx *ctx, 164 + const struct nft_expr *expr) 165 + { 166 + struct nft_dynset *priv = nft_expr_priv(expr); 167 + 168 + nf_tables_unbind_set(ctx, priv->set, &priv->binding); 169 + } 170 + 171 + static int nft_dynset_dump(struct sk_buff *skb, const struct nft_expr *expr) 172 + { 173 + const struct nft_dynset *priv = nft_expr_priv(expr); 174 + 175 + if (nla_put_be32(skb, NFTA_DYNSET_SREG_KEY, htonl(priv->sreg_key))) 176 + goto nla_put_failure; 177 + if (priv->set->flags & NFT_SET_MAP && 178 + nla_put_be32(skb, NFTA_DYNSET_SREG_DATA, htonl(priv->sreg_data))) 179 + goto nla_put_failure; 180 + if (nla_put_be32(skb, NFTA_DYNSET_OP, htonl(priv->op))) 181 + goto nla_put_failure; 182 + if (nla_put_string(skb, NFTA_DYNSET_SET_NAME, priv->set->name)) 183 + goto nla_put_failure; 184 + if (nla_put_be64(skb, NFTA_DYNSET_TIMEOUT, cpu_to_be64(priv->timeout))) 185 + goto nla_put_failure; 186 + return 0; 187 + 188 + nla_put_failure: 189 + return -1; 190 + } 191 + 192 + static struct nft_expr_type nft_dynset_type; 193 + static const struct nft_expr_ops nft_dynset_ops = { 194 + .type = &nft_dynset_type, 195 + .size = NFT_EXPR_SIZE(sizeof(struct nft_dynset)), 196 + .eval = nft_dynset_eval, 197 + .init = nft_dynset_init, 198 + .destroy = nft_dynset_destroy, 199 + .dump = nft_dynset_dump, 200 + }; 201 + 202 + static struct nft_expr_type nft_dynset_type __read_mostly = { 203 + .name = "dynset", 204 + .ops = &nft_dynset_ops, 205 + .policy = nft_dynset_policy, 206 + .maxattr = NFTA_DYNSET_MAX, 207 + .owner = THIS_MODULE, 208 + }; 209 + 210 + int __init nft_dynset_module_init(void) 211 + { 212 + return nft_register_expr(&nft_dynset_type); 213 + } 214 + 215 + void nft_dynset_module_exit(void) 216 + { 217 + nft_unregister_expr(&nft_dynset_type); 218 + }
+37
net/netfilter/nft_hash.c
··· 90 90 return !!he; 91 91 } 92 92 93 + static bool nft_hash_update(struct nft_set *set, const struct nft_data *key, 94 + void *(*new)(struct nft_set *, 95 + const struct nft_expr *, 96 + struct nft_data []), 97 + const struct nft_expr *expr, 98 + struct nft_data data[], 99 + const struct nft_set_ext **ext) 100 + { 101 + struct nft_hash *priv = nft_set_priv(set); 102 + struct nft_hash_elem *he; 103 + struct nft_hash_cmp_arg arg = { 104 + .genmask = NFT_GENMASK_ANY, 105 + .set = set, 106 + .key = key, 107 + }; 108 + 109 + he = rhashtable_lookup_fast(&priv->ht, &arg, nft_hash_params); 110 + if (he != NULL) 111 + goto out; 112 + 113 + he = new(set, expr, data); 114 + if (he == NULL) 115 + goto err1; 116 + if (rhashtable_lookup_insert_key(&priv->ht, &arg, &he->node, 117 + nft_hash_params)) 118 + goto err2; 119 + out: 120 + *ext = &he->ext; 121 + return true; 122 + 123 + err2: 124 + nft_set_elem_destroy(set, he); 125 + err1: 126 + return false; 127 + } 128 + 93 129 static int nft_hash_insert(const struct nft_set *set, 94 130 const struct nft_set_elem *elem) 95 131 { ··· 371 335 .deactivate = nft_hash_deactivate, 372 336 .remove = nft_hash_remove, 373 337 .lookup = nft_hash_lookup, 338 + .update = nft_hash_update, 374 339 .walk = nft_hash_walk, 375 340 .features = NFT_SET_MAP | NFT_SET_TIMEOUT, 376 341 .owner = THIS_MODULE,