blob: 22940cf79beba63db505eb759f7b52767a4362a1 [file] [log] [blame]
/*
* (C) 2014 by Giuseppe Longo <giuseppelng@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/ether.h>
#include <inttypes.h>
#include <xtables.h>
#include <libiptc/libxtc.h>
#include <linux/netfilter/nf_tables.h>
#include <ebtables/ethernetdb.h>
#include "nft-shared.h"
#include "nft-bridge.h"
#include "nft.h"
void ebt_cs_clean(struct ebtables_command_state *cs)
{
struct ebt_match *m, *nm;
xtables_rule_matches_free(&cs->matches);
for (m = cs->match_list; m;) {
nm = m->next;
if (!m->ismatch)
free(m->u.watcher->t);
free(m);
m = nm;
}
}
/* 0: default, print only 2 digits if necessary
* 2: always print 2 digits, a printed mac address
* then always has the same length
*/
int ebt_printstyle_mac;
static void ebt_print_mac(const unsigned char *mac)
{
if (ebt_printstyle_mac == 2) {
int j;
for (j = 0; j < ETH_ALEN; j++)
printf("%02x%s", mac[j],
(j==ETH_ALEN-1) ? "" : ":");
} else
printf("%s", ether_ntoa((struct ether_addr *) mac));
}
/* Put the mac address into 6 (ETH_ALEN) bytes returns 0 on success. */
static void ebt_print_mac_and_mask(const unsigned char *mac, const unsigned char *mask)
{
char hlpmsk[6] = {};
if (!memcmp(mac, eb_mac_type_unicast, 6) &&
!memcmp(mask, eb_msk_type_unicast, 6))
printf("Unicast");
else if (!memcmp(mac, eb_mac_type_multicast, 6) &&
!memcmp(mask, eb_msk_type_multicast, 6))
printf("Multicast");
else if (!memcmp(mac, eb_mac_type_broadcast, 6) &&
!memcmp(mask, eb_msk_type_broadcast, 6))
printf("Broadcast");
else if (!memcmp(mac, eb_mac_type_bridge_group, 6) &&
!memcmp(mask, eb_msk_type_bridge_group, 6))
printf("BGA");
else {
ebt_print_mac(mac);
if (memcmp(mask, hlpmsk, 6)) {
printf("/");
ebt_print_mac(mask);
}
}
}
static uint16_t ipt_to_ebt_flags(uint8_t invflags)
{
uint16_t result = 0;
if (invflags & IPT_INV_VIA_IN)
result |= EBT_IIN;
if (invflags & IPT_INV_VIA_OUT)
result |= EBT_IOUT;
if (invflags & IPT_INV_PROTO)
result |= EBT_IPROTO;
return result;
}
static void add_logical_iniface(struct nftnl_rule *r, char *iface, uint32_t op)
{
int iface_len;
iface_len = strlen(iface);
add_meta(r, NFT_META_BRI_IIFNAME);
if (iface[iface_len - 1] == '+')
add_cmp_ptr(r, op, iface, iface_len - 1);
else
add_cmp_ptr(r, op, iface, iface_len + 1);
}
static void add_logical_outiface(struct nftnl_rule *r, char *iface, uint32_t op)
{
int iface_len;
iface_len = strlen(iface);
add_meta(r, NFT_META_BRI_OIFNAME);
if (iface[iface_len - 1] == '+')
add_cmp_ptr(r, op, iface, iface_len - 1);
else
add_cmp_ptr(r, op, iface, iface_len + 1);
}
/* TODO: Use generic add_action() once we convert this to use
* iptables_command_state.
*/
static int _add_action(struct nftnl_rule *r, struct ebtables_command_state *cs)
{
int ret = 0;
if (cs->jumpto == NULL || strcmp(cs->jumpto, "CONTINUE") == 0)
return 0;
/* If no target at all, add nothing (default to continue) */
if (cs->target != NULL) {
/* Standard target? */
if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0)
ret = add_verdict(r, NF_ACCEPT);
else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0)
ret = add_verdict(r, NF_DROP);
else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0)
ret = add_verdict(r, NFT_RETURN);
else
ret = add_target(r, cs->target->t);
} else if (strlen(cs->jumpto) > 0) {
/* Not standard, then it's a jump to chain */
ret = add_jumpto(r, cs->jumpto, NFT_JUMP);
}
return ret;
}
static int nft_bridge_add(struct nftnl_rule *r, void *data)
{
struct ebtables_command_state *cs = data;
struct ebt_match *iter;
struct ebt_entry *fw = &cs->fw;
uint32_t op;
char *addr;
if (fw->in[0] != '\0') {
op = nft_invflags2cmp(fw->invflags, EBT_IIN);
add_iniface(r, fw->in, op);
}
if (fw->out[0] != '\0') {
op = nft_invflags2cmp(fw->invflags, EBT_IOUT);
add_outiface(r, fw->out, op);
}
if (fw->logical_in[0] != '\0') {
op = nft_invflags2cmp(fw->invflags, EBT_ILOGICALIN);
add_logical_iniface(r, fw->logical_in, op);
}
if (fw->logical_out[0] != '\0') {
op = nft_invflags2cmp(fw->invflags, EBT_ILOGICALOUT);
add_logical_outiface(r, fw->logical_out, op);
}
addr = ether_ntoa((struct ether_addr *) fw->sourcemac);
if (strcmp(addr, "0:0:0:0:0:0") != 0) {
op = nft_invflags2cmp(fw->invflags, EBT_ISOURCE);
add_payload(r, offsetof(struct ethhdr, h_source), 6,
NFT_PAYLOAD_LL_HEADER);
add_cmp_ptr(r, op, fw->sourcemac, 6);
}
addr = ether_ntoa((struct ether_addr *) fw->destmac);
if (strcmp(addr, "0:0:0:0:0:0") != 0) {
op = nft_invflags2cmp(fw->invflags, EBT_IDEST);
add_payload(r, offsetof(struct ethhdr, h_dest), 6,
NFT_PAYLOAD_LL_HEADER);
add_cmp_ptr(r, op, fw->destmac, 6);
}
if (fw->ethproto != 0) {
op = nft_invflags2cmp(fw->invflags, EBT_IPROTO);
add_payload(r, offsetof(struct ethhdr, h_proto), 2,
NFT_PAYLOAD_LL_HEADER);
add_cmp_u16(r, fw->ethproto, op);
}
add_compat(r, fw->ethproto, fw->invflags);
for (iter = cs->match_list; iter; iter = iter->next) {
if (iter->ismatch) {
if (add_match(r, iter->u.match->m))
break;
} else {
if (add_target(r, iter->u.watcher->t))
break;
}
}
if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0)
return -1;
return _add_action(r, cs);
}
static void nft_bridge_parse_meta(struct nft_xt_ctx *ctx,
struct nftnl_expr *e, void *data)
{
struct ebtables_command_state *cs = data;
struct ebt_entry *fw = &cs->fw;
uint8_t flags = 0;
int iface = 0;
const void *ifname;
uint32_t len;
iface = parse_meta(e, ctx->meta.key, fw->in, fw->in_mask,
fw->out, fw->out_mask, &flags);
if (!iface)
goto out;
switch (ctx->meta.key) {
case NFT_META_BRI_IIFNAME:
ifname = nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
flags |= IPT_INV_VIA_IN;
memcpy(fw->logical_in, ifname, len);
if (fw->logical_in[len] == '\0')
memset(fw->in_mask, 0xff, len);
else {
fw->logical_in[len] = '+';
fw->logical_in[len+1] = '\0';
memset(fw->in_mask, 0xff, len + 1);
}
break;
case NFT_META_BRI_OIFNAME:
ifname = nftnl_expr_get(e, NFTNL_EXPR_CMP_DATA, &len);
if (nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ)
flags |= IPT_INV_VIA_OUT;
memcpy(fw->logical_out, ifname, len);
if (fw->logical_out[len] == '\0')
memset(fw->out_mask, 0xff, len);
else {
fw->logical_out[len] = '+';
fw->logical_out[len+1] = '\0';
memset(fw->out_mask, 0xff, len + 1);
}
break;
default:
break;
}
out:
fw->invflags |= ipt_to_ebt_flags(flags);
}
static void nft_bridge_parse_payload(struct nft_xt_ctx *ctx,
struct nftnl_expr *e, void *data)
{
struct ebtables_command_state *cs = data;
struct ebt_entry *fw = &cs->fw;
unsigned char addr[ETH_ALEN];
unsigned short int ethproto;
bool inv;
int i;
switch (ctx->payload.offset) {
case offsetof(struct ethhdr, h_dest):
get_cmp_data(e, addr, sizeof(addr), &inv);
for (i = 0; i < ETH_ALEN; i++)
fw->destmac[i] = addr[i];
if (inv)
fw->invflags |= EBT_IDEST;
break;
case offsetof(struct ethhdr, h_source):
get_cmp_data(e, addr, sizeof(addr), &inv);
for (i = 0; i < ETH_ALEN; i++)
fw->sourcemac[i] = addr[i];
if (inv)
fw->invflags |= EBT_ISOURCE;
break;
case offsetof(struct ethhdr, h_proto):
get_cmp_data(e, &ethproto, sizeof(ethproto), &inv);
fw->ethproto = ethproto;
if (inv)
fw->invflags |= EBT_IPROTO;
break;
}
}
static void nft_bridge_parse_immediate(const char *jumpto, bool nft_goto,
void *data)
{
struct ebtables_command_state *cs = data;
cs->jumpto = jumpto;
}
static void parse_watcher(void *object, struct ebt_match **match_list,
bool ismatch)
{
struct ebt_match *m;
m = calloc(1, sizeof(struct ebt_match));
if (m == NULL)
xtables_error(OTHER_PROBLEM, "Can't allocate memory");
if (ismatch)
m->u.match = object;
else
m->u.watcher = object;
m->ismatch = ismatch;
if (*match_list == NULL)
*match_list = m;
else
(*match_list)->next = m;
}
static void nft_bridge_parse_match(struct xtables_match *m, void *data)
{
struct ebtables_command_state *cs = data;
parse_watcher(m, &cs->match_list, true);
}
static void nft_bridge_parse_target(struct xtables_target *t, void *data)
{
struct ebtables_command_state *cs = data;
/* harcoded names :-( */
if (strcmp(t->name, "log") == 0 ||
strcmp(t->name, "nflog") == 0) {
parse_watcher(t, &cs->match_list, false);
return;
}
cs->target = t;
}
void nft_rule_to_ebtables_command_state(struct nftnl_rule *r,
struct ebtables_command_state *cs)
{
struct nftnl_expr_iter *iter;
struct nftnl_expr *expr;
int family = nftnl_rule_get_u32(r, NFTNL_RULE_FAMILY);
struct nft_xt_ctx ctx = {
.state.cs_eb = cs,
.family = family,
};
iter = nftnl_expr_iter_create(r);
if (iter == NULL)
return;
expr = nftnl_expr_iter_next(iter);
while (expr != NULL) {
const char *name =
nftnl_expr_get_str(expr, NFTNL_EXPR_NAME);
if (strcmp(name, "counter") == 0)
nft_parse_counter(expr, &cs->counters);
else if (strcmp(name, "payload") == 0)
nft_parse_payload(&ctx, expr);
else if (strcmp(name, "meta") == 0)
nft_parse_meta(&ctx, expr);
else if (strcmp(name, "bitwise") == 0)
nft_parse_bitwise(&ctx, expr);
else if (strcmp(name, "cmp") == 0)
nft_parse_cmp(&ctx, expr);
else if (strcmp(name, "immediate") == 0)
nft_parse_immediate(&ctx, expr);
else if (strcmp(name, "match") == 0)
nft_parse_match(&ctx, expr);
else if (strcmp(name, "target") == 0)
nft_parse_target(&ctx, expr);
expr = nftnl_expr_iter_next(iter);
}
nftnl_expr_iter_destroy(iter);
if (cs->jumpto != NULL)
return;
if (cs->target != NULL && cs->target->name != NULL)
cs->target = xtables_find_target(cs->target->name, XTF_TRY_LOAD);
else
cs->jumpto = "CONTINUE";
}
static void print_iface(const char *iface)
{
char *c;
if ((c = strchr(iface, IF_WILDCARD)))
*c = '+';
printf("%s ", iface);
if (c)
*c = IF_WILDCARD;
}
static void nft_bridge_print_table_header(const char *tablename)
{
printf("Bridge table: %s\n\n", tablename);
}
static void nft_bridge_print_header(unsigned int format, const char *chain,
const char *pol,
const struct xt_counters *counters,
bool basechain, uint32_t refs)
{
printf("Bridge chain: %s, entries: %u, policy: %s\n",
chain, refs, basechain ? pol : "RETURN");
}
static void nft_bridge_print_firewall(struct nftnl_rule *r, unsigned int num,
unsigned int format)
{
struct xtables_match *matchp;
struct xtables_target *watcherp;
struct ebt_match *m;
struct ebtables_command_state cs = {};
char *addr;
nft_rule_to_ebtables_command_state(r, &cs);
if (format & FMT_LINENUMBERS)
printf("%d ", num);
/* Dont print anything about the protocol if no protocol was
* specified, obviously this means any protocol will do. */
if (cs.fw.ethproto != 0) {
printf("-p ");
if (cs.fw.invflags & EBT_IPROTO)
printf("! ");
if (cs.fw.bitmask & EBT_802_3)
printf("Length ");
else {
struct ethertypeent *ent;
ent = getethertypebynumber(ntohs(cs.fw.ethproto));
if (!ent)
printf("0x%x ", ntohs(cs.fw.ethproto));
else
printf("%s ", ent->e_name);
}
}
addr = ether_ntoa((struct ether_addr *) cs.fw.sourcemac);
if (strcmp(addr, "0:0:0:0:0:0") != 0) {
printf("-s ");
if (cs.fw.invflags & EBT_ISOURCE)
printf("! ");
ebt_print_mac_and_mask(cs.fw.sourcemac, cs.fw.sourcemsk);
printf(" ");
}
addr = ether_ntoa((struct ether_addr *) cs.fw.destmac);
if (strcmp(addr, "0:0:0:0:0:0") != 0) {
printf("-d ");
if (cs.fw.invflags & EBT_IDEST)
printf("! ");
ebt_print_mac_and_mask(cs.fw.destmac, cs.fw.destmsk);
printf(" ");
}
if (cs.fw.in[0] != '\0') {
printf("-i ");
if (cs.fw.invflags & EBT_IIN)
printf("! ");
print_iface(cs.fw.in);
}
if (cs.fw.logical_in[0] != '\0') {
printf("--logical-in ");
if (cs.fw.invflags & EBT_ILOGICALIN)
printf("! ");
print_iface(cs.fw.logical_in);
}
if (cs.fw.logical_out[0] != '\0') {
printf("--logical-out ");
if (cs.fw.invflags & EBT_ILOGICALOUT)
printf("! ");
print_iface(cs.fw.logical_out);
}
if (cs.fw.out[0] != '\0') {
printf("-o ");
if (cs.fw.invflags & EBT_IOUT)
printf("! ");
print_iface(cs.fw.out);
}
for (m = cs.match_list; m; m = m->next) {
if (m->ismatch) {
matchp = m->u.match;
if (matchp->print != NULL) {
matchp->print(&cs.fw, matchp->m,
format & FMT_NUMERIC);
}
} else {
watcherp = m->u.watcher;
if (watcherp->print != NULL) {
watcherp->print(&cs.fw, watcherp->t,
format & FMT_NUMERIC);
}
}
}
printf("-j ");
if (cs.jumpto != NULL)
printf("%s", cs.jumpto);
else if (cs.target != NULL && cs.target->print != NULL)
cs.target->print(&cs.fw, cs.target->t, format & FMT_NUMERIC);
if (!(format & FMT_NOCOUNTS))
printf(" , pcnt = %"PRIu64" -- bcnt = %"PRIu64"",
(uint64_t)cs.counters.pcnt, (uint64_t)cs.counters.bcnt);
if (!(format & FMT_NONEWLINE))
fputc('\n', stdout);
ebt_cs_clean(&cs);
}
static bool nft_bridge_is_same(const void *data_a, const void *data_b)
{
const struct ebt_entry *a = data_a;
const struct ebt_entry *b = data_b;
int i;
if (a->ethproto != b->ethproto ||
/* FIXME: a->flags != b->flags || */
a->invflags != b->invflags) {
DEBUGP("different proto/flags/invflags\n");
return false;
}
for (i = 0; i < ETH_ALEN; i++) {
if (a->sourcemac[i] != b->sourcemac[i]) {
DEBUGP("different source mac %x, %x (%d)\n",
a->sourcemac[i] & 0xff, b->sourcemac[i] & 0xff, i);
return false;
}
if (a->destmac[i] != b->destmac[i]) {
DEBUGP("different destination mac %x, %x (%d)\n",
a->destmac[i] & 0xff, b->destmac[i] & 0xff, i);
return false;
}
}
for (i = 0; i < IFNAMSIZ; i++) {
if (a->logical_in[i] != b->logical_in[i]) {
DEBUGP("different logical iniface %x, %x (%d)\n",
a->logical_in[i] & 0xff, b->logical_in[i] & 0xff, i);
return false;
}
if (a->logical_out[i] != b->logical_out[i]) {
DEBUGP("different logical outiface %x, %x (%d)\n",
a->logical_out[i] & 0xff, b->logical_out[i] & 0xff, i);
return false;
}
}
return is_same_interfaces((char *)a->in,
(char *)a->out,
a->in_mask,
a->out_mask,
(char *)b->in,
(char *)b->out,
b->in_mask,
b->out_mask);
}
static bool nft_bridge_rule_find(struct nft_family_ops *ops, struct nftnl_rule *r,
void *data)
{
struct ebtables_command_state *cs = data;
struct ebtables_command_state this = {};
nft_rule_to_ebtables_command_state(r, &this);
DEBUGP("comparing with... ");
if (!nft_bridge_is_same(cs, &this))
return false;
if (!compare_matches(cs->matches, this.matches)) {
DEBUGP("Different matches\n");
return false;
}
if (!compare_targets(cs->target, this.target)) {
DEBUGP("Different target\n");
return false;
}
if (cs->jumpto != NULL && strcmp(cs->jumpto, this.jumpto) != 0) {
DEBUGP("Different verdict\n");
return false;
}
return true;
}
struct nft_family_ops nft_family_ops_bridge = {
.add = nft_bridge_add,
.is_same = nft_bridge_is_same,
.print_payload = NULL,
.parse_meta = nft_bridge_parse_meta,
.parse_payload = nft_bridge_parse_payload,
.parse_immediate = nft_bridge_parse_immediate,
.parse_match = nft_bridge_parse_match,
.parse_target = nft_bridge_parse_target,
.print_table_header = nft_bridge_print_table_header,
.print_header = nft_bridge_print_header,
.print_firewall = nft_bridge_print_firewall,
.save_firewall = NULL,
.save_counters = NULL,
.post_parse = NULL,
.rule_find = nft_bridge_rule_find,
};