From a43cc8d53096de069fab5d9bf1a2cc7b655c21c7 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 6 Mar 2018 18:58:29 +0100 Subject: src: support for get element command You need a Linux kernel >= 4.15 to use this feature. This patch allows us to dump the content of an existing set. # nft list ruleset table ip x { set x { type ipv4_addr flags interval elements = { 1.1.1.1-2.2.2.2, 3.3.3.3, 5.5.5.5-6.6.6.6 } } } You check if a single element exists in the set: # nft get element x x { 1.1.1.5 } table ip x { set x { type ipv4_addr flags interval elements = { 1.1.1.1-2.2.2.2 } } } Output means '1.1.1.5' belongs to the '1.1.1.1-2.2.2.2' interval. You can also check for intervals: # nft get element x x { 1.1.1.1-2.2.2.2 } table ip x { set x { type ipv4_addr flags interval elements = { 1.1.1.1-2.2.2.2 } } } If you try to check for an element that doesn't exist, an error is displayed. # nft get element x x { 1.1.1.0 } Error: Could not receive set elements: No such file or directory get element x x { 1.1.1.0 } ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You can also check for multiple elements in one go: # nft get element x x { 1.1.1.5, 5.5.5.10 } table ip x { set x { type ipv4_addr flags interval elements = { 1.1.1.1-2.2.2.2, 5.5.5.5-6.6.6.6 } } } You can also use this to fetch the existing timeout for specific elements, in case you have a set with timeouts in place: # nft get element w z { 2.2.2.2 } table ip w { set z { type ipv4_addr timeout 30s elements = { 2.2.2.2 expires 17s } } } Signed-off-by: Pablo Neira Ayuso --- src/evaluate.c | 31 +++++++++++++++ src/mnl.c | 35 +++++++++++++++- src/netlink.c | 43 ++++++++++++++++++++ src/parser_bison.y | 12 +++++- src/rule.c | 84 +++++++++++++++++++++++++++++++++++++-- src/scanner.l | 1 + src/segtree.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 313 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/evaluate.c b/src/evaluate.c index 41ba1617..a2c1c728 100644 --- a/src/evaluate.c +++ b/src/evaluate.c @@ -3146,6 +3146,34 @@ static int cmd_evaluate_delete(struct eval_ctx *ctx, struct cmd *cmd) } } +static int cmd_evaluate_get(struct eval_ctx *ctx, struct cmd *cmd) +{ + struct table *table; + struct set *set; + int ret; + + ret = cache_update(ctx->nf_sock, ctx->cache, cmd->op, ctx->msgs, + ctx->debug_mask & NFT_DEBUG_NETLINK, ctx->octx); + if (ret < 0) + return ret; + + switch (cmd->obj) { + case CMD_OBJ_SETELEM: + table = table_lookup(&cmd->handle, ctx->cache); + if (table == NULL) + return cmd_error(ctx, "Could not process rule: Table '%s' does not exist", + cmd->handle.table); + set = set_lookup(table, cmd->handle.set); + if (set == NULL || set->flags & (NFT_SET_MAP | NFT_SET_EVAL)) + return cmd_error(ctx, "Could not process rule: Set '%s' does not exist", + cmd->handle.set); + + return setelem_evaluate(ctx, &cmd->expr); + default: + BUG("invalid command object type %u\n", cmd->obj); + } +} + static int cmd_evaluate_list_obj(struct eval_ctx *ctx, const struct cmd *cmd, uint32_t obj_type) { @@ -3486,6 +3514,7 @@ static const char * const cmd_op_name[] = { [CMD_CREATE] = "create", [CMD_INSERT] = "insert", [CMD_DELETE] = "delete", + [CMD_GET] = "get", [CMD_LIST] = "list", [CMD_FLUSH] = "flush", [CMD_RENAME] = "rename", @@ -3523,6 +3552,8 @@ int cmd_evaluate(struct eval_ctx *ctx, struct cmd *cmd) return cmd_evaluate_add(ctx, cmd); case CMD_DELETE: return cmd_evaluate_delete(ctx, cmd); + case CMD_GET: + return cmd_evaluate_get(ctx, cmd); case CMD_LIST: return cmd_evaluate_list(ctx, cmd); case CMD_RESET: diff --git a/src/mnl.c b/src/mnl.c index f620a3bd..3d48bc1b 100644 --- a/src/mnl.c +++ b/src/mnl.c @@ -941,6 +941,37 @@ int mnl_nft_setelem_batch_del(struct nftnl_set *nls, struct nftnl_batch *batch, seqnum); } +struct nftnl_set *mnl_nft_setelem_get_one(struct netlink_ctx *ctx, + struct nftnl_set *nls_in) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nftnl_set *nls_out; + struct nlmsghdr *nlh; + int err; + + nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM, + nftnl_set_get_u32(nls_in, NFTNL_SET_FAMILY), + NLM_F_ACK, ctx->seqnum); + nftnl_set_elems_nlmsg_build_payload(nlh, nls_in); + + nls_out = nftnl_set_alloc(); + if (!nls_out) + return NULL; + + nftnl_set_set_str(nls_out, NFTNL_SET_TABLE, + nftnl_set_get_str(nls_in, NFTNL_SET_TABLE)); + nftnl_set_set_str(nls_out, NFTNL_SET_NAME, + nftnl_set_get_str(nls_in, NFTNL_SET_NAME)); + + err = nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, set_elem_cb, nls_out); + if (err < 0) { + nftnl_set_free(nls_out); + return NULL; + } + + return nls_out; +} + int mnl_nft_setelem_get(struct netlink_ctx *ctx, struct nftnl_set *nls) { char buf[MNL_SOCKET_BUFFER_SIZE]; @@ -948,8 +979,8 @@ int mnl_nft_setelem_get(struct netlink_ctx *ctx, struct nftnl_set *nls) nlh = nftnl_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM, nftnl_set_get_u32(nls, NFTNL_SET_FAMILY), - NLM_F_DUMP|NLM_F_ACK, ctx->seqnum); - nftnl_set_nlmsg_build_payload(nlh, nls); + NLM_F_DUMP | NLM_F_ACK, ctx->seqnum); + nftnl_set_elems_nlmsg_build_payload(nlh, nls); return nft_mnl_talk(ctx, nlh, nlh->nlmsg_len, set_elem_cb, nls); } diff --git a/src/netlink.c b/src/netlink.c index 906568fe..526ec9c1 100644 --- a/src/netlink.c +++ b/src/netlink.c @@ -1371,6 +1371,7 @@ int netlink_get_setelems(struct netlink_ctx *ctx, const struct handle *h, int err; nls = alloc_nftnl_set(h); + netlink_dump_set(nls, ctx); err = mnl_nft_setelem_get(ctx, nls); if (err < 0) { @@ -1400,6 +1401,48 @@ out: return err; } +int netlink_get_setelem(struct netlink_ctx *ctx, const struct handle *h, + const struct location *loc, struct table *table, + struct set *set, struct expr *init) +{ + struct nftnl_set *nls, *nls_out = NULL; + int err = 0; + + nls = alloc_nftnl_set(h); + alloc_setelem_cache(init, nls); + + netlink_dump_set(nls, ctx); + + nls_out = mnl_nft_setelem_get_one(ctx, nls); + if (!nls_out) { + nftnl_set_free(nls); + if (errno == EINTR) + return -1; + + err = -1; + goto out; + } + + ctx->set = set; + set->init = set_expr_alloc(loc, set); + nftnl_set_elem_foreach(nls_out, list_setelem_cb, ctx); + + if (!(set->flags & NFT_SET_INTERVAL)) + list_expr_sort(&ctx->set->init->expressions); + + nftnl_set_free(nls); + nftnl_set_free(nls_out); + ctx->set = NULL; + + if (set->flags & NFT_SET_INTERVAL) + get_set_decompose(table, set); +out: + if (err < 0) + netlink_io_error(ctx, loc, "Could not receive set elements: %s", + strerror(errno)); + return err; +} + void netlink_dump_obj(struct nftnl_obj *nln, struct netlink_ctx *ctx) { FILE *fp = ctx->octx->output_fp; diff --git a/src/parser_bison.y b/src/parser_bison.y index e96340d9..2ccaf9ab 100644 --- a/src/parser_bison.y +++ b/src/parser_bison.y @@ -219,6 +219,7 @@ int nft_lex(void *, void *, void *); %token CREATE "create" %token INSERT "insert" %token DELETE "delete" +%token GET "get" %token LIST "list" %token RESET "reset" %token FLUSH "flush" @@ -504,8 +505,8 @@ int nft_lex(void *, void *, void *); %type line %destructor { cmd_free($$); } line -%type base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd -%destructor { cmd_free($$); } base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd +%type base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd get_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd +%destructor { cmd_free($$); } base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd get_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd %type table_spec tableid_spec chain_spec chainid_spec flowtable_spec chain_identifier ruleid_spec handle_spec position_spec rule_position ruleset_spec %destructor { handle_free(&$$); } table_spec tableid_spec chain_spec chainid_spec flowtable_spec chain_identifier ruleid_spec handle_spec position_spec rule_position ruleset_spec @@ -843,6 +844,7 @@ base_cmd : /* empty */ add_cmd { $$ = $1; } | CREATE create_cmd { $$ = $2; } | INSERT insert_cmd { $$ = $2; } | DELETE delete_cmd { $$ = $2; } + | GET get_cmd { $$ = $2; } | LIST list_cmd { $$ = $2; } | RESET reset_cmd { $$ = $2; } | FLUSH flush_cmd { $$ = $2; } @@ -1094,6 +1096,12 @@ delete_cmd : TABLE table_spec } ; +get_cmd : ELEMENT set_spec set_block_expr + { + $$ = cmd_alloc(CMD_GET, CMD_OBJ_SETELEM, &$2, &@$, $3); + } + ; + list_cmd : TABLE table_spec { $$ = cmd_alloc(CMD_LIST, CMD_OBJ_TABLE, &$2, &@$, NULL); diff --git a/src/rule.c b/src/rule.c index 99b12e97..72dd76da 100644 --- a/src/rule.c +++ b/src/rule.c @@ -210,6 +210,26 @@ struct set *set_alloc(const struct location *loc) return set; } +struct set *set_clone(const struct set *set) +{ + struct set *new_set; + + new_set = set_alloc(NULL); + handle_merge(&new_set->handle, &set->handle); + new_set->flags = set->flags; + new_set->gc_int = set->gc_int; + new_set->timeout = set->timeout; + new_set->key = expr_clone(set->key); + new_set->datatype = set->datatype; + new_set->datalen = set->datalen; + new_set->objtype = set->objtype; + new_set->policy = set->policy; + new_set->automerge = set->automerge; + new_set->desc.size = set->desc.size; + + return new_set; +} + struct set *set_get(struct set *set) { set->refcnt++; @@ -1772,6 +1792,14 @@ static int do_list_chains(struct netlink_ctx *ctx, struct cmd *cmd) return 0; } +static void __do_list_set(struct netlink_ctx *ctx, struct cmd *cmd, + struct table *table, struct set *set) +{ + table_print_declaration(table, ctx->octx); + set_print(set, ctx->octx); + nft_print(ctx->octx, "}\n"); +} + static int do_list_set(struct netlink_ctx *ctx, struct cmd *cmd, struct table *table) { @@ -1781,9 +1809,7 @@ static int do_list_set(struct netlink_ctx *ctx, struct cmd *cmd, if (set == NULL) return -1; - table_print_declaration(table, ctx->octx); - set_print(set, ctx->octx); - nft_print(ctx->octx, "}\n"); + __do_list_set(ctx, cmd, table, set); return 0; } @@ -1839,6 +1865,56 @@ static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd) return 0; } +static int do_get_setelems(struct netlink_ctx *ctx, struct cmd *cmd, + struct table *table) +{ + struct set *set, *new_set; + struct expr *init; + int err; + + set = set_lookup(table, cmd->handle.set); + + /* Create a list of elements based of what we got from command line. */ + if (set->flags & NFT_SET_INTERVAL) + init = get_set_intervals(set, cmd->expr); + else + init = cmd->expr; + + new_set = set_clone(set); + + /* Fetch from kernel the elements that have been requested .*/ + err = netlink_get_setelem(ctx, &cmd->handle, &cmd->location, + table, new_set, init); + if (err < 0) + return err; + + __do_list_set(ctx, cmd, table, new_set); + + if (set->flags & NFT_SET_INTERVAL) + expr_free(init); + + set_free(new_set); + + return 0; +} + +static int do_command_get(struct netlink_ctx *ctx, struct cmd *cmd) +{ + struct table *table = NULL; + + if (cmd->handle.table != NULL) + table = table_lookup(&cmd->handle, ctx->cache); + + switch (cmd->obj) { + case CMD_OBJ_SETELEM: + return do_get_setelems(ctx, cmd, table); + default: + BUG("invalid command object type %u\n", cmd->obj); + } + + return 0; +} + static int do_command_reset(struct netlink_ctx *ctx, struct cmd *cmd) { struct obj *obj, *next; @@ -2017,6 +2093,8 @@ int do_command(struct netlink_ctx *ctx, struct cmd *cmd) return do_command_replace(ctx, cmd); case CMD_DELETE: return do_command_delete(ctx, cmd); + case CMD_GET: + return do_command_get(ctx, cmd); case CMD_LIST: return do_command_list(ctx, cmd); case CMD_RESET: diff --git a/src/scanner.l b/src/scanner.l index 38e92db0..ab10738b 100644 --- a/src/scanner.l +++ b/src/scanner.l @@ -274,6 +274,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr}) "create" { return CREATE; } "insert" { return INSERT; } "delete" { return DELETE; } +"get" { return GET; } "list" { return LIST; } "reset" { return RESET; } "flush" { return FLUSH; } diff --git a/src/segtree.c b/src/segtree.c index 1c23d4b8..de68071c 100644 --- a/src/segtree.c +++ b/src/segtree.c @@ -578,6 +578,120 @@ int set_to_intervals(struct list_head *errs, struct set *set, return 0; } +static void set_elem_add(const struct set *set, struct expr *init, mpz_t value, + uint32_t flags) +{ + struct expr *expr; + + expr = constant_expr_alloc(&internal_location, set->key->dtype, + set->key->byteorder, set->key->len, NULL); + mpz_set(expr->value, value); + expr = set_elem_expr_alloc(&internal_location, expr); + expr->flags = flags; + + compound_expr_add(init, expr); +} + +struct expr *get_set_intervals(const struct set *set, const struct expr *init) +{ + struct expr *new_init; + mpz_t low, high; + struct expr *i; + + mpz_init2(low, set->key->len); + mpz_init2(high, set->key->len); + + new_init = list_expr_alloc(&internal_location); + + list_for_each_entry(i, &init->expressions, list) { + switch (i->key->ops->type) { + case EXPR_VALUE: + set_elem_add(set, new_init, i->key->value, i->flags); + break; + default: + range_expr_value_low(low, i); + set_elem_add(set, new_init, low, 0); + range_expr_value_high(high, i); + mpz_add_ui(high, high, 1); + set_elem_add(set, new_init, high, EXPR_F_INTERVAL_END); + break; + } + } + + mpz_clear(low); + mpz_clear(high); + + return new_init; +} + +static struct expr *get_set_interval_end(const struct table *table, + const char *set_name, + struct expr *left) +{ + struct set *set; + mpz_t low, high; + struct expr *i; + + set = set_lookup(table, set_name); + mpz_init2(low, set->key->len); + mpz_init2(high, set->key->len); + + list_for_each_entry(i, &set->init->expressions, list) { + switch (i->key->ops->type) { + case EXPR_RANGE: + range_expr_value_low(low, i); + if (mpz_cmp(low, left->key->value) == 0) { + left = range_expr_alloc(&internal_location, + expr_clone(left->key), + expr_clone(i->key->right)); + break; + } + break; + default: + break; + } + } + + mpz_clear(low); + mpz_clear(high); + + return left; +} + +void get_set_decompose(struct table *table, struct set *set) +{ + struct expr *i, *next, *new; + struct expr *left = NULL; + struct expr *new_init; + + new_init = set_expr_alloc(&internal_location, set); + + list_for_each_entry_safe(i, next, &set->init->expressions, list) { + if (i->flags & EXPR_F_INTERVAL_END && left) { + list_del(&left->list); + list_del(&i->list); + mpz_sub_ui(i->key->value, i->key->value, 1); + new = range_expr_alloc(&internal_location, left, i); + compound_expr_add(new_init, new); + left = NULL; + } else { + if (left) { + left = get_set_interval_end(table, + set->handle.set, + left); + compound_expr_add(new_init, left); + } + left = i; + } + } + if (left) { + left = get_set_interval_end(table, set->handle.set, left); + compound_expr_add(new_init, left); + } + + set->init = new_init; +} + static bool range_is_prefix(const mpz_t range) { mpz_t tmp; -- cgit v1.2.3