summaryrefslogtreecommitdiffstats
path: root/iptables/nft-bridge.c
diff options
context:
space:
mode:
Diffstat (limited to 'iptables/nft-bridge.c')
-rw-r--r--iptables/nft-bridge.c232
1 files changed, 222 insertions, 10 deletions
diff --git a/iptables/nft-bridge.c b/iptables/nft-bridge.c
index 2e4b309b..3f85cbbf 100644
--- a/iptables/nft-bridge.c
+++ b/iptables/nft-bridge.c
@@ -17,8 +17,11 @@
#include <libiptc/libxtc.h>
#include <linux/netfilter/nf_tables.h>
+#include <libnftnl/set.h>
+
#include "nft-shared.h"
#include "nft-bridge.h"
+#include "nft-cache.h"
#include "nft.h"
void ebt_cs_clean(struct iptables_command_state *cs)
@@ -126,7 +129,8 @@ static int _add_action(struct nftnl_rule *r, struct iptables_command_state *cs)
return add_action(r, cs, false);
}
-static int nft_bridge_add(struct nftnl_rule *r, void *data)
+static int nft_bridge_add(struct nft_handle *h,
+ struct nftnl_rule *r, void *data)
{
struct iptables_command_state *cs = data;
struct ebt_match *iter;
@@ -182,7 +186,7 @@ static int nft_bridge_add(struct nftnl_rule *r, void *data)
for (iter = cs->match_list; iter; iter = iter->next) {
if (iter->ismatch) {
- if (add_match(r, iter->u.match->m))
+ if (add_match(h, r, iter->u.match->m))
break;
} else {
if (add_target(r, iter->u.watcher->t))
@@ -290,6 +294,212 @@ static void nft_bridge_parse_immediate(const char *jumpto, bool nft_goto,
cs->jumpto = jumpto;
}
+/* return 0 if saddr, 1 if daddr, -1 on error */
+static int
+lookup_check_ether_payload(uint32_t base, uint32_t offset, uint32_t len)
+{
+ if (base != 0 || len != ETH_ALEN)
+ return -1;
+
+ switch (offset) {
+ case offsetof(struct ether_header, ether_dhost):
+ return 1;
+ case offsetof(struct ether_header, ether_shost):
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+/* return 0 if saddr, 1 if daddr, -1 on error */
+static int
+lookup_check_iphdr_payload(uint32_t base, uint32_t offset, uint32_t len)
+{
+ if (base != 1 || len != 4)
+ return -1;
+
+ switch (offset) {
+ case offsetof(struct iphdr, daddr):
+ return 1;
+ case offsetof(struct iphdr, saddr):
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+/* Make sure previous payload expression(s) is/are consistent and extract if
+ * matching on source or destination address and if matching on MAC and IP or
+ * only MAC address. */
+static int lookup_analyze_payloads(const struct nft_xt_ctx *ctx,
+ bool *dst, bool *ip)
+{
+ int val, val2 = -1;
+
+ if (ctx->flags & NFT_XT_CTX_PREV_PAYLOAD) {
+ val = lookup_check_ether_payload(ctx->prev_payload.base,
+ ctx->prev_payload.offset,
+ ctx->prev_payload.len);
+ if (val < 0) {
+ DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
+ ctx->prev_payload.base, ctx->prev_payload.offset,
+ ctx->prev_payload.len);
+ return -1;
+ }
+ if (!(ctx->flags & NFT_XT_CTX_PAYLOAD)) {
+ DEBUGP("Previous but no current payload?\n");
+ return -1;
+ }
+ val2 = lookup_check_iphdr_payload(ctx->payload.base,
+ ctx->payload.offset,
+ ctx->payload.len);
+ if (val2 < 0) {
+ DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
+ ctx->payload.base, ctx->payload.offset,
+ ctx->payload.len);
+ return -1;
+ } else if (val != val2) {
+ DEBUGP("mismatching payload match offsets\n");
+ return -1;
+ }
+ } else if (ctx->flags & NFT_XT_CTX_PAYLOAD) {
+ val = lookup_check_ether_payload(ctx->payload.base,
+ ctx->payload.offset,
+ ctx->payload.len);
+ if (val < 0) {
+ DEBUGP("unknown payload base/offset/len %d/%d/%d\n",
+ ctx->payload.base, ctx->payload.offset,
+ ctx->payload.len);
+ return -1;
+ }
+ } else {
+ DEBUGP("unknown LHS of lookup expression\n");
+ return -1;
+ }
+
+ if (dst)
+ *dst = (val == 1);
+ if (ip)
+ *ip = (val2 != -1);
+ return 0;
+}
+
+static int set_elems_to_among_pairs(struct nft_among_pair *pairs,
+ const struct nftnl_set *s, int cnt)
+{
+ struct nftnl_set_elems_iter *iter = nftnl_set_elems_iter_create(s);
+ struct nftnl_set_elem *elem;
+ size_t tmpcnt = 0;
+ const void *data;
+ uint32_t datalen;
+ int ret = -1;
+
+ if (!iter) {
+ fprintf(stderr, "BUG: set elems iter allocation failed\n");
+ return ret;
+ }
+
+ while ((elem = nftnl_set_elems_iter_next(iter))) {
+ data = nftnl_set_elem_get(elem, NFTNL_SET_ELEM_KEY, &datalen);
+ if (!data) {
+ fprintf(stderr, "BUG: set elem without key\n");
+ goto err;
+ }
+ if (datalen > sizeof(*pairs)) {
+ fprintf(stderr, "BUG: overlong set elem\n");
+ goto err;
+ }
+ nft_among_insert_pair(pairs, &tmpcnt, data);
+ }
+ ret = 0;
+err:
+ nftnl_set_elems_iter_destroy(iter);
+ return ret;
+}
+
+static struct nftnl_set *set_from_lookup_expr(struct nft_xt_ctx *ctx,
+ const struct nftnl_expr *e)
+{
+ const char *set_name = nftnl_expr_get_str(e, NFTNL_EXPR_LOOKUP_SET);
+ struct nftnl_set_list *slist;
+
+ slist = nft_set_list_get(ctx->h, ctx->table, set_name);
+ if (slist)
+ return nftnl_set_list_lookup_byname(slist, set_name);
+
+ return NULL;
+}
+
+static void nft_bridge_parse_lookup(struct nft_xt_ctx *ctx,
+ struct nftnl_expr *e, void *data)
+{
+ struct xtables_match *match = NULL;
+ struct nft_among_data *among_data;
+ bool is_dst, have_ip, inv;
+ struct ebt_match *ematch;
+ struct nftnl_set *s;
+ size_t poff, size;
+ uint32_t cnt;
+
+ if (lookup_analyze_payloads(ctx, &is_dst, &have_ip))
+ return;
+
+ s = set_from_lookup_expr(ctx, e);
+ if (!s)
+ xtables_error(OTHER_PROBLEM,
+ "BUG: lookup expression references unknown set");
+
+ cnt = nftnl_set_get_u32(s, NFTNL_SET_DESC_SIZE);
+
+ for (ematch = ctx->cs->match_list; ematch; ematch = ematch->next) {
+ if (!ematch->ismatch || strcmp(ematch->u.match->name, "among"))
+ continue;
+
+ match = ematch->u.match;
+ among_data = (struct nft_among_data *)match->m->data;
+
+ size = cnt + among_data->src.cnt + among_data->dst.cnt;
+ size *= sizeof(struct nft_among_pair);
+
+ size += XT_ALIGN(sizeof(struct xt_entry_match)) +
+ sizeof(struct nft_among_data);
+
+ match->m = xtables_realloc(match->m, size);
+ break;
+ }
+ if (!match) {
+ match = xtables_find_match("among", XTF_TRY_LOAD,
+ &ctx->cs->matches);
+
+ size = cnt * sizeof(struct nft_among_pair);
+ size += XT_ALIGN(sizeof(struct xt_entry_match)) +
+ sizeof(struct nft_among_data);
+
+ match->m = xtables_calloc(1, size);
+ strcpy(match->m->u.user.name, match->name);
+ match->m->u.user.revision = match->revision;
+ xs_init_match(match);
+
+ if (ctx->h->ops->parse_match != NULL)
+ ctx->h->ops->parse_match(match, ctx->cs);
+ }
+ if (!match)
+ return;
+
+ match->m->u.match_size = size;
+
+ inv = !!(nftnl_expr_get_u32(e, NFTNL_EXPR_LOOKUP_FLAGS) &
+ NFT_LOOKUP_F_INV);
+
+ among_data = (struct nft_among_data *)match->m->data;
+ poff = nft_among_prepare_data(among_data, is_dst, cnt, inv, have_ip);
+ if (set_elems_to_among_pairs(among_data->pairs + poff, s, cnt))
+ xtables_error(OTHER_PROBLEM,
+ "ebtables among pair parsing failed");
+
+ ctx->flags &= ~(NFT_XT_CTX_PAYLOAD | NFT_XT_CTX_PREV_PAYLOAD);
+}
+
static void parse_watcher(void *object, struct ebt_match **match_list,
bool ismatch)
{
@@ -332,11 +542,12 @@ static void nft_bridge_parse_target(struct xtables_target *t, void *data)
cs->target = t;
}
-static void nft_rule_to_ebtables_command_state(const struct nftnl_rule *r,
+static void nft_rule_to_ebtables_command_state(struct nft_handle *h,
+ const struct nftnl_rule *r,
struct iptables_command_state *cs)
{
cs->eb.bitmask = EBT_NOPROTO;
- nft_rule_to_iptables_command_state(r, cs);
+ nft_rule_to_iptables_command_state(h, r, cs);
}
static void print_iface(const char *option, const char *name, bool invert)
@@ -471,15 +682,15 @@ static void nft_bridge_save_rule(const void *data, unsigned int format)
fputc('\n', stdout);
}
-static void nft_bridge_print_rule(struct nftnl_rule *r, unsigned int num,
- unsigned int format)
+static void nft_bridge_print_rule(struct nft_handle *h, struct nftnl_rule *r,
+ unsigned int num, unsigned int format)
{
struct iptables_command_state cs = {};
if (format & FMT_LINENUMBERS)
printf("%d ", num);
- nft_rule_to_ebtables_command_state(r, &cs);
+ nft_rule_to_ebtables_command_state(h, r, &cs);
nft_bridge_save_rule(&cs, format);
ebt_cs_clean(&cs);
}
@@ -536,14 +747,14 @@ static bool nft_bridge_is_same(const void *data_a, const void *data_b)
return strcmp(a->in, b->in) == 0 && strcmp(a->out, b->out) == 0;
}
-static bool nft_bridge_rule_find(struct nft_family_ops *ops, struct nftnl_rule *r,
+static bool nft_bridge_rule_find(struct nft_handle *h, struct nftnl_rule *r,
void *data)
{
struct iptables_command_state *cs = data;
struct iptables_command_state this = {};
bool ret = false;
- nft_rule_to_ebtables_command_state(r, &this);
+ nft_rule_to_ebtables_command_state(h, r, &this);
DEBUGP("comparing with... ");
@@ -567,7 +778,7 @@ static bool nft_bridge_rule_find(struct nft_family_ops *ops, struct nftnl_rule *
ret = true;
out:
- ops->clear_cs(&this);
+ h->ops->clear_cs(&this);
return ret;
}
@@ -740,6 +951,7 @@ struct nft_family_ops nft_family_ops_bridge = {
.parse_meta = nft_bridge_parse_meta,
.parse_payload = nft_bridge_parse_payload,
.parse_immediate = nft_bridge_parse_immediate,
+ .parse_lookup = nft_bridge_parse_lookup,
.parse_match = nft_bridge_parse_match,
.parse_target = nft_bridge_parse_target,
.print_table_header = nft_bridge_print_table_header,