diff options
-rw-r--r-- | examples/Makefile.am | 4 | ||||
-rw-r--r-- | examples/nft-ruleset-get.c | 389 | ||||
-rw-r--r-- | examples/nft-set-get.c | 4 | ||||
-rw-r--r-- | include/libnftnl/expr.h | 7 | ||||
-rw-r--r-- | include/libnftnl/table.h | 1 | ||||
-rw-r--r-- | include/linux/netfilter.h | 71 | ||||
-rw-r--r-- | include/linux/netfilter/nf_tables.h | 31 | ||||
-rw-r--r-- | src/chain.c | 1 | ||||
-rw-r--r-- | src/expr/ct.c | 95 | ||||
-rw-r--r-- | src/expr/meta.c | 147 | ||||
-rw-r--r-- | src/expr/queue.c | 254 | ||||
-rw-r--r-- | src/table.c | 35 | ||||
-rw-r--r-- | src/utils.c | 4 | ||||
-rw-r--r-- | tests/Makefile.am | 4 | ||||
-rw-r--r-- | tests/jsonfiles/01-table.json | 2 | ||||
-rw-r--r-- | tests/jsonfiles/02-table.json | 2 | ||||
-rw-r--r-- | tests/jsonfiles/64-ruleset.json | 2 | ||||
-rw-r--r-- | tests/jsonfiles/65-rule-meta-target.json | 1 | ||||
-rw-r--r-- | tests/nft-expr_queue-test.c | 99 | ||||
-rw-r--r-- | tests/xmlfiles/01-table.xml | 2 | ||||
-rw-r--r-- | tests/xmlfiles/02-table.xml | 2 | ||||
-rw-r--r-- | tests/xmlfiles/75-ruleset.xml | 2 | ||||
-rw-r--r-- | tests/xmlfiles/76-rule-meta_target.xml | 1 |
23 files changed, 1103 insertions, 57 deletions
diff --git a/examples/Makefile.am b/examples/Makefile.am index 13b7e79..b433b2d 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -25,6 +25,7 @@ check_PROGRAMS = nft-table-add \ nft-set-elem-add \ nft-set-elem-get \ nft-set-elem-del \ + nft-ruleset-get \ nft-compat-get nft_table_add_SOURCES = nft-table-add.c @@ -102,5 +103,8 @@ nft_set_elem_del_LDADD = ../src/libnftnl.la ${LIBMNL_LIBS} nft_set_elem_get_SOURCES = nft-set-elem-get.c nft_set_elem_get_LDADD = ../src/libnftnl.la ${LIBMNL_LIBS} +nft_ruleset_get_SOURCES = nft-ruleset-get.c +nft_ruleset_get_LDADD = ../src/libnftnl.la ${LIBMNL_LIBS} + nft_compat_get_SOURCES = nft-compat-get.c nft_compat_get_LDADD = ../src/libnftnl.la ${LIBMNL_LIBS} diff --git a/examples/nft-ruleset-get.c b/examples/nft-ruleset-get.c new file mode 100644 index 0000000..18de2a9 --- /dev/null +++ b/examples/nft-ruleset-get.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2013 Arturo Borrero Gonzalez <arturo.borrero.glez@gmail.com> + * + * based on previous code from: + * + * Copyright (c) 2013 Pablo Neira Ayuso <pablo@netfilter.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <stdlib.h> +#include <time.h> +#include <string.h> +#include <netinet/in.h> +#include <errno.h> + +#include <linux/netfilter.h> +#include <linux/netfilter/nf_tables.h> + +#include <libmnl/libmnl.h> +#include <libnftnl/common.h> +#include <libnftnl/ruleset.h> +#include <libnftnl/table.h> +#include <libnftnl/chain.h> +#include <libnftnl/set.h> +#include <libnftnl/rule.h> + +static int seq; + +static void memory_allocation_error(void) +{ + perror("OOM"); + exit(EXIT_FAILURE); +} + +static int +mnl_talk(struct mnl_socket *nf_sock, const void *data, unsigned int len, + int (*cb)(const struct nlmsghdr *nlh, void *data), void *cb_data) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + uint32_t portid = mnl_socket_get_portid(nf_sock); + int ret; + + if (mnl_socket_sendto(nf_sock, data, len) < 0) + return -1; + + ret = mnl_socket_recvfrom(nf_sock, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, cb, cb_data); + if (ret <= 0) + goto out; + + ret = mnl_socket_recvfrom(nf_sock, buf, sizeof(buf)); + } +out: + if (ret < 0 && errno == EAGAIN) + return 0; + + return ret; +} + +/* + * Rule + */ +static int rule_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_rule_list *nlr_list = data; + struct nft_rule *r; + + r = nft_rule_alloc(); + if (r == NULL) + memory_allocation_error(); + + if (nft_rule_nlmsg_parse(nlh, r) < 0) + goto err_free; + + nft_rule_list_add_tail(r, nlr_list); + return MNL_CB_OK; + +err_free: + nft_rule_free(r); + return MNL_CB_OK; +} + +static struct nft_rule_list *mnl_rule_dump(struct mnl_socket *nf_sock, + int family) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_rule_list *nlr_list; + int ret; + + nlr_list = nft_rule_list_alloc(); + if (nlr_list == NULL) + memory_allocation_error(); + + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, + NLM_F_DUMP, seq); + + ret = mnl_talk(nf_sock, nlh, nlh->nlmsg_len, rule_cb, nlr_list); + if (ret < 0) + goto err; + + return nlr_list; +err: + nft_rule_list_free(nlr_list); + return NULL; +} + +/* + * Chain + */ +static int chain_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_chain_list *nlc_list = data; + struct nft_chain *c; + + c = nft_chain_alloc(); + if (c == NULL) + memory_allocation_error(); + + if (nft_chain_nlmsg_parse(nlh, c) < 0) + goto err_free; + + nft_chain_list_add_tail(c, nlc_list); + return MNL_CB_OK; + +err_free: + nft_chain_free(c); + return MNL_CB_OK; +} + +static struct nft_chain_list *mnl_chain_dump(struct mnl_socket *nf_sock, + int family) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain_list *nlc_list; + int ret; + + nlc_list = nft_chain_list_alloc(); + if (nlc_list == NULL) + memory_allocation_error(); + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, family, + NLM_F_DUMP, seq); + + ret = mnl_talk(nf_sock, nlh, nlh->nlmsg_len, chain_cb, nlc_list); + if (ret < 0) + goto err; + + return nlc_list; +err: + nft_chain_list_free(nlc_list); + return NULL; +} + +/* + * Table + */ +static int table_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_table_list *nlt_list = data; + struct nft_table *t; + + t = nft_table_alloc(); + if (t == NULL) + memory_allocation_error(); + + if (nft_table_nlmsg_parse(nlh, t) < 0) + goto err_free; + + nft_table_list_add_tail(t, nlt_list); + return MNL_CB_OK; + +err_free: + nft_table_free(t); + return MNL_CB_OK; +} + +static struct nft_table_list *mnl_table_dump(struct mnl_socket *nf_sock, + int family) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_table_list *nlt_list; + int ret; + + nlt_list = nft_table_list_alloc(); + if (nlt_list == NULL) + memory_allocation_error(); + + nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, family, + NLM_F_DUMP, seq); + + ret = mnl_talk(nf_sock, nlh, nlh->nlmsg_len, table_cb, nlt_list); + if (ret < 0) + goto err; + + return nlt_list; +err: + nft_table_list_free(nlt_list); + return NULL; +} + +/* + * Set elements + */ +static int set_elem_cb(const struct nlmsghdr *nlh, void *data) +{ + nft_set_elems_nlmsg_parse(nlh, data); + return MNL_CB_OK; +} + +static int mnl_setelem_get(struct mnl_socket *nf_sock, struct nft_set *nls) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t family = nft_set_attr_get_u32(nls, NFT_SET_ATTR_FAMILY); + + nlh = nft_set_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM, family, + NLM_F_DUMP|NLM_F_ACK, seq); + nft_set_nlmsg_build_payload(nlh, nls); + + return mnl_talk(nf_sock, nlh, nlh->nlmsg_len, set_elem_cb, nls); +} + +/* + * Set + */ +static int set_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_set_list *nls_list = data; + struct nft_set *s; + + s = nft_set_alloc(); + if (s == NULL) + memory_allocation_error(); + + if (nft_set_nlmsg_parse(nlh, s) < 0) + goto err_free; + + nft_set_list_add_tail(s, nls_list); + return MNL_CB_OK; + +err_free: + nft_set_free(s); + return MNL_CB_OK; +} + +static struct nft_set_list * +mnl_set_dump(struct mnl_socket *nf_sock, int family) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_set *s; + struct nft_set_list *nls_list; + struct nft_set *si; + struct nft_set_list_iter *i; + int ret; + + s = nft_set_alloc(); + if (s == NULL) + memory_allocation_error(); + + nlh = nft_set_nlmsg_build_hdr(buf, NFT_MSG_GETSET, family, + NLM_F_DUMP|NLM_F_ACK, seq); + nft_set_nlmsg_build_payload(nlh, s); + nft_set_free(s); + + nls_list = nft_set_list_alloc(); + if (nls_list == NULL) + memory_allocation_error(); + + ret = mnl_talk(nf_sock, nlh, nlh->nlmsg_len, set_cb, nls_list); + if (ret < 0) + goto err; + + i = nft_set_list_iter_create(nls_list); + if (i == NULL) + memory_allocation_error(); + + si = nft_set_list_iter_next(i); + while (si != NULL) { + if (mnl_setelem_get(nf_sock, si) != 0) { + perror("E: Unable to get set elements"); + nft_set_list_iter_destroy(i); + goto err; + } + si = nft_set_list_iter_next(i); + } + + nft_set_list_iter_destroy(i); + + return nls_list; +err: + nft_set_list_free(nls_list); + return NULL; +} + +/* + * ruleset + */ + +static struct nft_ruleset *mnl_ruleset_dump(struct mnl_socket *nf_sock) +{ + struct nft_ruleset *rs; + struct nft_table_list *t; + struct nft_chain_list *c; + struct nft_set_list *s; + struct nft_rule_list *r; + + rs = nft_ruleset_alloc(); + if (rs == NULL) + memory_allocation_error(); + + t = mnl_table_dump(nf_sock, NFPROTO_UNSPEC); + if (t != NULL) + nft_ruleset_attr_set(rs, NFT_RULESET_ATTR_TABLELIST, t); + + c = mnl_chain_dump(nf_sock, NFPROTO_UNSPEC); + if (c != NULL) + nft_ruleset_attr_set(rs, NFT_RULESET_ATTR_CHAINLIST, c); + + s = mnl_set_dump(nf_sock, NFPROTO_UNSPEC); + if (s != NULL) + nft_ruleset_attr_set(rs, NFT_RULESET_ATTR_SETLIST, s); + + r = mnl_rule_dump(nf_sock, NFPROTO_UNSPEC); + if (r != NULL) + nft_ruleset_attr_set(rs, NFT_RULESET_ATTR_RULELIST, r); + + return rs; +} + +int main(int argc, char *argv[]) +{ + struct mnl_socket *nl; + uint32_t type = NFT_OUTPUT_DEFAULT; + struct nft_ruleset *rs; + int ret; + + if (argc > 2) { + fprintf(stderr, "%s {xml|json}\n", + argv[0]); + exit(EXIT_FAILURE); + } + + if (argc == 2) { + if (strcmp(argv[1], "xml") == 0) + type = NFT_OUTPUT_XML; + else if (strcmp(argv[1], "json") == 0) + type = NFT_OUTPUT_JSON; + else { + fprintf(stderr, "Unknown type: {xml|json}\n"); + exit(EXIT_FAILURE); + } + } + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + perror("mnl_socket_open"); + exit(EXIT_FAILURE); + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + exit(EXIT_FAILURE); + } + + seq = time(NULL); + + rs = mnl_ruleset_dump(nl); + if (rs == NULL) { + perror("ruleset_dump"); + exit(EXIT_FAILURE); + } + + ret = nft_ruleset_fprintf(stdout, rs, type, 0); + fprintf(stdout, "\n"); + + if (ret == -1) + perror("E: Error during fprintf operations"); + + return 0; +} diff --git a/examples/nft-set-get.c b/examples/nft-set-get.c index 7a94506..2822025 100644 --- a/examples/nft-set-get.c +++ b/examples/nft-set-get.c @@ -74,8 +74,10 @@ int main(int argc, char *argv[]) family = NFPROTO_BRIDGE; else if (strcmp(argv[1], "arp") == 0) family = NFPROTO_ARP; + else if (strcmp(argv[1], "unspec") == 0) + family = NFPROTO_UNSPEC; else { - fprintf(stderr, "Unknown family: ip, ip6, bridge, arp\n"); + fprintf(stderr, "Unknown family: ip, ip6, bridge, arp, unspec\n"); exit(EXIT_FAILURE); } diff --git a/include/libnftnl/expr.h b/include/libnftnl/expr.h index a2f44df..6ec05a6 100644 --- a/include/libnftnl/expr.h +++ b/include/libnftnl/expr.h @@ -50,6 +50,7 @@ enum { enum { NFT_EXPR_META_KEY = NFT_RULE_EXPR_ATTR_BASE, NFT_EXPR_META_DREG, + NFT_EXPR_META_SREG, }; enum { @@ -123,6 +124,7 @@ enum { NFT_EXPR_CT_DREG = NFT_RULE_EXPR_ATTR_BASE, NFT_EXPR_CT_KEY, NFT_EXPR_CT_DIR, + NFT_EXPR_CT_SREG, }; enum { @@ -143,6 +145,11 @@ enum { NFT_EXPR_REJECT_CODE, }; +enum { + NFT_EXPR_QUEUE_NUM = NFT_RULE_EXPR_ATTR_BASE, + NFT_EXPR_QUEUE_TOTAL, + NFT_EXPR_QUEUE_FLAGS, +}; #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/include/libnftnl/table.h b/include/libnftnl/table.h index 22a8c1b..56e7e35 100644 --- a/include/libnftnl/table.h +++ b/include/libnftnl/table.h @@ -21,6 +21,7 @@ enum { NFT_TABLE_ATTR_NAME = 0, NFT_TABLE_ATTR_FAMILY, NFT_TABLE_ATTR_FLAGS, + NFT_TABLE_ATTR_USE, }; bool nft_table_attr_is_set(const struct nft_table *t, uint16_t attr); diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h new file mode 100644 index 0000000..be0bc18 --- /dev/null +++ b/include/linux/netfilter.h @@ -0,0 +1,71 @@ +#ifndef __LINUX_NETFILTER_H +#define __LINUX_NETFILTER_H + +#include <linux/types.h> + +#include <linux/sysctl.h> + + +/* Responses from hook functions. */ +#define NF_DROP 0 +#define NF_ACCEPT 1 +#define NF_STOLEN 2 +#define NF_QUEUE 3 +#define NF_REPEAT 4 +#define NF_STOP 5 +#define NF_MAX_VERDICT NF_STOP + +/* we overload the higher bits for encoding auxiliary data such as the queue + * number or errno values. Not nice, but better than additional function + * arguments. */ +#define NF_VERDICT_MASK 0x000000ff + +/* extra verdict flags have mask 0x0000ff00 */ +#define NF_VERDICT_FLAG_QUEUE_BYPASS 0x00008000 + +/* queue number (NF_QUEUE) or errno (NF_DROP) */ +#define NF_VERDICT_QMASK 0xffff0000 +#define NF_VERDICT_QBITS 16 + +#define NF_QUEUE_NR(x) ((((x) << 16) & NF_VERDICT_QMASK) | NF_QUEUE) + +#define NF_DROP_ERR(x) (((-x) << 16) | NF_DROP) + +/* only for userspace compatibility */ +/* Generic cache responses from hook functions. + <= 0x2000 is used for protocol-flags. */ +#define NFC_UNKNOWN 0x4000 +#define NFC_ALTERED 0x8000 + +/* NF_VERDICT_BITS should be 8 now, but userspace might break if this changes */ +#define NF_VERDICT_BITS 16 + +enum nf_inet_hooks { + NF_INET_PRE_ROUTING, + NF_INET_LOCAL_IN, + NF_INET_FORWARD, + NF_INET_LOCAL_OUT, + NF_INET_POST_ROUTING, + NF_INET_NUMHOOKS +}; + +enum { + NFPROTO_UNSPEC = 0, + NFPROTO_INET = 1, + NFPROTO_IPV4 = 2, + NFPROTO_ARP = 3, + NFPROTO_BRIDGE = 7, + NFPROTO_IPV6 = 10, + NFPROTO_DECNET = 12, + NFPROTO_NUMPROTO, +}; + +union nf_inet_addr { + __u32 all[4]; + __be32 ip; + __be32 ip6[4]; + struct in_addr in; + struct in6_addr in6; +}; + +#endif /* __LINUX_NETFILTER_H */ diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h index fbfd229..31ddd06 100644 --- a/include/linux/netfilter/nf_tables.h +++ b/include/linux/netfilter/nf_tables.h @@ -115,6 +115,7 @@ enum nft_table_attributes { NFTA_TABLE_UNSPEC, NFTA_TABLE_NAME, NFTA_TABLE_FLAGS, + NFTA_TABLE_USE, __NFTA_TABLE_MAX }; #define NFTA_TABLE_MAX (__NFTA_TABLE_MAX - 1) @@ -529,6 +530,8 @@ enum nft_exthdr_attributes { * @NFT_META_NFTRACE: packet nftrace bit * @NFT_META_RTCLASSID: realm value of packet's route (skb->dst->tclassid) * @NFT_META_SECMARK: packet secmark (skb->secmark) + * @NFT_META_NFPROTO: netfilter protocol + * @NFT_META_L4PROTO: layer 4 protocol number */ enum nft_meta_keys { NFT_META_LEN, @@ -546,18 +549,22 @@ enum nft_meta_keys { NFT_META_NFTRACE, NFT_META_RTCLASSID, NFT_META_SECMARK, + NFT_META_NFPROTO, + NFT_META_L4PROTO, }; /** * enum nft_meta_attributes - nf_tables meta expression netlink attributes * * @NFTA_META_DREG: destination register (NLA_U32) - * @NFTA_META_KEY: meta data item to load (NLA_U32: nft_meta_keys) + * @NFTA_META_KEY: meta data item to load or set (NLA_U32: nft_meta_keys) + * @NFTA_META_SREG: source register (NLA_U32) */ enum nft_meta_attributes { NFTA_META_UNSPEC, NFTA_META_DREG, NFTA_META_KEY, + NFTA_META_SREG, __NFTA_META_MAX }; #define NFTA_META_MAX (__NFTA_META_MAX - 1) @@ -601,12 +608,14 @@ enum nft_ct_keys { * @NFTA_CT_DREG: destination register (NLA_U32) * @NFTA_CT_KEY: conntrack data item to load (NLA_U32: nft_ct_keys) * @NFTA_CT_DIRECTION: direction in case of directional keys (NLA_U8) + * @NFTA_CT_SREG: source register (NLA_U32) */ enum nft_ct_attributes { NFTA_CT_UNSPEC, NFTA_CT_DREG, NFTA_CT_KEY, NFTA_CT_DIRECTION, + NFTA_CT_SREG, __NFTA_CT_MAX }; #define NFTA_CT_MAX (__NFTA_CT_MAX - 1) @@ -658,6 +667,26 @@ enum nft_log_attributes { #define NFTA_LOG_MAX (__NFTA_LOG_MAX - 1) /** + * enum nft_queue_attributes - nf_tables queue expression netlink attributes + * + * @NFTA_QUEUE_NUM: netlink queue to send messages to (NLA_U16) + * @NFTA_QUEUE_TOTAL: number of queues to load balance packets on (NLA_U16) + * @NFTA_QUEUE_FLAGS: various flags (NLA_U16) + */ +enum nft_queue_attributes { + NFTA_QUEUE_UNSPEC, + NFTA_QUEUE_NUM, + NFTA_QUEUE_TOTAL, + NFTA_QUEUE_FLAGS, + __NFTA_QUEUE_MAX +}; +#define NFTA_QUEUE_MAX (__NFTA_QUEUE_MAX - 1) + +#define NFT_QUEUE_FLAG_BYPASS 0x01 /* for compatibility with v2 */ +#define NFT_QUEUE_FLAG_CPU_FANOUT 0x02 /* use current CPU (no hashing) */ +#define NFT_QUEUE_FLAG_MASK 0x03 + +/** * enum nft_reject_types - nf_tables reject expression reject types * * @NFT_REJECT_ICMP_UNREACH: reject using ICMP unreachable diff --git a/src/chain.c b/src/chain.c index e26200d..2cc7c61 100644 --- a/src/chain.c +++ b/src/chain.c @@ -50,6 +50,7 @@ static const char *nft_hooknum2str(int family, int hooknum) switch (family) { case NFPROTO_IPV4: case NFPROTO_IPV6: + case NFPROTO_INET: case NFPROTO_BRIDGE: switch (hooknum) { case NF_INET_PRE_ROUTING: diff --git a/src/expr/ct.c b/src/expr/ct.c index 051a1c5..2df761c 100644 --- a/src/expr/ct.c +++ b/src/expr/ct.c @@ -25,6 +25,7 @@ struct nft_expr_ct { enum nft_ct_keys key; enum nft_registers dreg; + enum nft_registers sreg; uint8_t dir; }; @@ -51,6 +52,9 @@ nft_rule_expr_ct_set(struct nft_rule_expr *e, uint16_t type, case NFT_EXPR_CT_DREG: ct->dreg = *((uint32_t *)data); break; + case NFT_EXPR_CT_SREG: + ct->sreg = *((uint32_t *)data); + break; default: return -1; } @@ -73,6 +77,9 @@ nft_rule_expr_ct_get(const struct nft_rule_expr *e, uint16_t type, case NFT_EXPR_CT_DREG: *data_len = sizeof(ct->dreg); return &ct->dreg; + case NFT_EXPR_CT_SREG: + *data_len = sizeof(ct->sreg); + return &ct->sreg; } return NULL; } @@ -88,6 +95,7 @@ static int nft_rule_expr_ct_cb(const struct nlattr *attr, void *data) switch(type) { case NFTA_CT_KEY: case NFTA_CT_DREG: + case NFTA_CT_SREG: if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { perror("mnl_attr_validate"); return MNL_CB_ERROR; @@ -116,6 +124,8 @@ nft_rule_expr_ct_build(struct nlmsghdr *nlh, struct nft_rule_expr *e) mnl_attr_put_u32(nlh, NFTA_CT_DREG, htonl(ct->dreg)); if (e->flags & (1 << NFT_EXPR_CT_DIR)) mnl_attr_put_u8(nlh, NFTA_CT_DIRECTION, ct->dir); + if (e->flags & (1 << NFT_EXPR_CT_SREG)) + mnl_attr_put_u32(nlh, NFTA_CT_SREG, htonl(ct->sreg)); } static int @@ -135,6 +145,10 @@ nft_rule_expr_ct_parse(struct nft_rule_expr *e, struct nlattr *attr) ct->dreg = ntohl(mnl_attr_get_u32(tb[NFTA_CT_DREG])); e->flags |= (1 << NFT_EXPR_CT_DREG); } + if (tb[NFTA_CT_SREG]) { + ct->sreg = ntohl(mnl_attr_get_u32(tb[NFTA_CT_SREG])); + e->flags |= (1 << NFT_EXPR_CT_SREG); + } if (tb[NFTA_CT_DIRECTION]) { ct->dir = mnl_attr_get_u8(tb[NFTA_CT_DIRECTION]); e->flags |= (1 << NFT_EXPR_CT_DIR); @@ -215,10 +229,19 @@ static int nft_rule_expr_ct_json_parse(struct nft_rule_expr *e, json_t *root, uint8_t dir; int key; - if (nft_jansson_parse_reg(root, "dreg", NFT_TYPE_U32, ®, err) < 0) - return -1; + if (nft_jansson_node_exist(root, "dreg")) { + if (nft_jansson_parse_reg(root, "dreg", NFT_TYPE_U32, ®, err) < 0) + return -1; - nft_rule_expr_set_u32(e, NFT_EXPR_CT_DREG, reg); + nft_rule_expr_set_u32(e, NFT_EXPR_CT_DREG, reg); + } + + if (nft_jansson_node_exist(root, "sreg")) { + if (nft_jansson_parse_reg(root, "sreg", NFT_TYPE_U32, ®, err) < 0) + return -1; + + nft_rule_expr_set_u32(e, NFT_EXPR_CT_SREG, reg); + } if (nft_jansson_node_exist(root, "key")) { key_str = nft_jansson_parse_str(root, "key", err); @@ -269,11 +292,16 @@ static int nft_rule_expr_ct_xml_parse(struct nft_rule_expr *e, mxml_node_t *tree uint32_t reg; if (nft_mxml_reg_parse(tree, "dreg", ®, MXML_DESCEND_FIRST, - NFT_XML_MAND, err) != 0) - return -1; + NFT_XML_OPT, err) >= 0) { + ct->dreg = reg; + e->flags |= (1 << NFT_EXPR_CT_DREG); + } - ct->dreg = reg; - e->flags |= (1 << NFT_EXPR_CT_DREG); + if (nft_mxml_reg_parse(tree, "sreg", ®, MXML_DESCEND_FIRST, + NFT_XML_OPT, err) >= 0) { + ct->sreg = reg; + e->flags |= (1 << NFT_EXPR_CT_SREG); + } key_str = nft_mxml_str_parse(tree, "key", MXML_DESCEND_FIRST, NFT_XML_MAND, err); @@ -315,22 +343,32 @@ nft_expr_ct_snprintf_json(char *buf, size_t size, struct nft_rule_expr *e) int ret, len = size, offset = 0; struct nft_expr_ct *ct = nft_expr_data(e); - ret = snprintf(buf, len, "\"dreg\":%u", ct->dreg); - SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + if (e->flags & (1 << NFT_EXPR_CT_DREG)) { + ret = snprintf(buf+offset, len, "\"dreg\":%u,", ct->dreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } + + if (e->flags & (1 << NFT_EXPR_CT_SREG)) { + ret = snprintf(buf+offset, len, "\"sreg:\":%u,", ct->sreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } if (e->flags & (1 << NFT_EXPR_CT_KEY)) { - ret = snprintf(buf+offset, len, ",\"key\":\"%s\"", + ret = snprintf(buf+offset, len, "\"key\":\"%s\",", ctkey2str(ct->key)); SNPRINTF_BUFFER_SIZE(ret, size, len, offset); } if (nft_rule_expr_is_set(e, NFT_EXPR_CT_DIR)) { - ret = snprintf(buf+offset, len, ",\"dir\":\"%s\"", + ret = snprintf(buf+offset, len, "\"dir\":\"%s\",", ctdir2str(ct->dir)); SNPRINTF_BUFFER_SIZE(ret, size, len, offset); } - return offset; + /* Remove the last separator characther */ + buf[offset-1] = '\0'; + + return offset-1; } static int @@ -339,12 +377,21 @@ nft_expr_ct_snprintf_xml(char *buf, size_t size, struct nft_rule_expr *e) int ret, len = size, offset = 0; struct nft_expr_ct *ct = nft_expr_data(e); - ret = snprintf(buf, len, "<dreg>%u</dreg>", ct->dreg); - SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + if (e->flags & (1 << NFT_EXPR_CT_DREG)) { + ret = snprintf(buf+offset, len, "<dreg>%u</dreg>", ct->dreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } + + if (e->flags & (1 << NFT_EXPR_CT_SREG)) { + ret = snprintf(buf+offset, len, "<sreg>%u</sreg>", ct->sreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } - ret = snprintf(buf+offset, len, "<key>%s</key>", - ctkey2str(ct->key)); - SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + if (e->flags & (1 << NFT_EXPR_CT_KEY)) { + ret = snprintf(buf+offset, len, "<key>%s</key>", + ctkey2str(ct->key)); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } if (nft_rule_expr_is_set(e, NFT_EXPR_CT_DIR)) { ret = snprintf(buf+offset, len, "<dir>%s</dir>", @@ -361,9 +408,17 @@ nft_expr_ct_snprintf_default(char *buf, size_t size, struct nft_rule_expr *e) int ret, len = size, offset = 0; struct nft_expr_ct *ct = nft_expr_data(e); - ret = snprintf(buf, len, "load %s => reg %u ", - ctkey2str(ct->key), ct->dreg); - SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + if (e->flags & (1 << NFT_EXPR_CT_SREG)) { + ret = snprintf(buf, size, "set %s with reg %u ", + ctkey2str(ct->key), ct->sreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } + + if (e->flags & (1 << NFT_EXPR_CT_DREG)) { + ret = snprintf(buf, len, "load %s => reg %u ", + ctkey2str(ct->key), ct->dreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } if (nft_rule_expr_is_set(e, NFT_EXPR_CT_DIR)) { ret = snprintf(buf+offset, len, ", dir %s ", diff --git a/src/expr/meta.c b/src/expr/meta.c index e2a198a..bee2f4c 100644 --- a/src/expr/meta.c +++ b/src/expr/meta.c @@ -23,12 +23,13 @@ #include "expr_ops.h" #ifndef NFT_META_MAX -#define NFT_META_MAX (NFT_META_SECMARK + 1) +#define NFT_META_MAX (NFT_META_L4PROTO + 1) #endif struct nft_expr_meta { enum nft_meta_keys key; enum nft_registers dreg; + enum nft_registers sreg; }; static int @@ -44,6 +45,9 @@ nft_rule_expr_meta_set(struct nft_rule_expr *e, uint16_t type, case NFT_EXPR_META_DREG: meta->dreg = *((uint32_t *)data); break; + case NFT_EXPR_META_SREG: + meta->sreg = *((uint32_t *)data); + break; default: return -1; } @@ -63,6 +67,9 @@ nft_rule_expr_meta_get(const struct nft_rule_expr *e, uint16_t type, case NFT_EXPR_META_DREG: *data_len = sizeof(meta->dreg); return &meta->dreg; + case NFT_EXPR_META_SREG: + *data_len = sizeof(meta->sreg); + return &meta->sreg; } return NULL; } @@ -78,6 +85,7 @@ static int nft_rule_expr_meta_cb(const struct nlattr *attr, void *data) switch(type) { case NFTA_META_KEY: case NFTA_META_DREG: + case NFTA_META_SREG: if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { perror("mnl_attr_validate"); return MNL_CB_ERROR; @@ -98,6 +106,8 @@ nft_rule_expr_meta_build(struct nlmsghdr *nlh, struct nft_rule_expr *e) mnl_attr_put_u32(nlh, NFTA_META_KEY, htonl(meta->key)); if (e->flags & (1 << NFT_EXPR_META_DREG)) mnl_attr_put_u32(nlh, NFTA_META_DREG, htonl(meta->dreg)); + if (e->flags & (1 << NFT_EXPR_META_SREG)) + mnl_attr_put_u32(nlh, NFTA_META_SREG, htonl(meta->sreg)); } static int @@ -117,6 +127,10 @@ nft_rule_expr_meta_parse(struct nft_rule_expr *e, struct nlattr *attr) meta->dreg = ntohl(mnl_attr_get_u32(tb[NFTA_META_DREG])); e->flags |= (1 << NFT_EXPR_META_DREG); } + if (tb[NFTA_META_SREG]) { + meta->sreg = ntohl(mnl_attr_get_u32(tb[NFTA_META_SREG])); + e->flags |= (1 << NFT_EXPR_META_SREG); + } return 0; } @@ -124,6 +138,8 @@ nft_rule_expr_meta_parse(struct nft_rule_expr *e, struct nlattr *attr) static const char *meta_key2str_array[NFT_META_MAX] = { [NFT_META_LEN] = "len", [NFT_META_PROTOCOL] = "protocol", + [NFT_META_NFPROTO] = "nfproto", + [NFT_META_L4PROTO] = "l4proto", [NFT_META_PRIORITY] = "priority", [NFT_META_MARK] = "mark", [NFT_META_IIF] = "iif", @@ -168,11 +184,6 @@ static int nft_rule_expr_meta_json_parse(struct nft_rule_expr *e, json_t *root, uint32_t reg; int key; - if (nft_jansson_parse_reg(root, "dreg", NFT_TYPE_U32, ®, err) < 0) - return -1; - - nft_rule_expr_set_u32(e, NFT_EXPR_META_DREG, reg); - key_str = nft_jansson_parse_str(root, "key", err); if (key_str == NULL) return -1; @@ -183,6 +194,22 @@ static int nft_rule_expr_meta_json_parse(struct nft_rule_expr *e, json_t *root, nft_rule_expr_set_u32(e, NFT_EXPR_META_KEY, key); + if (nft_jansson_node_exist(root, "dreg")) { + if (nft_jansson_parse_reg(root, "dreg", NFT_TYPE_U32, ®, + err) < 0) + return -1; + + nft_rule_expr_set_u32(e, NFT_EXPR_META_DREG, reg); + } + + if (nft_jansson_node_exist(root, "sreg")) { + if (nft_jansson_parse_reg(root, "sreg", NFT_TYPE_U32, ®, + err) < 0) + return -1; + + nft_rule_expr_set_u32(e, NFT_EXPR_META_SREG, reg); + } + return 0; #else errno = EOPNOTSUPP; @@ -200,13 +227,6 @@ static int nft_rule_expr_meta_xml_parse(struct nft_rule_expr *e, mxml_node_t *tr int key; uint32_t reg; - if (nft_mxml_reg_parse(tree, "dreg", ®, MXML_DESCEND_FIRST, - NFT_XML_MAND, err) < 0) - return -1; - - meta->dreg = reg; - e->flags |= (1 << NFT_EXPR_META_DREG); - key_str = nft_mxml_str_parse(tree, "key", MXML_DESCEND_FIRST, NFT_XML_MAND, err); if (key_str == NULL) @@ -219,6 +239,18 @@ static int nft_rule_expr_meta_xml_parse(struct nft_rule_expr *e, mxml_node_t *tr meta->key = key; e->flags |= (1 << NFT_EXPR_META_KEY); + if (nft_mxml_reg_parse(tree, "dreg", ®, MXML_DESCEND_FIRST, + NFT_XML_OPT, err) >= 0) { + meta->dreg = reg; + e->flags |= (1 << NFT_EXPR_META_DREG); + } + + if (nft_mxml_reg_parse(tree, "sreg", ®, MXML_DESCEND_FIRST, + NFT_XML_OPT, err) >= 0) { + meta->sreg = reg; + e->flags |= (1 << NFT_EXPR_META_SREG); + } + return 0; #else errno = EOPNOTSUPP; @@ -227,23 +259,92 @@ static int nft_rule_expr_meta_xml_parse(struct nft_rule_expr *e, mxml_node_t *tr } static int -nft_rule_expr_meta_snprintf(char *buf, size_t len, uint32_t type, - uint32_t flags, struct nft_rule_expr *e) +nft_rule_expr_meta_snprintf_default(char *buf, size_t len, + struct nft_rule_expr *e) { struct nft_expr_meta *meta = nft_expr_data(e); - switch(type) { - case NFT_OUTPUT_DEFAULT: + if (e->flags & (1 << NFT_EXPR_META_SREG)) { + return snprintf(buf, len, "set %s with reg %u ", + meta_key2str(meta->key), meta->sreg); + } + if (e->flags & (1 << NFT_EXPR_META_DREG)) { return snprintf(buf, len, "load %s => reg %u ", meta_key2str(meta->key), meta->dreg); + } + return 0; +} + +static int +nft_rule_expr_meta_snprintf_xml(char *buf, size_t size, + struct nft_rule_expr *e) +{ + int ret, len = size, offset = 0; + struct nft_expr_meta *meta = nft_expr_data(e); + + if (e->flags & (1 << NFT_EXPR_META_DREG)) { + ret = snprintf(buf+offset, len, "<dreg>%u</dreg>", + meta->dreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } + + if (e->flags & (1 << NFT_EXPR_META_KEY)) { + ret = snprintf(buf+offset, len, "<key>%s</key>", + meta_key2str(meta->key)); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } + + if (e->flags & (1 << NFT_EXPR_META_SREG)) { + ret = snprintf(buf+offset, len, "<sreg>%u</sreg>", + meta->sreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } + + return offset; +} + +static int +nft_rule_expr_meta_snprintf_json(char *buf, size_t size, + struct nft_rule_expr *e) +{ + int ret, len = size, offset = 0; + struct nft_expr_meta *meta = nft_expr_data(e); + + if (e->flags & (1 << NFT_EXPR_META_DREG)) { + ret = snprintf(buf+offset, len, "\"dreg\":%u,", + meta->dreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } + + if (e->flags & (1 << NFT_EXPR_META_KEY)) { + ret = snprintf(buf+offset, len, "\"key\":\"%s\",", + meta_key2str(meta->key)); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } + + if (e->flags & (1 << NFT_EXPR_META_SREG)) { + ret = snprintf(buf+offset, len, "\"sreg\":%u,", + meta->sreg); + SNPRINTF_BUFFER_SIZE(ret, size, len, offset); + } + + /* Remove the last separator characther */ + buf[offset-1] = '\0'; + + return offset-1; +} + +static int +nft_rule_expr_meta_snprintf(char *buf, size_t len, uint32_t type, + uint32_t flags, struct nft_rule_expr *e) +{ + switch(type) { + case NFT_OUTPUT_DEFAULT: + return nft_rule_expr_meta_snprintf_default(buf, len, e); case NFT_OUTPUT_XML: - return snprintf(buf, len, "<dreg>%u</dreg>" - "<key>%s</key>", - meta->dreg, meta_key2str(meta->key)); + return nft_rule_expr_meta_snprintf_xml(buf, len, e); case NFT_OUTPUT_JSON: - return snprintf(buf, len, "\"dreg\":%u," - "\"key\":\"%s\"", - meta->dreg, meta_key2str(meta->key)); + return nft_rule_expr_meta_snprintf_json(buf, len, e); default: break; } diff --git a/src/expr/queue.c b/src/expr/queue.c new file mode 100644 index 0000000..c3d0e19 --- /dev/null +++ b/src/expr/queue.c @@ -0,0 +1,254 @@ +/* + * (C) 2013 by Eric Leblond <eric@regit.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <arpa/inet.h> +#include <errno.h> +#include <linux/netfilter/nf_tables.h> + +#include "internal.h" +#include <libmnl/libmnl.h> +#include <libnftnl/expr.h> +#include <libnftnl/rule.h> +#include "expr_ops.h" + +struct nft_expr_queue { + uint16_t queuenum; + uint16_t queues_total; + uint16_t flags; +}; + +static int nft_rule_expr_queue_set(struct nft_rule_expr *e, uint16_t type, + const void *data, uint32_t data_len) +{ + struct nft_expr_queue *queue = nft_expr_data(e); + + switch(type) { + case NFT_EXPR_QUEUE_NUM: + queue->queuenum = *((uint16_t *)data); + break; + case NFT_EXPR_QUEUE_TOTAL: + queue->queues_total = *((uint16_t *)data); + break; + case NFT_EXPR_QUEUE_FLAGS: + queue->flags = *((uint16_t *)data); + break; + default: + return -1; + } + return 0; +} + +static const void * +nft_rule_expr_queue_get(const struct nft_rule_expr *e, uint16_t type, + uint32_t *data_len) +{ + struct nft_expr_queue *queue = nft_expr_data(e); + + switch(type) { + case NFT_EXPR_QUEUE_NUM: + *data_len = sizeof(queue->queuenum); + return &queue->queuenum; + case NFT_EXPR_QUEUE_TOTAL: + *data_len = sizeof(queue->queues_total); + return &queue->queues_total; + case NFT_EXPR_QUEUE_FLAGS: + *data_len = sizeof(queue->flags); + return &queue->flags; + } + return NULL; +} + +static int nft_rule_expr_queue_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, NFTA_QUEUE_MAX) < 0) + return MNL_CB_OK; + + switch(type) { + case NFTA_QUEUE_NUM: + case NFTA_QUEUE_TOTAL: + case NFTA_QUEUE_FLAGS: + if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0) { + perror("mnl_attr_validate"); + return MNL_CB_ERROR; + } + break; + } + + tb[type] = attr; + return MNL_CB_OK; +} + +static void +nft_rule_expr_queue_build(struct nlmsghdr *nlh, struct nft_rule_expr *e) +{ + struct nft_expr_queue *queue = nft_expr_data(e); + + if (e->flags & (1 << NFT_EXPR_QUEUE_NUM)) + mnl_attr_put_u16(nlh, NFTA_QUEUE_NUM, htons(queue->queuenum)); + if (e->flags & (1 << NFT_EXPR_QUEUE_TOTAL)) + mnl_attr_put_u16(nlh, NFTA_QUEUE_TOTAL, htons(queue->queues_total)); + if (e->flags & (1 << NFT_EXPR_QUEUE_FLAGS)) + mnl_attr_put_u16(nlh, NFTA_QUEUE_FLAGS, htons(queue->flags)); +} + +static int +nft_rule_expr_queue_parse(struct nft_rule_expr *e, struct nlattr *attr) +{ + struct nft_expr_queue *queue = nft_expr_data(e); + struct nlattr *tb[NFTA_QUEUE_MAX+1] = {}; + + if (mnl_attr_parse_nested(attr, nft_rule_expr_queue_cb, tb) < 0) + return -1; + + if (tb[NFTA_QUEUE_NUM]) { + queue->queuenum = ntohs(mnl_attr_get_u16(tb[NFTA_QUEUE_NUM])); + e->flags |= (1 << NFT_EXPR_QUEUE_NUM); + } + if (tb[NFTA_QUEUE_TOTAL]) { + queue->queues_total = ntohs(mnl_attr_get_u16(tb[NFTA_QUEUE_TOTAL])); + e->flags |= (1 << NFT_EXPR_QUEUE_TOTAL); + } + if (tb[NFTA_QUEUE_FLAGS]) { + queue->flags = ntohs(mnl_attr_get_u16(tb[NFTA_QUEUE_FLAGS])); + e->flags |= (1 << NFT_EXPR_QUEUE_FLAGS); + } + + return 0; +} + +static int +nft_rule_expr_queue_json_parse(struct nft_rule_expr *e, json_t *root) +{ +#ifdef JSON_PARSING + uint32_t type; + uint16_t code; + + if (nft_jansson_parse_val(root, "num", NFT_TYPE_U16, &type) < 0) + return -1; + + nft_rule_expr_set_u32(e, NFT_EXPR_QUEUE_NUM, type); + + if (nft_jansson_parse_val(root, "total", NFT_TYPE_U16, &code) < 0) + return -1; + + nft_rule_expr_set_u16(e, NFT_EXPR_QUEUE_TOTAL, code); + + if (nft_jansson_parse_val(root, "flags", NFT_TYPE_U16, &code) < 0) + return -1; + + nft_rule_expr_set_u16(e, NFT_EXPR_QUEUE_FLAGS, code); + + return 0; +#else + errno = EOPNOTSUPP; + return -1; +#endif +} + +static int +nft_rule_expr_queue_xml_parse(struct nft_rule_expr *e, mxml_node_t *tree) +{ +#ifdef XML_PARSING + struct nft_expr_queue *queue = nft_expr_data(e); + + if (nft_mxml_num_parse(tree, "num", MXML_DESCEND_FIRST, BASE_DEC, + &queue->queuenum, NFT_TYPE_U16, NFT_XML_MAND) != 0) + return -1; + + e->flags |= (1 << NFT_EXPR_QUEUE_NUM); + + if (nft_mxml_num_parse(tree, "total", MXML_DESCEND_FIRST, BASE_DEC, + &queue->queues_total, NFT_TYPE_U8, NFT_XML_MAND) != 0) + return -1; + + e->flags |= (1 << NFT_EXPR_QUEUE_TOTAL); + + if (nft_mxml_num_parse(tree, "flags", MXML_DESCEND_FIRST, BASE_DEC, + &queue->flags, NFT_TYPE_U8, NFT_XML_MAND) != 0) + return -1; + + e->flags |= (1 << NFT_EXPR_QUEUE_FLAGS); + + return 0; +#else + errno = EOPNOTSUPP; + return -1; +#endif +} + +static int +nft_rule_expr_queue_snprintf(char *buf, size_t len, uint32_t type, + uint32_t flags, struct nft_rule_expr *e) +{ + struct nft_expr_queue *queue = nft_expr_data(e); + int ret; + int one = 0; + + switch(type) { + case NFT_OUTPUT_DEFAULT: + ret = snprintf(buf, len, "num %u total %u", + queue->queuenum, queue->queues_total); + if (queue->flags) { + ret += snprintf(buf + ret , len - ret, " options "); + if (queue->flags & NFT_QUEUE_FLAG_BYPASS) { + ret += snprintf(buf + ret , + len - ret, "bypass"); + one = 1; + } + if (queue->flags & NFT_QUEUE_FLAG_CPU_FANOUT) { + if (one) + ret += snprintf(buf + ret , + len - ret, ","); + ret += snprintf(buf + ret , + len - ret, "fanout"); + } + } + return ret; + case NFT_OUTPUT_XML: + return snprintf(buf, len, "<num>%u</num>" + "<total>%u</total>" + "<flags>%u</flags>", + queue->queuenum, queue->queues_total, + queue->flags); + case NFT_OUTPUT_JSON: + return snprintf(buf, len, "\"num\":%u," + "\"total\":%u," + "\"flags\":%u,", + queue->queuenum, queue->queues_total, + queue->flags); + default: + break; + } + return -1; +} + +struct expr_ops expr_ops_queue = { + .name = "queue", + .alloc_len = sizeof(struct nft_expr_queue), + .max_attr = NFTA_QUEUE_MAX, + .set = nft_rule_expr_queue_set, + .get = nft_rule_expr_queue_get, + .parse = nft_rule_expr_queue_parse, + .build = nft_rule_expr_queue_build, + .snprintf = nft_rule_expr_queue_snprintf, + .xml_parse = nft_rule_expr_queue_xml_parse, + .json_parse = nft_rule_expr_queue_json_parse, +}; + +static void __init expr_queue_init(void) +{ + nft_expr_ops_register(&expr_ops_queue); +} diff --git a/src/table.c b/src/table.c index f50a968..c834a4e 100644 --- a/src/table.c +++ b/src/table.c @@ -31,6 +31,7 @@ struct nft_table { const char *name; uint8_t family; uint32_t table_flags; + uint32_t use; uint32_t flags; }; @@ -70,6 +71,9 @@ void nft_table_attr_unset(struct nft_table *t, uint16_t attr) case NFT_TABLE_ATTR_FLAGS: case NFT_TABLE_ATTR_FAMILY: break; + case NFT_TABLE_ATTR_USE: + /* Cannot be unset, ignoring it */ + return; } t->flags &= ~(1 << attr); } @@ -93,6 +97,9 @@ void nft_table_attr_set(struct nft_table *t, uint16_t attr, const void *data) t->family = *((uint8_t *)data); t->flags |= (1 << NFT_TABLE_ATTR_FAMILY); break; + case NFT_TABLE_ATTR_USE: + /* Cannot be unset, ignoring it */ + break; } } EXPORT_SYMBOL(nft_table_attr_set); @@ -127,6 +134,8 @@ const void *nft_table_attr_get(struct nft_table *t, uint16_t attr) return &t->table_flags; case NFT_TABLE_ATTR_FAMILY: return &t->family; + case NFT_TABLE_ATTR_USE: + return &t->use; } return NULL; } @@ -182,6 +191,12 @@ static int nft_table_parse_attr_cb(const struct nlattr *attr, void *data) return MNL_CB_ERROR; } break; + case NFTA_TABLE_USE: + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { + perror("mnl_attr_validate"); + return MNL_CB_ERROR; + } + break; } tb[type] = attr; @@ -202,6 +217,10 @@ int nft_table_nlmsg_parse(const struct nlmsghdr *nlh, struct nft_table *t) t->table_flags = ntohl(mnl_attr_get_u32(tb[NFTA_TABLE_FLAGS])); t->flags |= (1 << NFT_TABLE_ATTR_FLAGS); } + if (tb[NFTA_TABLE_USE]) { + t->use = ntohl(mnl_attr_get_u32(tb[NFTA_TABLE_USE])); + t->flags |= (1 << NFT_TABLE_ATTR_USE); + } t->family = nfg->nfgen_family; t->flags |= (1 << NFT_TABLE_ATTR_FAMILY); @@ -368,23 +387,27 @@ static int nft_table_snprintf_json(char *buf, size_t size, struct nft_table *t) "{\"table\":{" "\"name\":\"%s\"," "\"family\":\"%s\"," - "\"flags\":%d" + "\"flags\":%d," + "\"use\":%d" "}" "}" , - t->name, nft_family2str(t->family), t->table_flags); + t->name, nft_family2str(t->family), + t->table_flags, t->use); } static int nft_table_snprintf_xml(char *buf, size_t size, struct nft_table *t) { return snprintf(buf, size, "<table><name>%s</name><family>%s</family>" - "<flags>%d</flags></table>", - t->name, nft_family2str(t->family), t->table_flags); + "<flags>%d</flags><use>%d</use></table>", + t->name, nft_family2str(t->family), + t->table_flags, t->use); } static int nft_table_snprintf_default(char *buf, size_t size, struct nft_table *t) { - return snprintf(buf, size, "table %s %s flags %x", - t->name, nft_family2str(t->family), t->table_flags); + return snprintf(buf, size, "table %s %s flags %x use %d", + t->name, nft_family2str(t->family), + t->table_flags, t->use); } int nft_table_snprintf(char *buf, size_t size, struct nft_table *t, diff --git a/src/utils.c b/src/utils.c index dd7fd1d..9691c4c 100644 --- a/src/utils.c +++ b/src/utils.c @@ -27,6 +27,8 @@ const char *nft_family2str(uint32_t family) return "ip"; case AF_INET6: return "ip6"; + case 1: + return "inet"; case AF_BRIDGE: return "bridge"; case 3: /* NFPROTO_ARP */ @@ -42,6 +44,8 @@ int nft_str2family(const char *family) return AF_INET; else if (strcmp(family, "ip6") == 0) return AF_INET6; + else if (strcmp(family, "inet") == 0) + return 1; else if (strcmp(family, "bridge") == 0) return AF_BRIDGE; else if (strcmp(family, "arp") == 0) diff --git a/tests/Makefile.am b/tests/Makefile.am index e82ebf4..d5df071 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -19,6 +19,7 @@ check_PROGRAMS = nft-parsing-test \ nft-expr_meta-test \ nft-expr_nat-test \ nft-expr_payload-test \ + nft-expr_queue-test \ nft-expr_reject-test \ nft-expr_target-test @@ -79,6 +80,9 @@ nft_expr_nat_test_LDADD = ../src/libnftnl.la ${LIBMNL_LIBS} nft_expr_payload_test_SOURCES = nft-expr_payload-test.c nft_expr_payload_test_LDADD = ../src/libnftnl.la ${LIBMNL_LIBS} +nft_expr_queue_test_SOURCES = nft-expr_queue-test.c +nft_expr_queue_test_LDADD = ../src/libnftnl.la ${LIBMNL_LIBS} + nft_expr_reject_test_SOURCES = nft-expr_reject-test.c nft_expr_reject_test_LDADD = ../src/libnftnl.la ${LIBMNL_LIBS} diff --git a/tests/jsonfiles/01-table.json b/tests/jsonfiles/01-table.json index 8668fb79..9be7b40 100644 --- a/tests/jsonfiles/01-table.json +++ b/tests/jsonfiles/01-table.json @@ -1 +1 @@ -{"nftables":[{"table":{"name":"filter","family":"ip","flags":0}}]} +{"nftables":[{"table":{"name":"filter","family":"ip","flags":0,"use":0}}]} diff --git a/tests/jsonfiles/02-table.json b/tests/jsonfiles/02-table.json index f8f5a1d..4ff5674 100644 --- a/tests/jsonfiles/02-table.json +++ b/tests/jsonfiles/02-table.json @@ -1 +1 @@ -{"nftables":[{"table":{"name":"filter2","family":"ip6","flags":0}}]} +{"nftables":[{"table":{"name":"filter2","family":"ip6","flags":0,"use":0}}]} diff --git a/tests/jsonfiles/64-ruleset.json b/tests/jsonfiles/64-ruleset.json index 54eb1ff..3495d83 100644 --- a/tests/jsonfiles/64-ruleset.json +++ b/tests/jsonfiles/64-ruleset.json @@ -1 +1 @@ -{"nftables":[{"table":{"name":"filter","family":"ip","flags":0}},{"table":{"name":"filter2","family":"ip6","flags":0}},{"chain":{"name":"input","handle":1,"bytes":10681449,"packets":16216,"family":"ip","table":"filter","use":0,"type":"filter","hooknum":"input","prio":0,"policy":"accept"}},{"chain":{"name":"forward","handle":2,"bytes":0,"packets":0,"family":"ip","table":"filter","use":0,"type":"filter","hooknum":"forward","prio":0,"policy":"accept"}},{"chain":{"name":"output","handle":3,"bytes":2375830,"packets":15184,"family":"ip","table":"filter","use":0,"type":"filter","hooknum":"output","prio":0,"policy":"accept"}},{"chain":{"name":"chain1","handle":4,"bytes":0,"packets":0,"family":"ip","table":"filter","use":0}},{"set":{"name":"set0","table":"filter","flags":3,"family":"ip","key_type":12,"key_len":2}},{"rule":{"family":"ip","table":"filter","chain":"output","handle":6,"expr":[{"type":"payload","dreg":1,"offset":16,"len":4,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":4,"data0":"0x0100a8c0"}}},{"type":"counter","pkts":0,"bytes":0},{"type":"immediate","dreg":0,"immediatedata":{"data_reg":{"type":"verdict","verdict":"drop"}}}]}},{"rule":{"family":"ip","table":"filter","chain":"output","handle":9,"expr":[{"type":"payload","dreg":1,"offset":9,"len":1,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":1,"data0":"0x00000006"}}},{"type":"payload","dreg":1,"offset":2,"len":2,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":2,"data0":"0x00001600"}}},{"type":"counter","pkts":0,"bytes":0}]}},{"rule":{"family":"ip","table":"filter","chain":"output","handle":10,"expr":[{"type":"payload","dreg":1,"offset":16,"len":4,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":4,"data0":"0x0100a8c0"}}},{"type":"counter","pkts":0,"bytes":0}]}},{"rule":{"family":"ip","table":"filter","chain":"output","handle":11,"expr":[{"type":"payload","dreg":1,"offset":16,"len":4,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":4,"data0":"0x0100a8c0"}}},{"type":"counter","pkts":0,"bytes":0},{"type":"immediate","dreg":0,"immediatedata":{"data_reg":{"type":"verdict","verdict":"drop"}}}]}}]} +{"nftables":[{"table":{"name":"filter","family":"ip","flags":0,"use":0}},{"table":{"name":"filter2","family":"ip6","flags":0,"use":0}},{"chain":{"name":"input","handle":1,"bytes":10681449,"packets":16216,"family":"ip","table":"filter","use":0,"type":"filter","hooknum":"input","prio":0,"policy":"accept"}},{"chain":{"name":"forward","handle":2,"bytes":0,"packets":0,"family":"ip","table":"filter","use":0,"type":"filter","hooknum":"forward","prio":0,"policy":"accept"}},{"chain":{"name":"output","handle":3,"bytes":2375830,"packets":15184,"family":"ip","table":"filter","use":0,"type":"filter","hooknum":"output","prio":0,"policy":"accept"}},{"chain":{"name":"chain1","handle":4,"bytes":0,"packets":0,"family":"ip","table":"filter","use":0}},{"set":{"name":"set0","table":"filter","flags":3,"family":"ip","key_type":12,"key_len":2}},{"rule":{"family":"ip","table":"filter","chain":"output","handle":6,"expr":[{"type":"payload","dreg":1,"offset":16,"len":4,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":4,"data0":"0x0100a8c0"}}},{"type":"counter","pkts":0,"bytes":0},{"type":"immediate","dreg":0,"immediatedata":{"data_reg":{"type":"verdict","verdict":"drop"}}}]}},{"rule":{"family":"ip","table":"filter","chain":"output","handle":9,"expr":[{"type":"payload","dreg":1,"offset":9,"len":1,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":1,"data0":"0x00000006"}}},{"type":"payload","dreg":1,"offset":2,"len":2,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":2,"data0":"0x00001600"}}},{"type":"counter","pkts":0,"bytes":0}]}},{"rule":{"family":"ip","table":"filter","chain":"output","handle":10,"expr":[{"type":"payload","dreg":1,"offset":16,"len":4,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":4,"data0":"0x0100a8c0"}}},{"type":"counter","pkts":0,"bytes":0}]}},{"rule":{"family":"ip","table":"filter","chain":"output","handle":11,"expr":[{"type":"payload","dreg":1,"offset":16,"len":4,"base":"link"},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":4,"data0":"0x0100a8c0"}}},{"type":"counter","pkts":0,"bytes":0},{"type":"immediate","dreg":0,"immediatedata":{"data_reg":{"type":"verdict","verdict":"drop"}}}]}}]} diff --git a/tests/jsonfiles/65-rule-meta-target.json b/tests/jsonfiles/65-rule-meta-target.json new file mode 100644 index 0000000..cf036bc --- /dev/null +++ b/tests/jsonfiles/65-rule-meta-target.json @@ -0,0 +1 @@ +{"nftables":[{"rule":{"family":"ip","table":"filter","chain":"output","handle":1,"expr":[{"type":"meta","key":"mark","sreg":1},{"type":"cmp","sreg":1,"op":"eq","cmpdata":{"data_reg":{"type":"value","len":4,"data0":"0x000003e8"}}},{"type":"counter","pkts":0,"bytes":0}]}}]} diff --git a/tests/nft-expr_queue-test.c b/tests/nft-expr_queue-test.c new file mode 100644 index 0000000..cfe01b6 --- /dev/null +++ b/tests/nft-expr_queue-test.c @@ -0,0 +1,99 @@ +/* + * (C) 2013 by Eric Leblond <eric@regit.org> + * + * Based on test framework by Ana Rey Botello <anarey@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <netinet/in.h> +#include <netinet/ip.h> +#include <linux/netfilter/nf_tables.h> +#include <linux/netfilter/xt_iprange.h> +#include <libmnl/libmnl.h> +#include <libnftnl/rule.h> +#include <libnftnl/expr.h> + +static int test_ok = 1; + +static void print_err(const char *msg) +{ + test_ok = 0; + printf("\033[31mERROR:\e[0m %s\n", msg); +} + +static void cmp_nft_rule_expr(struct nft_rule_expr *rule_a, + struct nft_rule_expr *rule_b) +{ + if (nft_rule_expr_get_u16(rule_a, NFT_EXPR_QUEUE_NUM) != + nft_rule_expr_get_u16(rule_b, NFT_EXPR_QUEUE_NUM)) + print_err("Expr NFT_EXPR_QUEUE_NUM mismatches"); + if (nft_rule_expr_get_u16(rule_a, NFT_EXPR_QUEUE_TOTAL) != + nft_rule_expr_get_u16(rule_b, NFT_EXPR_QUEUE_TOTAL)) + print_err("Expr NFT_EXPR_QUEUE_TOTAL mismatches"); +} + +int main(int argc, char *argv[]) +{ + struct nft_rule *a, *b; + struct nft_rule_expr *ex; + struct nlmsghdr *nlh; + char buf[4096]; + struct nft_rule_expr_iter *iter_a, *iter_b; + struct nft_rule_expr *rule_a, *rule_b; + + a = nft_rule_alloc(); + b = nft_rule_alloc(); + if (a == NULL || b == NULL) + print_err("OOM"); + ex = nft_rule_expr_alloc("queue"); + if (ex == NULL) + print_err("OOM"); + + nft_rule_expr_set_u16(ex, NFT_EXPR_QUEUE_NUM, 0x123); + nft_rule_expr_set_u16(ex, NFT_EXPR_QUEUE_TOTAL, 0x2); + nft_rule_expr_set_u16(ex, NFT_EXPR_QUEUE_FLAGS, 0x2); + + nft_rule_add_expr(a, ex); + + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_NEWRULE, AF_INET, 0, 1234); + nft_rule_nlmsg_build_payload(nlh, a); + + if (nft_rule_nlmsg_parse(nlh, b) < 0) + print_err("parsing problems"); + + iter_a = nft_rule_expr_iter_create(a); + iter_b = nft_rule_expr_iter_create(b); + if (iter_a == NULL || iter_b == NULL) + print_err("OOM"); + + rule_a = nft_rule_expr_iter_next(iter_a); + rule_b = nft_rule_expr_iter_next(iter_b); + if (rule_a == NULL || rule_b == NULL) + print_err("OOM"); + + cmp_nft_rule_expr(rule_a, rule_b); + + if (nft_rule_expr_iter_next(iter_a) != NULL || + nft_rule_expr_iter_next(iter_b) != NULL) + print_err("More 1 expr."); + + nft_rule_expr_iter_destroy(iter_a); + nft_rule_expr_iter_destroy(iter_b); + nft_rule_free(a); + nft_rule_free(b); + + if (!test_ok) + exit(EXIT_FAILURE); + + printf("%s: \033[32mOK\e[0m\n", argv[0]); + return EXIT_SUCCESS; +} diff --git a/tests/xmlfiles/01-table.xml b/tests/xmlfiles/01-table.xml index f806334..655b544 100644 --- a/tests/xmlfiles/01-table.xml +++ b/tests/xmlfiles/01-table.xml @@ -1 +1 @@ -<nftables><table><name>filter</name><family>ip</family><flags>0</flags></table></nftables> +<nftables><table><name>filter</name><family>ip</family><flags>0</flags><use>0</use></table></nftables> diff --git a/tests/xmlfiles/02-table.xml b/tests/xmlfiles/02-table.xml index ea4e154..5de924f 100644 --- a/tests/xmlfiles/02-table.xml +++ b/tests/xmlfiles/02-table.xml @@ -1 +1 @@ -<nftables><table><name>nat</name><family>ip6</family><flags>0</flags></table></nftables> +<nftables><table><name>nat</name><family>ip6</family><flags>0</flags><use>0</use></table></nftables> diff --git a/tests/xmlfiles/75-ruleset.xml b/tests/xmlfiles/75-ruleset.xml index 76f36a5..9e88433 100644 --- a/tests/xmlfiles/75-ruleset.xml +++ b/tests/xmlfiles/75-ruleset.xml @@ -1 +1 @@ -<nftables><table><name>filter</name><family>ip</family><flags>0</flags></table><table><name>filter</name><family>ip6</family><flags>0</flags></table><chain><name>input</name><handle>1</handle><bytes>0</bytes><packets>0</packets><table>filter</table><family>ip</family></chain><chain><name>output</name><handle>2</handle><bytes>0</bytes><packets>0</packets><table>filter</table><family>ip</family></chain><chain><name>forward</name><handle>1</handle><bytes>0</bytes><packets>0</packets><table>filter</table><family>ip6</family></chain><set><family>ip6</family><table>filter</table><name>set0</name><flags>3</flags><key_type>12</key_type><key_len>2</key_len><data_type>0</data_type><data_len>0</data_len><set_elem><flags>0</flags><key><data_reg type="value"><len>2</len><data0>0x00004300</data0></data_reg></key></set_elem><set_elem><flags>0</flags><key><data_reg type="value"><len>2</len><data0>0x00003500</data0></data_reg></key></set_elem></set><set><family>ip</family><table>filter</table><name>map0</name><flags>11</flags><key_type>12</key_type><key_len>2</key_len><data_type>4294967040</data_type><data_len>16</data_len><set_elem><flags>0</flags><key><data_reg type="value"><len>2</len><data0>0x00005000</data0></data_reg></key><data><data_reg type="verdict"><verdict>drop</verdict></data_reg></data></set_elem><set_elem><flags>0</flags><key><data_reg type="value"><len>2</len><data0>0x00001600</data0></data_reg></key><data><data_reg type="verdict"><verdict>accept</verdict></data_reg></data></set_elem></set><rule><family>ip</family><table>filter</table><chain>input</chain><handle>8</handle><expr type="counter"><pkts>0</pkts><bytes>0</bytes></expr><expr type="immediate"><dreg>0</dreg><immediatedata><data_reg type="verdict"><verdict>accept</verdict></data_reg></immediatedata></expr></rule><rule><family>ip</family><table>filter</table><chain>output</chain><handle>9</handle><expr type="payload"><dreg>1</dreg><offset>9</offset><len>1</len><base>network</base></expr><expr type="cmp"><sreg>1</sreg><op>eq</op><cmpdata><data_reg type="value"><len>1</len><data0>0x00000006</data0></data_reg></cmpdata></expr><expr type="payload"><dreg>1</dreg><offset>2</offset><len>2</len><base>transport</base></expr><expr type="lookup"><set>map0</set><sreg>1</sreg><dreg>0</dreg></expr></rule><rule><family>ip6</family><table>filter</table><chain>forward</chain><handle>2</handle><expr type="payload"><dreg>1</dreg><offset>6</offset><len>1</len><base>network</base></expr><expr type="cmp"><sreg>1</sreg><op>eq</op><cmpdata><data_reg type="value"><len>1</len><data0>0x00000011</data0></data_reg></cmpdata></expr><expr type="payload"><dreg>1</dreg><offset>2</offset><len>2</len><base>transport</base></expr><expr type="lookup"><set>set0</set><sreg>1</sreg><dreg>0</dreg></expr><expr type="counter"><pkts>0</pkts><bytes>0</bytes></expr><expr type="immediate"><dreg>0</dreg><immediatedata><data_reg type="verdict"><verdict>accept</verdict></data_reg></immediatedata></expr></rule></nftables> +<nftables><table><name>filter</name><family>ip</family><flags>0</flags><use>0</use></table><table><name>filter</name><family>ip6</family><flags>0</flags><use>0</use></table><chain><name>input</name><handle>1</handle><bytes>0</bytes><packets>0</packets><table>filter</table><family>ip</family></chain><chain><name>output</name><handle>2</handle><bytes>0</bytes><packets>0</packets><table>filter</table><family>ip</family></chain><chain><name>forward</name><handle>1</handle><bytes>0</bytes><packets>0</packets><table>filter</table><family>ip6</family></chain><set><family>ip6</family><table>filter</table><name>set0</name><flags>3</flags><key_type>12</key_type><key_len>2</key_len><data_type>0</data_type><data_len>0</data_len><set_elem><flags>0</flags><key><data_reg type="value"><len>2</len><data0>0x00004300</data0></data_reg></key></set_elem><set_elem><flags>0</flags><key><data_reg type="value"><len>2</len><data0>0x00003500</data0></data_reg></key></set_elem></set><set><family>ip</family><table>filter</table><name>map0</name><flags>11</flags><key_type>12</key_type><key_len>2</key_len><data_type>4294967040</data_type><data_len>16</data_len><set_elem><flags>0</flags><key><data_reg type="value"><len>2</len><data0>0x00005000</data0></data_reg></key><data><data_reg type="verdict"><verdict>drop</verdict></data_reg></data></set_elem><set_elem><flags>0</flags><key><data_reg type="value"><len>2</len><data0>0x00001600</data0></data_reg></key><data><data_reg type="verdict"><verdict>accept</verdict></data_reg></data></set_elem></set><rule><family>ip</family><table>filter</table><chain>input</chain><handle>8</handle><expr type="counter"><pkts>0</pkts><bytes>0</bytes></expr><expr type="immediate"><dreg>0</dreg><immediatedata><data_reg type="verdict"><verdict>accept</verdict></data_reg></immediatedata></expr></rule><rule><family>ip</family><table>filter</table><chain>output</chain><handle>9</handle><expr type="payload"><dreg>1</dreg><offset>9</offset><len>1</len><base>network</base></expr><expr type="cmp"><sreg>1</sreg><op>eq</op><cmpdata><data_reg type="value"><len>1</len><data0>0x00000006</data0></data_reg></cmpdata></expr><expr type="payload"><dreg>1</dreg><offset>2</offset><len>2</len><base>transport</base></expr><expr type="lookup"><set>map0</set><sreg>1</sreg><dreg>0</dreg></expr></rule><rule><family>ip6</family><table>filter</table><chain>forward</chain><handle>2</handle><expr type="payload"><dreg>1</dreg><offset>6</offset><len>1</len><base>network</base></expr><expr type="cmp"><sreg>1</sreg><op>eq</op><cmpdata><data_reg type="value"><len>1</len><data0>0x00000011</data0></data_reg></cmpdata></expr><expr type="payload"><dreg>1</dreg><offset>2</offset><len>2</len><base>transport</base></expr><expr type="lookup"><set>set0</set><sreg>1</sreg><dreg>0</dreg></expr><expr type="counter"><pkts>0</pkts><bytes>0</bytes></expr><expr type="immediate"><dreg>0</dreg><immediatedata><data_reg type="verdict"><verdict>accept</verdict></data_reg></immediatedata></expr></rule></nftables> diff --git a/tests/xmlfiles/76-rule-meta_target.xml b/tests/xmlfiles/76-rule-meta_target.xml new file mode 100644 index 0000000..970de61 --- /dev/null +++ b/tests/xmlfiles/76-rule-meta_target.xml @@ -0,0 +1 @@ +<nftables><rule><family>ip6</family><table>filter</table><chain>test</chain><handle>129</handle><expr type="meta"><key>mark</key><sreg>1</sreg></expr></rule></nftables> |