new file mode 100644
@@ -0,0 +1,278 @@
+/* ebt_among
+ *
+ * Authors:
+ * Grzegorz Borowiak <grzes@gnu.univ.gda.pl>
+ *
+ * August, 2003
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <netinet/ether.h>
+#include <linux/if_ether.h>
+#include <linux/netfilter_bridge/ebt_among.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <xtables.h>
+#include "iptables/nft.h"
+#include "iptables/nft-bridge.h"
+
+#define AMONG_DST '1'
+#define AMONG_SRC '2'
+#define AMONG_DST_F '3'
+#define AMONG_SRC_F '4'
+
+static const struct option bramong_opts[] = {
+ {"among-dst", required_argument, 0, AMONG_DST},
+ {"among-src", required_argument, 0, AMONG_SRC},
+ {"among-dst-file", required_argument, 0, AMONG_DST_F},
+ {"among-src-file", required_argument, 0, AMONG_SRC_F},
+ {0}
+};
+
+static void bramong_print_help(void)
+{
+ printf(
+"`among' options:\n"
+"--among-dst [!] list : matches if ether dst is in list\n"
+"--among-src [!] list : matches if ether src is in list\n"
+"--among-dst-file [!] file : obtain dst list from file\n"
+"--among-src-file [!] file : obtain src list from file\n"
+"list has form:\n"
+" xx:xx:xx:xx:xx:xx[=ip.ip.ip.ip],yy:yy:yy:yy:yy:yy[=ip.ip.ip.ip]"
+",...,zz:zz:zz:zz:zz:zz[=ip.ip.ip.ip][,]\n"
+"Things in brackets are optional.\n"
+"If you want to allow two (or more) IP addresses to one MAC address, you\n"
+"can specify two (or more) pairs with the same MAC, e.g.\n"
+" 00:00:00:fa:eb:fe=153.19.120.250,00:00:00:fa:eb:fe=192.168.0.1\n"
+ );
+}
+
+static int
+parse_nft_among_pairs(char *buf, struct nft_among_pair **pairsp,
+ int *cntp, bool *ipp)
+{
+ struct nft_among_pair *pairs;
+ int cnt = 0, i, idx = 0;
+ bool ip = false;
+ char *p;
+
+ if (*buf)
+ cnt++;
+ for (p = buf; *p; p++) {
+ if (*p == ',')
+ cnt++;
+ }
+ if (!cnt)
+ return -1;
+
+ pairs = xtables_calloc(cnt, sizeof(*pairs));
+ p = strtok(buf, ",");
+ while (p) {
+ struct nft_among_pair tmpair = {};
+ unsigned int tmp[ETH_ALEN] = {};
+ char *sep = index(p, '=');
+
+ if (sep) {
+ if (idx > 0 && !ip)
+ xtables_error(PARAMETER_PROBLEM,
+ "among: Mixed MAC and MAC=IP not allowed.");
+ ip = true;
+ *sep = '\0';
+ if (sscanf(sep + 1, "%d.%d.%d.%d",
+ &tmp[0], &tmp[1], &tmp[2], &tmp[3]) != 4)
+ xtables_error(PARAMETER_PROBLEM,
+ "Invalid IP address '%s'\n",
+ sep + 1);
+ for (i = 0; i < 4; i++) {
+ if (tmp[i] > 255)
+ xtables_error(PARAMETER_PROBLEM,
+ "Invalid IP address '%s'\n",
+ sep + 1);
+ tmpair.ip[i] = tmp[i];
+ }
+ } else if (idx > 0 && ip) {
+ xtables_error(PARAMETER_PROBLEM,
+ "among: Mixed MAC and MAC=IP not allowed.");
+ }
+ if (sscanf(p, "%x:%x:%x:%x:%x:%x",
+ &tmp[0], &tmp[1], &tmp[2],
+ &tmp[3], &tmp[4], &tmp[5]) != 6)
+ xtables_error(PARAMETER_PROBLEM,
+ "Invalid MAC address '%s'\n", p);
+ for (i = 0; i < ETH_ALEN; i++) {
+ if (tmp[i] > 255)
+ xtables_error(PARAMETER_PROBLEM,
+ "Invalid MAC address '%s'\n", p);
+ tmpair.mac[i] = tmp[i];
+ }
+ for (i = 0; i < idx; i++) {
+ if (memcmp(tmpair.buf, pairs[i].buf,
+ sizeof(tmpair.buf)) < 0)
+ break;
+ }
+ memmove(pairs + i + 1, pairs + i, sizeof(*pairs) * (idx - i));
+ memcpy(pairs + i, &tmpair, sizeof(tmpair));
+ idx++;
+ p = strtok(NULL, ",");
+ }
+
+ if (pairsp)
+ *pairsp = pairs;
+ if (cntp)
+ *cntp = cnt;
+ if (ipp)
+ *ipp = ip;
+ return 0;
+}
+
+static int bramong_parse(int c, char **argv, int invert,
+ unsigned int *flags, const void *entry,
+ struct xt_entry_match **match)
+{
+ struct nft_among_data *data = (struct nft_among_data *)(*match)->data;
+ struct xt_entry_match *new_match;
+ struct nft_among_pair *pairs;
+ struct stat stats;
+ bool dst = false;
+ int fd = -1, cnt;
+ size_t new_size;
+ long flen = 0;
+ int poff;
+ bool ip;
+ int ret;
+
+ switch (c) {
+ case AMONG_DST_F:
+ dst = true;
+ /* fall through */
+ case AMONG_SRC_F:
+ if ((fd = open(optarg, O_RDONLY)) == -1)
+ xtables_error(PARAMETER_PROBLEM,
+ "Couldn't open file '%s'", optarg);
+ fstat(fd, &stats);
+ flen = stats.st_size;
+ /* use mmap because the file will probably be big */
+ optarg = mmap(0, flen, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE, fd, 0);
+ if (optarg == MAP_FAILED)
+ xtables_error(PARAMETER_PROBLEM,
+ "Couldn't map file to memory");
+ if (optarg[flen-1] != '\n')
+ xtables_error(PARAMETER_PROBLEM,
+ "File should end with a newline");
+ if (strchr(optarg, '\n') != optarg+flen-1)
+ xtables_error(PARAMETER_PROBLEM,
+ "File should only contain one line");
+ optarg[flen-1] = '\0';
+ /* fall through */
+ case AMONG_DST:
+ if (c == AMONG_DST)
+ dst = true;
+ /* fall through */
+ case AMONG_SRC:
+ break;
+ default:
+ return 0;
+ }
+
+ ret = parse_nft_among_pairs(optarg, &pairs, &cnt, &ip);
+ if (ret)
+ return 0;
+ new_size = data->src.cnt + data->dst.cnt + cnt;
+ new_size *= sizeof(struct nft_among_pair);
+ new_size += XT_ALIGN(sizeof(struct xt_entry_match)) +
+ sizeof(struct nft_among_data);
+ new_match = xtables_calloc(1, new_size);
+ memcpy(new_match, *match, (*match)->u.match_size);
+ new_match->u.match_size = new_size;
+
+ data = (struct nft_among_data *)new_match->data;
+ if (dst) {
+ data->dst.cnt = cnt;
+ data->dst.inv = invert;
+ data->dst.ip = ip;
+ poff = data->src.cnt;
+ } else {
+ data->src.cnt = cnt;
+ data->src.inv = invert;
+ data->src.ip = ip;
+ poff = 0;
+ memmove(data->pairs + cnt, data->pairs,
+ data->dst.cnt * sizeof(struct nft_among_pair));
+ }
+ memcpy(data->pairs + poff, pairs, cnt * sizeof(struct nft_among_pair));
+ free(pairs);
+ free(*match);
+ *match = new_match;
+
+ if (c == AMONG_DST_F || c == AMONG_SRC_F) {
+ munmap(argv, flen);
+ close(fd);
+ }
+ return 1;
+}
+
+static void __bramong_print(struct nft_among_pair *pairs,
+ int cnt, bool inv, bool ip)
+{
+ const char *isep = "", *sep;
+ int i, j;
+
+ if (inv)
+ printf("! ");
+
+ for (i = 0; i < cnt; i++) {
+ printf("%s", isep);
+ isep = ",";
+
+ for (sep = "", j = 0; j < ETH_ALEN; sep = ":", j++)
+ printf("%s%02x", sep, pairs[i].mac[j]);
+
+ if (!ip)
+ continue;
+ for (sep = "=", j = 0; j < 4; sep = ".", j++)
+ printf("%s%u", sep, pairs[i].ip[j]);
+ }
+ printf(" ");
+}
+
+static void bramong_print(const void *ip, const struct xt_entry_match *match,
+ int numeric)
+{
+ struct nft_among_data *data = (struct nft_among_data *)match->data;
+
+ if (data->src.cnt) {
+ printf("--among-src ");
+ __bramong_print(data->pairs,
+ data->src.cnt, data->src.inv, data->src.ip);
+ }
+ if (data->dst.cnt) {
+ printf("--among-dst ");
+ __bramong_print(data->pairs + data->src.cnt,
+ data->dst.cnt, data->dst.inv, data->dst.ip);
+ }
+}
+
+static struct xtables_match bramong_match = {
+ .name = "among",
+ .revision = 0,
+ .version = XTABLES_VERSION,
+ .family = NFPROTO_BRIDGE,
+ .size = XT_ALIGN(sizeof(struct nft_among_data)),
+ .userspacesize = XT_ALIGN(sizeof(struct nft_among_data)),
+ .help = bramong_print_help,
+ .parse = bramong_parse,
+ .print = bramong_print,
+ .extra_opts = bramong_opts,
+};
+
+void _init(void)
+{
+ xtables_register_match(&bramong_match);
+}
new file mode 100644
@@ -0,0 +1,16 @@
+:INPUT,FORWARD,OUTPUT
+--among-dst de:ad:00:be:ee:ff,c0:ff:ee:00:ba:be;--among-dst c0:ff:ee:00:ba:be,de:ad:00:be:ee:ff;OK
+--among-dst ! c0:ff:ee:00:ba:be,de:ad:00:be:ee:ff;=;OK
+--among-src be:ef:00:c0:ff:ee,c0:ff:ee:00:ba:be,de:ad:00:be:ee:ff;=;OK
+--among-src de:ad:00:be:ee:ff=10.0.0.1,c0:ff:ee:00:ba:be=192.168.1.1;--among-src c0:ff:ee:00:ba:be=192.168.1.1,de:ad:00:be:ee:ff=10.0.0.1;OK
+--among-src ! c0:ff:ee:00:ba:be=192.168.1.1,de:ad:00:be:ee:ff=10.0.0.1;=;OK
+--among-src de:ad:00:be:ee:ff --among-dst c0:ff:ee:00:ba:be;=;OK
+--among-src de:ad:00:be:ee:ff=10.0.0.1 --among-dst c0:ff:ee:00:ba:be=192.168.1.1;=;OK
+--among-src ! de:ad:00:be:ee:ff --among-dst c0:ff:ee:00:ba:be;=;OK
+--among-src de:ad:00:be:ee:ff=10.0.0.1 --among-dst ! c0:ff:ee:00:ba:be=192.168.1.1;=;OK
+--among-src ! de:ad:00:be:ee:ff --among-dst c0:ff:ee:00:ba:be=192.168.1.1;=;OK
+--among-src de:ad:00:be:ee:ff=10.0.0.1 --among-dst ! c0:ff:ee:00:ba:be=192.168.1.1;=;OK
+--among-src;=;FAIL
+--among-src 00:11=10.0.0.1;=;FAIL
+--among-src de:ad:00:be:ee:ff=10.256.0.1;=;FAIL
+--among-src de:ad:00:be:ee:ff,c0:ff:ee:00:ba:be=192.168.1.1;=;FAIL
@@ -522,35 +522,39 @@ If the 802.3 DSAP and SSAP values are 0xaa then the SNAP type field must
be consulted to determine the payload protocol. This is a two byte
(hexadecimal) argument. Only 802.3 frames with DSAP/SSAP 0xaa are
checked for type.
-.\" .SS among
-.\" Match a MAC address or MAC/IP address pair versus a list of MAC addresses
-.\" and MAC/IP address pairs.
-.\" A list entry has the following format:
-.\" .IR xx:xx:xx:xx:xx:xx[=ip.ip.ip.ip][,] ". Multiple"
-.\" list entries are separated by a comma, specifying an IP address corresponding to
-.\" the MAC address is optional. Multiple MAC/IP address pairs with the same MAC address
-.\" but different IP address (and vice versa) can be specified. If the MAC address doesn't
-.\" match any entry from the list, the frame doesn't match the rule (unless "!" was used).
-.\" .TP
-.\" .BR "--among-dst " "[!] \fIlist\fP"
-.\" Compare the MAC destination to the given list. If the Ethernet frame has type
-.\" .IR IPv4 " or " ARP ,
-.\" then comparison with MAC/IP destination address pairs from the
-.\" list is possible.
-.\" .TP
-.\" .BR "--among-src " "[!] \fIlist\fP"
-.\" Compare the MAC source to the given list. If the Ethernet frame has type
-.\" .IR IPv4 " or " ARP ,
-.\" then comparison with MAC/IP source address pairs from the list
-.\" is possible.
-.\" .TP
-.\" .BR "--among-dst-file " "[!] \fIfile\fP"
-.\" Same as
-.\" .BR --among-dst " but the list is read in from the specified file."
-.\" .TP
-.\" .BR "--among-src-file " "[!] \fIfile\fP"
-.\" Same as
-.\" .BR --among-src " but the list is read in from the specified file."
+.SS among
+Match a MAC address or MAC/IP address pair versus a list of MAC addresses
+and MAC/IP address pairs.
+A list entry has the following format:
+.IR xx:xx:xx:xx:xx:xx[=ip.ip.ip.ip][,] ". Multiple"
+list entries are separated by a comma, specifying an IP address corresponding to
+the MAC address is optional. Multiple MAC/IP address pairs with the same MAC address
+but different IP address (and vice versa) can be specified. If the MAC address doesn't
+match any entry from the list, the frame doesn't match the rule (unless "!" was used).
+.TP
+.BR "--among-dst " "[!] \fIlist\fP"
+Compare the MAC destination to the given list. If the Ethernet frame has type
+.IR IPv4 " or " ARP ,
+then comparison with MAC/IP destination address pairs from the
+list is possible.
+.TP
+.BR "--among-src " "[!] \fIlist\fP"
+Compare the MAC source to the given list. If the Ethernet frame has type
+.IR IPv4 " or " ARP ,
+then comparison with MAC/IP source address pairs from the list
+is possible.
+.TP
+.BR "--among-dst-file " "[!] \fIfile\fP"
+Same as
+.BR --among-dst " but the list is read in from the specified file."
+.TP
+.BR "--among-src-file " "[!] \fIfile\fP"
+Same as
+.BR --among-src " but the list is read in from the specified file."
+.PP
+Note that in this implementation of ebtables, among lists uses must be
+internally homogeneous regarding whether IP addresses are present or not. Mixed
+use of MAC addresses and MAC/IP address pairs is not supported yet.
.SS arp
Specify (R)ARP fields. The protocol must be specified as
.IR ARP " or " RARP .
@@ -1108,8 +1112,8 @@ arp message and the hardware address length in the arp header is 6 bytes.
The version of ebtables this man page ships with does not support the
.B broute
table. Also there is no support for
-.BR among " and " string
-matches. And finally, this list is probably not complete.
+.B string
+match. And finally, this list is probably not complete.
.SH SEE ALSO
.BR xtables-nft "(8), " iptables "(8), " ip (8)
.PP
@@ -17,6 +17,8 @@
#include <libiptc/libxtc.h>
#include <linux/netfilter/nf_tables.h>
+#include <libnftnl/set.h>
+
#include "nft-shared.h"
#include "nft-bridge.h"
#include "nft.h"
@@ -291,6 +293,225 @@ 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 struct nft_among_pair *
+set_elems_to_among_pairs(const struct nftnl_set *s, int cnt)
+{
+ struct nftnl_set_elems_iter *iter = nftnl_set_elems_iter_create(s);
+ struct nft_among_pair *pairs;
+ struct nftnl_set_elem *elem;
+ int idx, tmpcnt = 0;
+ const char *buf;
+ uint32_t buflen;
+
+ if (!iter) {
+ fprintf(stderr, "BUG: set elems iter allocation failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ pairs = xtables_calloc(cnt, sizeof(*pairs));
+
+ while ((elem = nftnl_set_elems_iter_next(iter))) {
+ buf = nftnl_set_elem_get(elem, NFTNL_SET_ELEM_KEY, &buflen);
+ if (!buf) {
+ fprintf(stderr, "BUG: set elem without key\n");
+ exit(EXIT_FAILURE);
+ }
+ for (idx = 0; idx < tmpcnt; idx++) {
+ if (memcmp(buf, pairs[idx].buf, buflen) < 0)
+ break;
+ }
+ memmove(pairs + idx + 1, pairs + idx,
+ sizeof(*pairs) * (tmpcnt - idx));
+ memcpy(pairs[idx].buf, buf, buflen);
+ tmpcnt++;
+ }
+ nftnl_set_elems_iter_destroy(iter);
+
+ return pairs;
+}
+
+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;
+ struct nft_among_pair *pairs;
+ struct nftnl_set_list *slist;
+ bool is_dst, have_ip, inv;
+ struct ebt_match *ematch;
+ const char *set_name;
+ struct nftnl_set *s;
+ int poff, cnt;
+ size_t size;
+
+ if (lookup_analyze_payloads(ctx, &is_dst, &have_ip))
+ return;
+
+ set_name = nftnl_expr_get_str(e, NFTNL_EXPR_LOOKUP_SET);
+
+ slist = nft_set_list_get(ctx->h, ctx->table, set_name);
+ if (!slist)
+ return;
+
+ s = nftnl_set_list_lookup_byname(slist, set_name);
+ if (!s) {
+ fprintf(stderr,
+ "BUG: set '%s' in lookup expression not found\n",
+ set_name);
+ exit(EXIT_FAILURE);
+ }
+
+ 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 = XT_ALIGN(sizeof(struct xt_entry_match)) +
+ sizeof(struct nft_among_data) +
+ cnt * sizeof(struct nft_among_pair);
+ 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 == NULL)
+ 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;
+ if (is_dst) {
+ among_data->dst.cnt = cnt;
+ among_data->dst.inv = inv;
+ among_data->dst.ip = have_ip;
+ poff = among_data->src.cnt;
+ } else {
+ among_data->src.cnt = cnt;
+ among_data->src.inv = inv;
+ among_data->src.ip = have_ip;
+ poff = 0;
+ memmove(among_data->pairs + cnt, among_data->pairs,
+ among_data->dst.cnt * sizeof(struct nft_among_pair));
+ }
+
+ pairs = set_elems_to_among_pairs(s, cnt);
+ memcpy(among_data->pairs + poff, pairs, cnt * sizeof(*pairs));
+ free(pairs);
+
+ ctx->flags &= ~(NFT_XT_CTX_PAYLOAD | NFT_XT_CTX_PREV_PAYLOAD);
+}
+
static void parse_watcher(void *object, struct ebt_match **match_list,
bool ismatch)
{
@@ -742,6 +963,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,
@@ -122,4 +122,25 @@ void ebt_add_watcher(struct xtables_target *watcher,
struct iptables_command_state *cs);
int ebt_command_default(struct iptables_command_state *cs);
+struct nft_among_pair {
+ union {
+ struct {
+ unsigned char mac[ETH_ALEN];
+ unsigned char pad[2];
+ unsigned char ip[4];
+ };
+ unsigned char buf[ETH_ALEN + 2 + 4];
+ } __attribute__((packed));
+};
+
+struct nft_among_data {
+ struct {
+ int cnt;
+ bool inv;
+ bool ip;
+ } src, dst;
+ /* first source, then dest pairs */
+ struct nft_among_pair pairs[0];
+};
+
#endif
@@ -1077,6 +1077,153 @@ static int add_nft_limit(struct nftnl_rule *r, struct xt_entry_match *m)
return 0;
}
+static struct nftnl_set *add_anon_set(struct nft_handle *h, const char *table,
+ uint32_t flags, uint32_t key_type,
+ uint32_t key_len, uint32_t size)
+{
+ static uint32_t set_id = 0;
+ struct nftnl_set *s;
+
+ s = nftnl_set_alloc();
+ if (!s)
+ return NULL;
+
+ nftnl_set_set_u32(s, NFTNL_SET_FAMILY, h->family);
+ nftnl_set_set_str(s, NFTNL_SET_TABLE, table);
+ nftnl_set_set_str(s, NFTNL_SET_NAME, "__set%d");
+ nftnl_set_set_u32(s, NFTNL_SET_ID, ++set_id);
+ nftnl_set_set_u32(s, NFTNL_SET_FLAGS,
+ NFT_SET_ANONYMOUS | NFT_SET_CONSTANT | flags);
+ nftnl_set_set_u32(s, NFTNL_SET_KEY_TYPE, key_type);
+ nftnl_set_set_u32(s, NFTNL_SET_KEY_LEN, key_len);
+ nftnl_set_set_u32(s, NFTNL_SET_DESC_SIZE, size);
+
+ return batch_set_add(h, NFT_COMPAT_SET_ADD, s) ? s : NULL;
+}
+
+static struct nftnl_expr *
+gen_payload(uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg)
+{
+ struct nftnl_expr *e = nftnl_expr_alloc("payload");
+
+ if (!e)
+ return NULL;
+ nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base);
+ nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
+ nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len);
+ nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_DREG, dreg);
+ return e;
+}
+
+static struct nftnl_expr *
+gen_lookup(uint32_t sreg, const char *set_name, uint32_t set_id, uint32_t flags)
+{
+ struct nftnl_expr *e = nftnl_expr_alloc("lookup");
+
+ if (!e)
+ return NULL;
+ nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_SREG, sreg);
+ nftnl_expr_set_str(e, NFTNL_EXPR_LOOKUP_SET, set_name);
+ nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_SET_ID, set_id);
+ nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_FLAGS, flags);
+ return e;
+}
+
+/* simplified nftables:include/netlink.h, netlink_padded_len() */
+#define NETLINK_ALIGN 4
+
+/* from nftables:include/datatype.h, TYPE_BITS */
+#define CONCAT_TYPE_BITS 6
+
+/* from nftables:include/datatype.h, enum datatypes */
+#define NFT_DATATYPE_IPADDR 7
+#define NFT_DATATYPE_ETHERADDR 9
+
+static int __add_nft_among(struct nft_handle *h, const char *table,
+ struct nftnl_rule *r, struct nft_among_pair *pairs,
+ int cnt, bool dst, bool inv, bool ip)
+{
+ uint32_t set_id, type = NFT_DATATYPE_ETHERADDR, len = ETH_ALEN;
+ /* { !dst, dst } */
+ static const int eth_addr_off[] = {
+ offsetof(struct ether_header, ether_shost),
+ offsetof(struct ether_header, ether_dhost)
+ };
+ static const int ip_addr_off[] = {
+ offsetof(struct iphdr, saddr),
+ offsetof(struct iphdr, daddr)
+ };
+ struct nftnl_expr *e;
+ struct nftnl_set *s;
+ int idx = 0;
+
+ if (ip) {
+ type = type << CONCAT_TYPE_BITS | NFT_DATATYPE_IPADDR;
+ len += sizeof(struct in_addr) + NETLINK_ALIGN - 1;
+ len &= ~(NETLINK_ALIGN - 1);
+ }
+
+ s = add_anon_set(h, table, 0, type, len, cnt);
+ if (!s)
+ return -ENOMEM;
+ set_id = nftnl_set_get_u32(s, NFTNL_SET_ID);
+
+ for (idx = 0; idx < cnt; idx++) {
+ struct nftnl_set_elem *elem = nftnl_set_elem_alloc();
+
+ if (!elem)
+ return -ENOMEM;
+ nftnl_set_elem_set(elem, NFTNL_SET_ELEM_KEY,
+ pairs[idx].buf, len);
+ nftnl_set_elem_add(s, elem);
+ }
+
+ e = gen_payload(NFT_PAYLOAD_LL_HEADER,
+ eth_addr_off[dst], ETH_ALEN, NFT_REG_1);
+ if (!e)
+ return -ENOMEM;
+ nftnl_rule_add_expr(r, e);
+
+ if (ip) {
+ e = gen_payload(NFT_PAYLOAD_NETWORK_HEADER, ip_addr_off[dst],
+ sizeof(struct in_addr), NFT_REG32_02);
+ if (!e)
+ return -ENOMEM;
+ nftnl_rule_add_expr(r, e);
+ }
+
+ e = gen_lookup(NFT_REG_1, "__set%d", set_id, inv);
+ if (!e)
+ return -ENOMEM;
+ nftnl_rule_add_expr(r, e);
+
+ return 0;
+}
+
+static int add_nft_among(struct nft_handle *h,
+ struct nftnl_rule *r, struct xt_entry_match *m)
+{
+ struct nft_among_data *data = (struct nft_among_data *)m->data;
+ const char *table = nftnl_rule_get(r, NFTNL_RULE_TABLE);
+
+ if ((data->src.cnt && data->src.ip) ||
+ (data->dst.cnt && data->dst.ip)) {
+ uint16_t eth_p_ip = htons(ETH_P_IP);
+
+ add_meta(r, NFT_META_PROTOCOL);
+ add_cmp_ptr(r, NFT_CMP_EQ, ð_p_ip, 2);
+ }
+
+ if (data->src.cnt)
+ __add_nft_among(h, table, r, data->pairs, data->src.cnt,
+ false, data->src.inv, data->src.ip);
+ if (data->dst.cnt)
+ __add_nft_among(h, table, r, data->pairs + data->src.cnt,
+ data->dst.cnt, true, data->dst.inv,
+ data->dst.ip);
+ return 0;
+}
+
int add_match(struct nft_handle *h,
struct nftnl_rule *r, struct xt_entry_match *m)
{
@@ -594,6 +594,7 @@ void ebt_load_match_extensions(void)
ebt_load_match("pkttype");
ebt_load_match("vlan");
ebt_load_match("stp");
+ ebt_load_match("among");
ebt_load_watcher("log");
ebt_load_watcher("nflog");
Support among match as far as possible given the limitations of nftables sets, namely limited to homogeneous MAC address only or MAC and IP address only matches. Signed-off-by: Phil Sutter <phil@nwl.cc> --- Changes since v1: - Fix for overlong lines. - Use nftnl_set_list_lookup_byname() from libnftnl. --- extensions/libebt_among.c | 278 ++++++++++++++++++++++++++++++++++++++ extensions/libebt_among.t | 16 +++ iptables/ebtables-nft.8 | 66 ++++----- iptables/nft-bridge.c | 222 ++++++++++++++++++++++++++++++ iptables/nft-bridge.h | 21 +++ iptables/nft.c | 147 ++++++++++++++++++++ iptables/xtables-eb.c | 1 + 7 files changed, 720 insertions(+), 31 deletions(-) create mode 100644 extensions/libebt_among.c create mode 100644 extensions/libebt_among.t