| /* |
| * (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, ðproto, 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, |
| }; |