diff options
37 files changed, 10937 insertions, 5 deletions
diff --git a/configure.ac b/configure.ac index e83304c5..2521ccc8 100644 --- a/configure.ac +++ b/configure.ac @@ -60,6 +60,9 @@ AC_ARG_ENABLE([nfsynproxy], AC_ARG_WITH([pkgconfigdir], AS_HELP_STRING([--with-pkgconfigdir=PATH], [Path to the pkgconfig directory [[LIBDIR/pkgconfig]]]), [pkgconfigdir="$withval"], [pkgconfigdir='${libdir}/pkgconfig']) +AC_ARG_ENABLE([nftables], + AS_HELP_STRING([--disable-nftables], [Do not build nftables compat]), + [enable_nftables="$enableval"], [enable_nftables="yes"]) libiptc_LDFLAGS2=""; AX_CHECK_LINKER_FLAGS([-Wl,--no-as-needed], @@ -106,6 +109,7 @@ AM_CONDITIONAL([ENABLE_DEVEL], [test "$enable_devel" = "yes"]) AM_CONDITIONAL([ENABLE_LIBIPQ], [test "$enable_libipq" = "yes"]) AM_CONDITIONAL([ENABLE_BPFC], [test "$enable_bpfc" = "yes"]) AM_CONDITIONAL([ENABLE_SYNCONF], [test "$enable_nfsynproxy" = "yes"]) +AM_CONDITIONAL([ENABLE_NFTABLES], [test "$enable_nftables" = "yes"]) if test "x$enable_bpfc" = "xyes" || test "x$enable_nfsynproxy" = "xyes"; then AC_CHECK_LIB(pcap, pcap_compile,, AC_MSG_ERROR(missing libpcap library required by bpf compiler or nfsynproxy tool)) @@ -115,6 +119,46 @@ PKG_CHECK_MODULES([libnfnetlink], [libnfnetlink >= 1.0], [nfnetlink=1], [nfnetlink=0]) AM_CONDITIONAL([HAVE_LIBNFNETLINK], [test "$nfnetlink" = 1]) +if test "x$enable_nftables" = "xyes"; then + PKG_CHECK_MODULES([libmnl], [libmnl >= 1.0], [mnl=1], [mnl=0]) + + PKG_CHECK_MODULES([libnftnl], [libnftnl >= 1.0], [nftables=1], [nftables=0]) + + AM_PROG_LEX + AC_PROG_YACC + + if test -z "$ac_cv_prog_YACC" + then + echo "*** Error: No suitable bison/yacc found. ***" + echo " Please install the 'bison' package." + exit 1 + fi + if test -z "$ac_cv_prog_LEX" + then + echo "*** Error: No suitable flex/lex found. ***" + echo " Please install the 'flex' package." + exit 1 + fi + + AC_MSG_CHECKING(flex version) + flex_version=`$ac_cv_prog_LEX --version | sed 's/version//g' | awk '/flex/ {print $2}'` + flex_major=`echo $flex_version| cut -d . -f 1` + flex_minor=`echo $flex_version| cut -d . -f 2` + flex_rev=`echo $flex_version| cut -d . -f 3` + + if test "$flex_major" -eq "2" && test "$flex_minor" -eq "5" && test "$flex_rev" -ge "33"; then + AC_MSG_RESULT([$flex_version. OK]) + else + AC_MSG_WARN([flex version $flex_version found. + Version 2.5.33 or greater is required. You may experience problems + while compilating the nftables compatibility layer for iptables. + Please, consider to upgrade flex.]) + fi +fi + +AM_CONDITIONAL([HAVE_LIBMNL], [test "$mnl" = 1]) +AM_CONDITIONAL([HAVE_LIBNFTNL], [test "$nftables" = 1]) + regular_CFLAGS="-Wall -Waggregate-return -Wmissing-declarations \ -Wmissing-prototypes -Wredundant-decls -Wshadow -Wstrict-prototypes \ -Winline -pipe"; @@ -182,6 +226,7 @@ Iptables Configuration: Large file support: ${enable_largefile} BPF utils support: ${enable_bpfc} nfsynproxy util support: ${enable_nfsynproxy} + nftables support: ${enable_nftables} Build parameters: Put plugins into executable (static): ${enable_static} diff --git a/etc/xtables.conf b/etc/xtables.conf new file mode 100644 index 00000000..d37b0d7c --- /dev/null +++ b/etc/xtables.conf @@ -0,0 +1,75 @@ +family ipv4 { + table raw { + chain PREROUTING hook NF_INET_PRE_ROUTING prio -300 + chain OUTPUT hook NF_INET_LOCAL_OUT prio -300 + } + + table mangle { + chain PREROUTING hook NF_INET_PRE_ROUTING prio -150 + chain INPUT hook NF_INET_LOCAL_IN prio -150 + chain FORWARD hook NF_INET_FORWARD prio -150 + chain OUTPUT hook NF_INET_LOCAL_OUT prio -150 + chain POSTROUTING hook NF_INET_POST_ROUTING prio -150 + } + + table filter { + chain INPUT hook NF_INET_LOCAL_IN prio 0 + chain FORWARD hook NF_INET_FORWARD prio 0 + chain OUTPUT hook NF_INET_LOCAL_OUT prio 0 + } + + table nat { + chain PREROUTING hook NF_INET_PRE_ROUTING prio -100 + chain INPUT hook NF_INET_LOCAL_IN prio -100 + chain OUTPUT hook NF_INET_LOCAL_OUT prio 100 + chain POSTROUTING hook NF_INET_POST_ROUTING prio 100 + } + + table security { + chain INPUT hook NF_INET_LOCAL_IN prio 50 + chain FORWARD hook NF_INET_FORWARD prio 50 + chain OUTPUT hook NF_INET_LOCAL_OUT prio 50 + } +} + +family ipv6 { + table raw { + chain PREROUTING hook NF_INET_PRE_ROUTING prio -300 + chain OUTPUT hook NF_INET_LOCAL_OUT prio -300 + } + + table mangle { + chain PREROUTING hook NF_INET_PRE_ROUTING prio -150 + chain INPUT hook NF_INET_LOCAL_IN prio -150 + chain FORWARD hook NF_INET_FORWARD prio -150 + chain OUTPUT hook NF_INET_LOCAL_OUT prio -150 + chain POSTROUTING hook NF_INET_POST_ROUTING prio -150 + } + + table filter { + chain INPUT hook NF_INET_LOCAL_IN prio 0 + chain FORWARD hook NF_INET_FORWARD prio 0 + chain OUTPUT hook NF_INET_LOCAL_OUT prio 0 + } + + table nat { + chain PREROUTING hook NF_INET_PRE_ROUTING prio -100 + chain INPUT hook NF_INET_LOCAL_IN prio -100 + chain OUTPUT hook NF_INET_LOCAL_OUT prio 100 + chain POSTROUTING hook NF_INET_POST_ROUTING prio 100 + } + + table security { + chain INPUT hook NF_INET_LOCAL_IN prio 50 + chain FORWARD hook NF_INET_FORWARD prio 50 + chain OUTPUT hook NF_INET_LOCAL_OUT prio 50 + } +} + +family arp { + table filter { + chain INPUT hook NF_ARP_IN prio 0 + chain FORWARD hook NF_ARP_FORWARD prio 0 + chain OUTPUT hook NF_ARP_OUT prio 0 + } +}
\ No newline at end of file diff --git a/extensions/libxt_mangle.c b/extensions/libxt_mangle.c new file mode 100644 index 00000000..4b20feb3 --- /dev/null +++ b/extensions/libxt_mangle.c @@ -0,0 +1,388 @@ +/* + * 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. + * + * Authors: + * Libarptc code from: Bart De Schuymer <bdschuym@pandora.be> + * Port to libxtables: Tomasz Bursztyka <tomasz.bursztyka@linux.intel.com> + */ + +#include <stdio.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <getopt.h> +#include <errno.h> +#include <netinet/ether.h> + +#include <xtables.h> +#include <linux/netfilter_arp/arpt_mangle.h> + +static void mangle_help(void) +{ + printf( +"mangle target options:\n" +"--mangle-ip-s IP address\n" +"--mangle-ip-d IP address\n" +"--mangle-mac-s MAC address\n" +"--mangle-mac-d MAC address\n" +"--mangle-target target (DROP, CONTINUE or ACCEPT -- default is ACCEPT)\n" + ); +} + +enum { + MANGLE_IPS = 0, + MANGLE_IPT = 1, + MANGLE_DEVS = 2, + MANGLE_DEVT = 3, + MANGLE_TARGET = 4, +}; + +static const struct xt_option_entry mangle_opts[] = { + { .name = "mangle-ip-s", .id = MANGLE_IPS, .type = XTTYPE_STRING }, + { .name = "mangle-ip-d", .id = MANGLE_IPT, .type = XTTYPE_STRING }, + { .name = "mangle-mac-s", .id = MANGLE_DEVS, .type = XTTYPE_STRING }, + { .name = "mangle-mac-d", .id = MANGLE_DEVT, .type = XTTYPE_STRING }, + { .name = "mangle-target", .id = MANGLE_TARGET, + .type = XTTYPE_STRING }, + XTOPT_TABLEEND, +}; + + +static struct in_addr *network_to_addr(const char *name) +{ + struct netent *net; + static struct in_addr addr; + + if ((net = getnetbyname(name)) != NULL) { + if (net->n_addrtype != AF_INET) + return (struct in_addr *) NULL; + addr.s_addr = htonl((unsigned long) net->n_net); + return &addr; + } + + return (struct in_addr *) NULL; +} + +static void inaddrcpy(struct in_addr *dst, struct in_addr *src) +{ + dst->s_addr = src->s_addr; +} + +static struct in_addr *host_to_addr(const char *name, unsigned int *naddr) +{ + struct hostent *host; + struct in_addr *addr; + unsigned int i; + + *naddr = 0; + if ((host = gethostbyname(name)) != NULL) { + if (host->h_addrtype != AF_INET || + host->h_length != sizeof(struct in_addr)) + return (struct in_addr *) NULL; + + while (host->h_addr_list[*naddr] != (char *) NULL) + (*naddr)++; + addr = xtables_calloc(*naddr, sizeof(struct in_addr)); + for (i = 0; i < *naddr; i++) + inaddrcpy(&(addr[i]), + (struct in_addr *) host->h_addr_list[i]); + return addr; + } + + return (struct in_addr *) NULL; +} + +static int string_to_number(const char *s, unsigned int min, + unsigned int max, unsigned int *ret) +{ + long number; + char *end; + + /* Handle hex, octal, etc. */ + errno = 0; + number = strtol(s, &end, 0); + if (*end == '\0' && end != s) { + /* we parsed a number, let's see if we want this */ + if (errno != ERANGE && min <= number && number <= max) { + *ret = number; + return 0; + } + } + return -1; +} + +static struct in_addr *dotted_to_addr(const char *dotted) +{ + static struct in_addr addr; + unsigned char *addrp; + char *p, *q; + unsigned int onebyte; + int i; + char buf[20]; + + /* copy dotted string, because we need to modify it */ + strncpy(buf, dotted, sizeof(buf) - 1); + addrp = (unsigned char *) &(addr.s_addr); + + p = buf; + for (i = 0; i < 3; i++) { + if ((q = strchr(p, '.')) == NULL) + return (struct in_addr *) NULL; + + *q = '\0'; + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[i] = (unsigned char) onebyte; + p = q + 1; + } + + /* we've checked 3 bytes, now we check the last one */ + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[3] = (unsigned char) onebyte; + + return &addr; +} + +static struct in_addr *parse_hostnetwork(const char *name, + unsigned int *naddrs) +{ + struct in_addr *addrp, *addrptmp; + + if ((addrptmp = dotted_to_addr(name)) != NULL || + (addrptmp = network_to_addr(name)) != NULL) { + addrp = xtables_malloc(sizeof(struct in_addr)); + inaddrcpy(addrp, addrptmp); + *naddrs = 1; + return addrp; + } + if ((addrp = host_to_addr(name, naddrs)) != NULL) + return addrp; + + xtables_error(PARAMETER_PROBLEM, "host/network `%s' not found", name); +} + +static void mangle_parse(struct xt_option_call *cb) +{ + const struct arpt_entry *e = cb->xt_entry; + struct arpt_mangle *mangle = cb->data; + struct in_addr *ipaddr; + struct ether_addr *macaddr; + + /* mangle target is by default "ACCEPT". Setting it here, + * since original arpt_mangle.c init() no longer exists*/ + mangle->target = NF_ACCEPT; + + xtables_option_parse(cb); + switch (cb->entry->id) { + case MANGLE_IPS: +/* + if (e->arp.arpln_mask == 0) + xtables_error(PARAMETER_PROBLEM, "no pln defined"); + + if (e->arp.invflags & ARPT_INV_ARPPLN) + xtables_error(PARAMETER_PROBLEM, + "! pln not allowed for --mangle-ip-s"); +*/ +/* + if (e->arp.arpln != 4) + xtables_error(PARAMETER_PROBLEM, "only pln=4 supported"); +*/ + { + unsigned int nr; + ipaddr = parse_hostnetwork(cb->arg, &nr); + } + mangle->u_s.src_ip.s_addr = ipaddr->s_addr; + free(ipaddr); + mangle->flags |= ARPT_MANGLE_SIP; + break; + case MANGLE_IPT: +/* + if (e->arp.arpln_mask == 0) + xtables_error(PARAMETER_PROBLEM, "no pln defined"); + + if (e->arp.invflags & ARPT_INV_ARPPLN) + xtables_error(PARAMETER_PROBLEM, + "! pln not allowed for --mangle-ip-d"); +*/ +/* + if (e->arp.arpln != 4) + xtables_error(PARAMETER_PROBLEM, "only pln=4 supported"); +*/ + { + unsigned int nr; + ipaddr = parse_hostnetwork(cb->arg, &nr); + } + mangle->u_t.tgt_ip.s_addr = ipaddr->s_addr; + free(ipaddr); + mangle->flags |= ARPT_MANGLE_TIP; + break; + case MANGLE_DEVS: + if (e->arp.arhln_mask == 0) + xtables_error(PARAMETER_PROBLEM, + "no --h-length defined"); + if (e->arp.invflags & ARPT_INV_ARPHLN) + xtables_error(PARAMETER_PROBLEM, + "! --h-length not allowed for " + "--mangle-mac-s"); + if (e->arp.arhln != 6) + xtables_error(PARAMETER_PROBLEM, + "only --h-length 6 supported"); + macaddr = ether_aton(cb->arg); + if (macaddr == NULL) + xtables_error(PARAMETER_PROBLEM, "invalid source MAC"); + memcpy(mangle->src_devaddr, macaddr, e->arp.arhln); + mangle->flags |= ARPT_MANGLE_SDEV; + break; + case MANGLE_DEVT: + if (e->arp.arhln_mask == 0) + xtables_error(PARAMETER_PROBLEM, + "no --h-length defined"); + if (e->arp.invflags & ARPT_INV_ARPHLN) + xtables_error(PARAMETER_PROBLEM, + "! hln not allowed for --mangle-mac-d"); + if (e->arp.arhln != 6) + xtables_error(PARAMETER_PROBLEM, + "only --h-length 6 supported"); + macaddr = ether_aton(cb->arg); + if (macaddr == NULL) + xtables_error(PARAMETER_PROBLEM, "invalid target MAC"); + memcpy(mangle->tgt_devaddr, macaddr, e->arp.arhln); + mangle->flags |= ARPT_MANGLE_TDEV; + break; + case MANGLE_TARGET: + if (!strcmp(cb->arg, "DROP")) + mangle->target = NF_DROP; + else if (!strcmp(cb->arg, "ACCEPT")) + mangle->target = NF_ACCEPT; + else if (!strcmp(cb->arg, "CONTINUE")) + mangle->target = ARPT_CONTINUE; + else + xtables_error(PARAMETER_PROBLEM, + "bad target for --mangle-target"); + break; + } +} + +static void mangle_fcheck(struct xt_fcheck_call *cb) +{ +} + +static char *addr_to_dotted(const struct in_addr *addrp) +{ + static char buf[20]; + const unsigned char *bytep; + + bytep = (const unsigned char *) &(addrp->s_addr); + sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]); + return buf; +} + +static char *addr_to_host(const struct in_addr *addr) +{ + struct hostent *host; + + if ((host = gethostbyaddr((char *) addr, + sizeof(struct in_addr), AF_INET)) != NULL) + return (char *) host->h_name; + + return (char *) NULL; +} + +static char *addr_to_network(const struct in_addr *addr) +{ + struct netent *net; + + if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL) + return (char *) net->n_name; + + return (char *) NULL; +} + +static char *addr_to_anyname(const struct in_addr *addr) +{ + char *name; + + if ((name = addr_to_host(addr)) != NULL || + (name = addr_to_network(addr)) != NULL) + return name; + + return addr_to_dotted(addr); +} + +static void print_mac(const unsigned char *mac, int l) +{ + int j; + + for (j = 0; j < l; j++) + printf("%02x%s", mac[j], + (j==l-1) ? "" : ":"); +} + +static void mangle_print(const void *ip, const struct xt_entry_target *target, + int numeric) +{ + const struct arpt_mangle *m = (const void *)target; + char buf[100]; + + if (m->flags & ARPT_MANGLE_SIP) { + if (numeric) + sprintf(buf, "%s", addr_to_dotted(&(m->u_s.src_ip))); + else + sprintf(buf, "%s", addr_to_anyname(&(m->u_s.src_ip))); + printf("--mangle-ip-s %s ", buf); + } + if (m->flags & ARPT_MANGLE_SDEV) { + printf("--mangle-mac-s "); + print_mac((unsigned char *)m->src_devaddr, 6); + printf(" "); + } + if (m->flags & ARPT_MANGLE_TIP) { + if (numeric) + sprintf(buf, "%s", addr_to_dotted(&(m->u_t.tgt_ip))); + else + sprintf(buf, "%s", addr_to_anyname(&(m->u_t.tgt_ip))); + printf("--mangle-ip-d %s ", buf); + } + if (m->flags & ARPT_MANGLE_TDEV) { + printf("--mangle-mac-d "); + print_mac((unsigned char *)m->tgt_devaddr, 6); + printf(" "); + } + if (m->target != NF_ACCEPT) { + printf("--mangle-target "); + if (m->target == NF_DROP) + printf("DROP "); + else + printf("CONTINUE "); + } +} + +static void mangle_save(const void *ip, const struct xt_entry_target *target) +{ +} + +static struct xtables_target mangle_tg_reg = { + .family = NFPROTO_ARP, + .name = "mangle", + .version = XTABLES_VERSION, + .size = XT_ALIGN(sizeof(struct arpt_mangle)), + .userspacesize = XT_ALIGN(sizeof(struct arpt_mangle)), + .help = mangle_help, + .x6_parse = mangle_parse, + .x6_fcheck = mangle_fcheck, + .print = mangle_print, + .save = mangle_save, + .x6_options = mangle_opts, +}; + +void _init(void) +{ + xtables_register_target(&mangle_tg_reg); +} diff --git a/include/iptables.h b/include/iptables.h index ac9dc0e5..78c10abd 100644 --- a/include/iptables.h +++ b/include/iptables.h @@ -20,4 +20,6 @@ extern void print_rule4(const struct ipt_entry *e, extern struct xtables_globals iptables_globals; +extern struct xtables_globals xtables_globals; + #endif /*_IPTABLES_USER_H*/ diff --git a/include/linux/netfilter/nf_tables.h b/include/linux/netfilter/nf_tables.h new file mode 100644 index 00000000..fbfd229a --- /dev/null +++ b/include/linux/netfilter/nf_tables.h @@ -0,0 +1,718 @@ +#ifndef _LINUX_NF_TABLES_H +#define _LINUX_NF_TABLES_H + +#define NFT_CHAIN_MAXNAMELEN 32 + +enum nft_registers { + NFT_REG_VERDICT, + NFT_REG_1, + NFT_REG_2, + NFT_REG_3, + NFT_REG_4, + __NFT_REG_MAX +}; +#define NFT_REG_MAX (__NFT_REG_MAX - 1) + +/** + * enum nft_verdicts - nf_tables internal verdicts + * + * @NFT_CONTINUE: continue evaluation of the current rule + * @NFT_BREAK: terminate evaluation of the current rule + * @NFT_JUMP: push the current chain on the jump stack and jump to a chain + * @NFT_GOTO: jump to a chain without pushing the current chain on the jump stack + * @NFT_RETURN: return to the topmost chain on the jump stack + * + * The nf_tables verdicts share their numeric space with the netfilter verdicts. + */ +enum nft_verdicts { + NFT_CONTINUE = -1, + NFT_BREAK = -2, + NFT_JUMP = -3, + NFT_GOTO = -4, + NFT_RETURN = -5, +}; + +/** + * enum nf_tables_msg_types - nf_tables netlink message types + * + * @NFT_MSG_NEWTABLE: create a new table (enum nft_table_attributes) + * @NFT_MSG_GETTABLE: get a table (enum nft_table_attributes) + * @NFT_MSG_DELTABLE: delete a table (enum nft_table_attributes) + * @NFT_MSG_NEWCHAIN: create a new chain (enum nft_chain_attributes) + * @NFT_MSG_GETCHAIN: get a chain (enum nft_chain_attributes) + * @NFT_MSG_DELCHAIN: delete a chain (enum nft_chain_attributes) + * @NFT_MSG_NEWRULE: create a new rule (enum nft_rule_attributes) + * @NFT_MSG_GETRULE: get a rule (enum nft_rule_attributes) + * @NFT_MSG_DELRULE: delete a rule (enum nft_rule_attributes) + * @NFT_MSG_NEWSET: create a new set (enum nft_set_attributes) + * @NFT_MSG_GETSET: get a set (enum nft_set_attributes) + * @NFT_MSG_DELSET: delete a set (enum nft_set_attributes) + * @NFT_MSG_NEWSETELEM: create a new set element (enum nft_set_elem_attributes) + * @NFT_MSG_GETSETELEM: get a set element (enum nft_set_elem_attributes) + * @NFT_MSG_DELSETELEM: delete a set element (enum nft_set_elem_attributes) + */ +enum nf_tables_msg_types { + NFT_MSG_NEWTABLE, + NFT_MSG_GETTABLE, + NFT_MSG_DELTABLE, + NFT_MSG_NEWCHAIN, + NFT_MSG_GETCHAIN, + NFT_MSG_DELCHAIN, + NFT_MSG_NEWRULE, + NFT_MSG_GETRULE, + NFT_MSG_DELRULE, + NFT_MSG_NEWSET, + NFT_MSG_GETSET, + NFT_MSG_DELSET, + NFT_MSG_NEWSETELEM, + NFT_MSG_GETSETELEM, + NFT_MSG_DELSETELEM, + NFT_MSG_MAX, +}; + +/** + * enum nft_list_attributes - nf_tables generic list netlink attributes + * + * @NFTA_LIST_ELEM: list element (NLA_NESTED) + */ +enum nft_list_attributes { + NFTA_LIST_UNPEC, + NFTA_LIST_ELEM, + __NFTA_LIST_MAX +}; +#define NFTA_LIST_MAX (__NFTA_LIST_MAX - 1) + +/** + * enum nft_hook_attributes - nf_tables netfilter hook netlink attributes + * + * @NFTA_HOOK_HOOKNUM: netfilter hook number (NLA_U32) + * @NFTA_HOOK_PRIORITY: netfilter hook priority (NLA_U32) + */ +enum nft_hook_attributes { + NFTA_HOOK_UNSPEC, + NFTA_HOOK_HOOKNUM, + NFTA_HOOK_PRIORITY, + __NFTA_HOOK_MAX +}; +#define NFTA_HOOK_MAX (__NFTA_HOOK_MAX - 1) + +/** + * enum nft_table_flags - nf_tables table flags + * + * @NFT_TABLE_F_DORMANT: this table is not active + */ +enum nft_table_flags { + NFT_TABLE_F_DORMANT = 0x1, +}; + +/** + * enum nft_table_attributes - nf_tables table netlink attributes + * + * @NFTA_TABLE_NAME: name of the table (NLA_STRING) + * @NFTA_TABLE_FLAGS: bitmask of enum nft_table_flags (NLA_U32) + */ +enum nft_table_attributes { + NFTA_TABLE_UNSPEC, + NFTA_TABLE_NAME, + NFTA_TABLE_FLAGS, + __NFTA_TABLE_MAX +}; +#define NFTA_TABLE_MAX (__NFTA_TABLE_MAX - 1) + +/** + * enum nft_chain_attributes - nf_tables chain netlink attributes + * + * @NFTA_CHAIN_TABLE: name of the table containing the chain (NLA_STRING) + * @NFTA_CHAIN_HANDLE: numeric handle of the chain (NLA_U64) + * @NFTA_CHAIN_NAME: name of the chain (NLA_STRING) + * @NFTA_CHAIN_HOOK: hook specification for basechains (NLA_NESTED: nft_hook_attributes) + * @NFTA_CHAIN_POLICY: numeric policy of the chain (NLA_U32) + * @NFTA_CHAIN_USE: number of references to this chain (NLA_U32) + * @NFTA_CHAIN_TYPE: type name of the string (NLA_NUL_STRING) + * @NFTA_CHAIN_COUNTERS: counter specification of the chain (NLA_NESTED: nft_counter_attributes) + */ +enum nft_chain_attributes { + NFTA_CHAIN_UNSPEC, + NFTA_CHAIN_TABLE, + NFTA_CHAIN_HANDLE, + NFTA_CHAIN_NAME, + NFTA_CHAIN_HOOK, + NFTA_CHAIN_POLICY, + NFTA_CHAIN_USE, + NFTA_CHAIN_TYPE, + NFTA_CHAIN_COUNTERS, + __NFTA_CHAIN_MAX +}; +#define NFTA_CHAIN_MAX (__NFTA_CHAIN_MAX - 1) + +/** + * enum nft_rule_attributes - nf_tables rule netlink attributes + * + * @NFTA_RULE_TABLE: name of the table containing the rule (NLA_STRING) + * @NFTA_RULE_CHAIN: name of the chain containing the rule (NLA_STRING) + * @NFTA_RULE_HANDLE: numeric handle of the rule (NLA_U64) + * @NFTA_RULE_EXPRESSIONS: list of expressions (NLA_NESTED: nft_expr_attributes) + * @NFTA_RULE_COMPAT: compatibility specifications of the rule (NLA_NESTED: nft_rule_compat_attributes) + * @NFTA_RULE_POSITION: numeric handle of the previous rule (NLA_U64) + */ +enum nft_rule_attributes { + NFTA_RULE_UNSPEC, + NFTA_RULE_TABLE, + NFTA_RULE_CHAIN, + NFTA_RULE_HANDLE, + NFTA_RULE_EXPRESSIONS, + NFTA_RULE_COMPAT, + NFTA_RULE_POSITION, + __NFTA_RULE_MAX +}; +#define NFTA_RULE_MAX (__NFTA_RULE_MAX - 1) + +/** + * enum nft_rule_compat_flags - nf_tables rule compat flags + * + * @NFT_RULE_COMPAT_F_INV: invert the check result + */ +enum nft_rule_compat_flags { + NFT_RULE_COMPAT_F_INV = (1 << 1), + NFT_RULE_COMPAT_F_MASK = NFT_RULE_COMPAT_F_INV, +}; + +/** + * enum nft_rule_compat_attributes - nf_tables rule compat attributes + * + * @NFTA_RULE_COMPAT_PROTO: numerice value of handled protocol (NLA_U32) + * @NFTA_RULE_COMPAT_FLAGS: bitmask of enum nft_rule_compat_flags (NLA_U32) + */ +enum nft_rule_compat_attributes { + NFTA_RULE_COMPAT_UNSPEC, + NFTA_RULE_COMPAT_PROTO, + NFTA_RULE_COMPAT_FLAGS, + __NFTA_RULE_COMPAT_MAX +}; +#define NFTA_RULE_COMPAT_MAX (__NFTA_RULE_COMPAT_MAX - 1) + +/** + * enum nft_set_flags - nf_tables set flags + * + * @NFT_SET_ANONYMOUS: name allocation, automatic cleanup on unlink + * @NFT_SET_CONSTANT: set contents may not change while bound + * @NFT_SET_INTERVAL: set contains intervals + * @NFT_SET_MAP: set is used as a dictionary + */ +enum nft_set_flags { + NFT_SET_ANONYMOUS = 0x1, + NFT_SET_CONSTANT = 0x2, + NFT_SET_INTERVAL = 0x4, + NFT_SET_MAP = 0x8, +}; + +/** + * enum nft_set_attributes - nf_tables set netlink attributes + * + * @NFTA_SET_TABLE: table name (NLA_STRING) + * @NFTA_SET_NAME: set name (NLA_STRING) + * @NFTA_SET_FLAGS: bitmask of enum nft_set_flags (NLA_U32) + * @NFTA_SET_KEY_TYPE: key data type, informational purpose only (NLA_U32) + * @NFTA_SET_KEY_LEN: key data length (NLA_U32) + * @NFTA_SET_DATA_TYPE: mapping data type (NLA_U32) + * @NFTA_SET_DATA_LEN: mapping data length (NLA_U32) + */ +enum nft_set_attributes { + NFTA_SET_UNSPEC, + NFTA_SET_TABLE, + NFTA_SET_NAME, + NFTA_SET_FLAGS, + NFTA_SET_KEY_TYPE, + NFTA_SET_KEY_LEN, + NFTA_SET_DATA_TYPE, + NFTA_SET_DATA_LEN, + __NFTA_SET_MAX +}; +#define NFTA_SET_MAX (__NFTA_SET_MAX - 1) + +/** + * enum nft_set_elem_flags - nf_tables set element flags + * + * @NFT_SET_ELEM_INTERVAL_END: element ends the previous interval + */ +enum nft_set_elem_flags { + NFT_SET_ELEM_INTERVAL_END = 0x1, +}; + +/** + * enum nft_set_elem_attributes - nf_tables set element netlink attributes + * + * @NFTA_SET_ELEM_KEY: key value (NLA_NESTED: nft_data) + * @NFTA_SET_ELEM_DATA: data value of mapping (NLA_NESTED: nft_data_attributes) + * @NFTA_SET_ELEM_FLAGS: bitmask of nft_set_elem_flags (NLA_U32) + */ +enum nft_set_elem_attributes { + NFTA_SET_ELEM_UNSPEC, + NFTA_SET_ELEM_KEY, + NFTA_SET_ELEM_DATA, + NFTA_SET_ELEM_FLAGS, + __NFTA_SET_ELEM_MAX +}; +#define NFTA_SET_ELEM_MAX (__NFTA_SET_ELEM_MAX - 1) + +/** + * enum nft_set_elem_list_attributes - nf_tables set element list netlink attributes + * + * @NFTA_SET_ELEM_LIST_TABLE: table of the set to be changed (NLA_STRING) + * @NFTA_SET_ELEM_LIST_SET: name of the set to be changed (NLA_STRING) + * @NFTA_SET_ELEM_LIST_ELEMENTS: list of set elements (NLA_NESTED: nft_set_elem_attributes) + */ +enum nft_set_elem_list_attributes { + NFTA_SET_ELEM_LIST_UNSPEC, + NFTA_SET_ELEM_LIST_TABLE, + NFTA_SET_ELEM_LIST_SET, + NFTA_SET_ELEM_LIST_ELEMENTS, + __NFTA_SET_ELEM_LIST_MAX +}; +#define NFTA_SET_ELEM_LIST_MAX (__NFTA_SET_ELEM_LIST_MAX - 1) + +/** + * enum nft_data_types - nf_tables data types + * + * @NFT_DATA_VALUE: generic data + * @NFT_DATA_VERDICT: netfilter verdict + * + * The type of data is usually determined by the kernel directly and is not + * explicitly specified by userspace. The only difference are sets, where + * userspace specifies the key and mapping data types. + * + * The values 0xffffff00-0xffffffff are reserved for internally used types. + * The remaining range can be freely used by userspace to encode types, all + * values are equivalent to NFT_DATA_VALUE. + */ +enum nft_data_types { + NFT_DATA_VALUE, + NFT_DATA_VERDICT = 0xffffff00U, +}; + +#define NFT_DATA_RESERVED_MASK 0xffffff00U + +/** + * enum nft_data_attributes - nf_tables data netlink attributes + * + * @NFTA_DATA_VALUE: generic data (NLA_BINARY) + * @NFTA_DATA_VERDICT: nf_tables verdict (NLA_NESTED: nft_verdict_attributes) + */ +enum nft_data_attributes { + NFTA_DATA_UNSPEC, + NFTA_DATA_VALUE, + NFTA_DATA_VERDICT, + __NFTA_DATA_MAX +}; +#define NFTA_DATA_MAX (__NFTA_DATA_MAX - 1) + +/** + * enum nft_verdict_attributes - nf_tables verdict netlink attributes + * + * @NFTA_VERDICT_CODE: nf_tables verdict (NLA_U32: enum nft_verdicts) + * @NFTA_VERDICT_CHAIN: jump target chain name (NLA_STRING) + */ +enum nft_verdict_attributes { + NFTA_VERDICT_UNSPEC, + NFTA_VERDICT_CODE, + NFTA_VERDICT_CHAIN, + __NFTA_VERDICT_MAX +}; +#define NFTA_VERDICT_MAX (__NFTA_VERDICT_MAX - 1) + +/** + * enum nft_expr_attributes - nf_tables expression netlink attributes + * + * @NFTA_EXPR_NAME: name of the expression type (NLA_STRING) + * @NFTA_EXPR_DATA: type specific data (NLA_NESTED) + */ +enum nft_expr_attributes { + NFTA_EXPR_UNSPEC, + NFTA_EXPR_NAME, + NFTA_EXPR_DATA, + __NFTA_EXPR_MAX +}; +#define NFTA_EXPR_MAX (__NFTA_EXPR_MAX - 1) + +/** + * enum nft_immediate_attributes - nf_tables immediate expression netlink attributes + * + * @NFTA_IMMEDIATE_DREG: destination register to load data into (NLA_U32) + * @NFTA_IMMEDIATE_DATA: data to load (NLA_NESTED: nft_data_attributes) + */ +enum nft_immediate_attributes { + NFTA_IMMEDIATE_UNSPEC, + NFTA_IMMEDIATE_DREG, + NFTA_IMMEDIATE_DATA, + __NFTA_IMMEDIATE_MAX +}; +#define NFTA_IMMEDIATE_MAX (__NFTA_IMMEDIATE_MAX - 1) + +/** + * enum nft_bitwise_attributes - nf_tables bitwise expression netlink attributes + * + * @NFTA_BITWISE_SREG: source register (NLA_U32: nft_registers) + * @NFTA_BITWISE_DREG: destination register (NLA_U32: nft_registers) + * @NFTA_BITWISE_LEN: length of operands (NLA_U32) + * @NFTA_BITWISE_MASK: mask value (NLA_NESTED: nft_data_attributes) + * @NFTA_BITWISE_XOR: xor value (NLA_NESTED: nft_data_attributes) + * + * The bitwise expression performs the following operation: + * + * dreg = (sreg & mask) ^ xor + * + * which allow to express all bitwise operations: + * + * mask xor + * NOT: 1 1 + * OR: 0 x + * XOR: 1 x + * AND: x 0 + */ +enum nft_bitwise_attributes { + NFTA_BITWISE_UNSPEC, + NFTA_BITWISE_SREG, + NFTA_BITWISE_DREG, + NFTA_BITWISE_LEN, + NFTA_BITWISE_MASK, + NFTA_BITWISE_XOR, + __NFTA_BITWISE_MAX +}; +#define NFTA_BITWISE_MAX (__NFTA_BITWISE_MAX - 1) + +/** + * enum nft_byteorder_ops - nf_tables byteorder operators + * + * @NFT_BYTEORDER_NTOH: network to host operator + * @NFT_BYTEORDER_HTON: host to network opertaor + */ +enum nft_byteorder_ops { + NFT_BYTEORDER_NTOH, + NFT_BYTEORDER_HTON, +}; + +/** + * enum nft_byteorder_attributes - nf_tables byteorder expression netlink attributes + * + * @NFTA_BYTEORDER_SREG: source register (NLA_U32: nft_registers) + * @NFTA_BYTEORDER_DREG: destination register (NLA_U32: nft_registers) + * @NFTA_BYTEORDER_OP: operator (NLA_U32: enum nft_byteorder_ops) + * @NFTA_BYTEORDER_LEN: length of the data (NLA_U32) + * @NFTA_BYTEORDER_SIZE: data size in bytes (NLA_U32: 2 or 4) + */ +enum nft_byteorder_attributes { + NFTA_BYTEORDER_UNSPEC, + NFTA_BYTEORDER_SREG, + NFTA_BYTEORDER_DREG, + NFTA_BYTEORDER_OP, + NFTA_BYTEORDER_LEN, + NFTA_BYTEORDER_SIZE, + __NFTA_BYTEORDER_MAX +}; +#define NFTA_BYTEORDER_MAX (__NFTA_BYTEORDER_MAX - 1) + +/** + * enum nft_cmp_ops - nf_tables relational operator + * + * @NFT_CMP_EQ: equal + * @NFT_CMP_NEQ: not equal + * @NFT_CMP_LT: less than + * @NFT_CMP_LTE: less than or equal to + * @NFT_CMP_GT: greater than + * @NFT_CMP_GTE: greater than or equal to + */ +enum nft_cmp_ops { + NFT_CMP_EQ, + NFT_CMP_NEQ, + NFT_CMP_LT, + NFT_CMP_LTE, + NFT_CMP_GT, + NFT_CMP_GTE, +}; + +/** + * enum nft_cmp_attributes - nf_tables cmp expression netlink attributes + * + * @NFTA_CMP_SREG: source register of data to compare (NLA_U32: nft_registers) + * @NFTA_CMP_OP: cmp operation (NLA_U32: nft_cmp_ops) + * @NFTA_CMP_DATA: data to compare against (NLA_NESTED: nft_data_attributes) + */ +enum nft_cmp_attributes { + NFTA_CMP_UNSPEC, + NFTA_CMP_SREG, + NFTA_CMP_OP, + NFTA_CMP_DATA, + __NFTA_CMP_MAX +}; +#define NFTA_CMP_MAX (__NFTA_CMP_MAX - 1) + +/** + * enum nft_lookup_attributes - nf_tables set lookup expression netlink attributes + * + * @NFTA_LOOKUP_SET: name of the set where to look for (NLA_STRING) + * @NFTA_LOOKUP_SREG: source register of the data to look for (NLA_U32: nft_registers) + * @NFTA_LOOKUP_DREG: destination register (NLA_U32: nft_registers) + */ +enum nft_lookup_attributes { + NFTA_LOOKUP_UNSPEC, + NFTA_LOOKUP_SET, + NFTA_LOOKUP_SREG, + NFTA_LOOKUP_DREG, + __NFTA_LOOKUP_MAX +}; +#define NFTA_LOOKUP_MAX (__NFTA_LOOKUP_MAX - 1) + +/** + * enum nft_payload_bases - nf_tables payload expression offset bases + * + * @NFT_PAYLOAD_LL_HEADER: link layer header + * @NFT_PAYLOAD_NETWORK_HEADER: network header + * @NFT_PAYLOAD_TRANSPORT_HEADER: transport header + */ +enum nft_payload_bases { + NFT_PAYLOAD_LL_HEADER, + NFT_PAYLOAD_NETWORK_HEADER, + NFT_PAYLOAD_TRANSPORT_HEADER, +}; + +/** + * enum nft_payload_attributes - nf_tables payload expression netlink attributes + * + * @NFTA_PAYLOAD_DREG: destination register to load data into (NLA_U32: nft_registers) + * @NFTA_PAYLOAD_BASE: payload base (NLA_U32: nft_payload_bases) + * @NFTA_PAYLOAD_OFFSET: payload offset relative to base (NLA_U32) + * @NFTA_PAYLOAD_LEN: payload length (NLA_U32) + */ +enum nft_payload_attributes { + NFTA_PAYLOAD_UNSPEC, + NFTA_PAYLOAD_DREG, + NFTA_PAYLOAD_BASE, + NFTA_PAYLOAD_OFFSET, + NFTA_PAYLOAD_LEN, + __NFTA_PAYLOAD_MAX +}; +#define NFTA_PAYLOAD_MAX (__NFTA_PAYLOAD_MAX - 1) + +/** + * enum nft_exthdr_attributes - nf_tables IPv6 extension header expression netlink attributes + * + * @NFTA_EXTHDR_DREG: destination register (NLA_U32: nft_registers) + * @NFTA_EXTHDR_TYPE: extension header type (NLA_U8) + * @NFTA_EXTHDR_OFFSET: extension header offset (NLA_U32) + * @NFTA_EXTHDR_LEN: extension header length (NLA_U32) + */ +enum nft_exthdr_attributes { + NFTA_EXTHDR_UNSPEC, + NFTA_EXTHDR_DREG, + NFTA_EXTHDR_TYPE, + NFTA_EXTHDR_OFFSET, + NFTA_EXTHDR_LEN, + __NFTA_EXTHDR_MAX +}; +#define NFTA_EXTHDR_MAX (__NFTA_EXTHDR_MAX - 1) + +/** + * enum nft_meta_keys - nf_tables meta expression keys + * + * @NFT_META_LEN: packet length (skb->len) + * @NFT_META_PROTOCOL: packet ethertype protocol (skb->protocol), invalid in OUTPUT + * @NFT_META_PRIORITY: packet priority (skb->priority) + * @NFT_META_MARK: packet mark (skb->mark) + * @NFT_META_IIF: packet input interface index (dev->ifindex) + * @NFT_META_OIF: packet output interface index (dev->ifindex) + * @NFT_META_IIFNAME: packet input interface name (dev->name) + * @NFT_META_OIFNAME: packet output interface name (dev->name) + * @NFT_META_IIFTYPE: packet input interface type (dev->type) + * @NFT_META_OIFTYPE: packet output interface type (dev->type) + * @NFT_META_SKUID: originating socket UID (fsuid) + * @NFT_META_SKGID: originating socket GID (fsgid) + * @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) + */ +enum nft_meta_keys { + NFT_META_LEN, + NFT_META_PROTOCOL, + NFT_META_PRIORITY, + NFT_META_MARK, + NFT_META_IIF, + NFT_META_OIF, + NFT_META_IIFNAME, + NFT_META_OIFNAME, + NFT_META_IIFTYPE, + NFT_META_OIFTYPE, + NFT_META_SKUID, + NFT_META_SKGID, + NFT_META_NFTRACE, + NFT_META_RTCLASSID, + NFT_META_SECMARK, +}; + +/** + * 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) + */ +enum nft_meta_attributes { + NFTA_META_UNSPEC, + NFTA_META_DREG, + NFTA_META_KEY, + __NFTA_META_MAX +}; +#define NFTA_META_MAX (__NFTA_META_MAX - 1) + +/** + * enum nft_ct_keys - nf_tables ct expression keys + * + * @NFT_CT_STATE: conntrack state (bitmask of enum ip_conntrack_info) + * @NFT_CT_DIRECTION: conntrack direction (enum ip_conntrack_dir) + * @NFT_CT_STATUS: conntrack status (bitmask of enum ip_conntrack_status) + * @NFT_CT_MARK: conntrack mark value + * @NFT_CT_SECMARK: conntrack secmark value + * @NFT_CT_EXPIRATION: relative conntrack expiration time in ms + * @NFT_CT_HELPER: connection tracking helper assigned to conntrack + * @NFT_CT_L3PROTOCOL: conntrack layer 3 protocol + * @NFT_CT_SRC: conntrack layer 3 protocol source (IPv4/IPv6 address) + * @NFT_CT_DST: conntrack layer 3 protocol destination (IPv4/IPv6 address) + * @NFT_CT_PROTOCOL: conntrack layer 4 protocol + * @NFT_CT_PROTO_SRC: conntrack layer 4 protocol source + * @NFT_CT_PROTO_DST: conntrack layer 4 protocol destination + */ +enum nft_ct_keys { + NFT_CT_STATE, + NFT_CT_DIRECTION, + NFT_CT_STATUS, + NFT_CT_MARK, + NFT_CT_SECMARK, + NFT_CT_EXPIRATION, + NFT_CT_HELPER, + NFT_CT_L3PROTOCOL, + NFT_CT_SRC, + NFT_CT_DST, + NFT_CT_PROTOCOL, + NFT_CT_PROTO_SRC, + NFT_CT_PROTO_DST, +}; + +/** + * enum nft_ct_attributes - nf_tables ct expression netlink attributes + * + * @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) + */ +enum nft_ct_attributes { + NFTA_CT_UNSPEC, + NFTA_CT_DREG, + NFTA_CT_KEY, + NFTA_CT_DIRECTION, + __NFTA_CT_MAX +}; +#define NFTA_CT_MAX (__NFTA_CT_MAX - 1) + +/** + * enum nft_limit_attributes - nf_tables limit expression netlink attributes + * + * @NFTA_LIMIT_RATE: refill rate (NLA_U64) + * @NFTA_LIMIT_UNIT: refill unit (NLA_U64) + */ +enum nft_limit_attributes { + NFTA_LIMIT_UNSPEC, + NFTA_LIMIT_RATE, + NFTA_LIMIT_UNIT, + __NFTA_LIMIT_MAX +}; +#define NFTA_LIMIT_MAX (__NFTA_LIMIT_MAX - 1) + +/** + * enum nft_counter_attributes - nf_tables counter expression netlink attributes + * + * @NFTA_COUNTER_BYTES: number of bytes (NLA_U64) + * @NFTA_COUNTER_PACKETS: number of packets (NLA_U64) + */ +enum nft_counter_attributes { + NFTA_COUNTER_UNSPEC, + NFTA_COUNTER_BYTES, + NFTA_COUNTER_PACKETS, + __NFTA_COUNTER_MAX +}; +#define NFTA_COUNTER_MAX (__NFTA_COUNTER_MAX - 1) + +/** + * enum nft_log_attributes - nf_tables log expression netlink attributes + * + * @NFTA_LOG_GROUP: netlink group to send messages to (NLA_U32) + * @NFTA_LOG_PREFIX: prefix to prepend to log messages (NLA_STRING) + * @NFTA_LOG_SNAPLEN: length of payload to include in netlink message (NLA_U32) + * @NFTA_LOG_QTHRESHOLD: queue threshold (NLA_U32) + */ +enum nft_log_attributes { + NFTA_LOG_UNSPEC, + NFTA_LOG_GROUP, + NFTA_LOG_PREFIX, + NFTA_LOG_SNAPLEN, + NFTA_LOG_QTHRESHOLD, + __NFTA_LOG_MAX +}; +#define NFTA_LOG_MAX (__NFTA_LOG_MAX - 1) + +/** + * enum nft_reject_types - nf_tables reject expression reject types + * + * @NFT_REJECT_ICMP_UNREACH: reject using ICMP unreachable + * @NFT_REJECT_TCP_RST: reject using TCP RST + */ +enum nft_reject_types { + NFT_REJECT_ICMP_UNREACH, + NFT_REJECT_TCP_RST, +}; + +/** + * enum nft_reject_attributes - nf_tables reject expression netlink attributes + * + * @NFTA_REJECT_TYPE: packet type to use (NLA_U32: nft_reject_types) + * @NFTA_REJECT_ICMP_CODE: ICMP code to use (NLA_U8) + */ +enum nft_reject_attributes { + NFTA_REJECT_UNSPEC, + NFTA_REJECT_TYPE, + NFTA_REJECT_ICMP_CODE, + __NFTA_REJECT_MAX +}; +#define NFTA_REJECT_MAX (__NFTA_REJECT_MAX - 1) + +/** + * enum nft_nat_types - nf_tables nat expression NAT types + * + * @NFT_NAT_SNAT: source NAT + * @NFT_NAT_DNAT: destination NAT + */ +enum nft_nat_types { + NFT_NAT_SNAT, + NFT_NAT_DNAT, +}; + +/** + * enum nft_nat_attributes - nf_tables nat expression netlink attributes + * + * @NFTA_NAT_TYPE: NAT type (NLA_U32: nft_nat_types) + * @NFTA_NAT_FAMILY: NAT family (NLA_U32) + * @NFTA_NAT_REG_ADDR_MIN: source register of address range start (NLA_U32: nft_registers) + * @NFTA_NAT_REG_ADDR_MAX: source register of address range end (NLA_U32: nft_registers) + * @NFTA_NAT_REG_PROTO_MIN: source register of proto range start (NLA_U32: nft_registers) + * @NFTA_NAT_REG_PROTO_MAX: source register of proto range end (NLA_U32: nft_registers) + */ +enum nft_nat_attributes { + NFTA_NAT_UNSPEC, + NFTA_NAT_TYPE, + NFTA_NAT_FAMILY, + NFTA_NAT_REG_ADDR_MIN, + NFTA_NAT_REG_ADDR_MAX, + NFTA_NAT_REG_PROTO_MIN, + NFTA_NAT_REG_PROTO_MAX, + __NFTA_NAT_MAX +}; +#define NFTA_NAT_MAX (__NFTA_NAT_MAX - 1) + +#endif /* _LINUX_NF_TABLES_H */ diff --git a/include/linux/netfilter/nf_tables_compat.h b/include/linux/netfilter/nf_tables_compat.h new file mode 100644 index 00000000..36fb81d8 --- /dev/null +++ b/include/linux/netfilter/nf_tables_compat.h @@ -0,0 +1,20 @@ +#ifndef _NFT_COMPAT_NFNETLINK_H_ +#define _NFT_COMPAT_NFNETLINK_H_ + +#define NFT_COMPAT_NAME_MAX 32 + +enum { + NFNL_MSG_COMPAT_GET, + NFNL_MSG_COMPAT_MAX +}; + +enum { + NFTA_COMPAT_UNSPEC = 0, + NFTA_COMPAT_NAME, + NFTA_COMPAT_REV, + NFTA_COMPAT_TYPE, + __NFTA_COMPAT_MAX, +}; +#define NFTA_COMPAT_MAX (__NFTA_COMPAT_MAX - 1) + +#endif diff --git a/include/linux/netfilter/nfnetlink.h b/include/linux/netfilter/nfnetlink.h new file mode 100644 index 00000000..06eea26b --- /dev/null +++ b/include/linux/netfilter/nfnetlink.h @@ -0,0 +1,64 @@ +#ifndef _NFNETLINK_H +#define _NFNETLINK_H +#include <linux/types.h> +#include <linux/netfilter/nfnetlink_compat.h> + +enum nfnetlink_groups { + NFNLGRP_NONE, +#define NFNLGRP_NONE NFNLGRP_NONE + NFNLGRP_CONNTRACK_NEW, +#define NFNLGRP_CONNTRACK_NEW NFNLGRP_CONNTRACK_NEW + NFNLGRP_CONNTRACK_UPDATE, +#define NFNLGRP_CONNTRACK_UPDATE NFNLGRP_CONNTRACK_UPDATE + NFNLGRP_CONNTRACK_DESTROY, +#define NFNLGRP_CONNTRACK_DESTROY NFNLGRP_CONNTRACK_DESTROY + NFNLGRP_CONNTRACK_EXP_NEW, +#define NFNLGRP_CONNTRACK_EXP_NEW NFNLGRP_CONNTRACK_EXP_NEW + NFNLGRP_CONNTRACK_EXP_UPDATE, +#define NFNLGRP_CONNTRACK_EXP_UPDATE NFNLGRP_CONNTRACK_EXP_UPDATE + NFNLGRP_CONNTRACK_EXP_DESTROY, +#define NFNLGRP_CONNTRACK_EXP_DESTROY NFNLGRP_CONNTRACK_EXP_DESTROY + NFNLGRP_NFTABLES, +#define NFNLGRP_NFTABLES NFNLGRP_NFTABLES + __NFNLGRP_MAX, +}; +#define NFNLGRP_MAX (__NFNLGRP_MAX - 1) + +/* General form of address family dependent message. + */ +struct nfgenmsg { + __u8 nfgen_family; /* AF_xxx */ + __u8 version; /* nfnetlink version */ + __be16 res_id; /* resource id */ +}; + +#define NFNETLINK_V0 0 + +/* netfilter netlink message types are split in two pieces: + * 8 bit subsystem, 8bit operation. + */ + +#define NFNL_SUBSYS_ID(x) ((x & 0xff00) >> 8) +#define NFNL_MSG_TYPE(x) (x & 0x00ff) + +/* No enum here, otherwise __stringify() trick of MODULE_ALIAS_NFNL_SUBSYS() + * won't work anymore */ +#define NFNL_SUBSYS_NONE 0 +#define NFNL_SUBSYS_CTNETLINK 1 +#define NFNL_SUBSYS_CTNETLINK_EXP 2 +#define NFNL_SUBSYS_QUEUE 3 +#define NFNL_SUBSYS_ULOG 4 +#define NFNL_SUBSYS_OSF 5 +#define NFNL_SUBSYS_IPSET 6 +#define NFNL_SUBSYS_ACCT 7 +#define NFNL_SUBSYS_CTNETLINK_TIMEOUT 8 +#define NFNL_SUBSYS_CTHELPER 9 +#define NFNL_SUBSYS_NFTABLES 10 +#define NFNL_SUBSYS_NFT_COMPAT 11 +#define NFNL_SUBSYS_COUNT 12 + +/* Reserved control nfnetlink messages */ +#define NFNL_MSG_BATCH_BEGIN NLMSG_MIN_TYPE +#define NFNL_MSG_BATCH_END NLMSG_MIN_TYPE+1 + +#endif /* _NFNETLINK_H */ diff --git a/include/linux/netfilter_arp.h b/include/linux/netfilter_arp.h new file mode 100644 index 00000000..92bc6ddc --- /dev/null +++ b/include/linux/netfilter_arp.h @@ -0,0 +1,19 @@ +#ifndef __LINUX_ARP_NETFILTER_H +#define __LINUX_ARP_NETFILTER_H + +/* ARP-specific defines for netfilter. + * (C)2002 Rusty Russell IBM -- This code is GPL. + */ + +#include <linux/netfilter.h> + +/* There is no PF_ARP. */ +#define NF_ARP 0 + +/* ARP Hooks */ +#define NF_ARP_IN 0 +#define NF_ARP_OUT 1 +#define NF_ARP_FORWARD 2 +#define NF_ARP_NUMHOOKS 3 + +#endif /* __LINUX_ARP_NETFILTER_H */ diff --git a/include/linux/netfilter_arp/arp_tables.h b/include/linux/netfilter_arp/arp_tables.h new file mode 100644 index 00000000..bb1ec648 --- /dev/null +++ b/include/linux/netfilter_arp/arp_tables.h @@ -0,0 +1,204 @@ +/* + * Format of an ARP firewall descriptor + * + * src, tgt, src_mask, tgt_mask, arpop, arpop_mask are always stored in + * network byte order. + * flags are stored in host byte order (of course). + */ + +#ifndef _ARPTABLES_H +#define _ARPTABLES_H + +#include <linux/types.h> + +#include <linux/netfilter_arp.h> + +#include <linux/netfilter/x_tables.h> + +#define ARPT_FUNCTION_MAXNAMELEN XT_FUNCTION_MAXNAMELEN +#define ARPT_TABLE_MAXNAMELEN XT_TABLE_MAXNAMELEN +#define arpt_entry_target xt_entry_target +#define arpt_standard_target xt_standard_target +#define arpt_error_target xt_error_target +#define ARPT_CONTINUE XT_CONTINUE +#define ARPT_RETURN XT_RETURN +#define arpt_counters_info xt_counters_info +#define arpt_counters xt_counters +#define ARPT_STANDARD_TARGET XT_STANDARD_TARGET +#define ARPT_ERROR_TARGET XT_ERROR_TARGET +#define ARPT_ENTRY_ITERATE(entries, size, fn, args...) \ + XT_ENTRY_ITERATE(struct arpt_entry, entries, size, fn, ## args) + +#define ARPT_DEV_ADDR_LEN_MAX 16 + +struct arpt_devaddr_info { + char addr[ARPT_DEV_ADDR_LEN_MAX]; + char mask[ARPT_DEV_ADDR_LEN_MAX]; +}; + +/* Yes, Virginia, you have to zero the padding. */ +struct arpt_arp { + /* Source and target IP addr */ + struct in_addr src, tgt; + /* Mask for src and target IP addr */ + struct in_addr smsk, tmsk; + + /* Device hw address length, src+target device addresses */ + __u8 arhln, arhln_mask; + struct arpt_devaddr_info src_devaddr; + struct arpt_devaddr_info tgt_devaddr; + + /* ARP operation code. */ + __be16 arpop, arpop_mask; + + /* ARP hardware address and protocol address format. */ + __be16 arhrd, arhrd_mask; + __be16 arpro, arpro_mask; + + /* The protocol address length is only accepted if it is 4 + * so there is no use in offering a way to do filtering on it. + */ + + char iniface[IFNAMSIZ], outiface[IFNAMSIZ]; + unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ]; + + /* Flags word */ + __u8 flags; + /* Inverse flags */ + __u16 invflags; +}; + +/* Values for "flag" field in struct arpt_ip (general arp structure). + * No flags defined yet. + */ +#define ARPT_F_MASK 0x00 /* All possible flag bits mask. */ + +/* Values for "inv" field in struct arpt_arp. */ +#define ARPT_INV_VIA_IN 0x0001 /* Invert the sense of IN IFACE. */ +#define ARPT_INV_VIA_OUT 0x0002 /* Invert the sense of OUT IFACE */ +#define ARPT_INV_SRCIP 0x0004 /* Invert the sense of SRC IP. */ +#define ARPT_INV_TGTIP 0x0008 /* Invert the sense of TGT IP. */ +#define ARPT_INV_SRCDEVADDR 0x0010 /* Invert the sense of SRC DEV ADDR. */ +#define ARPT_INV_TGTDEVADDR 0x0020 /* Invert the sense of TGT DEV ADDR. */ +#define ARPT_INV_ARPOP 0x0040 /* Invert the sense of ARP OP. */ +#define ARPT_INV_ARPHRD 0x0080 /* Invert the sense of ARP HRD. */ +#define ARPT_INV_ARPPRO 0x0100 /* Invert the sense of ARP PRO. */ +#define ARPT_INV_ARPHLN 0x0200 /* Invert the sense of ARP HLN. */ +#define ARPT_INV_MASK 0x03FF /* All possible flag bits mask. */ + +/* This structure defines each of the firewall rules. Consists of 3 + parts which are 1) general ARP header stuff 2) match specific + stuff 3) the target to perform if the rule matches */ +struct arpt_entry +{ + struct arpt_arp arp; + + /* Size of arpt_entry + matches */ + __u16 target_offset; + /* Size of arpt_entry + matches + target */ + __u16 next_offset; + + /* Back pointer */ + unsigned int comefrom; + + /* Packet and byte counters. */ + struct xt_counters counters; + + /* The matches (if any), then the target. */ + unsigned char elems[0]; +}; + +/* + * New IP firewall options for [gs]etsockopt at the RAW IP level. + * Unlike BSD Linux inherits IP options so you don't have to use a raw + * socket for this. Instead we check rights in the calls. + * + * ATTENTION: check linux/in.h before adding new number here. + */ +#define ARPT_BASE_CTL 96 + +#define ARPT_SO_SET_REPLACE (ARPT_BASE_CTL) +#define ARPT_SO_SET_ADD_COUNTERS (ARPT_BASE_CTL + 1) +#define ARPT_SO_SET_MAX ARPT_SO_SET_ADD_COUNTERS + +#define ARPT_SO_GET_INFO (ARPT_BASE_CTL) +#define ARPT_SO_GET_ENTRIES (ARPT_BASE_CTL + 1) +/* #define ARPT_SO_GET_REVISION_MATCH (APRT_BASE_CTL + 2) */ +#define ARPT_SO_GET_REVISION_TARGET (ARPT_BASE_CTL + 3) +#define ARPT_SO_GET_MAX (ARPT_SO_GET_REVISION_TARGET) + +/* The argument to ARPT_SO_GET_INFO */ +struct arpt_getinfo { + /* Which table: caller fills this in. */ + char name[XT_TABLE_MAXNAMELEN]; + + /* Kernel fills these in. */ + /* Which hook entry points are valid: bitmask */ + unsigned int valid_hooks; + + /* Hook entry points: one per netfilter hook. */ + unsigned int hook_entry[NF_ARP_NUMHOOKS]; + + /* Underflow points. */ + unsigned int underflow[NF_ARP_NUMHOOKS]; + + /* Number of entries */ + unsigned int num_entries; + + /* Size of entries. */ + unsigned int size; +}; + +/* The argument to ARPT_SO_SET_REPLACE. */ +struct arpt_replace { + /* Which table. */ + char name[XT_TABLE_MAXNAMELEN]; + + /* Which hook entry points are valid: bitmask. You can't + change this. */ + unsigned int valid_hooks; + + /* Number of entries */ + unsigned int num_entries; + + /* Total size of new entries */ + unsigned int size; + + /* Hook entry points. */ + unsigned int hook_entry[NF_ARP_NUMHOOKS]; + + /* Underflow points. */ + unsigned int underflow[NF_ARP_NUMHOOKS]; + + /* Information about old entries: */ + /* Number of counters (must be equal to current number of entries). */ + unsigned int num_counters; + /* The old entries' counters. */ + struct xt_counters *counters; + + /* The entries (hang off end: not really an array). */ + struct arpt_entry entries[0]; +}; + +/* The argument to ARPT_SO_GET_ENTRIES. */ +struct arpt_get_entries { + /* Which table: user fills this in. */ + char name[XT_TABLE_MAXNAMELEN]; + + /* User fills this in: total entry size. */ + unsigned int size; + + /* The entries. */ + struct arpt_entry entrytable[0]; +}; + +/* Helper functions */ +static __inline__ struct xt_entry_target *arpt_get_target(struct arpt_entry *e) +{ + return (void *)e + e->target_offset; +} + +/* + * Main firewall chains definitions and global var's definitions. + */ +#endif /* _ARPTABLES_H */ diff --git a/include/linux/netfilter_arp/arpt_mangle.h b/include/linux/netfilter_arp/arpt_mangle.h new file mode 100644 index 00000000..250f5029 --- /dev/null +++ b/include/linux/netfilter_arp/arpt_mangle.h @@ -0,0 +1,26 @@ +#ifndef _ARPT_MANGLE_H +#define _ARPT_MANGLE_H +#include <linux/netfilter_arp/arp_tables.h> + +#define ARPT_MANGLE_ADDR_LEN_MAX sizeof(struct in_addr) +struct arpt_mangle +{ + char src_devaddr[ARPT_DEV_ADDR_LEN_MAX]; + char tgt_devaddr[ARPT_DEV_ADDR_LEN_MAX]; + union { + struct in_addr src_ip; + } u_s; + union { + struct in_addr tgt_ip; + } u_t; + u_int8_t flags; + int target; +}; + +#define ARPT_MANGLE_SDEV 0x01 +#define ARPT_MANGLE_TDEV 0x02 +#define ARPT_MANGLE_SIP 0x04 +#define ARPT_MANGLE_TIP 0x08 +#define ARPT_MANGLE_MASK 0x0f + +#endif /* _ARPT_MANGLE_H */ diff --git a/include/xtables.h b/include/xtables.h index 02172670..bad11a89 100644 --- a/include/xtables.h +++ b/include/xtables.h @@ -401,6 +401,7 @@ struct xtables_globals struct option *orig_opts; struct option *opts; void (*exit_err)(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3))); + int (*compat_rev)(const char *name, uint8_t rev, int opt); }; #define XT_GETOPT_TABLEEND {.name = NULL, .has_arg = false} @@ -432,6 +433,8 @@ extern struct xtables_match *xtables_find_match(const char *name, enum xtables_tryload, struct xtables_rule_match **match); extern struct xtables_target *xtables_find_target(const char *name, enum xtables_tryload); +extern int xtables_compatible_revision(const char *name, uint8_t revision, + int opt); extern void xtables_rule_matches_free(struct xtables_rule_match **matches); diff --git a/iptables/.gitignore b/iptables/.gitignore index 31baf7d7..6c0ade1a 100644 --- a/iptables/.gitignore +++ b/iptables/.gitignore @@ -11,5 +11,8 @@ /iptables-static /iptables-xml /xtables-multi +/xtables-config-parser.c +/xtables-config-parser.h +/xtables-config-syntax.c /xtables.pc diff --git a/iptables/Makefile.am b/iptables/Makefile.am index a4246eb3..41bca7c7 100644 --- a/iptables/Makefile.am +++ b/iptables/Makefile.am @@ -1,7 +1,8 @@ # -*- Makefile -*- AM_CFLAGS = ${regular_CFLAGS} -AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS} +AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS} ${libmnl_CFLAGS} ${libnftnl_CFLAGS} +AM_YFLAGS = -d xtables_multi_SOURCES = xtables-multi.c iptables-xml.c xtables_multi_CFLAGS = ${AM_CFLAGS} @@ -24,11 +25,37 @@ endif xtables_multi_SOURCES += xshared.c xtables_multi_LDADD += ../libxtables/libxtables.la -lm +# nftables compatibility layer +if ENABLE_NFTABLES +xtables_compat_multi_SOURCES = xtables-compat-multi.c iptables-xml.c +xtables_compat_multi_CFLAGS = ${AM_CFLAGS} +xtables_compat_multi_LDADD = ../extensions/libext.a +if ENABLE_STATIC +xtables_compat_multi_CFLAGS += -DALL_INCLUSIVE +endif +xtables_compat_multi_CFLAGS += -DENABLE_NFTABLES -DENABLE_IPV4 -DENABLE_IPV6 +xtables_compat_multi_SOURCES += xtables-config-parser.y xtables-config-syntax.l +xtables_compat_multi_SOURCES += xtables-save.c xtables-restore.c \ + xtables-standalone.c xtables.c nft.c \ + nft-shared.c nft-ipv4.c nft-ipv6.c nft-arp.c \ + xtables-config.c xtables-events.c \ + xtables-arp-standalone.c xtables-arp.c +xtables_compat_multi_LDADD += ${libmnl_LIBS} ${libnftnl_LIBS} +# yacc and lex generate dirty code +xtables_compat_multi-xtables-config-parser.o xtables_compat_multi-xtables-config-syntax.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -Wno-implicit-function-declaration -Wno-nested-externs -Wno-undef -Wno-redundant-decls +xtables_compat_multi_SOURCES += xshared.c +xtables_compat_multi_LDADD += ../libxtables/libxtables.la -lm +endif + sbin_PROGRAMS = xtables-multi +if ENABLE_NFTABLES +sbin_PROGRAMS += xtables-compat-multi +endif man_MANS = iptables.8 iptables-restore.8 iptables-save.8 \ iptables-xml.1 ip6tables.8 ip6tables-restore.8 \ ip6tables-save.8 iptables-extensions.8 -CLEANFILES = iptables.8 +CLEANFILES = iptables.8 \ + xtables-config-parser.c xtables-config-syntax.c vx_bin_links = iptables-xml if ENABLE_IPV4 @@ -37,6 +64,11 @@ endif if ENABLE_IPV6 v6_sbin_links = ip6tables ip6tables-restore ip6tables-save endif +if ENABLE_NFTABLES +x_sbin_links = iptables-compat iptables-compat-restore iptables-compat-save \ + ip6tables-compat ip6tables-compat-restore ip6tables-compat-save \ + arptables-compat xtables-config xtables-events +endif iptables-extensions.8: iptables-extensions.8.tmpl ../extensions/matches.man ../extensions/targets.man ${AM_VERBOSE_GEN} sed \ @@ -52,3 +84,4 @@ install-exec-hook: for i in ${vx_bin_links}; do ${LN_S} -f "${sbindir}/xtables-multi" "${DESTDIR}${bindir}/$$i"; done; for i in ${v4_sbin_links}; do ${LN_S} -f xtables-multi "${DESTDIR}${sbindir}/$$i"; done; for i in ${v6_sbin_links}; do ${LN_S} -f xtables-multi "${DESTDIR}${sbindir}/$$i"; done; + for i in ${x_sbin_links}; do ${LN_S} -f xtables-compat-multi "${DESTDIR}${sbindir}/$$i"; done; diff --git a/iptables/ip6tables.c b/iptables/ip6tables.c index a5199d5e..2ebfd6c0 100644 --- a/iptables/ip6tables.c +++ b/iptables/ip6tables.c @@ -121,6 +121,7 @@ struct xtables_globals ip6tables_globals = { .program_version = IPTABLES_VERSION, .orig_opts = original_opts, .exit_err = ip6tables_exit_error, + .compat_rev = xtables_compatible_revision, }; /* Table of legal combinations of commands and options. If any of the diff --git a/iptables/iptables.c b/iptables/iptables.c index 5cd2596e..471bff06 100644 --- a/iptables/iptables.c +++ b/iptables/iptables.c @@ -120,6 +120,7 @@ struct xtables_globals iptables_globals = { .program_version = IPTABLES_VERSION, .orig_opts = original_opts, .exit_err = iptables_exit_error, + .compat_rev = xtables_compatible_revision, }; /* Table of legal combinations of commands and options. If any of the diff --git a/iptables/nft-arp.c b/iptables/nft-arp.c new file mode 100644 index 00000000..17101369 --- /dev/null +++ b/iptables/nft-arp.c @@ -0,0 +1,649 @@ +/* + * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org> + * (C) 2013 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. + * + * This code has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <netdb.h> +#include <net/if_arp.h> + +#include <xtables.h> +#include <libiptc/libxtc.h> +#include <net/if_arp.h> +#include <netinet/if_ether.h> + +#include <linux/netfilter_arp/arp_tables.h> +#include <linux/netfilter/nf_tables.h> + +#include "nft-shared.h" +#include "nft.h" + +/* a few names */ +char *opcodes[] = +{ + "Request", + "Reply", + "Request_Reverse", + "Reply_Reverse", + "DRARP_Request", + "DRARP_Reply", + "DRARP_Error", + "InARP_Request", + "ARP_NAK", +}; + +static char * +addr_to_dotted(const struct in_addr *addrp) +{ + static char buf[20]; + const unsigned char *bytep; + + bytep = (const unsigned char *) &(addrp->s_addr); + sprintf(buf, "%d.%d.%d.%d", bytep[0], bytep[1], bytep[2], bytep[3]); + return buf; +} + +static char * +addr_to_host(const struct in_addr *addr) +{ + struct hostent *host; + + if ((host = gethostbyaddr((char *) addr, + sizeof(struct in_addr), AF_INET)) != NULL) + return (char *) host->h_name; + + return (char *) NULL; +} + +static char * +addr_to_network(const struct in_addr *addr) +{ + struct netent *net; + + if ((net = getnetbyaddr((long) ntohl(addr->s_addr), AF_INET)) != NULL) + return (char *) net->n_name; + + return (char *) NULL; +} + +static char * +addr_to_anyname(const struct in_addr *addr) +{ + char *name; + + if ((name = addr_to_host(addr)) != NULL || + (name = addr_to_network(addr)) != NULL) + return name; + + return addr_to_dotted(addr); +} + +static char * +mask_to_dotted(const struct in_addr *mask) +{ + int i; + static char buf[20]; + u_int32_t maskaddr, bits; + + maskaddr = ntohl(mask->s_addr); + + if (maskaddr == 0xFFFFFFFFL) + /* we don't want to see "/32" */ + return ""; + + i = 32; + bits = 0xFFFFFFFEL; + while (--i >= 0 && maskaddr != bits) + bits <<= 1; + if (i >= 0) + sprintf(buf, "/%d", i); + else + /* mask was not a decent combination of 1's and 0's */ + sprintf(buf, "/%s", addr_to_dotted(mask)); + + return buf; +} + +static void print_mac(const unsigned char *mac, int l) +{ + int j; + + for (j = 0; j < l; j++) + printf("%02x%s", mac[j], + (j==l-1) ? "" : ":"); +} + +static void print_mac_and_mask(const unsigned char *mac, const unsigned char *mask, int l) +{ + int i; + + print_mac(mac, l); + for (i = 0; i < l ; i++) + if (mask[i] != 255) + break; + if (i == l) + return; + printf("/"); + print_mac(mask, l); +} + +static uint8_t arpt_to_ipt_flags(uint16_t invflags) +{ + uint8_t result = 0; + + if (invflags & ARPT_INV_VIA_IN) + result |= IPT_INV_VIA_IN; + + if (invflags & ARPT_INV_VIA_OUT) + result |= IPT_INV_VIA_OUT; + + if (invflags & ARPT_INV_SRCIP) + result |= IPT_INV_SRCIP; + + if (invflags & ARPT_INV_TGTIP) + result |= IPT_INV_DSTIP; + + if (invflags & ARPT_INV_ARPPRO) + result |= IPT_INV_PROTO; + + if (invflags & ARPT_INV_MASK) + result |= IPT_INV_MASK; + + return result; +} + +static int nft_arp_add(struct nft_rule *r, void *data) +{ + struct arpt_entry *fw = data; + uint8_t flags = arpt_to_ipt_flags(fw->arp.invflags); + struct xt_entry_target *t; + char *targname; + int ret; + + if (fw->arp.iniface[0] != '\0') + add_iniface(r, fw->arp.iniface, flags); + + if (fw->arp.outiface[0] != '\0') + add_outiface(r, fw->arp.outiface, flags); + + if (fw->arp.arhrd != 0) { + add_payload(r, offsetof(struct arphdr, ar_hrd), 2); + add_cmp_u16(r, fw->arp.arhrd, NFT_CMP_EQ); + } + + if (fw->arp.arpro != 0) { + add_payload(r, offsetof(struct arphdr, ar_pro), 2); + add_cmp_u16(r, fw->arp.arpro, NFT_CMP_EQ); + } + + if (fw->arp.arhln != 0) + add_proto(r, offsetof(struct arphdr, ar_hln), 1, + fw->arp.arhln, flags); + + add_proto(r, offsetof(struct arphdr, ar_pln), 1, 4, 0); + + if (fw->arp.arpop != 0) { + add_payload(r, offsetof(struct arphdr, ar_op), 2); + add_cmp_u16(r, fw->arp.arpop, NFT_CMP_EQ); + } + + if (fw->arp.src_devaddr.addr[0] != '\0') { + add_payload(r, sizeof(struct arphdr), fw->arp.arhln); + add_cmp_ptr(r, NFT_CMP_EQ, fw->arp.src_devaddr.addr, fw->arp.arhln); + } + + if (fw->arp.src.s_addr != 0) + add_addr(r, sizeof(struct arphdr) + fw->arp.arhln, + &fw->arp.src.s_addr, 4, flags); + + if (fw->arp.tgt_devaddr.addr[0] != '\0') { + add_payload(r, sizeof(struct arphdr) + fw->arp.arhln + 4, fw->arp.arhln); + add_cmp_ptr(r, NFT_CMP_EQ, fw->arp.tgt_devaddr.addr, fw->arp.arhln); + } + + if (fw->arp.tgt.s_addr != 0) + add_addr(r, sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr), + &fw->arp.tgt.s_addr, 4, flags); + + /* Counters need to me added before the target, otherwise they are + * increased for each rule because of the way nf_tables works. + */ + if (add_counters(r, fw->counters.pcnt, fw->counters.bcnt) < 0) + return -1; + + t = nft_arp_get_target(fw); + targname = t->u.user.name; + + /* Standard target? */ + if (strcmp(targname, XTC_LABEL_ACCEPT) == 0) + ret = add_verdict(r, NF_ACCEPT); + else if (strcmp(targname, XTC_LABEL_DROP) == 0) + ret = add_verdict(r, NF_DROP); + else if (strcmp(targname, XTC_LABEL_RETURN) == 0) + ret = add_verdict(r, NFT_RETURN); + else if (xtables_find_target(targname, XTF_TRY_LOAD) != NULL) + ret = add_target(r, t); + else + ret = add_jumpto(r, targname, NFT_JUMP); + + return ret; +} + +static uint16_t ipt_to_arpt_flags(uint8_t invflags) +{ + uint16_t result = 0; + + if (invflags & IPT_INV_VIA_IN) + result |= ARPT_INV_VIA_IN; + + if (invflags & IPT_INV_VIA_OUT) + result |= ARPT_INV_VIA_OUT; + + if (invflags & IPT_INV_SRCIP) + result |= ARPT_INV_SRCIP; + + if (invflags & IPT_INV_DSTIP) + result |= ARPT_INV_TGTIP; + + if (invflags & IPT_INV_PROTO) + result |= ARPT_INV_ARPPRO; + + if (invflags & IPT_INV_MASK) + result |= ARPT_INV_MASK; + + return result; +} + +static void nft_arp_parse_meta(struct nft_rule_expr *e, uint8_t key, + void *data) +{ + struct arpt_entry *fw = data; + uint8_t flags = 0; + + parse_meta(e, key, fw->arp.iniface, fw->arp.iniface_mask, + fw->arp.outiface, fw->arp.outiface_mask, + &flags); + + fw->arp.invflags |= ipt_to_arpt_flags(flags); +} + +static void nft_arp_parse_target(struct xtables_target *target, void *data) +{ + struct arpt_entry *fw = data; + struct xt_entry_target **t; + + fw->target_offset = offsetof(struct arpt_entry, elems); + fw->next_offset = fw->target_offset + target->t->u.target_size; + + t = (void *) &fw->elems; + *t = target->t; +} + +static void nft_arp_parse_immediate(const char *jumpto, bool nft_goto, + void *data) +{ + struct xtables_target *target; + size_t size; + + target = xtables_find_target(XT_STANDARD_TARGET, + XTF_LOAD_MUST_SUCCEED); + + size = XT_ALIGN(sizeof(struct xt_entry_target)) + target->size; + + target->t = xtables_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + target->t->u.user.revision = target->revision; + + nft_arp_parse_target(target, data); +} + +static void nft_arp_parse_payload(struct nft_rule_expr_iter *iter, + uint32_t offset, void *data) +{ + struct arpt_entry *fw = data; + struct in_addr addr; + unsigned short int ar_hrd, ar_pro, ar_op, ar_hln; + bool inv; + + switch (offset) { + case offsetof(struct arphdr, ar_hrd): + get_cmp_data(iter, &ar_hrd, sizeof(ar_hrd), &inv); + fw->arp.arhrd = ar_hrd; + fw->arp.arhrd_mask = 0xffff; + if (inv) + fw->arp.invflags |= ARPT_INV_ARPHRD; + break; + case offsetof(struct arphdr, ar_pro): + get_cmp_data(iter, &ar_pro, sizeof(ar_pro), &inv); + fw->arp.arpro = ar_pro; + fw->arp.arpro_mask = 0xffff; + if (inv) + fw->arp.invflags |= ARPT_INV_ARPPRO; + break; + case offsetof(struct arphdr, ar_op): + get_cmp_data(iter, &ar_op, sizeof(ar_op), &inv); + fw->arp.arpop = ar_op; + fw->arp.arpop_mask = 0xffff; + if (inv) + fw->arp.invflags |= ARPT_INV_ARPOP; + break; + case offsetof(struct arphdr, ar_hln): + get_cmp_data(iter, &ar_hln, sizeof(ar_op), &inv); + fw->arp.arhln = ar_hln; + fw->arp.arhln_mask = 0xff; + if (inv) + fw->arp.invflags |= ARPT_INV_ARPOP; + break; + default: + if (!fw->arp.arhln) + break; + + if (offset == sizeof(struct arphdr) + fw->arp.arhln) { + get_cmp_data(iter, &addr, sizeof(addr), &inv); + fw->arp.src.s_addr = addr.s_addr; + fw->arp.smsk.s_addr = 0xffffffff; + if (inv) + fw->arp.invflags |= ARPT_INV_SRCIP; + } else if (offset == sizeof(struct arphdr) + fw->arp.arhln + sizeof(struct in_addr)) { + get_cmp_data(iter, &addr, sizeof(addr), &inv); + fw->arp.tgt.s_addr = addr.s_addr; + fw->arp.tmsk.s_addr = 0xffffffff; + if (inv) + fw->arp.invflags |= ARPT_INV_TGTIP; + } + break; + } +} + +void nft_rule_to_arpt_entry(struct nft_rule *r, struct arpt_entry *fw) +{ + struct nft_rule_expr_iter *iter; + struct nft_rule_expr *expr; + int family = nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY); + + iter = nft_rule_expr_iter_create(r); + if (iter == NULL) + return; + + expr = nft_rule_expr_iter_next(iter); + while (expr != NULL) { + const char *name = + nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME); + + if (strcmp(name, "counter") == 0) + nft_parse_counter(expr, iter, &fw->counters); + else if (strcmp(name, "payload") == 0) + nft_parse_payload(expr, iter, family, fw); + else if (strcmp(name, "meta") == 0) + nft_parse_meta(expr, iter, family, fw); + else if (strcmp(name, "immediate") == 0) + nft_parse_immediate(expr, iter, family, fw); + else if (strcmp(name, "target") == 0) + nft_parse_target(expr, iter, family, fw); + + expr = nft_rule_expr_iter_next(iter); + } + + nft_rule_expr_iter_destroy(iter); +} + +static void +nft_arp_print_firewall(struct nft_rule *r, unsigned int num, + unsigned int format) +{ + struct arpt_entry fw = {}; + const char *targname; + struct xtables_target *target = NULL; + const struct xt_entry_target *t; + char buf[BUFSIZ]; + int i; + char iface[IFNAMSIZ+2]; + int print_iface = 0; + + nft_rule_to_arpt_entry(r, &fw); + + if (format & FMT_LINENUMBERS) + printf("%u ", num); + + if (fw.target_offset) { + t = nft_arp_get_target(&fw); + targname = t->u.user.name; + target = xtables_find_target(targname, XTF_TRY_LOAD); + if (!(format & FMT_NOTARGET)) + printf("-j %s ", targname); + } + + iface[0] = '\0'; + + if (fw.arp.iniface[0] != '\0') { + strcat(iface, fw.arp.iniface); + print_iface = 1; + } + else if (format & FMT_VIA) { + print_iface = 1; + if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + } + if (print_iface) + printf("%s-i %s ", fw.arp.invflags & ARPT_INV_VIA_IN ? "! ": "", iface); + + print_iface = 0; + iface[0] = '\0'; + + if (fw.arp.outiface[0] != '\0') { + strcat(iface, fw.arp.outiface); + print_iface = 1; + } + else if (format & FMT_VIA) { + print_iface = 1; + if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + } + if (print_iface) + printf("%s-o %s ", fw.arp.invflags & ARPT_INV_VIA_OUT ? "! " : "", iface); + + if (fw.arp.smsk.s_addr != 0L) { + printf("%s", fw.arp.invflags & ARPT_INV_SRCIP + ? "! " : ""); + if (format & FMT_NUMERIC) + sprintf(buf, "%s", addr_to_dotted(&(fw.arp.src))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw.arp.src))); + strncat(buf, mask_to_dotted(&(fw.arp.smsk)), + sizeof(buf) - strlen(buf) - 1); + printf("-s %s ", buf); + } + + for (i = 0; i < ARPT_DEV_ADDR_LEN_MAX; i++) + if (fw.arp.src_devaddr.mask[i] != 0) + break; + if (i == ARPT_DEV_ADDR_LEN_MAX) + goto after_devsrc; + printf("%s", fw.arp.invflags & ARPT_INV_SRCDEVADDR + ? "! " : ""); + printf("--src-mac "); + print_mac_and_mask((unsigned char *)fw.arp.src_devaddr.addr, + (unsigned char *)fw.arp.src_devaddr.mask, ETH_ALEN); + printf(" "); +after_devsrc: + + if (fw.arp.tmsk.s_addr != 0L) { + printf("%s",fw.arp.invflags & ARPT_INV_TGTIP + ? "! " : ""); + if (format & FMT_NUMERIC) + sprintf(buf, "%s", addr_to_dotted(&(fw.arp.tgt))); + else + sprintf(buf, "%s", addr_to_anyname(&(fw.arp.tgt))); + strncat(buf, mask_to_dotted(&(fw.arp.tmsk)), + sizeof(buf) - strlen(buf) - 1); + printf("-d %s ", buf); + } + + for (i = 0; i <ARPT_DEV_ADDR_LEN_MAX; i++) + if (fw.arp.tgt_devaddr.mask[i] != 0) + break; + if (i == ARPT_DEV_ADDR_LEN_MAX) + goto after_devdst; + printf("%s",fw.arp.invflags & ARPT_INV_TGTDEVADDR + ? "! " : ""); + printf("--dst-mac "); + print_mac_and_mask((unsigned char *)fw.arp.tgt_devaddr.addr, + (unsigned char *)fw.arp.tgt_devaddr.mask, ETH_ALEN); + printf(" "); +after_devdst: + + if (fw.arp.arhln_mask != 0) { + printf("%s",fw.arp.invflags & ARPT_INV_ARPHLN + ? "! " : ""); + printf("--h-length %d", fw.arp.arhln); + if (fw.arp.arhln_mask != 255) + printf("/%d", fw.arp.arhln_mask); + printf(" "); + } + + if (fw.arp.arpop_mask != 0) { + int tmp = ntohs(fw.arp.arpop); + + printf("%s",fw.arp.invflags & ARPT_INV_ARPOP + ? "! " : ""); + if (tmp <= NUMOPCODES && !(format & FMT_NUMERIC)) + printf("--opcode %s", opcodes[tmp-1]); + else + printf("--opcode %d", tmp); + if (fw.arp.arpop_mask != 65535) + printf("/%d", ntohs(fw.arp.arpop_mask)); + printf(" "); + } + + if (fw.arp.arhrd_mask != 0) { + uint16_t tmp = ntohs(fw.arp.arhrd); + + printf("%s", fw.arp.invflags & ARPT_INV_ARPHRD + ? "! " : ""); + if (tmp == 1 && !(format & FMT_NUMERIC)) + printf("--h-type %s", "Ethernet"); + else + printf("--h-type %u", tmp); + if (fw.arp.arhrd_mask != 65535) + printf("/%d", ntohs(fw.arp.arhrd_mask)); + printf(" "); + } + + if (fw.arp.arpro_mask != 0) { + int tmp = ntohs(fw.arp.arpro); + + printf("%s", fw.arp.invflags & ARPT_INV_ARPPRO + ? "! " : ""); + if (tmp == 0x0800 && !(format & FMT_NUMERIC)) + printf("--proto-type %s", "IPv4"); + else + printf("--proto-type 0x%x", tmp); + if (fw.arp.arpro_mask != 65535) + printf("/%x", ntohs(fw.arp.arpro_mask)); + printf(" "); + } + + if (target) { + if (target->print) + /* Print the target information. */ + target->print(&fw.arp, t, format & FMT_NUMERIC); + } + + if (!(format & FMT_NOCOUNTS)) { + printf(", pcnt="); + xtables_print_num(fw.counters.pcnt, format); + printf("-- bcnt="); + xtables_print_num(fw.counters.bcnt, format); + } + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static bool nft_arp_is_same(const void *data_a, + const void *data_b) +{ + const struct arpt_entry *a = data_a; + const struct arpt_entry *b = data_b; + + if (a->arp.src.s_addr != b->arp.src.s_addr + || a->arp.tgt.s_addr != b->arp.tgt.s_addr + || a->arp.smsk.s_addr != b->arp.tmsk.s_addr + || a->arp.arpro != b->arp.arpro + || a->arp.flags != b->arp.flags + || a->arp.invflags != b->arp.invflags) { + DEBUGP("different src/dst/proto/flags/invflags\n"); + return false; + } + + return is_same_interfaces(a->arp.src_devaddr.addr, + a->arp.tgt_devaddr.addr, + (unsigned char*)a->arp.src_devaddr.mask, + (unsigned char*)a->arp.tgt_devaddr.mask, + b->arp.src_devaddr.addr, + a->arp.tgt_devaddr.addr, + (unsigned char*)b->arp.src_devaddr.mask, + (unsigned char*)b->arp.tgt_devaddr.mask); +} + +static bool nft_arp_rule_find(struct nft_family_ops *ops, struct nft_rule *r, + void *data) +{ + struct arpt_entry *fw = data; + struct xt_entry_target *t_fw, *t_this; + char *targname_fw, *targname_this; + struct arpt_entry this = {}; + + /* Delete by matching rule case */ + nft_rule_to_arpt_entry(r, &this); + + if (!ops->is_same(fw, &this)) + return false; + + t_fw = nft_arp_get_target(fw); + t_this = nft_arp_get_target(&this); + + targname_fw = t_fw->u.user.name; + targname_this = t_this->u.user.name; + + if (!strcmp(targname_fw, targname_this) && + (!strcmp(targname_fw, "mangle") || + !strcmp(targname_fw, "CLASSIFY"))) { + if (memcmp(t_fw->data, t_this->data, + t_fw->u.user.target_size - sizeof(*t_fw)) != 0) { + DEBUGP("Different target\n"); + return false; + } + return true; + } + + if (strcmp(targname_fw, targname_this) != 0) { + DEBUGP("Different verdict\n"); + return false; + } + + return true; +} + +struct nft_family_ops nft_family_ops_arp = { + .add = nft_arp_add, + .is_same = nft_arp_is_same, + .print_payload = NULL, + .parse_meta = nft_arp_parse_meta, + .parse_payload = nft_arp_parse_payload, + .parse_immediate = nft_arp_parse_immediate, + .print_firewall = nft_arp_print_firewall, + .post_parse = NULL, + .rule_find = nft_arp_rule_find, + .parse_target = nft_arp_parse_target, +}; diff --git a/iptables/nft-ipv4.c b/iptables/nft-ipv4.c new file mode 100644 index 00000000..d05e80eb --- /dev/null +++ b/iptables/nft-ipv4.c @@ -0,0 +1,424 @@ +/* + * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org> + * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.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. + * + * This code has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <string.h> +#include <stdio.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netinet/ip.h> + +#include <xtables.h> + +#include <linux/netfilter/nf_tables.h> + +#include "nft.h" +#include "nft-shared.h" + +static int nft_ipv4_add(struct nft_rule *r, void *data) +{ + struct iptables_command_state *cs = data; + struct xtables_rule_match *matchp; + uint32_t op; + + if (cs->fw.ip.iniface[0] != '\0') + add_iniface(r, cs->fw.ip.iniface, cs->fw.ip.invflags); + + if (cs->fw.ip.outiface[0] != '\0') + add_outiface(r, cs->fw.ip.outiface, cs->fw.ip.invflags); + + if (cs->fw.ip.src.s_addr != 0) + add_addr(r, offsetof(struct iphdr, saddr), + &cs->fw.ip.src.s_addr, 4, cs->fw.ip.invflags); + + if (cs->fw.ip.dst.s_addr != 0) + add_addr(r, offsetof(struct iphdr, daddr), + &cs->fw.ip.dst.s_addr, 4, cs->fw.ip.invflags); + + if (cs->fw.ip.proto != 0) + add_proto(r, offsetof(struct iphdr, protocol), 1, + cs->fw.ip.proto, cs->fw.ip.invflags); + + if (cs->fw.ip.flags & IPT_F_FRAG) { + add_payload(r, offsetof(struct iphdr, frag_off), 2); + /* get the 13 bits that contain the fragment offset */ + add_bitwise_u16(r, 0x1fff, !0x1fff); + + /* if offset is non-zero, this is a fragment */ + if (cs->fw.ip.invflags & IPT_INV_FRAG) + op = NFT_CMP_EQ; + else + op = NFT_CMP_NEQ; + + add_cmp_u16(r, 0, op); + } + + add_compat(r, cs->fw.ip.proto, cs->fw.ip.invflags); + + for (matchp = cs->matches; matchp; matchp = matchp->next) { + if (add_match(r, matchp->match->m) < 0) + break; + } + + /* Counters need to me added before the target, otherwise they are + * increased for each rule because of the way nf_tables works. + */ + if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0) + return -1; + + return add_action(r, cs, !!(cs->fw.ip.flags & IPT_F_GOTO)); +} + +static bool nft_ipv4_is_same(const void *data_a, + const void *data_b) +{ + const struct iptables_command_state *a = data_a; + const struct iptables_command_state *b = data_b; + + if (a->fw.ip.src.s_addr != b->fw.ip.src.s_addr + || a->fw.ip.dst.s_addr != b->fw.ip.dst.s_addr + || a->fw.ip.smsk.s_addr != b->fw.ip.smsk.s_addr + || a->fw.ip.dmsk.s_addr != b->fw.ip.dmsk.s_addr + || a->fw.ip.proto != b->fw.ip.proto + || a->fw.ip.flags != b->fw.ip.flags + || a->fw.ip.invflags != b->fw.ip.invflags) { + DEBUGP("different src/dst/proto/flags/invflags\n"); + return false; + } + + return is_same_interfaces(a->fw.ip.iniface, a->fw.ip.outiface, + a->fw.ip.iniface_mask, a->fw.ip.outiface_mask, + b->fw.ip.iniface, b->fw.ip.outiface, + b->fw.ip.iniface_mask, b->fw.ip.outiface_mask); +} + +static void get_frag(struct nft_rule_expr_iter *iter, bool *inv) +{ + struct nft_rule_expr *e; + const char *name; + uint8_t op; + + e = nft_rule_expr_iter_next(iter); + if (e == NULL) + return; + + /* we assume correct mask and xor */ + name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME); + if (strcmp(name, "bitwise") != 0) { + DEBUGP("skipping no bitwise after payload\n"); + return; + } + + /* Now check for cmp */ + e = nft_rule_expr_iter_next(iter); + if (e == NULL) + return; + + /* we assume correct data */ + name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME); + if (strcmp(name, "cmp") != 0) { + DEBUGP("skipping no cmp after payload\n"); + return; + } + + op = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP); + if (op == NFT_CMP_EQ) + *inv = true; + else + *inv = false; +} + +static const char *mask_to_str(uint32_t mask) +{ + static char mask_str[sizeof("255.255.255.255")]; + uint32_t bits, hmask = ntohl(mask); + struct in_addr mask_addr = { + .s_addr = mask, + }; + int i; + + if (mask == 0xFFFFFFFFU) { + sprintf(mask_str, "32"); + return mask_str; + } + + i = 32; + bits = 0xFFFFFFFEU; + while (--i >= 0 && hmask != bits) + bits <<= 1; + if (i >= 0) + sprintf(mask_str, "%u", i); + else + sprintf(mask_str, "%s", inet_ntoa(mask_addr)); + + return mask_str; +} + +static void nft_ipv4_parse_meta(struct nft_rule_expr *e, uint8_t key, + void *data) +{ + struct iptables_command_state *cs = data; + + parse_meta(e, key, cs->fw.ip.iniface, cs->fw.ip.iniface_mask, + cs->fw.ip.outiface, cs->fw.ip.outiface_mask, + &cs->fw.ip.invflags); +} + +static void nft_ipv4_parse_payload(struct nft_rule_expr_iter *iter, + uint32_t offset, void *data) +{ + struct iptables_command_state *cs = data; + + switch(offset) { + struct in_addr addr; + uint8_t proto; + bool inv; + + case offsetof(struct iphdr, saddr): + get_cmp_data(iter, &addr, sizeof(addr), &inv); + cs->fw.ip.src.s_addr = addr.s_addr; + cs->fw.ip.smsk.s_addr = 0xffffffff; + if (inv) + cs->fw.ip.invflags |= IPT_INV_SRCIP; + break; + case offsetof(struct iphdr, daddr): + get_cmp_data(iter, &addr, sizeof(addr), &inv); + cs->fw.ip.dst.s_addr = addr.s_addr; + cs->fw.ip.dmsk.s_addr = 0xffffffff; + if (inv) + cs->fw.ip.invflags |= IPT_INV_DSTIP; + break; + case offsetof(struct iphdr, protocol): + get_cmp_data(iter, &proto, sizeof(proto), &inv); + cs->fw.ip.proto = proto; + if (inv) + cs->fw.ip.invflags |= IPT_INV_PROTO; + break; + case offsetof(struct iphdr, frag_off): + cs->fw.ip.flags |= IPT_F_FRAG; + get_frag(iter, &inv); + if (inv) + cs->fw.ip.invflags |= IPT_INV_FRAG; + break; + default: + DEBUGP("unknown payload offset %d\n", offset); + break; + } +} + +static void nft_ipv4_parse_immediate(const char *jumpto, bool nft_goto, + void *data) +{ + struct iptables_command_state *cs = data; + + cs->jumpto = jumpto; + + if (nft_goto) + cs->fw.ip.flags |= IPT_F_GOTO; +} + +static void print_ipv4_addr(const struct iptables_command_state *cs, + unsigned int format) +{ + char buf[BUFSIZ]; + + fputc(cs->fw.ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout); + if (cs->fw.ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","%s "), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.src)); + else + strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.src)); + strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.smsk)); + printf(FMT("%-19s ","%s "), buf); + } + + fputc(cs->fw.ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout); + if (cs->fw.ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","-> %s"), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.dst)); + else + strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.dst)); + strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.dmsk)); + printf(FMT("%-19s ","-> %s"), buf); + } +} + +static void print_fragment(unsigned int flags, unsigned int invflags, + unsigned int format) +{ + if (!(format & FMT_OPTIONS)) + return; + + if (format & FMT_NOTABLE) + fputs("opt ", stdout); + fputc(invflags & IPT_INV_FRAG ? '!' : '-', stdout); + fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout); + fputc(' ', stdout); +} + +static void nft_ipv4_print_firewall(struct nft_rule *r, unsigned int num, + unsigned int format) +{ + struct iptables_command_state cs = {}; + + nft_rule_to_iptables_command_state(r, &cs); + + print_firewall_details(&cs, cs.jumpto, cs.fw.ip.flags, + cs.fw.ip.invflags, cs.fw.ip.proto, + num, format); + print_fragment(cs.fw.ip.flags, cs.fw.ip.invflags, format); + print_ifaces(cs.fw.ip.iniface, cs.fw.ip.outiface, cs.fw.ip.invflags, + format); + print_ipv4_addr(&cs, format); + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + +#ifdef IPT_F_GOTO + if (cs.fw.ip.flags & IPT_F_GOTO) + printf("[goto] "); +#endif + + print_matches_and_target(&cs, format); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static void save_ipv4_addr(char letter, const struct in_addr *addr, + uint32_t mask, int invert) +{ + if (!mask && !invert && !addr->s_addr) + return; + + printf("%s-%c %s/%s ", invert ? "! " : "", letter, inet_ntoa(*addr), + mask_to_str(mask)); +} + +static void nft_ipv4_save_firewall(const void *data, unsigned int format) +{ + const struct iptables_command_state *cs = data; + + save_firewall_details(cs, cs->fw.ip.invflags, cs->fw.ip.proto, + cs->fw.ip.iniface, cs->fw.ip.iniface_mask, + cs->fw.ip.outiface, cs->fw.ip.outiface_mask, + format); + + if (cs->fw.ip.flags & IPT_F_FRAG) { + if (cs->fw.ip.invflags & IPT_INV_FRAG) + printf("! "); + printf("-f "); + } + + save_ipv4_addr('s', &cs->fw.ip.src, cs->fw.ip.smsk.s_addr, + cs->fw.ip.invflags & IPT_INV_SRCIP); + save_ipv4_addr('d', &cs->fw.ip.dst, cs->fw.ip.dmsk.s_addr, + cs->fw.ip.invflags & IPT_INV_DSTIP); + + save_matches_and_target(cs->matches, cs->target, + cs->jumpto, cs->fw.ip.flags, &cs->fw); + + if (cs->target == NULL && strlen(cs->jumpto) > 0) { + printf("-%c %s", cs->fw.ip.flags & IPT_F_GOTO ? 'g' : 'j', + cs->jumpto); + } + printf("\n"); +} + +static void nft_ipv4_proto_parse(struct iptables_command_state *cs, + struct xtables_args *args) +{ + cs->fw.ip.proto = args->proto; + cs->fw.ip.invflags = args->invflags; +} + +static void nft_ipv4_post_parse(int command, + struct iptables_command_state *cs, + struct xtables_args *args) +{ + cs->fw.ip.flags = args->flags; + /* We already set invflags in proto_parse, but we need to refresh it + * to include new parsed options. + */ + cs->fw.ip.invflags = args->invflags; + + strncpy(cs->fw.ip.iniface, args->iniface, IFNAMSIZ); + memcpy(cs->fw.ip.iniface_mask, + args->iniface_mask, IFNAMSIZ*sizeof(unsigned char)); + + strncpy(cs->fw.ip.outiface, args->outiface, IFNAMSIZ); + memcpy(cs->fw.ip.outiface_mask, + args->outiface_mask, IFNAMSIZ*sizeof(unsigned char)); + + if (args->goto_set) + cs->fw.ip.flags |= IPT_F_GOTO; + + cs->counters.pcnt = args->pcnt_cnt; + cs->counters.bcnt = args->bcnt_cnt; + + if (command & (CMD_REPLACE | CMD_INSERT | + CMD_DELETE | CMD_APPEND | CMD_CHECK)) { + if (!(cs->options & OPT_DESTINATION)) + args->dhostnetworkmask = "0.0.0.0/0"; + if (!(cs->options & OPT_SOURCE)) + args->shostnetworkmask = "0.0.0.0/0"; + } + + if (args->shostnetworkmask) + xtables_ipparse_multiple(args->shostnetworkmask, + &args->s.addr.v4, &args->s.mask.v4, + &args->s.naddrs); + if (args->dhostnetworkmask) + xtables_ipparse_multiple(args->dhostnetworkmask, + &args->d.addr.v4, &args->d.mask.v4, + &args->d.naddrs); + + if ((args->s.naddrs > 1 || args->d.naddrs > 1) && + (cs->fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP))) + xtables_error(PARAMETER_PROBLEM, + "! not allowed with multiple" + " source or destination IP addresses"); +} + +static void nft_ipv4_parse_target(struct xtables_target *t, void *data) +{ + struct iptables_command_state *cs = data; + + cs->target = t; +} + +static bool nft_ipv4_rule_find(struct nft_family_ops *ops, + struct nft_rule *r, void *data) +{ + struct iptables_command_state *cs = data; + + return nft_ipv46_rule_find(ops, r, cs); +} + +struct nft_family_ops nft_family_ops_ipv4 = { + .add = nft_ipv4_add, + .is_same = nft_ipv4_is_same, + .parse_meta = nft_ipv4_parse_meta, + .parse_payload = nft_ipv4_parse_payload, + .parse_immediate = nft_ipv4_parse_immediate, + .print_firewall = nft_ipv4_print_firewall, + .save_firewall = nft_ipv4_save_firewall, + .proto_parse = nft_ipv4_proto_parse, + .post_parse = nft_ipv4_post_parse, + .parse_target = nft_ipv4_parse_target, + .rule_find = nft_ipv4_rule_find, +}; diff --git a/iptables/nft-ipv6.c b/iptables/nft-ipv6.c new file mode 100644 index 00000000..f08598ae --- /dev/null +++ b/iptables/nft-ipv6.c @@ -0,0 +1,345 @@ +/* + * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org> + * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.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. + * + * This code has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <string.h> +#include <stdio.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <arpa/inet.h> +#include <netinet/ip6.h> + +#include <xtables.h> + +#include "nft.h" +#include "nft-shared.h" + +static int nft_ipv6_add(struct nft_rule *r, void *data) +{ + struct iptables_command_state *cs = data; + struct xtables_rule_match *matchp; + + if (cs->fw6.ipv6.iniface[0] != '\0') + add_iniface(r, cs->fw6.ipv6.iniface, cs->fw6.ipv6.invflags); + + if (cs->fw6.ipv6.outiface[0] != '\0') + add_outiface(r, cs->fw6.ipv6.outiface, cs->fw6.ipv6.invflags); + + if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src)) + add_addr(r, offsetof(struct ip6_hdr, ip6_src), + &cs->fw6.ipv6.src, 16, cs->fw6.ipv6.invflags); + + if (!IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dst)) + add_addr(r, offsetof(struct ip6_hdr, ip6_dst), + &cs->fw6.ipv6.dst, 16, cs->fw6.ipv6.invflags); + + if (cs->fw6.ipv6.proto != 0) + add_proto(r, offsetof(struct ip6_hdr, ip6_nxt), 1, + cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags); + + add_compat(r, cs->fw6.ipv6.proto, cs->fw6.ipv6.invflags); + + for (matchp = cs->matches; matchp; matchp = matchp->next) { + if (add_match(r, matchp->match->m) < 0) + break; + } + + /* Counters need to me added before the target, otherwise they are + * increased for each rule because of the way nf_tables works. + */ + if (add_counters(r, cs->counters.pcnt, cs->counters.bcnt) < 0) + return -1; + + return add_action(r, cs, !!(cs->fw6.ipv6.flags & IP6T_F_GOTO)); +} + +static bool nft_ipv6_is_same(const void *data_a, + const void *data_b) +{ + const struct iptables_command_state *a = data_a; + const struct iptables_command_state *b = data_b; + + if (memcmp(a->fw6.ipv6.src.s6_addr, b->fw6.ipv6.src.s6_addr, + sizeof(struct in6_addr)) != 0 + || memcmp(a->fw6.ipv6.dst.s6_addr, b->fw6.ipv6.dst.s6_addr, + sizeof(struct in6_addr)) != 0 + || a->fw6.ipv6.proto != b->fw6.ipv6.proto + || a->fw6.ipv6.flags != b->fw6.ipv6.flags + || a->fw6.ipv6.invflags != b->fw6.ipv6.invflags) { + DEBUGP("different src/dst/proto/flags/invflags\n"); + return false; + } + + return is_same_interfaces(a->fw6.ipv6.iniface, a->fw6.ipv6.outiface, + a->fw6.ipv6.iniface_mask, + a->fw6.ipv6.outiface_mask, + b->fw6.ipv6.iniface, b->fw6.ipv6.outiface, + b->fw6.ipv6.iniface_mask, + b->fw6.ipv6.outiface_mask); +} + +static void nft_ipv6_parse_meta(struct nft_rule_expr *e, uint8_t key, + void *data) +{ + struct iptables_command_state *cs = data; + + parse_meta(e, key, cs->fw6.ipv6.iniface, + cs->fw6.ipv6.iniface_mask, cs->fw6.ipv6.outiface, + cs->fw6.ipv6.outiface_mask, &cs->fw6.ipv6.invflags); +} + +static void nft_ipv6_parse_payload(struct nft_rule_expr_iter *iter, + uint32_t offset, void *data) +{ + struct iptables_command_state *cs = data; + switch (offset) { + struct in6_addr addr; + uint8_t proto; + bool inv; + + case offsetof(struct ip6_hdr, ip6_src): + get_cmp_data(iter, &addr, sizeof(addr), &inv); + memcpy(cs->fw6.ipv6.src.s6_addr, &addr, sizeof(addr)); + if (inv) + cs->fw6.ipv6.invflags |= IPT_INV_SRCIP; + break; + case offsetof(struct ip6_hdr, ip6_dst): + get_cmp_data(iter, &addr, sizeof(addr), &inv); + memcpy(cs->fw6.ipv6.dst.s6_addr, &addr, sizeof(addr)); + if (inv) + cs->fw6.ipv6.invflags |= IPT_INV_DSTIP; + break; + case offsetof(struct ip6_hdr, ip6_nxt): + get_cmp_data(iter, &proto, sizeof(proto), &inv); + cs->fw6.ipv6.flags |= IP6T_F_PROTO; + cs->fw6.ipv6.proto = proto; + if (inv) + cs->fw6.ipv6.invflags |= IPT_INV_PROTO; + default: + DEBUGP("unknown payload offset %d\n", offset); + break; + } +} + +static void nft_ipv6_parse_immediate(const char *jumpto, bool nft_goto, + void *data) +{ + struct iptables_command_state *cs = data; + + cs->jumpto = jumpto; + + if (nft_goto) + cs->fw6.ipv6.flags |= IP6T_F_GOTO; +} + +static void print_ipv6_addr(const struct iptables_command_state *cs, + unsigned int format) +{ + char buf[BUFSIZ]; + + fputc(cs->fw6.ipv6.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout); + if (IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.src) + && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","%s "), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, + xtables_ip6addr_to_numeric(&cs->fw6.ipv6.src)); + else + strcpy(buf, + xtables_ip6addr_to_anyname(&cs->fw6.ipv6.src)); + strcat(buf, xtables_ip6mask_to_numeric(&cs->fw6.ipv6.smsk)); + printf(FMT("%-19s ","%s "), buf); + } + + + fputc(cs->fw6.ipv6.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout); + if (IN6_IS_ADDR_UNSPECIFIED(&cs->fw6.ipv6.dst) + && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","-> %s"), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, + xtables_ip6addr_to_numeric(&cs->fw6.ipv6.dst)); + else + strcpy(buf, + xtables_ip6addr_to_anyname(&cs->fw6.ipv6.dst)); + strcat(buf, xtables_ip6mask_to_numeric(&cs->fw6.ipv6.dmsk)); + printf(FMT("%-19s ","-> %s"), buf); + } +} + +static void nft_ipv6_print_firewall(struct nft_rule *r, unsigned int num, + unsigned int format) +{ + struct iptables_command_state cs = {}; + + nft_rule_to_iptables_command_state(r, &cs); + + print_firewall_details(&cs, cs.jumpto, cs.fw6.ipv6.flags, + cs.fw6.ipv6.invflags, cs.fw6.ipv6.proto, + num, format); + print_ifaces(cs.fw6.ipv6.iniface, cs.fw6.ipv6.outiface, + cs.fw6.ipv6.invflags, format); + print_ipv6_addr(&cs, format); + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + + if (cs.fw6.ipv6.flags & IP6T_F_GOTO) + printf("[goto] "); + + print_matches_and_target(&cs, format); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static void save_ipv6_addr(char letter, const struct in6_addr *addr, + int invert) +{ + char addr_str[INET6_ADDRSTRLEN]; + + if (!invert && IN6_IS_ADDR_UNSPECIFIED(addr)) + return; + + inet_ntop(AF_INET6, addr, addr_str, INET6_ADDRSTRLEN); + printf("%s-%c %s ", invert ? "! " : "", letter, addr_str); +} + +static void nft_ipv6_save_firewall(const void *data, unsigned int format) +{ + const struct iptables_command_state *cs = data; + + save_firewall_details(cs, cs->fw6.ipv6.invflags, cs->fw6.ipv6.proto, + cs->fw6.ipv6.iniface, cs->fw6.ipv6.iniface_mask, + cs->fw6.ipv6.outiface, cs->fw6.ipv6.outiface_mask, + format); + + save_ipv6_addr('s', &cs->fw6.ipv6.src, + cs->fw6.ipv6.invflags & IPT_INV_SRCIP); + save_ipv6_addr('d', &cs->fw6.ipv6.dst, + cs->fw6.ipv6.invflags & IPT_INV_DSTIP); + + save_matches_and_target(cs->matches, cs->target, + cs->jumpto, cs->fw6.ipv6.flags, &cs->fw6); + + if (cs->target == NULL && strlen(cs->jumpto) > 0) { + printf("-%c %s", cs->fw6.ipv6.flags & IP6T_F_GOTO ? 'g' : 'j', + cs->jumpto); + } + printf("\n"); +} + +/* These are invalid numbers as upper layer protocol */ +static int is_exthdr(uint16_t proto) +{ + return (proto == IPPROTO_ROUTING || + proto == IPPROTO_FRAGMENT || + proto == IPPROTO_AH || + proto == IPPROTO_DSTOPTS); +} + +static void nft_ipv6_proto_parse(struct iptables_command_state *cs, + struct xtables_args *args) +{ + cs->fw6.ipv6.proto = args->proto; + cs->fw6.ipv6.invflags = args->invflags; + + if (is_exthdr(cs->fw6.ipv6.proto) + && (cs->fw6.ipv6.invflags & XT_INV_PROTO) == 0) + fprintf(stderr, + "Warning: never matched protocol: %s. " + "use extension match instead.\n", + cs->protocol); +} + +static void nft_ipv6_post_parse(int command, struct iptables_command_state *cs, + struct xtables_args *args) +{ + if (args->proto != 0) + args->flags |= IP6T_F_PROTO; + + cs->fw6.ipv6.flags = args->flags; + /* We already set invflags in proto_parse, but we need to refresh it + * to include new parsed options. + */ + cs->fw6.ipv6.invflags = args->invflags; + + strncpy(cs->fw6.ipv6.iniface, args->iniface, IFNAMSIZ); + memcpy(cs->fw6.ipv6.iniface_mask, + args->iniface_mask, IFNAMSIZ*sizeof(unsigned char)); + + strncpy(cs->fw6.ipv6.outiface, args->outiface, IFNAMSIZ); + memcpy(cs->fw6.ipv6.outiface_mask, + args->outiface_mask, IFNAMSIZ*sizeof(unsigned char)); + + if (args->goto_set) + cs->fw6.ipv6.flags |= IP6T_F_GOTO; + + cs->fw6.counters.pcnt = args->pcnt_cnt; + cs->fw6.counters.bcnt = args->bcnt_cnt; + + if (command & (CMD_REPLACE | CMD_INSERT | + CMD_DELETE | CMD_APPEND | CMD_CHECK)) { + if (!(cs->options & OPT_DESTINATION)) + args->dhostnetworkmask = "::0/0"; + if (!(cs->options & OPT_SOURCE)) + args->shostnetworkmask = "::0/0"; + } + + if (args->shostnetworkmask) + xtables_ip6parse_multiple(args->shostnetworkmask, + &args->s.addr.v6, + &args->s.mask.v6, + &args->s.naddrs); + if (args->dhostnetworkmask) + xtables_ip6parse_multiple(args->dhostnetworkmask, + &args->d.addr.v6, + &args->d.mask.v6, + &args->d.naddrs); + + if ((args->s.naddrs > 1 || args->d.naddrs > 1) && + (cs->fw6.ipv6.invflags & (IP6T_INV_SRCIP | IP6T_INV_DSTIP))) + xtables_error(PARAMETER_PROBLEM, + "! not allowed with multiple" + " source or destination IP addresses"); +} + +static void nft_ipv6_parse_target(struct xtables_target *t, void *data) +{ + struct iptables_command_state *cs = data; + + cs->target = t; +} + +static bool nft_ipv6_rule_find(struct nft_family_ops *ops, + struct nft_rule *r, void *data) +{ + struct iptables_command_state *cs = data; + + return nft_ipv46_rule_find(ops, r, cs); +} + +struct nft_family_ops nft_family_ops_ipv6 = { + .add = nft_ipv6_add, + .is_same = nft_ipv6_is_same, + .parse_meta = nft_ipv6_parse_meta, + .parse_payload = nft_ipv6_parse_payload, + .parse_immediate = nft_ipv6_parse_immediate, + .print_firewall = nft_ipv6_print_firewall, + .save_firewall = nft_ipv6_save_firewall, + .proto_parse = nft_ipv6_proto_parse, + .post_parse = nft_ipv6_post_parse, + .parse_target = nft_ipv6_parse_target, + .rule_find = nft_ipv6_rule_find, +}; diff --git a/iptables/nft-shared.c b/iptables/nft-shared.c new file mode 100644 index 00000000..ada71e6b --- /dev/null +++ b/iptables/nft-shared.c @@ -0,0 +1,773 @@ +/* + * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org> + * (C) 2013 by Tomasz Bursztyka <tomasz.bursztyka@linux.intel.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. + * + * This code has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <netdb.h> +#include <errno.h> + +#include <xtables.h> + +#include <linux/netfilter/nf_tables.h> + +#include <libmnl/libmnl.h> +#include <libnftnl/rule.h> +#include <libnftnl/expr.h> + +#include "nft-shared.h" +#include "xshared.h" +#include "nft.h" + +extern struct nft_family_ops nft_family_ops_ipv4; +extern struct nft_family_ops nft_family_ops_ipv6; +extern struct nft_family_ops nft_family_ops_arp; + +void add_meta(struct nft_rule *r, uint32_t key) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("meta"); + if (expr == NULL) + return; + + nft_rule_expr_set_u32(expr, NFT_EXPR_META_KEY, key); + nft_rule_expr_set_u32(expr, NFT_EXPR_META_DREG, NFT_REG_1); + + nft_rule_add_expr(r, expr); +} + +void add_payload(struct nft_rule *r, int offset, int len) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("payload"); + if (expr == NULL) + return; + + nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_BASE, + NFT_PAYLOAD_NETWORK_HEADER); + nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_DREG, NFT_REG_1); + nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_OFFSET, offset); + nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_LEN, len); + + nft_rule_add_expr(r, expr); +} + +/* bitwise operation is = sreg & mask ^ xor */ +void add_bitwise_u16(struct nft_rule *r, int mask, int xor) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("bitwise"); + if (expr == NULL) + return; + + nft_rule_expr_set_u32(expr, NFT_EXPR_BITWISE_SREG, NFT_REG_1); + nft_rule_expr_set_u32(expr, NFT_EXPR_BITWISE_DREG, NFT_REG_1); + nft_rule_expr_set_u32(expr, NFT_EXPR_BITWISE_LEN, sizeof(uint16_t)); + nft_rule_expr_set(expr, NFT_EXPR_BITWISE_MASK, &mask, sizeof(uint16_t)); + nft_rule_expr_set(expr, NFT_EXPR_BITWISE_XOR, &xor, sizeof(uint16_t)); + + nft_rule_add_expr(r, expr); +} + +void add_cmp_ptr(struct nft_rule *r, uint32_t op, void *data, size_t len) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("cmp"); + if (expr == NULL) + return; + + nft_rule_expr_set_u32(expr, NFT_EXPR_CMP_SREG, NFT_REG_1); + nft_rule_expr_set_u32(expr, NFT_EXPR_CMP_OP, op); + nft_rule_expr_set(expr, NFT_EXPR_CMP_DATA, data, len); + + nft_rule_add_expr(r, expr); +} + +void add_cmp_u8(struct nft_rule *r, uint8_t val, uint32_t op) +{ + add_cmp_ptr(r, op, &val, sizeof(val)); +} + +void add_cmp_u16(struct nft_rule *r, uint16_t val, uint32_t op) +{ + add_cmp_ptr(r, op, &val, sizeof(val)); +} + +void add_cmp_u32(struct nft_rule *r, uint32_t val, uint32_t op) +{ + add_cmp_ptr(r, op, &val, sizeof(val)); +} + +void add_iniface(struct nft_rule *r, char *iface, int invflags) +{ + int iface_len; + uint32_t op; + + iface_len = strlen(iface); + + if (invflags & IPT_INV_VIA_IN) + op = NFT_CMP_NEQ; + else + op = NFT_CMP_EQ; + + add_meta(r, NFT_META_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); +} + +void add_outiface(struct nft_rule *r, char *iface, int invflags) +{ + int iface_len; + uint32_t op; + + iface_len = strlen(iface); + + if (invflags & IPT_INV_VIA_OUT) + op = NFT_CMP_NEQ; + else + op = NFT_CMP_EQ; + + add_meta(r, NFT_META_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); +} + +void add_addr(struct nft_rule *r, int offset, + void *data, size_t len, int invflags) +{ + uint32_t op; + + add_payload(r, offset, len); + + if (invflags & IPT_INV_SRCIP || invflags & IPT_INV_DSTIP) + op = NFT_CMP_NEQ; + else + op = NFT_CMP_EQ; + + add_cmp_ptr(r, op, data, len); +} + +void add_proto(struct nft_rule *r, int offset, size_t len, + uint8_t proto, int invflags) +{ + uint32_t op; + + add_payload(r, offset, len); + + if (invflags & XT_INV_PROTO) + op = NFT_CMP_NEQ; + else + op = NFT_CMP_EQ; + + add_cmp_u8(r, proto, op); +} + +bool is_same_interfaces(const char *a_iniface, const char *a_outiface, + unsigned const char *a_iniface_mask, + unsigned const char *a_outiface_mask, + const char *b_iniface, const char *b_outiface, + unsigned const char *b_iniface_mask, + unsigned const char *b_outiface_mask) +{ + int i; + + for (i = 0; i < IFNAMSIZ; i++) { + if (a_iniface_mask[i] != b_iniface_mask[i]) { + DEBUGP("different iniface mask %x, %x (%d)\n", + a_iniface_mask[i] & 0xff, b_iniface_mask[i] & 0xff, i); + return false; + } + if ((a_iniface[i] & a_iniface_mask[i]) + != (b_iniface[i] & b_iniface_mask[i])) { + DEBUGP("different iniface\n"); + return false; + } + if (a_outiface_mask[i] != b_outiface_mask[i]) { + DEBUGP("different outiface mask\n"); + return false; + } + if ((a_outiface[i] & a_outiface_mask[i]) + != (b_outiface[i] & b_outiface_mask[i])) { + DEBUGP("different outiface\n"); + return false; + } + } + + return true; +} + +void parse_meta(struct nft_rule_expr *e, uint8_t key, char *iniface, + unsigned char *iniface_mask, char *outiface, + unsigned char *outiface_mask, uint8_t *invflags) +{ + uint32_t value; + const void *ifname; + uint32_t len; + + switch(key) { + case NFT_META_IIF: + value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA); + if (nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ) + *invflags |= IPT_INV_VIA_IN; + + if_indextoname(value, iniface); + + memset(iniface_mask, 0xff, strlen(iniface)+1); + break; + case NFT_META_OIF: + value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA); + if (nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ) + *invflags |= IPT_INV_VIA_OUT; + + if_indextoname(value, outiface); + + memset(outiface_mask, 0xff, strlen(outiface)+1); + break; + case NFT_META_IIFNAME: + ifname = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len); + if (nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ) + *invflags |= IPT_INV_VIA_IN; + + memcpy(iniface, ifname, len); + + if (iniface[len] == '\0') + memset(iniface_mask, 0xff, len); + else { + iniface[len] = '+'; + iniface[len+1] = '\0'; + memset(iniface_mask, 0xff, len + 1); + } + break; + case NFT_META_OIFNAME: + ifname = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len); + if (nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ) + *invflags |= IPT_INV_VIA_OUT; + + memcpy(outiface, ifname, len); + + if (outiface[len] == '\0') + memset(outiface_mask, 0xff, len); + else { + outiface[len] = '+'; + outiface[len+1] = '\0'; + memset(outiface_mask, 0xff, len + 1); + } + break; + default: + DEBUGP("unknown meta key %d\n", key); + break; + } +} + +void nft_parse_target(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + int family, void *data) +{ + uint32_t tg_len; + const char *targname = nft_rule_expr_get_str(e, NFT_EXPR_TG_NAME); + const void *targinfo = nft_rule_expr_get(e, NFT_EXPR_TG_INFO, &tg_len); + struct xtables_target *target; + struct xt_entry_target *t; + struct nft_family_ops *ops; + size_t size; + + target = xtables_find_target(targname, XTF_TRY_LOAD); + if (target == NULL) + return; + + size = XT_ALIGN(sizeof(struct xt_entry_target)) + tg_len; + + t = calloc(1, size); + if (t == NULL) { + fprintf(stderr, "OOM"); + exit(EXIT_FAILURE); + } + memcpy(&t->data, targinfo, tg_len); + t->u.target_size = size; + t->u.user.revision = nft_rule_expr_get_u32(e, NFT_EXPR_TG_REV); + strcpy(t->u.user.name, target->name); + + target->t = t; + + ops = nft_family_ops_lookup(family); + ops->parse_target(target, data); +} + +static void +nft_parse_match(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + struct iptables_command_state *cs) +{ + uint32_t mt_len; + const char *mt_name = nft_rule_expr_get_str(e, NFT_EXPR_MT_NAME); + const void *mt_info = nft_rule_expr_get(e, NFT_EXPR_MT_INFO, &mt_len); + struct xtables_match *match; + struct xt_entry_match *m; + + match = xtables_find_match(mt_name, XTF_TRY_LOAD, &cs->matches); + if (match == NULL) + return; + + m = calloc(1, sizeof(struct xt_entry_match) + mt_len); + if (m == NULL) { + fprintf(stderr, "OOM"); + exit(EXIT_FAILURE); + } + + memcpy(&m->data, mt_info, mt_len); + m->u.match_size = mt_len + XT_ALIGN(sizeof(struct xt_entry_match)); + m->u.user.revision = nft_rule_expr_get_u32(e, NFT_EXPR_TG_REV); + strcpy(m->u.user.name, match->name); + + match->m = m; +} + +void print_proto(uint16_t proto, int invert) +{ + const struct protoent *pent = getprotobynumber(proto); + + if (invert) + printf("! "); + + if (pent) { + printf("-p %s ", pent->p_name); + return; + } + + printf("-p %u ", proto); +} + +void get_cmp_data(struct nft_rule_expr_iter *iter, + void *data, size_t dlen, bool *inv) +{ + struct nft_rule_expr *e; + const char *name; + uint32_t len; + uint8_t op; + + e = nft_rule_expr_iter_next(iter); + if (e == NULL) + return; + + name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME); + if (strcmp(name, "cmp") != 0) { + DEBUGP("skipping no cmp after meta\n"); + return; + } + + memcpy(data, nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len), dlen); + op = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_OP); + if (op == NFT_CMP_NEQ) + *inv = true; + else + *inv = false; +} + +void +nft_parse_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + int family, void *data) +{ + uint8_t key = nft_rule_expr_get_u32(e, NFT_EXPR_META_KEY); + struct nft_family_ops *ops = nft_family_ops_lookup(family); + const char *name; + + e = nft_rule_expr_iter_next(iter); + if (e == NULL) + return; + + name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME); + if (strcmp(name, "cmp") != 0) { + DEBUGP("skipping no cmp after meta\n"); + return; + } + + ops->parse_meta(e, key, data); +} + +void +nft_parse_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + int family, void *data) +{ + struct nft_family_ops *ops = nft_family_ops_lookup(family); + uint32_t offset; + + offset = nft_rule_expr_get_u32(e, NFT_EXPR_PAYLOAD_OFFSET); + + ops->parse_payload(iter, offset, data); +} + +void +nft_parse_counter(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + struct xt_counters *counters) +{ + counters->pcnt = nft_rule_expr_get_u64(e, NFT_EXPR_CTR_PACKETS); + counters->bcnt = nft_rule_expr_get_u64(e, NFT_EXPR_CTR_BYTES); +} + +void +nft_parse_immediate(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + int family, void *data) +{ + int verdict = nft_rule_expr_get_u32(e, NFT_EXPR_IMM_VERDICT); + const char *chain = nft_rule_expr_get_str(e, NFT_EXPR_IMM_CHAIN); + struct nft_family_ops *ops; + const char *jumpto = NULL; + bool nft_goto = false; + + /* Standard target? */ + switch(verdict) { + case NF_ACCEPT: + jumpto = "ACCEPT"; + break; + case NF_DROP: + jumpto = "DROP"; + break; + case NFT_RETURN: + jumpto = "RETURN"; + break;; + case NFT_GOTO: + nft_goto = true; + case NFT_JUMP: + jumpto = chain; + break; + } + + ops = nft_family_ops_lookup(family); + ops->parse_immediate(jumpto, nft_goto, data); +} + +void nft_rule_to_iptables_command_state(struct nft_rule *r, + struct iptables_command_state *cs) +{ + struct nft_rule_expr_iter *iter; + struct nft_rule_expr *expr; + int family = nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY); + + iter = nft_rule_expr_iter_create(r); + if (iter == NULL) + return; + + expr = nft_rule_expr_iter_next(iter); + while (expr != NULL) { + const char *name = + nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME); + + if (strcmp(name, "counter") == 0) + nft_parse_counter(expr, iter, &cs->counters); + else if (strcmp(name, "payload") == 0) + nft_parse_payload(expr, iter, family, cs); + else if (strcmp(name, "meta") == 0) + nft_parse_meta(expr, iter, family, cs); + else if (strcmp(name, "immediate") == 0) + nft_parse_immediate(expr, iter, family, cs); + else if (strcmp(name, "match") == 0) + nft_parse_match(expr, iter, cs); + else if (strcmp(name, "target") == 0) + nft_parse_target(expr, iter, family, cs); + + expr = nft_rule_expr_iter_next(iter); + } + + nft_rule_expr_iter_destroy(iter); + + if (cs->target != NULL) + cs->jumpto = cs->target->name; + else if (cs->jumpto != NULL) + cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD); + else + cs->jumpto = ""; +} + +void print_firewall_details(const struct iptables_command_state *cs, + const char *targname, uint8_t flags, + uint8_t invflags, uint8_t proto, + unsigned int num, unsigned int format) +{ + if (format & FMT_LINENUMBERS) + printf(FMT("%-4u ", "%u "), num); + + if (!(format & FMT_NOCOUNTS)) { + xtables_print_num(cs->counters.pcnt, format); + xtables_print_num(cs->counters.bcnt, format); + } + + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ", "%s "), targname ? targname : ""); + + fputc(invflags & XT_INV_PROTO ? '!' : ' ', stdout); + { + const char *pname = + proto_to_name(proto, format&FMT_NUMERIC); + if (pname) + printf(FMT("%-5s", "%s "), pname); + else + printf(FMT("%-5hu", "%hu "), proto); + } +} + +void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags, + unsigned int format) +{ + char iface[IFNAMSIZ+2]; + + if (!(format & FMT_VIA)) + return; + + if (invflags & IPT_INV_VIA_IN) { + iface[0] = '!'; + iface[1] = '\0'; + } else + iface[0] = '\0'; + + if (iniface[0] != '\0') + strcat(iface, iniface); + else if (format & FMT_NUMERIC) + strcat(iface, "*"); + else + strcat(iface, "any"); + + printf(FMT(" %-6s ","in %s "), iface); + + if (invflags & IPT_INV_VIA_OUT) { + iface[0] = '!'; + iface[1] = '\0'; + } else + iface[0] = '\0'; + + if (outiface[0] != '\0') + strcat(iface, outiface); + else if (format & FMT_NUMERIC) + strcat(iface, "*"); + else + strcat(iface, "any"); + + printf(FMT("%-6s ","out %s "), iface); +} + +static void +print_iface(char letter, const char *iface, const unsigned char *mask, int inv) +{ + unsigned int i; + + if (mask[0] == 0) + return; + + printf("%s-%c ", inv ? "! " : "", letter); + + for (i = 0; i < IFNAMSIZ; i++) { + if (mask[i] != 0) { + if (iface[i] != '\0') + printf("%c", iface[i]); + } else { + if (iface[i-1] != '\0') + printf("+"); + break; + } + } + + printf(" "); +} + +void save_firewall_details(const struct iptables_command_state *cs, + uint8_t invflags, uint16_t proto, + const char *iniface, + unsigned const char *iniface_mask, + const char *outiface, + unsigned const char *outiface_mask, + unsigned int format) +{ + if (!(format & FMT_NOCOUNTS)) { + printf("-c "); + xtables_print_num(cs->counters.pcnt, format); + xtables_print_num(cs->counters.bcnt, format); + } + + if (iniface != NULL) { + print_iface('i', iniface, iniface_mask, + invflags & IPT_INV_VIA_IN); + } + if (outiface != NULL) { + print_iface('o', outiface, outiface_mask, + invflags & IPT_INV_VIA_OUT); + } + + if (proto > 0) { + const struct protoent *pent = getprotobynumber(proto); + + if (invflags & XT_INV_PROTO) + printf("! "); + + if (pent) + printf("-p %s ", pent->p_name); + else + printf("-p %u ", proto); + } +} + +void save_matches_and_target(struct xtables_rule_match *m, + struct xtables_target *target, + const char *jumpto, uint8_t flags, const void *fw) +{ + struct xtables_rule_match *matchp; + + for (matchp = m; matchp; matchp = matchp->next) { + if (matchp->match->alias) { + printf("-m %s", + matchp->match->alias(matchp->match->m)); + } else + printf("-m %s", matchp->match->name); + + if (matchp->match->save != NULL) { + /* cs->fw union makes the trick */ + matchp->match->save(fw, matchp->match->m); + } + printf(" "); + } + + if (target != NULL) { + if (target->alias) { + printf("-j %s", target->alias(target->t)); + } else + printf("-j %s", jumpto); + + if (target->save != NULL) + target->save(fw, target->t); + } +} + +void print_matches_and_target(struct iptables_command_state *cs, + unsigned int format) +{ + struct xtables_rule_match *matchp; + + for (matchp = cs->matches; matchp; matchp = matchp->next) { + if (matchp->match->print != NULL) { + matchp->match->print(&cs->fw, matchp->match->m, + format & FMT_NUMERIC); + } + } + + if (cs->target != NULL) { + if (cs->target->print != NULL) { + cs->target->print(&cs->fw, cs->target->t, + format & FMT_NUMERIC); + } + } +} + +struct nft_family_ops *nft_family_ops_lookup(int family) +{ + switch (family) { + case AF_INET: + return &nft_family_ops_ipv4; + case AF_INET6: + return &nft_family_ops_ipv6; + case NFPROTO_ARP: + return &nft_family_ops_arp; + default: + break; + } + + return NULL; +} + +static bool +compare_matches(struct xtables_rule_match *mt1, struct xtables_rule_match *mt2) +{ + struct xtables_rule_match *mp1; + struct xtables_rule_match *mp2; + + for (mp1 = mt1, mp2 = mt2; mp1 && mp2; mp1 = mp1->next, mp2 = mp2->next) { + struct xt_entry_match *m1 = mp1->match->m; + struct xt_entry_match *m2 = mp2->match->m; + + if (strcmp(m1->u.user.name, m2->u.user.name) != 0) { + DEBUGP("mismatching match name\n"); + return false; + } + + if (m1->u.user.match_size != m2->u.user.match_size) { + DEBUGP("mismatching match size\n"); + return false; + } + + if (memcmp(m1->data, m2->data, + mp1->match->userspacesize) != 0) { + DEBUGP("mismatch match data\n"); + return false; + } + } + + /* Both cursors should be NULL */ + if (mp1 != mp2) { + DEBUGP("mismatch matches amount\n"); + return false; + } + + return true; +} + +bool compare_targets(struct xtables_target *tg1, struct xtables_target *tg2) +{ + if (tg1 == NULL && tg2 == NULL) + return true; + + if ((tg1 == NULL && tg2 != NULL) || (tg1 != NULL && tg2 == NULL)) + return false; + + if (strcmp(tg1->t->u.user.name, tg2->t->u.user.name) != 0) + return false; + + if (memcmp(tg1->t->data, tg2->t->data, tg1->userspacesize) != 0) + return false; + + return true; +} + +bool nft_ipv46_rule_find(struct nft_family_ops *ops, + struct nft_rule *r, struct iptables_command_state *cs) +{ + struct iptables_command_state this = {}; + + nft_rule_to_iptables_command_state(r, &this); + + DEBUGP("comparing with... "); +#ifdef DEBUG_DEL + nft_rule_print_save(&this, r, NFT_RULE_APPEND, 0); +#endif + if (!ops->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 (strcmp(cs->jumpto, this.jumpto) != 0) { + DEBUGP("Different verdict\n"); + return false; + } + + return true; +} diff --git a/iptables/nft-shared.h b/iptables/nft-shared.h new file mode 100644 index 00000000..145f19d1 --- /dev/null +++ b/iptables/nft-shared.h @@ -0,0 +1,195 @@ +#ifndef _NFT_SHARED_H_ +#define _NFT_SHARED_H_ + +#include <stdbool.h> + +#include <libnftnl/rule.h> +#include <libnftnl/expr.h> + +#include "xshared.h" + +#if 0 +#define DEBUGP(x, args...) fprintf(stdout, x, ## args) +#define NLDEBUG +#define DEBUG_DEL +#else +#define DEBUGP(x, args...) +#endif + +/* + * iptables print output emulation + */ + +#define FMT_NUMERIC 0x0001 +#define FMT_NOCOUNTS 0x0002 +#define FMT_KILOMEGAGIGA 0x0004 +#define FMT_OPTIONS 0x0008 +#define FMT_NOTABLE 0x0010 +#define FMT_NOTARGET 0x0020 +#define FMT_VIA 0x0040 +#define FMT_NONEWLINE 0x0080 +#define FMT_LINENUMBERS 0x0100 + +#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \ + | FMT_NUMERIC | FMT_NOTABLE) +#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab)) + +struct xtables_args; + +struct nft_family_ops { + int (*add)(struct nft_rule *r, void *data); + bool (*is_same)(const void *data_a, + const void *data_b); + void (*print_payload)(struct nft_rule_expr *e, + struct nft_rule_expr_iter *iter); + void (*parse_meta)(struct nft_rule_expr *e, uint8_t key, + void *data); + void (*parse_payload)(struct nft_rule_expr_iter *iter, + uint32_t offset, void *data); + void (*parse_immediate)(const char *jumpto, bool nft_goto, void *data); + void (*print_firewall)(struct nft_rule *r, unsigned int num, + unsigned int format); + void (*save_firewall)(const void *data, unsigned int format); + void (*proto_parse)(struct iptables_command_state *cs, + struct xtables_args *args); + void (*post_parse)(int command, struct iptables_command_state *cs, + struct xtables_args *args); + void (*parse_target)(struct xtables_target *t, void *data); + bool (*rule_find)(struct nft_family_ops *ops, struct nft_rule *r, + void *data); +}; + +void add_meta(struct nft_rule *r, uint32_t key); +void add_payload(struct nft_rule *r, int offset, int len); +void add_bitwise_u16(struct nft_rule *r, int mask, int xor); +void add_cmp_ptr(struct nft_rule *r, uint32_t op, void *data, size_t len); +void add_cmp_u8(struct nft_rule *r, uint8_t val, uint32_t op); +void add_cmp_u16(struct nft_rule *r, uint16_t val, uint32_t op); +void add_cmp_u32(struct nft_rule *r, uint32_t val, uint32_t op); +void add_iniface(struct nft_rule *r, char *iface, int invflags); +void add_outiface(struct nft_rule *r, char *iface, int invflags); +void add_addr(struct nft_rule *r, int offset, + void *data, size_t len, int invflags); +void add_proto(struct nft_rule *r, int offset, size_t len, + uint8_t proto, int invflags); +void add_compat(struct nft_rule *r, uint32_t proto, bool inv); + +bool is_same_interfaces(const char *a_iniface, const char *a_outiface, + unsigned const char *a_iniface_mask, + unsigned const char *a_outiface_mask, + const char *b_iniface, const char *b_outiface, + unsigned const char *b_iniface_mask, + unsigned const char *b_outiface_mask); + +void parse_meta(struct nft_rule_expr *e, uint8_t key, char *iniface, + unsigned char *iniface_mask, char *outiface, + unsigned char *outiface_mask, uint8_t *invflags); +void print_proto(uint16_t proto, int invert); +void get_cmp_data(struct nft_rule_expr_iter *iter, + void *data, size_t dlen, bool *inv); +void nft_parse_target(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + int family, void *data); +void nft_parse_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + int family, void *data); +void nft_parse_payload(struct nft_rule_expr *e, + struct nft_rule_expr_iter *iter, + int family, void *data); +void nft_parse_counter(struct nft_rule_expr *e, + struct nft_rule_expr_iter *iter, + struct xt_counters *counters); +void nft_parse_immediate(struct nft_rule_expr *e, + struct nft_rule_expr_iter *iter, + int family, void *data); +void nft_rule_to_iptables_command_state(struct nft_rule *r, + struct iptables_command_state *cs); +void print_firewall_details(const struct iptables_command_state *cs, + const char *targname, uint8_t flags, + uint8_t invflags, uint8_t proto, + unsigned int num, unsigned int format); +void print_ifaces(const char *iniface, const char *outiface, uint8_t invflags, + unsigned int format); +void print_matches_and_target(struct iptables_command_state *cs, + unsigned int format); +void save_firewall_details(const struct iptables_command_state *cs, + uint8_t invflags, uint16_t proto, + const char *iniface, + unsigned const char *iniface_mask, + const char *outiface, + unsigned const char *outiface_mask, + unsigned int format); +void save_matches_and_target(struct xtables_rule_match *m, + struct xtables_target *target, + const char *jumpto, + uint8_t flags, const void *fw); + +struct nft_family_ops *nft_family_ops_lookup(int family); + +struct nft_handle; +bool nft_ipv46_rule_find(struct nft_family_ops *ops, struct nft_rule *r, + struct iptables_command_state *cs); + +bool compare_targets(struct xtables_target *tg1, struct xtables_target *tg2); + +struct addr_mask { + union { + struct in_addr *v4; + struct in6_addr *v6; + } addr; + + unsigned int naddrs; + + union { + struct in_addr *v4; + struct in6_addr *v6; + } mask; +}; + +struct xtables_args { + int family; + uint16_t proto; + uint8_t flags; + uint8_t invflags; + char iniface[IFNAMSIZ], outiface[IFNAMSIZ]; + unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ]; + bool goto_set; + const char *shostnetworkmask, *dhostnetworkmask; + const char *pcnt, *bcnt; + struct addr_mask s, d; + unsigned long long pcnt_cnt, bcnt_cnt; +}; + +#define CMD_NONE 0x0000U +#define CMD_INSERT 0x0001U +#define CMD_DELETE 0x0002U +#define CMD_DELETE_NUM 0x0004U +#define CMD_REPLACE 0x0008U +#define CMD_APPEND 0x0010U +#define CMD_LIST 0x0020U +#define CMD_FLUSH 0x0040U +#define CMD_ZERO 0x0080U +#define CMD_NEW_CHAIN 0x0100U +#define CMD_DELETE_CHAIN 0x0200U +#define CMD_SET_POLICY 0x0400U +#define CMD_RENAME_CHAIN 0x0800U +#define CMD_LIST_RULES 0x1000U +#define CMD_ZERO_NUM 0x2000U +#define CMD_CHECK 0x4000U + +/* + * ARP + */ +extern char *opcodes[]; +#define NUMOPCODES 9 + +#include <linux/netfilter_arp/arp_tables.h> + +static inline struct xt_entry_target *nft_arp_get_target(struct arpt_entry *fw) +{ + struct xt_entry_target **target; + + target = (void *) &fw->elems; + + return *target; +} + +#endif diff --git a/iptables/nft.c b/iptables/nft.c new file mode 100644 index 00000000..1237659f --- /dev/null +++ b/iptables/nft.c @@ -0,0 +1,2542 @@ +/* + * (C) 2012 by 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 as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This code has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <stdbool.h> +#include <errno.h> +#include <netdb.h> /* getprotobynumber */ +#include <time.h> +#include <stdarg.h> +#include <inttypes.h> + +#include <xtables.h> +#include <libiptc/libxtc.h> +#include <libiptc/xtcshared.h> + +#include <stdlib.h> +#include <string.h> + +#include <linux/netfilter/x_tables.h> +#include <linux/netfilter_ipv4/ip_tables.h> +#include <linux/netfilter_ipv6/ip6_tables.h> +#include <netinet/ip6.h> + +#include <linux/netlink.h> +#include <linux/netfilter/nfnetlink.h> +#include <linux/netfilter/nf_tables.h> +#include <linux/netfilter/nf_tables_compat.h> + +#include <libmnl/libmnl.h> +#include <libnftnl/table.h> +#include <libnftnl/chain.h> +#include <libnftnl/rule.h> +#include <libnftnl/expr.h> + +#include <netinet/in.h> /* inet_ntoa */ +#include <arpa/inet.h> + +#include "nft.h" +#include "xshared.h" /* proto_to_name */ +#include "nft-shared.h" +#include "xtables-config-parser.h" + +static void *nft_fn; + +int mnl_talk(struct nft_handle *h, struct nlmsghdr *nlh, + int (*cb)(const struct nlmsghdr *nlh, void *data), + void *data) +{ + int ret; + char buf[MNL_SOCKET_BUFFER_SIZE]; + + if (mnl_socket_sendto(h->nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(h->nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, h->seq, h->portid, cb, data); + if (ret <= 0) + break; + + ret = mnl_socket_recvfrom(h->nl, buf, sizeof(buf)); + } + if (ret == -1) { + return -1; + } + + return 0; +} + +static LIST_HEAD(batch_page_list); +static int batch_num_pages; + +struct batch_page { + struct list_head head; + struct mnl_nlmsg_batch *batch; +}; + +/* selected batch page is 256 Kbytes long to load ruleset of + * half a million rules without hitting -EMSGSIZE due to large + * iovec. + */ +#define BATCH_PAGE_SIZE getpagesize() * 32 + +static struct mnl_nlmsg_batch *mnl_nft_batch_alloc(void) +{ + static char *buf; + + /* libmnl needs higher buffer to handle batch overflows */ + buf = malloc(BATCH_PAGE_SIZE + getpagesize()); + if (buf == NULL) + return NULL; + + return mnl_nlmsg_batch_start(buf, BATCH_PAGE_SIZE); +} + +static struct mnl_nlmsg_batch * +mnl_nft_batch_page_add(struct mnl_nlmsg_batch *batch) +{ + struct batch_page *batch_page; + + batch_page = malloc(sizeof(struct batch_page)); + if (batch_page == NULL) + return NULL; + + batch_page->batch = batch; + list_add_tail(&batch_page->head, &batch_page_list); + batch_num_pages++; + + return mnl_nft_batch_alloc(); +} + +static int nlbuffsiz; + +static void mnl_nft_set_sndbuffer(const struct mnl_socket *nl) +{ + int newbuffsiz; + + if (batch_num_pages * BATCH_PAGE_SIZE <= nlbuffsiz) + return; + + newbuffsiz = batch_num_pages * BATCH_PAGE_SIZE; + + /* Rise sender buffer length to avoid hitting -EMSGSIZE */ + if (setsockopt(mnl_socket_get_fd(nl), SOL_SOCKET, SO_SNDBUFFORCE, + &newbuffsiz, sizeof(socklen_t)) < 0) + return; + + nlbuffsiz = newbuffsiz; +} + +static ssize_t mnl_nft_socket_sendmsg(const struct mnl_socket *nl) +{ + static const struct sockaddr_nl snl = { + .nl_family = AF_NETLINK + }; + struct iovec iov[batch_num_pages]; + struct msghdr msg = { + .msg_name = (struct sockaddr *) &snl, + .msg_namelen = sizeof(snl), + .msg_iov = iov, + .msg_iovlen = batch_num_pages, + }; + struct batch_page *batch_page, *next; + int i = 0; + + mnl_nft_set_sndbuffer(nl); + + list_for_each_entry_safe(batch_page, next, &batch_page_list, head) { + iov[i].iov_base = mnl_nlmsg_batch_head(batch_page->batch); + iov[i].iov_len = mnl_nlmsg_batch_size(batch_page->batch); + i++; +#ifdef NL_DEBUG + mnl_nlmsg_fprintf(stdout, + mnl_nlmsg_batch_head(batch_page->batch), + mnl_nlmsg_batch_size(batch_page->batch), + sizeof(struct nfgenmsg)); +#endif + list_del(&batch_page->head); + free(batch_page->batch); + free(batch_page); + batch_num_pages--; + } + + return sendmsg(mnl_socket_get_fd(nl), &msg, 0); +} + +static int cb_err(const struct nlmsghdr *nlh, void *data) +{ + /* We can provide better error reporting than iptables-restore */ + errno = EINVAL; + return MNL_CB_ERROR; +} + +static mnl_cb_t cb_ctl_array[NLMSG_MIN_TYPE] = { + [NLMSG_ERROR] = cb_err, +}; + +static int mnl_nft_batch_talk(struct nft_handle *h) +{ + int ret, fd = mnl_socket_get_fd(h->nl); + char rcv_buf[MNL_SOCKET_BUFFER_SIZE]; + fd_set readfds; + struct timeval tv = { + .tv_sec = 0, + .tv_usec = 0 + }; + int err = 0; + + ret = mnl_nft_socket_sendmsg(h->nl); + if (ret == -1) { + perror("mnl_socket_sendmsg"); + return -1; + } + + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + + /* receive and digest all the acknowledgments from the kernel. */ + ret = select(fd+1, &readfds, NULL, NULL, &tv); + if (ret == -1) { + perror("select"); + return -1; + } + while (ret > 0 && FD_ISSET(fd, &readfds)) { + ret = mnl_socket_recvfrom(h->nl, rcv_buf, sizeof(rcv_buf)); + if (ret == -1) { + perror("mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run2(rcv_buf, ret, 0, h->portid, + NULL, NULL, cb_ctl_array, + MNL_ARRAY_SIZE(cb_ctl_array)); + /* Continue on error, make sure we get all acknoledgments */ + if (ret == -1) + err = errno; + + ret = select(fd+1, &readfds, NULL, NULL, &tv); + if (ret == -1) { + perror("select"); + return -1; + } + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + } + return err ? -1 : 0; +} + +static void mnl_nft_batch_put(struct mnl_nlmsg_batch *batch, int type, + uint32_t seq) +{ + struct nlmsghdr *nlh; + struct nfgenmsg *nfg; + + nlh = mnl_nlmsg_put_header(mnl_nlmsg_batch_current(batch)); + nlh->nlmsg_type = type; + nlh->nlmsg_flags = NLM_F_REQUEST; + nlh->nlmsg_seq = seq; + + nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_INET; + nfg->version = NFNETLINK_V0; + nfg->res_id = NFNL_SUBSYS_NFTABLES; + + if (!mnl_nlmsg_batch_next(batch)) + mnl_nft_batch_page_add(batch); +} + +static void mnl_nft_batch_begin(struct mnl_nlmsg_batch *batch, uint32_t seq) +{ + mnl_nft_batch_put(batch, NFNL_MSG_BATCH_BEGIN, seq); +} + +static void mnl_nft_batch_end(struct mnl_nlmsg_batch *batch, uint32_t seq) +{ + mnl_nft_batch_put(batch, NFNL_MSG_BATCH_END, seq); +} + +struct builtin_table xtables_ipv4[TABLES_MAX] = { + [RAW] = { + .name = "raw", + .chains = { + { + .name = "PREROUTING", + .type = "filter", + .prio = -300, /* NF_IP_PRI_RAW */ + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = -300, /* NF_IP_PRI_RAW */ + .hook = NF_INET_LOCAL_OUT, + }, + }, + }, + [MANGLE] = { + .name = "mangle", + .chains = { + { + .name = "PREROUTING", + .type = "filter", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "INPUT", + .type = "filter", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "FORWARD", + .type = "filter", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_FORWARD, + }, + { + .name = "OUTPUT", + .type = "route", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_LOCAL_OUT, + }, + { + .name = "POSTROUTING", + .type = "filter", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_POST_ROUTING, + }, + }, + }, + [FILTER] = { + .name = "filter", + .chains = { + { + .name = "INPUT", + .type = "filter", + .prio = 0, /* NF_IP_PRI_FILTER */ + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "FORWARD", + .type = "filter", + .prio = 0, /* NF_IP_PRI_FILTER */ + .hook = NF_INET_FORWARD, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = 0, /* NF_IP_PRI_FILTER */ + .hook = NF_INET_LOCAL_OUT, + }, + }, + }, + [SECURITY] = { + .name = "security", + .chains = { + { + .name = "INPUT", + .type = "filter", + .prio = 150, /* NF_IP_PRI_SECURITY */ + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "FORWARD", + .type = "filter", + .prio = 150, /* NF_IP_PRI_SECURITY */ + .hook = NF_INET_FORWARD, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = 150, /* NF_IP_PRI_SECURITY */ + .hook = NF_INET_LOCAL_OUT, + }, + }, + }, + [NAT] = { + .name = "nat", + .chains = { + { + .name = "PREROUTING", + .type = "nat", + .prio = -100, /* NF_IP_PRI_NAT_DST */ + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "INPUT", + .type = "nat", + .prio = 100, /* NF_IP_PRI_NAT_SRC */ + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "POSTROUTING", + .type = "nat", + .prio = 100, /* NF_IP_PRI_NAT_SRC */ + .hook = NF_INET_POST_ROUTING, + }, + { + .name = "OUTPUT", + .type = "nat", + .prio = -100, /* NF_IP_PRI_NAT_DST */ + .hook = NF_INET_LOCAL_OUT, + }, + }, + }, +}; + +#include <linux/netfilter_arp.h> + +struct builtin_table xtables_arp[TABLES_MAX] = { + [FILTER] = { + .name = "filter", + .chains = { + { + .name = "INPUT", + .type = "filter", + .prio = NF_IP_PRI_FILTER, + .hook = NF_ARP_IN, + }, + { + .name = "FORWARD", + .type = "filter", + .prio = NF_IP_PRI_FILTER, + .hook = NF_ARP_FORWARD, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = NF_IP_PRI_FILTER, + .hook = NF_ARP_OUT, + }, + }, + }, +}; + +int +nft_table_builtin_add(struct nft_handle *h, struct builtin_table *_t, + bool dormant) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_table *t; + int ret; + + if (_t->initialized) + return 0; + + t = nft_table_alloc(); + if (t == NULL) + return -1; + + nft_table_attr_set(t, NFT_TABLE_ATTR_NAME, (char *)_t->name); + if (dormant) { + nft_table_attr_set_u32(t, NFT_TABLE_ATTR_FLAGS, + NFT_TABLE_F_DORMANT); + } + + nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, h->family, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_table_nlmsg_build_payload(nlh, t); + nft_table_free(t); + +#ifdef NLDEBUG + char tmp[1024]; + + nft_table_snprintf(tmp, sizeof(tmp), t, 0, 0); + printf("DEBUG: table: %s\n", tmp); + mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg)); +#endif + + ret = mnl_talk(h, nlh, NULL, NULL); + if (ret == 0 || errno == EEXIST) + _t->initialized = true; + + return ret; +} + +struct nft_chain * +nft_chain_builtin_alloc(struct builtin_table *table, + struct builtin_chain *chain, int policy) +{ + struct nft_chain *c; + + c = nft_chain_alloc(); + if (c == NULL) + return NULL; + + nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table->name); + nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)chain->name); + nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_HOOKNUM, chain->hook); + nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_PRIO, chain->prio); + nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_POLICY, policy); + nft_chain_attr_set(c, NFT_CHAIN_ATTR_TYPE, (char *)chain->type); + + return c; +} + +void +nft_chain_builtin_add(struct nft_handle *h, struct builtin_table *table, + struct builtin_chain *chain, int policy) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain *c; + + c = nft_chain_builtin_alloc(table, chain, policy); + if (c == NULL) + return; + + /* NLM_F_CREATE requests module autoloading */ + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family, + NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, + h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + nft_chain_free(c); + + mnl_talk(h, nlh, NULL, NULL); +} + +/* find if built-in table already exists */ +struct builtin_table * +nft_table_builtin_find(struct nft_handle *h, const char *table) +{ + int i; + bool found = false; + + for (i=0; i<TABLES_MAX; i++) { + if (h->tables[i].name == NULL) + break; + + if (strcmp(h->tables[i].name, table) != 0) + continue; + + found = true; + break; + } + + return found ? &h->tables[i] : NULL; +} + +/* find if built-in chain already exists */ +struct builtin_chain * +nft_chain_builtin_find(struct builtin_table *t, const char *chain) +{ + int i; + bool found = false; + + for (i=0; i<NF_IP_NUMHOOKS && t->chains[i].name != NULL; i++) { + if (strcmp(t->chains[i].name, chain) != 0) + continue; + + found = true; + break; + } + return found ? &t->chains[i] : NULL; +} + +static void +__nft_chain_builtin_init(struct nft_handle *h, + struct builtin_table *table, const char *chain, + int policy) +{ + int i, default_policy; + + /* Initialize all built-in chains. Exception, for e one received as + * parameter, set the default policy as requested. + */ + for (i=0; i<NF_IP_NUMHOOKS && table->chains[i].name != NULL; i++) { + if (chain && strcmp(table->chains[i].name, chain) == 0) + default_policy = policy; + else + default_policy = NF_ACCEPT; + + nft_chain_builtin_add(h, table, &table->chains[i], + default_policy); + } +} + +int +nft_chain_builtin_init(struct nft_handle *h, const char *table, + const char *chain, int policy) +{ + int ret = 0; + struct builtin_table *t; + + t = nft_table_builtin_find(h, table); + if (t == NULL) { + ret = -1; + goto out; + } + if (nft_table_builtin_add(h, t, false) < 0) { + /* Built-in table already initialized, skip. */ + if (errno == EEXIST) + goto out; + } + __nft_chain_builtin_init(h, t, chain, policy); +out: + return ret; +} + +static bool nft_chain_builtin(struct nft_chain *c) +{ + /* Check if this chain has hook number, in that case is built-in. + * Should we better export the flags to user-space via nf_tables? + */ + return nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM) != NULL; +} + +int nft_init(struct nft_handle *h, struct builtin_table *t) +{ + h->nl = mnl_socket_open(NETLINK_NETFILTER); + if (h->nl == NULL) { + perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(h->nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + return -1; + } + h->portid = mnl_socket_get_portid(h->nl); + h->tables = t; + + INIT_LIST_HEAD(&h->rule_list); + + h->batch = mnl_nft_batch_alloc(); + + return 0; +} + +void nft_fini(struct nft_handle *h) +{ + mnl_socket_close(h->nl); + free(mnl_nlmsg_batch_head(h->batch)); + mnl_nlmsg_batch_stop(h->batch); +} + +int nft_table_add(struct nft_handle *h, const struct nft_table *t) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, h->family, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_table_nlmsg_build_payload(nlh, t); + + return mnl_talk(h, nlh, NULL, NULL); +} + +int nft_chain_add(struct nft_handle *h, const struct nft_chain *c) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + + return mnl_talk(h, nlh, NULL, NULL); +} + +int nft_table_set_dormant(struct nft_handle *h, const char *table) +{ + int ret = 0, i; + struct builtin_table *t; + + t = nft_table_builtin_find(h, table); + if (t == NULL) { + ret = -1; + goto out; + } + /* Add this table as dormant */ + if (nft_table_builtin_add(h, t, true) < 0) { + /* Built-in table already initialized, skip. */ + if (errno == EEXIST) + goto out; + } + for (i=0; t->chains[i].name != NULL && i<NF_INET_NUMHOOKS; i++) + __nft_chain_builtin_init(h, t, t->chains[i].name, NF_ACCEPT); +out: + return ret; +} + +int nft_table_wake_dormant(struct nft_handle *h, const char *table) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_table *t; + + t = nft_table_alloc(); + if (t == NULL) + return -1; + + nft_table_attr_set(t, NFT_TABLE_ATTR_NAME, (char *)table); + nft_table_attr_set_u32(t, NFT_TABLE_ATTR_FLAGS, 0); + + nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, h->family, + NLM_F_ACK, h->seq); + nft_table_nlmsg_build_payload(nlh, t); + nft_table_free(t); + + return mnl_talk(h, nlh, NULL, NULL); +} + +static void nft_chain_print_debug(struct nft_chain *c, struct nlmsghdr *nlh) +{ +#ifdef NLDEBUG + char tmp[1024]; + + nft_chain_snprintf(tmp, sizeof(tmp), c, 0, 0); + printf("DEBUG: chain: %s\n", tmp); + mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg)); +#endif +} + +static int +__nft_chain_set(struct nft_handle *h, const char *table, + const char *chain, int policy, + const struct xt_counters *counters) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain *c; + struct builtin_table *_t; + struct builtin_chain *_c; + + _t = nft_table_builtin_find(h, table); + /* if this built-in table does not exists, create it */ + if (_t != NULL) + nft_table_builtin_add(h, _t, false); + + _c = nft_chain_builtin_find(_t, chain); + if (_c != NULL) { + /* This is a built-in chain */ + c = nft_chain_builtin_alloc(_t, _c, policy); + if (c == NULL) + return -1; + } else { + errno = ENOENT; + return -1; + } + + if (counters) { + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_BYTES, + counters->bcnt); + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_PACKETS, + counters->pcnt); + } + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family, + h->restore ? NLM_F_ACK|NLM_F_CREATE : + NLM_F_ACK, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + + nft_chain_print_debug(c, nlh); + + nft_chain_free(c); + + return mnl_talk(h, nlh, NULL, NULL); +} + +int nft_chain_set(struct nft_handle *h, const char *table, + const char *chain, const char *policy, + const struct xt_counters *counters) +{ + int ret = -1; + + nft_fn = nft_chain_set; + + if (strcmp(policy, "DROP") == 0) + ret = __nft_chain_set(h, table, chain, NF_DROP, counters); + else if (strcmp(policy, "ACCEPT") == 0) + ret = __nft_chain_set(h, table, chain, NF_ACCEPT, counters); + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +static int __add_match(struct nft_rule_expr *e, struct xt_entry_match *m) +{ + void *info; + + nft_rule_expr_set(e, NFT_EXPR_MT_NAME, m->u.user.name, strlen(m->u.user.name)); + nft_rule_expr_set_u32(e, NFT_EXPR_MT_REV, m->u.user.revision); + + info = calloc(1, m->u.match_size); + if (info == NULL) + return -ENOMEM; + + memcpy(info, m->data, m->u.match_size - sizeof(*m)); + nft_rule_expr_set(e, NFT_EXPR_MT_INFO, info, m->u.match_size - sizeof(*m)); + + return 0; +} + +int add_match(struct nft_rule *r, struct xt_entry_match *m) +{ + struct nft_rule_expr *expr; + int ret; + + expr = nft_rule_expr_alloc("match"); + if (expr == NULL) + return -ENOMEM; + + ret = __add_match(expr, m); + nft_rule_add_expr(r, expr); + + return ret; +} + +static int __add_target(struct nft_rule_expr *e, struct xt_entry_target *t) +{ + void *info; + + nft_rule_expr_set(e, NFT_EXPR_TG_NAME, t->u.user.name, + strlen(t->u.user.name)); + nft_rule_expr_set_u32(e, NFT_EXPR_TG_REV, t->u.user.revision); + + info = calloc(1, t->u.target_size); + if (info == NULL) + return -ENOMEM; + + memcpy(info, t->data, t->u.target_size - sizeof(*t)); + nft_rule_expr_set(e, NFT_EXPR_TG_INFO, info, t->u.target_size - sizeof(*t)); + + return 0; +} + +int add_target(struct nft_rule *r, struct xt_entry_target *t) +{ + struct nft_rule_expr *expr; + int ret; + + expr = nft_rule_expr_alloc("target"); + if (expr == NULL) + return -ENOMEM; + + ret = __add_target(expr, t); + nft_rule_add_expr(r, expr); + + return ret; +} + +int add_jumpto(struct nft_rule *r, const char *name, int verdict) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("immediate"); + if (expr == NULL) + return -ENOMEM; + + nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_DREG, NFT_REG_VERDICT); + nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_VERDICT, verdict); + nft_rule_expr_set_str(expr, NFT_EXPR_IMM_CHAIN, (char *)name); + nft_rule_add_expr(r, expr); + + return 0; +} + +int add_verdict(struct nft_rule *r, int verdict) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("immediate"); + if (expr == NULL) + return -ENOMEM; + + nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_DREG, NFT_REG_VERDICT); + nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_VERDICT, verdict); + nft_rule_add_expr(r, expr); + + return 0; +} + +int add_action(struct nft_rule *r, struct iptables_command_state *cs, + bool goto_set) +{ + int ret = 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 go / jump to chain */ + if (goto_set) + ret = add_jumpto(r, cs->jumpto, NFT_GOTO); + else + ret = add_jumpto(r, cs->jumpto, NFT_JUMP); + } + return ret; +} + +static void nft_rule_print_debug(struct nft_rule *r, struct nlmsghdr *nlh) +{ +#ifdef NLDEBUG + char tmp[1024]; + + nft_rule_snprintf(tmp, sizeof(tmp), r, 0, 0); + printf("DEBUG: rule: %s\n", tmp); + mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg)); +#endif +} + +int add_counters(struct nft_rule *r, uint64_t packets, uint64_t bytes) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("counter"); + if (expr == NULL) + return -ENOMEM; + + nft_rule_expr_set_u64(expr, NFT_EXPR_CTR_BYTES, packets); + nft_rule_expr_set_u64(expr, NFT_EXPR_CTR_PACKETS, bytes); + + nft_rule_add_expr(r, expr); + + return 0; +} + +void add_compat(struct nft_rule *r, uint32_t proto, bool inv) +{ + nft_rule_attr_set_u32(r, NFT_RULE_ATTR_COMPAT_PROTO, proto); + nft_rule_attr_set_u32(r, NFT_RULE_ATTR_COMPAT_FLAGS, + inv ? NFT_RULE_COMPAT_F_INV : 0); +} + +static struct nft_rule * +nft_rule_new(struct nft_handle *h, const char *chain, const char *table, + void *data) +{ + struct nft_rule *r; + + r = nft_rule_alloc(); + if (r == NULL) + return NULL; + + nft_rule_attr_set_u32(r, NFT_RULE_ATTR_FAMILY, h->family); + nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table); + nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain); + + if (h->ops->add(r, data) < 0) + goto err; + + return r; +err: + nft_rule_free(r); + return NULL; +} + +enum rule_update_type { + NFT_DO_APPEND, + NFT_DO_INSERT, + NFT_DO_REPLACE, + NFT_DO_DELETE, + NFT_DO_FLUSH, + NFT_DO_COMMIT, + NFT_DO_ABORT, +}; + +struct rule_update { + struct list_head head; + enum rule_update_type type; + struct nft_rule *rule; +}; + +static int rule_update_add(struct nft_handle *h, enum rule_update_type type, + struct nft_rule *r) +{ + struct rule_update *rupd; + + rupd = calloc(1, sizeof(struct rule_update)); + if (rupd == NULL) + return -1; + + rupd->rule = r; + rupd->type = type; + list_add_tail(&rupd->head, &h->rule_list); + h->rule_list_num++; + + return 0; +} + +int +nft_rule_append(struct nft_handle *h, const char *chain, const char *table, + void *data, uint64_t handle, bool verbose) +{ + struct nft_rule *r; + int type; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, chain, NF_ACCEPT); + + nft_fn = nft_rule_append; + + r = nft_rule_new(h, chain, table, data); + if (r == NULL) + return 0; + + if (handle > 0) { + nft_rule_attr_set(r, NFT_RULE_ATTR_HANDLE, &handle); + type = NFT_DO_REPLACE; + } else + type = NFT_DO_APPEND; + + if (rule_update_add(h, type, r) < 0) + nft_rule_free(r); + + return 1; +} + +void +nft_rule_print_save(const void *data, + struct nft_rule *r, enum nft_rule_print type, + unsigned int format) +{ + const char *chain = nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN); + int family = nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY); + struct nft_family_ops *ops; + + /* print chain name */ + switch(type) { + case NFT_RULE_APPEND: + printf("-A %s ", chain); + break; + case NFT_RULE_DEL: + printf("-D %s ", chain); + break; + } + + ops = nft_family_ops_lookup(family); + + if (ops->save_firewall) + ops->save_firewall(data, format); + +} + +static int nft_chain_list_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_chain *c; + struct nft_chain_list *list = data; + + c = nft_chain_alloc(); + if (c == NULL) { + perror("OOM"); + goto err; + } + + if (nft_chain_nlmsg_parse(nlh, c) < 0) { + perror("nft_rule_nlmsg_parse"); + goto out; + } + + nft_chain_list_add_tail(c, list); + + return MNL_CB_OK; +out: + nft_chain_free(c); +err: + return MNL_CB_OK; +} + +static struct nft_chain_list *nft_chain_list_get(struct nft_handle *h) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain_list *list; + + list = nft_chain_list_alloc(); + if (list == NULL) { + errno = ENOMEM; + return NULL; + } + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, h->family, + NLM_F_DUMP, h->seq); + + mnl_talk(h, nlh, nft_chain_list_cb, list); + + return list; +} + +struct nft_chain_list *nft_chain_dump(struct nft_handle *h) +{ + return nft_chain_list_get(h); +} + +static const char *policy_name[NF_ACCEPT+1] = { + [NF_DROP] = "DROP", + [NF_ACCEPT] = "ACCEPT", +}; + +static void nft_chain_print_save(struct nft_chain *c, bool basechain) +{ + const char *chain = nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + uint64_t pkts = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_PACKETS); + uint64_t bytes = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_BYTES); + + /* print chain name */ + if (basechain) { + uint32_t pol = NF_ACCEPT; + + /* no default chain policy? don't crash, display accept */ + if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_POLICY)) + pol = nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_POLICY); + + printf(":%s %s [%"PRIu64":%"PRIu64"]\n", chain, policy_name[pol], + pkts, bytes); + } else + printf(":%s - [%"PRIu64":%"PRIu64"]\n", chain, pkts, bytes); +} + +int nft_chain_save(struct nft_handle *h, struct nft_chain_list *list, + const char *table) +{ + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + return 0; + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_table = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + bool basechain = false; + + if (strcmp(table, chain_table) != 0) + goto next; + + basechain = nft_chain_builtin(c); + nft_chain_print_save(c, basechain); +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); + nft_chain_list_free(list); + + return 1; +} + +static int nft_rule_list_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_rule *r; + struct nft_rule_list *list = data; + + r = nft_rule_alloc(); + if (r == NULL) { + perror("OOM"); + goto err; + } + + if (nft_rule_nlmsg_parse(nlh, r) < 0) { + perror("nft_rule_nlmsg_parse"); + goto out; + } + + nft_rule_list_add_tail(r, list); + + return MNL_CB_OK; +out: + nft_rule_free(r); + nft_rule_list_free(list); +err: + return MNL_CB_OK; +} + +static struct nft_rule_list *nft_rule_list_get(struct nft_handle *h) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_rule_list *list; + int ret; + + list = nft_rule_list_alloc(); + if (list == NULL) + return 0; + + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, h->family, + NLM_F_DUMP, h->seq); + + ret = mnl_talk(h, nlh, nft_rule_list_cb, list); + if (ret < 0) { + nft_rule_list_free(list); + return NULL; + } + + return list; +} + +int nft_rule_save(struct nft_handle *h, const char *table, bool counters) +{ + struct nft_rule_list *list; + struct nft_rule_list_iter *iter; + struct nft_rule *r; + + list = nft_rule_list_get(h); + if (list == NULL) + return 0; + + iter = nft_rule_list_iter_create(list); + if (iter == NULL) + return 0; + + r = nft_rule_list_iter_next(iter); + while (r != NULL) { + const char *rule_table = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_TABLE); + struct iptables_command_state cs = {}; + + if (strcmp(table, rule_table) != 0) + goto next; + + nft_rule_to_iptables_command_state(r, &cs); + + nft_rule_print_save(&cs, r, NFT_RULE_APPEND, + counters ? 0 : FMT_NOCOUNTS); + +next: + r = nft_rule_list_iter_next(iter); + } + + nft_rule_list_iter_destroy(iter); + nft_rule_list_free(list); + + /* the core expects 1 for success and 0 for error */ + return 1; +} + +static void +__nft_rule_flush(struct nft_handle *h, const char *table, const char *chain) +{ + struct nft_rule *r; + + r = nft_rule_alloc(); + if (r == NULL) + return; + + nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table); + nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain); + + if (rule_update_add(h, NFT_DO_FLUSH, r) < 0) + nft_rule_free(r); +} + +int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table) +{ + int ret; + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + nft_fn = nft_rule_flush; + + list = nft_chain_list_get(h); + if (list == NULL) { + ret = 0; + goto err; + } + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + goto err; + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *table_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + + if (strcmp(table, table_name) != 0) + goto next; + + if (chain != NULL && strcmp(chain, chain_name) != 0) + goto next; + + __nft_rule_flush(h, table_name, chain_name); + + if (chain != NULL) + break; +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); +err: + nft_chain_list_free(list); + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *table) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain *c; + int ret; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, NULL, NF_ACCEPT); + + c = nft_chain_alloc(); + if (c == NULL) + return 0; + + nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table); + nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)chain); + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + nft_chain_free(c); + + ret = mnl_talk(h, nlh, NULL, NULL); + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +static int __nft_chain_del(struct nft_handle *h, struct nft_chain *c) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELCHAIN, h->family, + NLM_F_ACK, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + + return mnl_talk(h, nlh, NULL, NULL); +} + +int nft_chain_user_del(struct nft_handle *h, const char *chain, const char *table) +{ + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + int ret = 0; + int deleted_ctr = 0; + + list = nft_chain_list_get(h); + if (list == NULL) + goto err; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + goto err; + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *table_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + + /* don't delete built-in chain */ + if (nft_chain_builtin(c)) + goto next; + + if (strcmp(table, table_name) != 0) + goto next; + + if (chain != NULL && strcmp(chain, chain_name) != 0) + goto next; + + ret = __nft_chain_del(h, c); + if (ret < 0) + break; + + deleted_ctr++; + + if (chain != NULL) + break; +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); +err: + nft_chain_list_free(list); + + /* chain not found */ + if (ret < 0 && deleted_ctr == 0) + errno = ENOENT; + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +struct nft_chain * +nft_chain_list_find(struct nft_chain_list *list, + const char *table, const char *chain) +{ + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + return NULL; + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *table_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + + if (strcmp(table, table_name) != 0) + goto next; + + if (strcmp(chain, chain_name) != 0) + goto next; + + nft_chain_list_iter_destroy(iter); + return c; +next: + c = nft_chain_list_iter_next(iter); + } + nft_chain_list_iter_destroy(iter); + return NULL; +} + +static struct nft_chain * +nft_chain_find(struct nft_handle *h, const char *table, const char *chain) +{ + struct nft_chain_list *list; + + list = nft_chain_list_get(h); + if (list == NULL) + return NULL; + + return nft_chain_list_find(list, table, chain); +} + +int nft_chain_user_rename(struct nft_handle *h,const char *chain, + const char *table, const char *newname) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain *c; + uint64_t handle; + int ret; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, NULL, NF_ACCEPT); + + /* Find the old chain to be renamed */ + c = nft_chain_find(h, table, chain); + if (c == NULL) { + errno = ENOENT; + return -1; + } + handle = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_HANDLE); + + /* Now prepare the new name for the chain */ + c = nft_chain_alloc(); + if (c == NULL) + return -1; + + nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table); + nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)newname); + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_HANDLE, handle); + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family, + NLM_F_ACK, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + nft_chain_free(c); + + ret = mnl_talk(h, nlh, NULL, NULL); + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +static int nft_table_list_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_table *t; + struct nft_table_list *list = data; + + t = nft_table_alloc(); + if (t == NULL) { + perror("OOM"); + goto err; + } + + if (nft_table_nlmsg_parse(nlh, t) < 0) { + perror("nft_rule_nlmsg_parse"); + goto out; + } + + nft_table_list_add_tail(t, list); + + return MNL_CB_OK; +out: + nft_table_free(t); +err: + return MNL_CB_OK; +} + +static struct nft_table_list *nft_table_list_get(struct nft_handle *h) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_table_list *list; + + list = nft_table_list_alloc(); + if (list == NULL) + return 0; + + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, h->family, + NLM_F_DUMP, h->seq); + + mnl_talk(h, nlh, nft_table_list_cb, list); + + return list; +} + +bool nft_table_find(struct nft_handle *h, const char *tablename) +{ + struct nft_table_list *list; + struct nft_table_list_iter *iter; + struct nft_table *t; + bool ret = false; + + list = nft_table_list_get(h); + if (list == NULL) + goto err; + + iter = nft_table_list_iter_create(list); + if (iter == NULL) + goto err; + + t = nft_table_list_iter_next(iter); + while (t != NULL) { + const char *this_tablename = + nft_table_attr_get(t, NFT_TABLE_ATTR_NAME); + + if (strcmp(tablename, this_tablename) == 0) + return true; + + t = nft_table_list_iter_next(iter); + } + + nft_table_list_free(list); + +err: + return ret; +} + +int nft_for_each_table(struct nft_handle *h, + int (*func)(struct nft_handle *h, const char *tablename, bool counters), + bool counters) +{ + int ret = 1; + struct nft_table_list *list; + struct nft_table_list_iter *iter; + struct nft_table *t; + + list = nft_table_list_get(h); + if (list == NULL) { + ret = 0; + goto err; + } + + iter = nft_table_list_iter_create(list); + if (iter == NULL) + return 0; + + t = nft_table_list_iter_next(iter); + while (t != NULL) { + const char *tablename = + nft_table_attr_get(t, NFT_TABLE_ATTR_NAME); + + func(h, tablename, counters); + + t = nft_table_list_iter_next(iter); + } + + nft_table_list_free(list); + +err: + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +int nft_table_purge_chains(struct nft_handle *h, const char *this_table, + struct nft_chain_list *chain_list) +{ + struct nft_chain_list_iter *iter; + struct nft_chain *chain_obj; + + iter = nft_chain_list_iter_create(chain_list); + if (iter == NULL) + return 0; + + chain_obj = nft_chain_list_iter_next(iter); + while (chain_obj != NULL) { + const char *table = + nft_chain_attr_get_str(chain_obj, NFT_CHAIN_ATTR_TABLE); + + if (strcmp(this_table, table) != 0) + goto next; + + if (nft_chain_builtin(chain_obj)) + goto next; + + if ( __nft_chain_del(h, chain_obj) < 0) { + if (errno != EBUSY) + return -1; + } +next: + chain_obj = nft_chain_list_iter_next(iter); + } + nft_chain_list_iter_destroy(iter); + + return 0; +} + +static int __nft_rule_del(struct nft_handle *h, struct nft_rule_list *list, + struct nft_rule *r) +{ + int ret; + + nft_rule_list_del(r); + + ret = rule_update_add(h, NFT_DO_DELETE, r); + if (ret < 0) { + nft_rule_free(r); + return -1; + } + return 1; +} + +struct nft_rule_list *nft_rule_list_create(struct nft_handle *h) +{ + return nft_rule_list_get(h); +} + +void nft_rule_list_destroy(struct nft_rule_list *list) +{ + nft_rule_list_free(list); +} + +static struct nft_rule * +nft_rule_find(struct nft_handle *h, struct nft_rule_list *list, + const char *chain, const char *table, void *data, int rulenum) +{ + struct nft_rule *r; + struct nft_rule_list_iter *iter; + int rule_ctr = 0; + bool found = false; + + iter = nft_rule_list_iter_create(list); + if (iter == NULL) + return 0; + + r = nft_rule_list_iter_next(iter); + while (r != NULL) { + const char *rule_table = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_TABLE); + const char *rule_chain = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN); + + if (strcmp(table, rule_table) != 0 || + strcmp(chain, rule_chain) != 0) { + DEBUGP("different chain / table\n"); + goto next; + } + + if (rulenum >= 0) { + /* Delete by rule number case */ + if (rule_ctr != rulenum) + goto next; + found = true; + break; + } else { + found = h->ops->rule_find(h->ops, r, data); + if (found) + break; + } +next: + rule_ctr++; + r = nft_rule_list_iter_next(iter); + } + + nft_rule_list_iter_destroy(iter); + + return found ? r : NULL; +} + +int nft_rule_check(struct nft_handle *h, const char *chain, + const char *table, void *data, bool verbose) +{ + struct nft_rule_list *list; + int ret; + + nft_fn = nft_rule_check; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + ret = nft_rule_find(h, list, chain, table, data, -1) ? 1 : 0; + if (ret == 0) + errno = ENOENT; + + nft_rule_list_destroy(list); + + return ret; +} + +int nft_rule_delete(struct nft_handle *h, const char *chain, + const char *table, void *data, bool verbose) +{ + int ret = 0; + struct nft_rule *r; + struct nft_rule_list *list; + + nft_fn = nft_rule_delete; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + r = nft_rule_find(h, list, chain, table, data, -1); + if (r != NULL) { + ret =__nft_rule_del(h, list, r); + if (ret < 0) + errno = ENOMEM; + } else + errno = ENOENT; + + nft_rule_list_destroy(list); + + return ret; +} + +static int +nft_rule_add(struct nft_handle *h, const char *chain, + const char *table, struct iptables_command_state *cs, + uint64_t handle, bool verbose) +{ + struct nft_rule *r; + + r = nft_rule_new(h, chain, table, cs); + if (r == NULL) + return 0; + + if (handle > 0) + nft_rule_attr_set_u64(r, NFT_RULE_ATTR_POSITION, handle); + + if (rule_update_add(h, NFT_DO_INSERT, r) < 0) { + nft_rule_free(r); + return 0; + } + + return 1; +} + +int nft_rule_insert(struct nft_handle *h, const char *chain, + const char *table, void *data, int rulenum, bool verbose) +{ + struct nft_rule_list *list; + struct nft_rule *r; + uint64_t handle = 0; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, chain, NF_ACCEPT); + + nft_fn = nft_rule_insert; + + if (rulenum > 0) { + list = nft_rule_list_create(h); + if (list == NULL) + goto err; + + r = nft_rule_find(h, list, chain, table, data, rulenum); + if (r == NULL) { + errno = ENOENT; + goto err; + } + + handle = nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE); + DEBUGP("adding after rule handle %"PRIu64"\n", handle); + + nft_rule_list_destroy(list); + } + + return nft_rule_add(h, chain, table, data, handle, verbose); +err: + nft_rule_list_destroy(list); + return 0; +} + +int nft_rule_delete_num(struct nft_handle *h, const char *chain, + const char *table, int rulenum, bool verbose) +{ + int ret = 0; + struct nft_rule *r; + struct nft_rule_list *list; + + nft_fn = nft_rule_delete_num; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + r = nft_rule_find(h, list, chain, table, NULL, rulenum); + if (r != NULL) { + ret = 1; + + DEBUGP("deleting rule by number %d\n", rulenum); + ret = __nft_rule_del(h, list, r); + if (ret < 0) + errno = ENOMEM; + } else + errno = ENOENT; + + nft_rule_list_destroy(list); + + return ret; +} + +int nft_rule_replace(struct nft_handle *h, const char *chain, + const char *table, void *data, int rulenum, bool verbose) +{ + int ret = 0; + struct nft_rule *r; + struct nft_rule_list *list; + + nft_fn = nft_rule_replace; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + r = nft_rule_find(h, list, chain, table, data, rulenum); + if (r != NULL) { + DEBUGP("replacing rule with handle=%llu\n", + (unsigned long long) + nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE)); + + ret = nft_rule_append(h, chain, table, data, + nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE), + verbose); + } else + errno = ENOENT; + + nft_rule_list_destroy(list); + + return ret; +} + +static void +print_header(unsigned int format, const char *chain, const char *pol, + const struct xt_counters *counters, bool basechain, uint32_t refs) +{ + printf("Chain %s", chain); + if (basechain) { + printf(" (policy %s", pol); + if (!(format & FMT_NOCOUNTS)) { + fputc(' ', stdout); + xtables_print_num(counters->pcnt, (format|FMT_NOTABLE)); + fputs("packets, ", stdout); + xtables_print_num(counters->bcnt, (format|FMT_NOTABLE)); + fputs("bytes", stdout); + } + printf(")\n"); + } else { + printf(" (%u references)\n", refs); + } + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4s ", "%s "), "num"); + if (!(format & FMT_NOCOUNTS)) { + if (format & FMT_KILOMEGAGIGA) { + printf(FMT("%5s ","%s "), "pkts"); + printf(FMT("%5s ","%s "), "bytes"); + } else { + printf(FMT("%8s ","%s "), "pkts"); + printf(FMT("%10s ","%s "), "bytes"); + } + } + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ","%s "), "target"); + fputs(" prot ", stdout); + if (format & FMT_OPTIONS) + fputs("opt", stdout); + if (format & FMT_VIA) { + printf(FMT(" %-6s ","%s "), "in"); + printf(FMT("%-6s ","%s "), "out"); + } + printf(FMT(" %-19s ","%s "), "source"); + printf(FMT(" %-19s "," %s "), "destination"); + printf("\n"); +} + +static int +__nft_rule_list(struct nft_handle *h, const char *chain, const char *table, + int rulenum, unsigned int format, + void (*cb)(struct nft_rule *r, unsigned int num, + unsigned int format)) +{ + struct nft_rule_list *list; + struct nft_rule_list_iter *iter; + struct nft_rule *r; + int rule_ctr = 0, ret = 0; + + list = nft_rule_list_get(h); + if (list == NULL) + return 0; + + iter = nft_rule_list_iter_create(list); + if (iter == NULL) + goto err; + + r = nft_rule_list_iter_next(iter); + while (r != NULL) { + const char *rule_table = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_TABLE); + const char *rule_chain = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN); + + rule_ctr++; + + if (strcmp(table, rule_table) != 0 || + strcmp(chain, rule_chain) != 0) + goto next; + + if (rulenum > 0 && rule_ctr != rulenum) { + /* List by rule number case */ + goto next; + } + + cb(r, rule_ctr, format); + if (rulenum > 0 && rule_ctr == rulenum) { + ret = 1; + break; + } + +next: + r = nft_rule_list_iter_next(iter); + } + + nft_rule_list_iter_destroy(iter); +err: + nft_rule_list_free(list); + + if (ret == 0) + errno = ENOENT; + + return ret; +} + +int nft_rule_list(struct nft_handle *h, const char *chain, const char *table, + int rulenum, unsigned int format) +{ + const struct nft_family_ops *ops; + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + bool found = false; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, NULL, NF_ACCEPT); + + ops = nft_family_ops_lookup(h->family); + + if (chain && rulenum) { + __nft_rule_list(h, chain, table, + rulenum, format, ops->print_firewall); + return 1; + } + + list = nft_chain_dump(h); + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + goto err; + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_table = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + uint32_t policy = + nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_POLICY); + uint32_t refs = + nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_USE); + struct xt_counters ctrs = { + .pcnt = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_PACKETS), + .bcnt = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_BYTES), + }; + bool basechain = false; + + if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM)) + basechain = true; + + if (strcmp(table, chain_table) != 0) + goto next; + if (chain && strcmp(chain, chain_name) != 0) + goto next; + + if (found) + printf("\n"); + + print_header(format, chain_name, policy_name[policy], + &ctrs, basechain, refs); + + __nft_rule_list(h, chain_name, table, + rulenum, format, ops->print_firewall); + + /* we printed the chain we wanted, stop processing. */ + if (chain) + break; + + found = true; + +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); +err: + nft_chain_list_free(list); + + return 1; +} + +static void +list_save(struct nft_rule *r, unsigned int num, unsigned int format) +{ + struct iptables_command_state cs = {}; + + nft_rule_to_iptables_command_state(r, &cs); + + nft_rule_print_save(&cs, r, NFT_RULE_APPEND, !(format & FMT_NOCOUNTS)); +} + +static int +nft_rule_list_chain_save(struct nft_handle *h, const char *chain, + const char *table, struct nft_chain_list *list, + int counters) +{ + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + return 0; + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_table = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + uint32_t policy = + nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_POLICY); + + if (strcmp(table, chain_table) != 0 || + (chain && strcmp(chain, chain_name) != 0)) + goto next; + + /* this is a base chain */ + if (nft_chain_builtin(c)) { + printf("-P %s %s", chain_name, policy_name[policy]); + + if (counters) { + printf(" -c %"PRIu64" %"PRIu64"\n", + nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_PACKETS), + nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_BYTES)); + } else + printf("\n"); + } else { + printf("-N %s\n", chain_name); + } +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); + + return 1; +} + +int nft_rule_list_save(struct nft_handle *h, const char *chain, + const char *table, int rulenum, int counters) +{ + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + int ret = 1; + + list = nft_chain_dump(h); + + /* Dump policies and custom chains first */ + if (!rulenum) + nft_rule_list_chain_save(h, chain, table, list, counters); + + /* Now dump out rules in this table */ + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + goto err; + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_table = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + + if (strcmp(table, chain_table) != 0) + goto next; + if (chain && strcmp(chain, chain_name) != 0) + goto next; + + ret = __nft_rule_list(h, chain_name, table, rulenum, + counters ? 0 : FMT_NOCOUNTS, list_save); + + /* we printed the chain we wanted, stop processing. */ + if (chain) + break; +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); +err: + nft_chain_list_free(list); + + return ret; +} + +int nft_rule_zero_counters(struct nft_handle *h, const char *chain, + const char *table, int rulenum) +{ + struct iptables_command_state cs = {}; + struct nft_rule_list *list; + struct nft_rule *r; + int ret = 0; + + nft_fn = nft_rule_delete; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + r = nft_rule_find(h, list, chain, table, NULL, rulenum); + if (r == NULL) { + errno = ENOENT; + ret = 1; + goto error; + } + + nft_rule_to_iptables_command_state(r, &cs); + + cs.counters.pcnt = cs.counters.bcnt = 0; + + ret = nft_rule_append(h, chain, table, &cs, + nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE), + false); + +error: + nft_rule_list_destroy(list); + + return ret; +} + +static int nft_action(struct nft_handle *h, int action) +{ + int flags = NLM_F_CREATE, type; + struct rule_update *n, *tmp; + struct nlmsghdr *nlh; + uint32_t seq = 1; + int ret; + + mnl_nft_batch_begin(h->batch, seq++); + + list_for_each_entry_safe(n, tmp, &h->rule_list, head) { + switch (n->type) { + case NFT_DO_APPEND: + type = NFT_MSG_NEWRULE; + flags |= NLM_F_APPEND; + break; + case NFT_DO_INSERT: + type = NFT_MSG_NEWRULE; + break; + case NFT_DO_REPLACE: + type = NFT_MSG_NEWRULE; + flags |= NLM_F_REPLACE; + break; + case NFT_DO_DELETE: + case NFT_DO_FLUSH: + type = NFT_MSG_DELRULE; + break; + default: + return 0; + } + + nlh = nft_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(h->batch), + type, h->family, flags, seq++); + nft_rule_nlmsg_build_payload(nlh, n->rule); + nft_rule_print_debug(n->rule, nlh); + + h->rule_list_num--; + list_del(&n->head); + nft_rule_free(n->rule); + free(n); + + if (!mnl_nlmsg_batch_next(h->batch)) + h->batch = mnl_nft_batch_page_add(h->batch); + } + + switch (action) { + case NFT_DO_COMMIT: + mnl_nft_batch_end(h->batch, seq++); + break; + case NFT_DO_ABORT: + break; + } + + if (!mnl_nlmsg_batch_is_empty(h->batch)) + h->batch = mnl_nft_batch_page_add(h->batch); + + ret = mnl_nft_batch_talk(h); + if (ret < 0) + perror("mnl_nft_batch_talk:"); + + mnl_nlmsg_batch_reset(h->batch); + + return ret == 0 ? 1 : 0; +} + +int nft_commit(struct nft_handle *h) +{ + return nft_action(h, NFT_DO_COMMIT); +} + +int nft_abort(struct nft_handle *h) +{ + return nft_action(h, NFT_DO_ABORT); +} + +int nft_compatible_revision(const char *name, uint8_t rev, int opt) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq, type; + int ret = 0; + + if (opt == IPT_SO_GET_REVISION_MATCH || + opt == IP6T_SO_GET_REVISION_MATCH) + type = 0; + else + type = 1; + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (NFNL_SUBSYS_NFT_COMPAT << 8) | NFNL_MSG_COMPAT_GET; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + + struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_INET; + nfg->version = NFNETLINK_V0; + nfg->res_id = 0; + + mnl_attr_put_strz(nlh, NFTA_COMPAT_NAME, name); + mnl_attr_put_u32(nlh, NFTA_COMPAT_REV, htonl(rev)); + mnl_attr_put_u32(nlh, NFTA_COMPAT_TYPE, htonl(type)); + + DEBUGP("requesting `%s' rev=%d type=%d via nft_compat\n", + name, rev, type); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + perror("mnl_socket_open"); + return 0; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + goto err; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + goto err; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret == -1) { + perror("mnl_socket_recvfrom"); + goto err; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret == -1) { + perror("mnl_cb_run"); + goto err; + } + +err: + mnl_socket_close(nl); + + return ret < 0 ? 0 : 1; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char *nft_strerror(int err) +{ + unsigned int i; + static struct table_struct { + void *fn; + int err; + const char *message; + } table[] = + { + { nft_chain_user_del, ENOTEMPTY, "Chain is not empty" }, + { nft_chain_user_del, EINVAL, "Can't delete built-in chain" }, + { nft_chain_user_del, EMLINK, + "Can't delete chain with references left" }, + { nft_chain_user_add, EEXIST, "Chain already exists" }, + { nft_rule_add, E2BIG, "Index of insertion too big" }, + { nft_rule_replace, E2BIG, "Index of replacement too big" }, + { nft_rule_delete_num, E2BIG, "Index of deletion too big" }, +/* { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, + { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, */ + { nft_rule_add, ELOOP, "Loop found in table" }, + { nft_rule_add, EINVAL, "Target problem" }, + /* ENOENT for DELETE probably means no matching rule */ + { nft_rule_delete, ENOENT, + "Bad rule (does a matching rule exist in that chain?)" }, + { nft_chain_set, ENOENT, "Bad built-in chain name" }, + { nft_chain_set, EINVAL, "Bad policy name" }, + { NULL, EPERM, "Permission denied (you must be root)" }, + { NULL, 0, "Incompatible with this kernel" }, + { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, + { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, + { NULL, ENOMEM, "Memory allocation problem" }, + { NULL, ENOENT, "No chain/target/match by that name" }, + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((!table[i].fn || table[i].fn == nft_fn) + && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} + +static void xtables_config_perror(uint32_t flags, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + if (flags & NFT_LOAD_VERBOSE) + vfprintf(stderr, fmt, args); + + va_end(args); +} + +int nft_xtables_config_load(struct nft_handle *h, const char *filename, + uint32_t flags) +{ + struct nft_table_list *table_list = nft_table_list_alloc(); + struct nft_chain_list *chain_list = nft_chain_list_alloc(); + struct nft_table_list_iter *titer = NULL; + struct nft_chain_list_iter *citer = NULL; + struct nft_table *table; + struct nft_chain *chain; + uint32_t table_family, chain_family; + bool found = false; + + if (h->restore) + return 0; + + if (xtables_config_parse(filename, table_list, chain_list) < 0) { + if (errno == ENOENT) { + xtables_config_perror(flags, + "configuration file `%s' does not exists\n", + filename); + } else { + xtables_config_perror(flags, + "Fatal error parsing config file: %s\n", + strerror(errno)); + } + goto err; + } + + /* Stage 1) create tables */ + titer = nft_table_list_iter_create(table_list); + while ((table = nft_table_list_iter_next(titer)) != NULL) { + table_family = nft_table_attr_get_u32(table, + NFT_TABLE_ATTR_FAMILY); + if (h->family != table_family) + continue; + + found = true; + + if (nft_table_add(h, table) < 0) { + if (errno == EEXIST) { + xtables_config_perror(flags, + "table `%s' already exists, skipping\n", + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME)); + } else { + xtables_config_perror(flags, + "table `%s' cannot be create, reason `%s'. Exitting\n", + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME), + strerror(errno)); + goto err; + } + continue; + } + xtables_config_perror(flags, "table `%s' has been created\n", + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME)); + } + nft_table_list_iter_destroy(titer); + nft_table_list_free(table_list); + + if (!found) + goto err; + + /* Stage 2) create chains */ + citer = nft_chain_list_iter_create(chain_list); + while ((chain = nft_chain_list_iter_next(citer)) != NULL) { + chain_family = nft_chain_attr_get_u32(chain, + NFT_CHAIN_ATTR_TABLE); + if (h->family != chain_family) + continue; + + if (nft_chain_add(h, chain) < 0) { + if (errno == EEXIST) { + xtables_config_perror(flags, + "chain `%s' already exists in table `%s', skipping\n", + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME), + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_TABLE)); + } else { + xtables_config_perror(flags, + "chain `%s' cannot be create, reason `%s'. Exitting\n", + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME), + strerror(errno)); + goto err; + } + continue; + } + + xtables_config_perror(flags, + "chain `%s' in table `%s' has been created\n", + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME), + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_TABLE)); + } + nft_chain_list_iter_destroy(citer); + nft_chain_list_free(chain_list); + + return 0; + +err: + nft_table_list_free(table_list); + nft_chain_list_free(chain_list); + + if (titer != NULL) + nft_table_list_iter_destroy(titer); + if (citer != NULL) + nft_chain_list_iter_destroy(citer); + + return -1; +} + +int nft_chain_zero_counters(struct nft_handle *h, const char *chain, + const char *table) +{ + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + struct nlmsghdr *nlh; + char buf[MNL_SOCKET_BUFFER_SIZE]; + int ret = 0; + + list = nft_chain_list_get(h); + if (list == NULL) + goto err; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + goto err; + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_name = + nft_chain_attr_get(c, NFT_CHAIN_ATTR_NAME); + const char *chain_table = + nft_chain_attr_get(c, NFT_CHAIN_ATTR_TABLE); + + if (strcmp(table, chain_table) != 0) + goto next; + + if (chain != NULL && strcmp(chain, chain_name) != 0) + goto next; + + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_PACKETS, 0); + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_BYTES, 0); + + nft_chain_attr_unset(c, NFT_CHAIN_ATTR_HANDLE); + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, + h->family, NLM_F_ACK, h->seq); + + nft_chain_nlmsg_build_payload(nlh, c); + + ret = mnl_talk(h, nlh, NULL, NULL); + if (ret < 0) + perror("mnl_talk:nft_chain_zero_counters"); + + if (chain != NULL) + break; +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); + +err: + nft_chain_list_free(list); + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} diff --git a/iptables/nft.h b/iptables/nft.h new file mode 100644 index 00000000..c31371c0 --- /dev/null +++ b/iptables/nft.h @@ -0,0 +1,175 @@ +#ifndef _NFT_H_ +#define _NFT_H_ + +#include "xshared.h" +#include "nft-shared.h" +#include <libiptc/linux_list.h> + +#define FILTER 0 +#define MANGLE 1 +#define RAW 2 +#define SECURITY 3 +#define NAT 4 +#define TABLES_MAX 5 + +struct builtin_chain { + const char *name; + const char *type; + uint32_t prio; + uint32_t hook; +}; + +struct builtin_table { + const char *name; + struct builtin_chain chains[NF_INET_NUMHOOKS]; + bool initialized; +}; + +struct nft_handle { + int family; + struct mnl_socket *nl; + uint32_t portid; + uint32_t seq; + struct list_head rule_list; + int rule_list_num; + struct mnl_nlmsg_batch *batch; + struct nft_family_ops *ops; + struct builtin_table *tables; + bool restore; +}; + +extern struct builtin_table xtables_ipv4[TABLES_MAX]; +extern struct builtin_table xtables_arp[TABLES_MAX]; + +int mnl_talk(struct nft_handle *h, struct nlmsghdr *nlh, + int (*cb)(const struct nlmsghdr *nlh, void *data), + void *data); +int nft_init(struct nft_handle *h, struct builtin_table *t); +void nft_fini(struct nft_handle *h); + +/* + * Operations with tables. + */ +struct nft_table; +struct nft_chain_list; + +int nft_table_builtin_add(struct nft_handle *h, struct builtin_table *_t, bool dormant); +struct builtin_table *nft_table_builtin_find(struct nft_handle *h, const char *table); +int nft_table_add(struct nft_handle *h, const struct nft_table *t); +int nft_for_each_table(struct nft_handle *h, int (*func)(struct nft_handle *h, const char *tablename, bool counters), bool counters); +bool nft_table_find(struct nft_handle *h, const char *tablename); +int nft_table_set_dormant(struct nft_handle *h, const char *table); +int nft_table_wake_dormant(struct nft_handle *h, const char *table); +int nft_table_purge_chains(struct nft_handle *h, const char *table, struct nft_chain_list *list); + +/* + * Operations with chains. + */ +struct nft_chain; + +struct nft_chain *nft_chain_builtin_alloc(struct builtin_table *table, struct builtin_chain *chain, int policy); +void nft_chain_builtin_add(struct nft_handle *h, struct builtin_table *table, struct builtin_chain *chain, int policy); +struct builtin_chain *nft_chain_builtin_find(struct builtin_table *t, const char *chain); +int nft_chain_builtin_init(struct nft_handle *h, const char *table, const char *chain, int policy); +int nft_chain_add(struct nft_handle *h, const struct nft_chain *c); +int nft_chain_set(struct nft_handle *h, const char *table, const char *chain, const char *policy, const struct xt_counters *counters); +struct nft_chain_list *nft_chain_dump(struct nft_handle *h); +struct nft_chain *nft_chain_list_find(struct nft_chain_list *list, const char *table, const char *chain); +int nft_chain_save(struct nft_handle *h, struct nft_chain_list *list, const char *table); +int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *table); +int nft_chain_user_del(struct nft_handle *h, const char *chain, const char *table); +int nft_chain_user_rename(struct nft_handle *h, const char *chain, const char *table, const char *newname); +int nft_chain_zero_counters(struct nft_handle *h, const char *chain, const char *table); + +/* + * Operations with rule-set. + */ +struct nft_rule; + +int nft_rule_append(struct nft_handle *h, const char *chain, const char *table, void *data, uint64_t handle, bool verbose); +int nft_rule_insert(struct nft_handle *h, const char *chain, const char *table, void *data, int rulenum, bool verbose); +int nft_rule_check(struct nft_handle *h, const char *chain, const char *table, void *data, bool verbose); +int nft_rule_delete(struct nft_handle *h, const char *chain, const char *table, void *data, bool verbose); +int nft_rule_delete_num(struct nft_handle *h, const char *chain, const char *table, int rulenum, bool verbose); +int nft_rule_replace(struct nft_handle *h, const char *chain, const char *table, void *data, int rulenum, bool verbose); +int nft_rule_list(struct nft_handle *h, const char *chain, const char *table, int rulenum, unsigned int format); +int nft_rule_list_save(struct nft_handle *h, const char *chain, const char *table, int rulenum, int counters); +int nft_rule_save(struct nft_handle *h, const char *table, bool counters); +int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table); +int nft_rule_zero_counters(struct nft_handle *h, const char *chain, const char *table, int rulenum); + +struct nft_rule_list *nft_rule_list_create(struct nft_handle *h); +void nft_rule_list_destroy(struct nft_rule_list *list); + +/* + * Operations used in userspace tools + */ +int add_counters(struct nft_rule *r, uint64_t packets, uint64_t bytes); +int add_verdict(struct nft_rule *r, int verdict); +int add_match(struct nft_rule *r, struct xt_entry_match *m); +int add_target(struct nft_rule *r, struct xt_entry_target *t); +int add_jumpto(struct nft_rule *r, const char *name, int verdict); +int add_action(struct nft_rule *r, struct iptables_command_state *cs, bool goto_set); + +enum nft_rule_print { + NFT_RULE_APPEND, + NFT_RULE_DEL, +}; + +void nft_rule_print_save(const void *data, + struct nft_rule *r, enum nft_rule_print type, + unsigned int format); + +/* + * global commit and abort + */ +int nft_commit(struct nft_handle *h); +int nft_abort(struct nft_handle *h); + +/* + * revision compatibility. + */ +int nft_compatible_revision(const char *name, uint8_t rev, int opt); + +/* + * Error reporting. + */ +const char *nft_strerror(int err); + +/* For xtables.c */ +int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table, bool restore); +/* For xtables-arptables.c */ +int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table); + +/* + * Parse config for tables and chain helper functions + */ +#define XTABLES_CONFIG_DEFAULT "/etc/xtables.conf" + +struct nft_table_list; +struct nft_chain_list; + +extern int xtables_config_parse(const char *filename, struct nft_table_list *table_list, struct nft_chain_list *chain_list); + +enum { + NFT_LOAD_VERBOSE = (1 << 0), +}; + +int nft_xtables_config_load(struct nft_handle *h, const char *filename, uint32_t flags); + +/* + * ARP + */ + +struct arpt_entry; + +int nft_arp_rule_append(struct nft_handle *h, const char *chain, + const char *table, struct arpt_entry *fw, + bool verbose); +int nft_arp_rule_insert(struct nft_handle *h, const char *chain, + const char *table, struct arpt_entry *fw, + int rulenum, bool verbose); + +void nft_rule_to_arpt_entry(struct nft_rule *r, struct arpt_entry *fw); + +#endif diff --git a/iptables/xshared.h b/iptables/xshared.h index 1e2b9b8e..27c5b786 100644 --- a/iptables/xshared.h +++ b/iptables/xshared.h @@ -58,6 +58,7 @@ struct iptables_command_state { unsigned int options; struct xtables_rule_match *matches; struct xtables_target *target; + struct xt_counters counters; char *protocol; int proto_used; const char *jumpto; diff --git a/iptables/xtables-arp-standalone.c b/iptables/xtables-arp-standalone.c new file mode 100644 index 00000000..23b6bcb4 --- /dev/null +++ b/iptables/xtables-arp-standalone.c @@ -0,0 +1,88 @@ +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * Based on the ipchains code by Paul Russell and Michael Neuling + * + * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>: + * Paul 'Rusty' Russell <rusty@rustcorp.com.au> + * Marc Boucher <marc+nf@mbsi.ca> + * James Morris <jmorris@intercode.com.au> + * Harald Welte <laforge@gnumonks.org> + * Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> + * + * arptables -- IP firewall administration for kernels with + * firewall table (aimed for the 2.3 kernels) + * + * See the accompanying manual page arptables(8) for information + * about proper usage of this program. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <xtables.h> +#include "nft.h" +#include <linux/netfilter_arp/arp_tables.h> + +#include "xtables-multi.h" + +extern struct xtables_globals xtables_globals; +extern const char *program_version, *program_name; + +static const struct xtables_afinfo afinfo_arp = { + .kmod = "arp_tables", + .proc_exists = "/proc/net/arp_tables_names", + .libprefix = "libarp_", + .family = NFPROTO_ARP, + .ipproto = IPPROTO_IP, + .so_rev_match = -1, + .so_rev_target = -1, +}; + +int xtables_arp_main(int argc, char *argv[]) +{ + int ret; + char *table = "filter"; + struct nft_handle h = { + .family = NFPROTO_ARP, + }; + + xtables_globals.program_name = "arptables"; + /* This code below could be replaced by xtables_init_all, which + * doesn't support NFPROTO_ARP yet. + */ + xtables_init(); + afinfo = &afinfo_arp; + ret = xtables_set_params(&xtables_globals); + if (ret < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + xtables_globals.program_name, + xtables_globals.program_version); + exit(1); + } + +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); +#endif + + ret = do_commandarp(&h, argc, argv, &table); + if (ret) + ret = nft_commit(&h); + + exit(!ret); +} diff --git a/iptables/xtables-arp.c b/iptables/xtables-arp.c new file mode 100644 index 00000000..0c79a387 --- /dev/null +++ b/iptables/xtables-arp.c @@ -0,0 +1,1502 @@ +/* Code to take an arptables-style command line and do it. */ + +/* + * arptables: + * Author: Bart De Schuymer <bdschuym@pandora.be>, but + * almost all code is from the iptables userspace program, which has main + * authors: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + Currently, only support for specifying hardware addresses for Ethernet + is available. + This tool is not luser-proof: you can specify an Ethernet source address + and set hardware length to something different than 6, f.e. +*/ + +#include <getopt.h> +#include <string.h> +#include <netdb.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <dlfcn.h> +#include <ctype.h> +#include <stdarg.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/wait.h> +#include <net/if.h> +#include <netinet/ether.h> +#include <xtables.h> + +#include "xshared.h" + +#include "nft.h" +#include <linux/netfilter_arp/arp_tables.h> + +typedef char arpt_chainlabel[32]; + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +/* XXX: command defined by nft-shared.h do not overlap with these two */ +#undef CMD_CHECK +#undef CMD_RENAME_CHAIN + +#define CMD_NONE 0x0000U +#define CMD_INSERT 0x0001U +#define CMD_DELETE 0x0002U +#define CMD_DELETE_NUM 0x0004U +#define CMD_REPLACE 0x0008U +#define CMD_APPEND 0x0010U +#define CMD_LIST 0x0020U +#define CMD_FLUSH 0x0040U +#define CMD_ZERO 0x0080U +#define CMD_NEW_CHAIN 0x0100U +#define CMD_DELETE_CHAIN 0x0200U +#define CMD_SET_POLICY 0x0400U +#define CMD_CHECK 0x0800U +#define CMD_RENAME_CHAIN 0x1000U +#define NUMBER_OF_CMD 13 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'N', 'X', 'P', 'E' }; + +#define OPTION_OFFSET 256 + +#define OPT_NONE 0x00000U +#define OPT_NUMERIC 0x00001U +#define OPT_S_IP 0x00002U +#define OPT_D_IP 0x00004U +#define OPT_S_MAC 0x00008U +#define OPT_D_MAC 0x00010U +#define OPT_H_LENGTH 0x00020U +#define OPT_P_LENGTH 0x00040U +#define OPT_OPCODE 0x00080U +#define OPT_H_TYPE 0x00100U +#define OPT_P_TYPE 0x00200U +#define OPT_JUMP 0x00400U +#define OPT_VERBOSE 0x00800U +#define OPT_VIANAMEIN 0x01000U +#define OPT_VIANAMEOUT 0x02000U +#define OPT_LINENUMBERS 0x04000U +#define OPT_COUNTERS 0x08000U +#define NUMBER_OF_OPT 16 +static const char optflags[NUMBER_OF_OPT] += { 'n', 's', 'd', 2, 3, 7, 8, 4, 5, 6, 'j', 'v', 'i', 'o', '0', 'c'}; + +static struct option original_opts[] = { + { "append", 1, 0, 'A' }, + { "delete", 1, 0, 'D' }, + { "insert", 1, 0, 'I' }, + { "replace", 1, 0, 'R' }, + { "list", 2, 0, 'L' }, + { "flush", 2, 0, 'F' }, + { "zero", 2, 0, 'Z' }, + { "new-chain", 1, 0, 'N' }, + { "delete-chain", 2, 0, 'X' }, + { "rename-chain", 1, 0, 'E' }, + { "policy", 1, 0, 'P' }, + { "source-ip", 1, 0, 's' }, + { "destination-ip", 1, 0, 'd' }, + { "src-ip", 1, 0, 's' }, + { "dst-ip", 1, 0, 'd' }, + { "source-mac", 1, 0, 2}, + { "destination-mac", 1, 0, 3}, + { "src-mac", 1, 0, 2}, + { "dst-mac", 1, 0, 3}, + { "h-length", 1, 0, 'l' }, + { "p-length", 1, 0, 8 }, + { "opcode", 1, 0, 4 }, + { "h-type", 1, 0, 5 }, + { "proto-type", 1, 0, 6 }, + { "in-interface", 1, 0, 'i' }, + { "jump", 1, 0, 'j' }, + { "table", 1, 0, 't' }, + { "match", 1, 0, 'm' }, + { "numeric", 0, 0, 'n' }, + { "out-interface", 1, 0, 'o' }, + { "verbose", 0, 0, 'v' }, + { "exact", 0, 0, 'x' }, + { "version", 0, 0, 'V' }, + { "help", 2, 0, 'h' }, + { "line-numbers", 0, 0, '0' }, + { "modprobe", 1, 0, 'M' }, + { 0 } +}; + +int RUNTIME_NF_ARP_NUMHOOKS = 3; + +static struct option *opts = original_opts; +static unsigned int global_option_offset = 0; + +extern void xtables_exit_error(enum xtables_exittype status, const char *msg, ...); +extern struct xtables_globals xtables_globals; + +/* Table of legal combinations of commands and options. If any of the + * given commands make an option legal, that option is legal (applies to + * CMD_LIST and CMD_ZERO only). + * Key: + * + compulsory + * x illegal + * optional + */ + +static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = +/* Well, it's better than "Re: Linux vs FreeBSD" */ +{ + /* -n -s -d -p -j -v -x -i -o -f --line */ +/*INSERT*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*DELETE*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*DELETE_NUM*/{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*REPLACE*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*APPEND*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*LIST*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*FLUSH*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*ZERO*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*NEW_CHAIN*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*DEL_CHAIN*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*SET_POLICY*/{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*CHECK*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}, +/*RENAME*/ {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '} +}; + +static int inverse_for_options[NUMBER_OF_OPT] = +{ +/* -n */ 0, +/* -s */ ARPT_INV_SRCIP, +/* -d */ ARPT_INV_TGTIP, +/* 2 */ ARPT_INV_SRCDEVADDR, +/* 3 */ ARPT_INV_TGTDEVADDR, +/* -l */ ARPT_INV_ARPHLN, +/* 8 */ 0, +/* 4 */ ARPT_INV_ARPOP, +/* 5 */ ARPT_INV_ARPHRD, +/* 6 */ ARPT_INV_ARPPRO, +/* -j */ 0, +/* -v */ 0, +/* -i */ ARPT_INV_VIA_IN, +/* -o */ ARPT_INV_VIA_OUT, +/*--line*/ 0, +/* -c */ 0, +}; + +const char *program_version = XTABLES_VERSION; +const char *program_name = "arptables"; + +/* A few hardcoded protocols for 'all' and in case the user has no + /etc/protocols */ +struct pprot { + char *name; + u_int8_t num; +}; + +/* Primitive headers... */ +/* defined in netinet/in.h */ +#if 0 +#ifndef IPPROTO_ESP +#define IPPROTO_ESP 50 +#endif +#ifndef IPPROTO_AH +#define IPPROTO_AH 51 +#endif +#endif + +/***********************************************/ +/* ARPTABLES SPECIFIC NEW FUNCTIONS ADDED HERE */ +/***********************************************/ + +unsigned char mac_type_unicast[ETH_ALEN] = {0,0,0,0,0,0}; +unsigned char msk_type_unicast[ETH_ALEN] = {1,0,0,0,0,0}; +unsigned char mac_type_multicast[ETH_ALEN] = {1,0,0,0,0,0}; +unsigned char msk_type_multicast[ETH_ALEN] = {1,0,0,0,0,0}; +unsigned char mac_type_broadcast[ETH_ALEN] = {255,255,255,255,255,255}; +unsigned char msk_type_broadcast[ETH_ALEN] = {255,255,255,255,255,255}; + +/* + * put the mac address into 6 (ETH_ALEN) bytes + */ +static int getmac_and_mask(char *from, char *to, char *mask) +{ + char *p; + int i; + struct ether_addr *addr; + + if (strcasecmp(from, "Unicast") == 0) { + memcpy(to, mac_type_unicast, ETH_ALEN); + memcpy(mask, msk_type_unicast, ETH_ALEN); + return 0; + } + if (strcasecmp(from, "Multicast") == 0) { + memcpy(to, mac_type_multicast, ETH_ALEN); + memcpy(mask, msk_type_multicast, ETH_ALEN); + return 0; + } + if (strcasecmp(from, "Broadcast") == 0) { + memcpy(to, mac_type_broadcast, ETH_ALEN); + memcpy(mask, msk_type_broadcast, ETH_ALEN); + return 0; + } + if ( (p = strrchr(from, '/')) != NULL) { + *p = '\0'; + if (!(addr = ether_aton(p + 1))) + return -1; + memcpy(mask, addr, ETH_ALEN); + } else + memset(mask, 0xff, ETH_ALEN); + if (!(addr = ether_aton(from))) + return -1; + memcpy(to, addr, ETH_ALEN); + for (i = 0; i < ETH_ALEN; i++) + to[i] &= mask[i]; + return 0; +} + +static int getlength_and_mask(char *from, uint8_t *to, uint8_t *mask) +{ + char *p, *buffer; + int i; + + if ( (p = strrchr(from, '/')) != NULL) { + *p = '\0'; + i = strtol(p+1, &buffer, 10); + if (*buffer != '\0' || i < 0 || i > 255) + return -1; + *mask = (uint8_t)i; + } else + *mask = 255; + i = strtol(from, &buffer, 10); + if (*buffer != '\0' || i < 0 || i > 255) + return -1; + *to = (uint8_t)i; + return 0; +} + +static int get16_and_mask(char *from, uint16_t *to, uint16_t *mask, int base) +{ + char *p, *buffer; + int i; + + if ( (p = strrchr(from, '/')) != NULL) { + *p = '\0'; + i = strtol(p+1, &buffer, base); + if (*buffer != '\0' || i < 0 || i > 65535) + return -1; + *mask = htons((uint16_t)i); + } else + *mask = 65535; + i = strtol(from, &buffer, base); + if (*buffer != '\0' || i < 0 || i > 65535) + return -1; + *to = htons((uint16_t)i); + return 0; +} + +static int +string_to_number(const char *s, unsigned int min, unsigned int max, + unsigned int *ret) +{ + long number; + char *end; + + /* Handle hex, octal, etc. */ + errno = 0; + number = strtol(s, &end, 0); + if (*end == '\0' && end != s) { + /* we parsed a number, let's see if we want this */ + if (errno != ERANGE && min <= number && number <= max) { + *ret = number; + return 0; + } + } + return -1; +} + +/*********************************************/ +/* ARPTABLES SPECIFIC NEW FUNCTIONS END HERE */ +/*********************************************/ + +static struct in_addr * +dotted_to_addr(const char *dotted) +{ + static struct in_addr addr; + unsigned char *addrp; + char *p, *q; + unsigned int onebyte; + int i; + char buf[20]; + + /* copy dotted string, because we need to modify it */ + strncpy(buf, dotted, sizeof(buf) - 1); + addrp = (unsigned char *) &(addr.s_addr); + + p = buf; + for (i = 0; i < 3; i++) { + if ((q = strchr(p, '.')) == NULL) + return (struct in_addr *) NULL; + + *q = '\0'; + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[i] = (unsigned char) onebyte; + p = q + 1; + } + + /* we've checked 3 bytes, now we check the last one */ + if (string_to_number(p, 0, 255, &onebyte) == -1) + return (struct in_addr *) NULL; + + addrp[3] = (unsigned char) onebyte; + + return &addr; +} + +static struct in_addr * +network_to_addr(const char *name) +{ + struct netent *net; + static struct in_addr addr; + + if ((net = getnetbyname(name)) != NULL) { + if (net->n_addrtype != AF_INET) + return (struct in_addr *) NULL; + addr.s_addr = htonl((unsigned long) net->n_net); + return &addr; + } + + return (struct in_addr *) NULL; +} + +static void +inaddrcpy(struct in_addr *dst, struct in_addr *src) +{ + /* memcpy(dst, src, sizeof(struct in_addr)); */ + dst->s_addr = src->s_addr; +} + +static void +exit_tryhelp(int status) +{ + fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", + program_name, program_name ); + exit(status); +} + +static void +exit_printhelp(void) +{ + struct xtables_target *t = NULL; + int i; + + printf("%s v%s\n\n" +"Usage: %s -[AD] chain rule-specification [options]\n" +" %s -[RI] chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LFZ] [chain] [options]\n" +" %s -[NX] chain\n" +" %s -E old-chain-name new-chain-name\n" +" %s -P chain target [options]\n" +" %s -h (print this help information)\n\n", + program_name, program_version, program_name, program_name, + program_name, program_name, program_name, program_name, + program_name, program_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\n" +" --delete -D chain Delete matching rule from chain\n" +" --delete -D chain rulenum\n" +" Delete rule rulenum (1 = first) from chain\n" +" --insert -I chain [rulenum]\n" +" Insert in chain as rulenum (default 1=first)\n" +" --replace -R chain rulenum\n" +" Replace rule rulenum (1 = first) in chain\n" +" --list -L [chain] List the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain] Zero counters in chain or all chains\n" +" --new -N chain Create a new user-defined chain\n" +" --delete-chain\n" +" -X [chain] Delete a user-defined chain\n" +" --policy -P chain target\n" +" Change policy on chain to target\n" +" --rename-chain\n" +" -E old-chain new-chain\n" +" Change chain name, (moving any references)\n" + +"Options:\n" +" --source-ip -s [!] address[/mask]\n" +" source specification\n" +" --destination-ip -d [!] address[/mask]\n" +" destination specification\n" +" --source-mac [!] address[/mask]\n" +" --destination-mac [!] address[/mask]\n" +" --h-length -l length[/mask] hardware length (nr of bytes)\n" +" --opcode code[/mask] operation code (2 bytes)\n" +" --h-type type[/mask] hardware type (2 bytes, hexadecimal)\n" +" --proto-type type[/mask] protocol type (2 bytes)\n" +" --in-interface -i [!] input name[+]\n" +" network interface name ([+] for wildcard)\n" +" --out-interface -o [!] output name[+]\n" +" network interface name ([+] for wildcard)\n" +" --jump -j target\n" +" target for rule (may load target extension)\n" +" --match -m match\n" +" extended match (may load extension)\n" +" --numeric -n numeric output of addresses and ports\n" +" --table -t table table to manipulate (default: `filter')\n" +" --verbose -v verbose mode\n" +" --line-numbers print line numbers when listing\n" +" --exact -x expand numbers (display exact values)\n" +" --modprobe=<command> try to insert modules using this command\n" +" --set-counters PKTS BYTES set the counter during insert/append\n" +"[!] --version -V print package version.\n"); + printf(" opcode strings: \n"); + for (i = 0; i < NUMOPCODES; i++) + printf(" %d = %s\n", i + 1, opcodes[i]); + printf( +" hardware type string: 1 = Ethernet\n" +" protocol type string: 0x800 = IPv4\n"); + + /* Print out any special helps. A user might like to be able + to add a --help to the commandline, and see expected + results. So we call help for all matches & targets */ + for (t = xtables_targets; t; t = t->next) { + if (strcmp(t->name, "CLASSIFY") && strcmp(t->name, "mangle")) + continue; + printf("\n"); + t->help(); + } + exit(0); +} + +static void +generic_opt_check(int command, int options) +{ + int i, j, legal = 0; + + /* Check that commands are valid with options. Complicated by the + * fact that if an option is legal with *any* command given, it is + * legal overall (ie. -z and -l). + */ + for (i = 0; i < NUMBER_OF_OPT; i++) { + legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */ + + for (j = 0; j < NUMBER_OF_CMD; j++) { + if (!(command & (1<<j))) + continue; + + if (!(options & (1<<i))) { + if (commands_v_options[j][i] == '+') + xtables_error(PARAMETER_PROBLEM, + "You need to supply the `-%c' " + "option for this command\n", + optflags[i]); + } else { + if (commands_v_options[j][i] != 'x') + legal = 1; + else if (legal == 0) + legal = -1; + } + } + if (legal == -1) + xtables_error(PARAMETER_PROBLEM, + "Illegal option `-%c' with this command\n", + optflags[i]); + } +} + +static char +opt2char(int option) +{ + const char *ptr; + for (ptr = optflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static char +cmd2char(int option) +{ + const char *ptr; + for (ptr = cmdflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static void +add_command(unsigned int *cmd, const int newcmd, const unsigned int othercmds, int invert) +{ + if (invert) + xtables_error(PARAMETER_PROBLEM, "unexpected ! flag"); + if (*cmd & (~othercmds)) + xtables_error(PARAMETER_PROBLEM, "Can't use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +static int +check_inverse(const char option[], int *invert, int *optidx, int argc) +{ + if (option && strcmp(option, "!") == 0) { + if (*invert) + xtables_error(PARAMETER_PROBLEM, + "Multiple `!' flags not allowed"); + *invert = TRUE; + if (optidx) { + *optidx = *optidx+1; + if (argc && *optidx > argc) + xtables_error(PARAMETER_PROBLEM, + "no argument following `!'"); + } + + return TRUE; + } + return FALSE; +} + +static struct in_addr * +host_to_addr(const char *name, unsigned int *naddr) +{ + struct hostent *host; + struct in_addr *addr; + unsigned int i; + + *naddr = 0; + if ((host = gethostbyname(name)) != NULL) { + if (host->h_addrtype != AF_INET || + host->h_length != sizeof(struct in_addr)) + return (struct in_addr *) NULL; + + while (host->h_addr_list[*naddr] != (char *) NULL) + (*naddr)++; + addr = xtables_calloc(*naddr, sizeof(struct in_addr)); + for (i = 0; i < *naddr; i++) + inaddrcpy(&(addr[i]), + (struct in_addr *) host->h_addr_list[i]); + return addr; + } + + return (struct in_addr *) NULL; +} + +/* + * All functions starting with "parse" should succeed, otherwise + * the program fails. + * Most routines return pointers to static data that may change + * between calls to the same or other routines with a few exceptions: + * "host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask" + * return global static data. +*/ + +static struct in_addr * +parse_hostnetwork(const char *name, unsigned int *naddrs) +{ + struct in_addr *addrp, *addrptmp; + + if ((addrptmp = dotted_to_addr(name)) != NULL || + (addrptmp = network_to_addr(name)) != NULL) { + addrp = xtables_malloc(sizeof(struct in_addr)); + inaddrcpy(addrp, addrptmp); + *naddrs = 1; + return addrp; + } + if ((addrp = host_to_addr(name, naddrs)) != NULL) + return addrp; + + xtables_error(PARAMETER_PROBLEM, "host/network `%s' not found", name); +} + +static struct in_addr * +parse_mask(char *mask) +{ + static struct in_addr maskaddr; + struct in_addr *addrp; + unsigned int bits; + + if (mask == NULL) { + /* no mask at all defaults to 32 bits */ + maskaddr.s_addr = 0xFFFFFFFF; + return &maskaddr; + } + if ((addrp = dotted_to_addr(mask)) != NULL) + /* dotted_to_addr already returns a network byte order addr */ + return addrp; + if (string_to_number(mask, 0, 32, &bits) == -1) + xtables_error(PARAMETER_PROBLEM, + "invalid mask `%s' specified", mask); + if (bits != 0) { + maskaddr.s_addr = htonl(0xFFFFFFFF << (32 - bits)); + return &maskaddr; + } + + maskaddr.s_addr = 0L; + return &maskaddr; +} + +static void +parse_hostnetworkmask(const char *name, struct in_addr **addrpp, + struct in_addr *maskp, unsigned int *naddrs) +{ + struct in_addr *addrp; + char buf[256]; + char *p; + int i, j, k, n; + + strncpy(buf, name, sizeof(buf) - 1); + if ((p = strrchr(buf, '/')) != NULL) { + *p = '\0'; + addrp = parse_mask(p + 1); + } else + addrp = parse_mask(NULL); + inaddrcpy(maskp, addrp); + + /* if a null mask is given, the name is ignored, like in "any/0" */ + if (maskp->s_addr == 0L) + strcpy(buf, "0.0.0.0"); + + addrp = *addrpp = parse_hostnetwork(buf, naddrs); + n = *naddrs; + for (i = 0, j = 0; i < n; i++) { + addrp[j++].s_addr &= maskp->s_addr; + for (k = 0; k < j - 1; k++) { + if (addrp[k].s_addr == addrp[j - 1].s_addr) { + (*naddrs)--; + j--; + break; + } + } + } +} + +static void +parse_interface(const char *arg, char *vianame, unsigned char *mask) +{ + int vialen = strlen(arg); + unsigned int i; + + memset(mask, 0, IFNAMSIZ); + memset(vianame, 0, IFNAMSIZ); + + if (vialen + 1 > IFNAMSIZ) + xtables_error(PARAMETER_PROBLEM, + "interface name `%s' must be shorter than IFNAMSIZ" + " (%i)", arg, IFNAMSIZ-1); + + strcpy(vianame, arg); + if (vialen == 0) + memset(mask, 0, IFNAMSIZ); + else if (vianame[vialen - 1] == '+') { + memset(mask, 0xFF, vialen - 1); + memset(mask + vialen - 1, 0, IFNAMSIZ - vialen + 1); + /* Don't remove `+' here! -HW */ + } else { + /* Include nul-terminator in match */ + memset(mask, 0xFF, vialen + 1); + memset(mask + vialen + 1, 0, IFNAMSIZ - vialen - 1); + for (i = 0; vianame[i]; i++) { + if (!isalnum(vianame[i]) + && vianame[i] != '_' + && vianame[i] != '.') { + printf("Warning: wierd character in interface" + " `%s' (No aliases, :, ! or *).\n", + vianame); + break; + } + } + } +} + +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + unsigned int rulenum; + + if (!xtables_strtoui(rule, NULL, &rulenum, 1, INT_MAX)) + xtables_error(PARAMETER_PROBLEM, + "Invalid rule number `%s'", rule); + + return rulenum; +} + +static const char * +parse_target(const char *targetname) +{ + const char *ptr; + + if (strlen(targetname) < 1) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname)+1 > sizeof(arpt_chainlabel)) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%zu chars max)", + targetname, sizeof(arpt_chainlabel)-1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +static void +set_option(unsigned int *options, unsigned int option, u_int16_t *invflg, + int invert) +{ + if (*options & option) + xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed", + opt2char(option)); + *options |= option; + + if (invert) { + unsigned int i; + for (i = 0; 1 << i != option; i++); + + if (!inverse_for_options[i]) + xtables_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +static int +list_entries(struct nft_handle *h, const char *chain, const char *table, + int rulenum, int verbose, int numeric, int expanded, + int linenumbers) +{ + unsigned int format; + + format = FMT_OPTIONS; + if (!verbose) + format |= FMT_NOCOUNTS; + else + format |= FMT_VIA; + + if (numeric) + format |= FMT_NUMERIC; + + if (!expanded) + format |= FMT_KILOMEGAGIGA; + + if (linenumbers) + format |= FMT_LINENUMBERS; + + return nft_rule_list(h, chain, table, rulenum, format); +} + +static struct arpt_entry * +generate_entry(const struct arpt_entry *fw, + struct arpt_entry_target *target) +{ + struct arpt_entry_target **t; + struct arpt_entry *e; + unsigned int size; + + + size = sizeof(struct arpt_entry); + + e = xtables_malloc(size); + *e = *fw; + e->target_offset = offsetof(struct arpt_entry, elems); + e->next_offset = e->target_offset + target->u.target_size; + + t = (void *) &e->elems; + *t = target; + + return e; +} + +static struct xtables_target *command_jump(struct arpt_entry *fw, + const char *jumpto) +{ + struct xtables_target *target; + size_t size; + + /* XTF_TRY_LOAD (may be chain name) */ + target = xtables_find_target(jumpto, XTF_TRY_LOAD); + + if (!target) + return NULL; + + size = XT_ALIGN(sizeof(struct xt_entry_target)) + + target->size; + + target->t = xtables_calloc(1, size); + target->t->u.target_size = size; + strncpy(target->t->u.user.name, jumpto, sizeof(target->t->u.user.name)); + target->t->u.user.name[sizeof(target->t->u.user.name)-1] = '\0'; + target->t->u.user.revision = target->revision; + + xs_init_target(target); + + if (target->x6_options != NULL) + opts = xtables_options_xfrm(xtables_globals.orig_opts, + opts, target->x6_options, + &target->option_offset); + else + opts = xtables_merge_options(xtables_globals.orig_opts, + opts, target->extra_opts, + &target->option_offset); + + return target; +} + +static int +append_entry(struct nft_handle *h, + const char *chain, + const char *table, + struct arpt_entry *fw, + int rulenum, + unsigned int nsaddrs, + const struct in_addr saddrs[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + bool verbose, bool append) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->arp.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->arp.tgt.s_addr = daddrs[j].s_addr; + if (append) { + ret = nft_rule_append(h, chain, table, fw, 0, + verbose); + } else { + ret = nft_rule_insert(h, chain, table, fw, + rulenum, verbose); + } + } + } + + return ret; +} + +static int +replace_entry(const char *chain, + const char *table, + struct arpt_entry *fw, + unsigned int rulenum, + const struct in_addr *saddr, + const struct in_addr *daddr, + bool verbose, struct nft_handle *h) +{ + fw->arp.src.s_addr = saddr->s_addr; + fw->arp.tgt.s_addr = daddr->s_addr; + + return nft_rule_replace(h, chain, table, fw, rulenum, verbose); +} + +static int +delete_entry(const char *chain, + const char *table, + struct arpt_entry *fw, + unsigned int nsaddrs, + const struct in_addr saddrs[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + bool verbose, struct nft_handle *h) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + fw->arp.src.s_addr = saddrs[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + fw->arp.tgt.s_addr = daddrs[j].s_addr; + ret = nft_rule_delete(h, chain, table, fw, verbose); + } + } + + return ret; +} + +int do_commandarp(struct nft_handle *h, int argc, char *argv[], char **table) +{ + struct arpt_entry fw, *e = NULL; + int invert = 0; + unsigned int nsaddrs = 0, ndaddrs = 0; + struct in_addr *saddrs = NULL, *daddrs = NULL; + + int c, verbose = 0; + const char *chain = NULL; + const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, options = 0, command = 0; + const char *pcnt = NULL, *bcnt = NULL; + int ret = 1; + struct xtables_target *target = NULL; + struct xtables_target *t; + + const char *jumpto = ""; + + memset(&fw, 0, sizeof(fw)); + opts = original_opts; + global_option_offset = 0; + + xtables_globals.orig_opts = original_opts; + + /* re-set optind to 0 in case do_command gets called + * a second time */ + optind = 0; + + for (t = xtables_targets; t; t = t->next) { + t->tflags = 0; + t->used = 0; + } + + /* Suppress error messages: we may add new options if we + demand-load a protocol. */ + opterr = 0; + + while ((c = getopt_long(argc, argv, + "-A:D:R:I:L::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:l:i:vnt:m:c:", + opts, NULL)) != -1) { + switch (c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_DELETE_NUM; + } + break; + + case 'R': + add_command(&command, CMD_REPLACE, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else rulenum = 1; + break; + + case 'L': + add_command(&command, CMD_LIST, CMD_ZERO, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'Z': + add_command(&command, CMD_ZERO, CMD_LIST, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'N': + if (optarg && *optarg == '-') + xtables_error(PARAMETER_PROBLEM, + "chain name not allowed to start " + "with `-'\n"); + if (xtables_find_target(optarg, XTF_TRY_LOAD)) + xtables_error(PARAMETER_PROBLEM, + "chain name may not clash " + "with target name\n"); + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'E': + add_command(&command, CMD_RENAME_CHAIN, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires old-chain-name and " + "new-chain-name", + cmd2char(CMD_RENAME_CHAIN)); + break; + + case 'P': + add_command(&command, CMD_SET_POLICY, CMD_NONE, + invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a chain and a policy", + cmd2char(CMD_SET_POLICY)); + break; + + case 'h': + if (!optarg) + optarg = argv[optind]; + + exit_printhelp(); + break; + case 's': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_S_IP, &fw.arp.invflags, + invert); + shostnetworkmask = argv[optind-1]; + break; + + case 'd': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_D_IP, &fw.arp.invflags, + invert); + dhostnetworkmask = argv[optind-1]; + break; + + case 2:/* src-mac */ + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_S_MAC, &fw.arp.invflags, + invert); + if (getmac_and_mask(argv[optind - 1], + fw.arp.src_devaddr.addr, fw.arp.src_devaddr.mask)) + xtables_error(PARAMETER_PROBLEM, "Problem with specified " + "source mac"); + break; + + case 3:/* dst-mac */ + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_D_MAC, &fw.arp.invflags, + invert); + + if (getmac_and_mask(argv[optind - 1], + fw.arp.tgt_devaddr.addr, fw.arp.tgt_devaddr.mask)) + xtables_error(PARAMETER_PROBLEM, "Problem with specified " + "destination mac"); + break; + + case 'l':/* hardware length */ + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_H_LENGTH, &fw.arp.invflags, + invert); + getlength_and_mask(argv[optind - 1], &fw.arp.arhln, + &fw.arp.arhln_mask); + + if (fw.arp.arhln != 6) { + xtables_error(PARAMETER_PROBLEM, + "Only harware address length of" + " 6 is supported currently."); + } + + break; + + case 8:/* protocol length */ + xtables_error(PARAMETER_PROBLEM, "not supported"); +/* + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_P_LENGTH, &fw.arp.invflags, + invert); + + getlength_and_mask(argv[optind - 1], &fw.arp.arpln, + &fw.arp.arpln_mask); + break; +*/ + + case 4:/* opcode */ + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_OPCODE, &fw.arp.invflags, + invert); + if (get16_and_mask(argv[optind - 1], &fw.arp.arpop, &fw.arp.arpop_mask, 10)) { + int i; + + for (i = 0; i < NUMOPCODES; i++) + if (!strcasecmp(opcodes[i], optarg)) + break; + if (i == NUMOPCODES) + xtables_error(PARAMETER_PROBLEM, "Problem with specified opcode"); + fw.arp.arpop = htons(i+1); + } + break; + + case 5:/* h-type */ + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_H_TYPE, &fw.arp.invflags, + invert); + if (get16_and_mask(argv[optind - 1], &fw.arp.arhrd, &fw.arp.arhrd_mask, 16)) { + if (strcasecmp(argv[optind-1], "Ethernet")) + xtables_error(PARAMETER_PROBLEM, "Problem with specified hardware type"); + fw.arp.arhrd = htons(1); + } + break; + + case 6:/* proto-type */ + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_P_TYPE, &fw.arp.invflags, + invert); + if (get16_and_mask(argv[optind - 1], &fw.arp.arpro, &fw.arp.arpro_mask, 0)) { + if (strcasecmp(argv[optind-1], "ipv4")) + xtables_error(PARAMETER_PROBLEM, "Problem with specified protocol type"); + fw.arp.arpro = htons(0x800); + } + break; + + case 'j': + set_option(&options, OPT_JUMP, &fw.arp.invflags, + invert); + jumpto = parse_target(optarg); + target = command_jump(&fw, jumpto); + break; + + case 'i': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEIN, &fw.arp.invflags, + invert); + parse_interface(argv[optind-1], + fw.arp.iniface, + fw.arp.iniface_mask); +/* fw.nfcache |= NFC_IP_IF_IN; */ + break; + + case 'o': + check_inverse(optarg, &invert, &optind, argc); + set_option(&options, OPT_VIANAMEOUT, &fw.arp.invflags, + invert); + parse_interface(argv[optind-1], + fw.arp.outiface, + fw.arp.outiface_mask); + /* fw.nfcache |= NFC_IP_IF_OUT; */ + break; + + case 'v': + if (!verbose) + set_option(&options, OPT_VERBOSE, + &fw.arp.invflags, invert); + verbose++; + break; + + case 'm': /*{ + size_t size; + + if (invert) + exit_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = find_match(optarg, LOAD_MUST_SUCCEED); + size = ARPT_ALIGN(sizeof(struct arpt_entry_match)) + + m->size; + m->m = fw_calloc(1, size); + m->m->u.match_size = size; + strcpy(m->m->u.user.name, m->name); + m->init(m->m, &fw.nfcache); + opts = merge_options(opts, m->extra_opts, &m->option_offset); + }*/ + break; + + case 'n': + set_option(&options, OPT_NUMERIC, &fw.arp.invflags, + invert); + break; + + case 't': + if (invert) + xtables_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = argv[optind-1]; + break; + + case 'V': + if (invert) + printf("Not %s ;-)\n", program_version); + else + printf("%s v%s\n", + program_name, program_version); + exit(0); + + case '0': + set_option(&options, OPT_LINENUMBERS, &fw.arp.invflags, + invert); + break; + + case 'M': + //modprobe = optarg; + break; + + case 'c': + + set_option(&options, OPT_COUNTERS, &fw.arp.invflags, + invert); + pcnt = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + bcnt = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires packet and byte counter", + opt2char(OPT_COUNTERS)); + + if (sscanf(pcnt, "%llu", &fw.counters.pcnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c packet counter not numeric", + opt2char(OPT_COUNTERS)); + + if (sscanf(bcnt, "%llu", &fw.counters.bcnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c byte counter not numeric", + opt2char(OPT_COUNTERS)); + + break; + + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (invert) + xtables_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + invert = TRUE; + optarg[0] = '\0'; + continue; + } + printf("Bad argument `%s'\n", optarg); + exit_tryhelp(2); + + default: + if (target) { + xtables_option_tpcall(c, argv, + invert, target, &fw); + } + break; + } + invert = FALSE; + } + + if (target) + xtables_option_tfcall(target); + + if (optind < argc) + xtables_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + xtables_error(PARAMETER_PROBLEM, "no command specified"); + if (invert) + xtables_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND)) { + if (!(options & OPT_D_IP)) + dhostnetworkmask = "0.0.0.0/0"; + if (!(options & OPT_S_IP)) + shostnetworkmask = "0.0.0.0/0"; + } + + if (shostnetworkmask) + parse_hostnetworkmask(shostnetworkmask, &saddrs, + &(fw.arp.smsk), &nsaddrs); + + if (dhostnetworkmask) + parse_hostnetworkmask(dhostnetworkmask, &daddrs, + &(fw.arp.tmsk), &ndaddrs); + + if ((nsaddrs > 1 || ndaddrs > 1) && + (fw.arp.invflags & (ARPT_INV_SRCIP | ARPT_INV_TGTIP))) + xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple" + " source or destination IP addresses"); + + if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1)) + xtables_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, options); + + if (chain && strlen(chain) > ARPT_FUNCTION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %i chars)", + chain, ARPT_FUNCTION_MAXNAMELEN); + + if (nft_init(h, xtables_arp) < 0) + xtables_error(OTHER_PROBLEM, + "Could not initialize nftables layer."); + + h->ops = nft_family_ops_lookup(h->family); + if (h->ops == NULL) + xtables_error(PARAMETER_PROBLEM, "Unknown family"); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_INSERT + || command == CMD_REPLACE) { + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + /* -o not valid with incoming packets. */ + if (options & OPT_VIANAMEOUT) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEOUT), + chain); + } + + if (strcmp(chain, "POSTROUTING") == 0 + || strcmp(chain, "OUTPUT") == 0) { + /* -i not valid with outgoing packets */ + if (options & OPT_VIANAMEIN) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + if (!target && strlen(jumpto) != 0) { + size_t size; + + target = xtables_find_target(XT_STANDARD_TARGET, + XTF_LOAD_MUST_SUCCEED); + size = sizeof(struct arpt_entry_target) + target->size; + target->t = xtables_calloc(1, size); + target->t->u.target_size = size; + strcpy(target->t->u.user.name, jumpto); + } + + if (!target) { + xtables_error(PARAMETER_PROBLEM, + "No target provided or" + " initalization failed"); + } + + e = generate_entry(&fw, target->t); + } + + switch (command) { + case CMD_APPEND: + ret = append_entry(h, chain, *table, e, 0, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, true); + break; + case CMD_DELETE: + ret = delete_entry(chain, *table, e, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, h); + break; + case CMD_DELETE_NUM: + ret = nft_rule_delete_num(h, chain, *table, rulenum - 1, verbose); + break; + case CMD_REPLACE: + ret = replace_entry(chain, *table, e, rulenum - 1, + saddrs, daddrs, options&OPT_VERBOSE, h); + break; + case CMD_INSERT: + ret = append_entry(h, chain, *table, e, rulenum - 1, + nsaddrs, saddrs, ndaddrs, daddrs, + options&OPT_VERBOSE, false); + break; + case CMD_LIST: + ret = list_entries(h, chain, *table, + rulenum, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + /*options&OPT_EXPANDED*/0, + options&OPT_LINENUMBERS); + break; + case CMD_FLUSH: + ret = nft_rule_flush(h, chain, *table); + break; + case CMD_ZERO: + ret = nft_chain_zero_counters(h, chain, *table); + break; + case CMD_LIST|CMD_ZERO: + ret = list_entries(h, chain, *table, rulenum, + options&OPT_VERBOSE, + options&OPT_NUMERIC, + /*options&OPT_EXPANDED*/0, + options&OPT_LINENUMBERS); + if (ret) + ret = nft_chain_zero_counters(h, chain, *table); + break; + case CMD_NEW_CHAIN: + ret = nft_chain_user_add(h, chain, *table); + break; + case CMD_DELETE_CHAIN: + ret = nft_chain_user_del(h, chain, *table); + break; + case CMD_RENAME_CHAIN: + ret = nft_chain_user_rename(h, chain, *table, newname); + break; + case CMD_SET_POLICY: + ret = nft_chain_set(h, *table, chain, policy, NULL); + if (ret < 0) + xtables_error(PARAMETER_PROBLEM, "Wrong policy `%s'\n", + policy); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + +/* if (verbose > 1) + dump_entries(*handle);*/ + + return ret; +} diff --git a/iptables/xtables-compat-multi.c b/iptables/xtables-compat-multi.c new file mode 100644 index 00000000..47810524 --- /dev/null +++ b/iptables/xtables-compat-multi.c @@ -0,0 +1,39 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "xshared.h" + +#include "xtables-multi.h" + +static const struct subcommand multi_subcommands[] = { + {"iptables-xml", iptables_xml_main}, + {"xml", iptables_xml_main}, + {"iptables", xtables_ip4_main}, + {"iptables-compat", xtables_ip4_main}, + {"main4", xtables_ip4_main}, + {"save4", xtables_ip4_save_main}, + {"restore4", xtables_ip4_restore_main}, + {"iptables-save", xtables_ip4_save_main}, + {"iptables-restore", xtables_ip4_restore_main}, + {"iptables-compat-save", xtables_ip4_save_main}, + {"iptables-compat-restore", xtables_ip4_restore_main}, + {"ip6tables", xtables_ip6_main}, + {"ip6tables-compat", xtables_ip6_main}, + {"main6", xtables_ip6_main}, + {"save6", xtables_ip6_save_main}, + {"restore6", xtables_ip6_restore_main}, + {"ip6tables-save", xtables_ip6_save_main}, + {"ip6tables-restore", xtables_ip6_restore_main}, + {"ip6tables-compat-save", xtables_ip6_save_main}, + {"ip6tables-compat-restore", xtables_ip6_restore_main}, + {"arptables", xtables_arp_main}, + {"arptables-compat", xtables_arp_main}, + {"xtables-config", xtables_config_main}, + {"xtables-events", xtables_events_main}, + {NULL}, +}; + +int main(int argc, char **argv) +{ + return subcmd_main(argc, argv, multi_subcommands); +} diff --git a/iptables/xtables-config-parser.y b/iptables/xtables-config-parser.y new file mode 100644 index 00000000..2770a1b9 --- /dev/null +++ b/iptables/xtables-config-parser.y @@ -0,0 +1,250 @@ +%{ +/* + * (C) 2012 by 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <stdarg.h> +#include <libiptc/linux_list.h> +#include <libnftnl/table.h> +#include <libnftnl/chain.h> + +#include <netinet/in.h> +#include <linux/netfilter.h> + +extern char *yytext; +extern int yylineno; + +static LIST_HEAD(xtables_stack); + +struct stack_elem { + struct list_head head; + int token; + size_t size; + char data[]; +}; + +static void *stack_push(int token, size_t size) +{ + struct stack_elem *e; + + e = calloc(1, sizeof(struct stack_elem) + size); + + e->token = token; + e->size = size; + + list_add(&e->head, &xtables_stack); + + return e->data; +} + +static struct stack_elem *stack_pop(void) +{ + struct stack_elem *e; + + e = list_entry(xtables_stack.next, struct stack_elem, head); + + if (&e->head == &xtables_stack) + return NULL; + + list_del(&e->head); + return e; +} + +static inline void stack_put_i32(void *data, int value) +{ + memcpy(data, &value, sizeof(int)); +} + +static inline void stack_put_str(void *data, const char *str) +{ + memcpy(data, str, strlen(str)); +} + +static void stack_free(struct stack_elem *e) +{ + free(e); +} + +%} + +%union { + int val; + char *string; +} + +%token T_FAMILY +%token T_TABLE +%token T_CHAIN +%token T_HOOK +%token T_PRIO + +%token <string> T_STRING +%token <val> T_INTEGER + +%% + +configfile : + | lines + ; + +lines : line + | lines line + ; + +line : family + ; + +family : T_FAMILY T_STRING '{' tables '}' + { + void *data = stack_push(T_FAMILY, strlen($2)+1); + stack_put_str(data, $2); + } + ; + +tables : table + | tables table + ; + +table : T_TABLE T_STRING '{' chains '}' + { + /* added in reverse order to pop it in order */ + void *data = stack_push(T_TABLE, strlen($2)+1); + stack_put_str(data, $2); + } + ; + +chains : chain + | chains chain + ; + +chain : T_CHAIN T_STRING T_HOOK T_STRING T_PRIO T_INTEGER + { + /* added in reverse order to pop it in order */ + void *data = stack_push(T_PRIO, sizeof(int32_t)); + stack_put_i32(data, $6); + data = stack_push(T_HOOK, strlen($4)+1); + stack_put_str(data, $4); + data = stack_push(T_CHAIN, strlen($2)+1); + stack_put_str(data, $2); + } + ; + +%% + +int __attribute__((noreturn)) +yyerror(char *msg) +{ + fprintf(stderr, "parsing config file in line (%d), symbol '%s': %s\n", + yylineno, yytext, msg); + exit(EXIT_FAILURE); +} + +static int hooknametonum(const char *hookname) +{ + if (strcmp(hookname, "NF_INET_LOCAL_IN") == 0) + return NF_INET_LOCAL_IN; + else if (strcmp(hookname, "NF_INET_FORWARD") == 0) + return NF_INET_FORWARD; + else if (strcmp(hookname, "NF_INET_LOCAL_OUT") == 0) + return NF_INET_LOCAL_OUT; + else if (strcmp(hookname, "NF_INET_PRE_ROUTING") == 0) + return NF_INET_PRE_ROUTING; + else if (strcmp(hookname, "NF_INET_POST_ROUTING") == 0) + return NF_INET_POST_ROUTING; + + return -1; +} + +static int32_t familytonumber(const char *family) +{ + if (strcmp(family, "ipv4") == 0) + return AF_INET; + else if (strcmp(family, "ipv6") == 0) + return AF_INET6; + + return -1; +} + +int xtables_config_parse(char *filename, struct nft_table_list *table_list, + struct nft_chain_list *chain_list) +{ + FILE *fp; + struct stack_elem *e; + struct nft_table *table = NULL; + struct nft_chain *chain = NULL; + int prio = 0; + int32_t family = 0; + + fp = fopen(filename, "r"); + if (!fp) + return -1; + + yyrestart(fp); + yyparse(); + fclose(fp); + + for (e = stack_pop(); e != NULL; e = stack_pop()) { + switch(e->token) { + case T_FAMILY: + family = familytonumber(e->data); + if (family == -1) + return -1; + break; + case T_TABLE: + table = nft_table_alloc(); + if (table == NULL) { + perror("nft_table_alloc"); + return -1; + } + nft_table_attr_set_u32(table, NFT_TABLE_ATTR_FAMILY, family); + nft_table_attr_set(table, NFT_TABLE_ATTR_NAME, e->data); + /* This is intentionally prepending, instead of + * appending, since the elements in the stack are in + * the reverse order that chains appear in the + * configuration file. + */ + nft_table_list_add(table, table_list); + break; + case T_PRIO: + memcpy(&prio, e->data, sizeof(int32_t)); + break; + case T_CHAIN: + chain = nft_chain_alloc(); + if (chain == NULL) { + perror("nft_chain_alloc"); + return -1; + } + nft_chain_attr_set(chain, NFT_CHAIN_ATTR_TABLE, + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME)); + nft_chain_attr_set_u32(chain, NFT_CHAIN_ATTR_FAMILY, + nft_table_attr_get_u32(table, NFT_TABLE_ATTR_FAMILY)); + nft_chain_attr_set_s32(chain, NFT_CHAIN_ATTR_PRIO, prio); + nft_chain_attr_set(chain, NFT_CHAIN_ATTR_NAME, e->data); + /* Intentionally prepending, instead of appending */ + nft_chain_list_add(chain, chain_list); + break; + case T_HOOK: + nft_chain_attr_set_u32(chain, NFT_CHAIN_ATTR_HOOKNUM, + hooknametonum(e->data)); + break; + default: + printf("unknown token type %d\n", e->token); + break; + } + stack_free(e); + } + + return 0; +} diff --git a/iptables/xtables-config-syntax.l b/iptables/xtables-config-syntax.l new file mode 100644 index 00000000..a895c8bc --- /dev/null +++ b/iptables/xtables-config-syntax.l @@ -0,0 +1,54 @@ +%{ +/* + * (C) 2012 by 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <string.h> +#include "xtables-config-parser.h" +%} + +%option yylineno +%option noinput +%option nounput + +ws [ \t]+ +comment #.*$ +nl [\n\r] + +is_on [o|O][n|N] +is_off [o|O][f|F][f|F] +integer [\-\+]?[0-9]+ +string [a-zA-Z][a-zA-Z0-9\.\-\_]* + +%% +"family" { return T_FAMILY; } +"table" { return T_TABLE; } +"chain" { return T_CHAIN; } +"hook" { return T_HOOK; } +"prio" { return T_PRIO; } + +{integer} { yylval.val = atoi(yytext); return T_INTEGER; } +{string} { yylval.string = strdup(yytext); return T_STRING; } + +{comment} ; +{ws} ; +{nl} ; + +<<EOF>> { yyterminate(); } + +. { return yytext[0]; } + +%% + +int +yywrap() +{ + return 1; +} diff --git a/iptables/xtables-config.c b/iptables/xtables-config.c new file mode 100644 index 00000000..b7cf6094 --- /dev/null +++ b/iptables/xtables-config.c @@ -0,0 +1,46 @@ +/* + * (C) 2012 by 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 as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This code has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> + +#include "xtables-multi.h" +#include "nft.h" + +int xtables_config_main(int argc, char *argv[]) +{ + struct nft_handle h = { + .family = AF_INET, + }; + const char *filename = NULL; + + if (argc > 2) { + fprintf(stderr, "Usage: %s [<config_file>]\n", argv[0]); + return EXIT_SUCCESS; + } + if (argc == 1) + filename = XTABLES_CONFIG_DEFAULT; + else + filename = argv[1]; + + if (nft_init(&h, xtables_ipv4) < 0) { + fprintf(stderr, "Failed to initialize nft: %s\n", + strerror(errno)); + return EXIT_FAILURE; + } + + return nft_xtables_config_load(&h, filename, NFT_LOAD_VERBOSE) == 0 ? + EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/iptables/xtables-events.c b/iptables/xtables-events.c new file mode 100644 index 00000000..4be8ab8c --- /dev/null +++ b/iptables/xtables-events.c @@ -0,0 +1,217 @@ +/* + * (C) 2012-2013 by 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <stdlib.h> +#include <time.h> +#include <string.h> +#include <netinet/in.h> +#include <getopt.h> + +#include <linux/netfilter/nfnetlink.h> +#include <linux/netfilter/nf_tables.h> + +#include <libmnl/libmnl.h> +#include <libnftnl/table.h> +#include <libnftnl/chain.h> +#include <libnftnl/rule.h> + +#include <include/xtables.h> +#include "iptables.h" /* for xtables_globals */ +#include "xtables-multi.h" +#include "nft.h" + +static int table_cb(const struct nlmsghdr *nlh, int type) +{ + struct nft_table *t; + char buf[4096]; + + t = nft_table_alloc(); + if (t == NULL) { + perror("OOM"); + goto err; + } + + if (nft_table_nlmsg_parse(nlh, t) < 0) { + perror("nft_table_nlmsg_parse"); + goto err_free; + } + + nft_table_snprintf(buf, sizeof(buf), t, NFT_OUTPUT_DEFAULT, 0); + /* FIXME: define syntax to represent table events */ + printf("# [table: %s]\t%s\n", type == NFT_MSG_NEWTABLE ? "NEW" : "DEL", buf); + +err_free: + nft_table_free(t); +err: + return MNL_CB_OK; +} + +static bool counters; + +static int rule_cb(const struct nlmsghdr *nlh, int type) +{ + struct iptables_command_state cs = {}; + struct nft_rule *r; + + r = nft_rule_alloc(); + if (r == NULL) { + perror("OOM"); + goto err; + } + + if (nft_rule_nlmsg_parse(nlh, r) < 0) { + perror("nft_rule_nlmsg_parse"); + goto err_free; + } + + nft_rule_to_iptables_command_state(r, &cs); + + switch(nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY)) { + case AF_INET: + printf("-4 "); + break; + case AF_INET6: + printf("-6 "); + break; + default: + break; + } + + + nft_rule_print_save(&cs, r, + type == NFT_MSG_NEWRULE ? NFT_RULE_APPEND : + NFT_RULE_DEL, + counters ? 0 : FMT_NOCOUNTS); +err_free: + nft_rule_free(r); +err: + return MNL_CB_OK; +} + +static int chain_cb(const struct nlmsghdr *nlh, int type) +{ + struct nft_chain *t; + char buf[4096]; + + t = nft_chain_alloc(); + if (t == NULL) { + perror("OOM"); + goto err; + } + + if (nft_chain_nlmsg_parse(nlh, t) < 0) { + perror("nft_chain_nlmsg_parse"); + goto err_free; + } + + nft_chain_snprintf(buf, sizeof(buf), t, NFT_OUTPUT_DEFAULT, 0); + /* FIXME: define syntax to represent chain events */ + printf("# [chain: %s]\t%s\n", type == NFT_MSG_NEWCHAIN ? "NEW" : "DEL", buf); + +err_free: + nft_chain_free(t); +err: + return MNL_CB_OK; +} + +static int events_cb(const struct nlmsghdr *nlh, void *data) +{ + int ret = MNL_CB_OK; + int type = nlh->nlmsg_type & 0xFF; + + switch(type) { + case NFT_MSG_NEWTABLE: + case NFT_MSG_DELTABLE: + ret = table_cb(nlh, type); + break; + case NFT_MSG_NEWCHAIN: + case NFT_MSG_DELCHAIN: + ret = chain_cb(nlh, type); + break; + case NFT_MSG_NEWRULE: + case NFT_MSG_DELRULE: + ret = rule_cb(nlh, type); + break; + } + + return ret; +} + +static const struct option options[] = { + {.name = "counters", .has_arg = false, .val = 'c'}, + {NULL}, +}; + +static void print_usage(const char *name, const char *version) +{ + fprintf(stderr, "Usage: %s [-c]\n" + " [ --counters ]\n", name); + exit(EXIT_FAILURE); +} + +int xtables_events_main(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + int ret, c; + + xtables_globals.program_name = "xtables-events"; + /* XXX xtables_init_all does several things we don't want */ + c = xtables_init_all(&xtables_globals, NFPROTO_IPV4); + if (c < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + xtables_globals.program_name, + xtables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + + opterr = 0; + while ((c = getopt_long(argc, argv, "c", options, NULL)) != -1) { + switch (c) { + case 'c': + counters = true; + break; + default: + print_usage(argv[0], XTABLES_VERSION); + exit(EXIT_FAILURE); + } + } + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + perror("mnl_socket_open"); + exit(EXIT_FAILURE); + } + + if (mnl_socket_bind(nl, (1 << (NFNLGRP_NFTABLES-1)), MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + exit(EXIT_FAILURE); + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, 0, 0, events_cb, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + perror("error"); + exit(EXIT_FAILURE); + } + mnl_socket_close(nl); + + return EXIT_SUCCESS; +} diff --git a/iptables/xtables-multi.c b/iptables/xtables-multi.c index 8014d5fb..5f487355 100644 --- a/iptables/xtables-multi.c +++ b/iptables/xtables-multi.c @@ -13,6 +13,10 @@ #include "ip6tables-multi.h" #endif +#ifdef ENABLE_NFTABLES +#include "xtables-multi.h" +#endif + static const struct subcommand multi_subcommands[] = { #ifdef ENABLE_IPV4 {"iptables", iptables_main}, @@ -32,6 +36,14 @@ static const struct subcommand multi_subcommands[] = { {"ip6tables-restore", ip6tables_restore_main}, {"restore6", ip6tables_restore_main}, #endif +#ifdef ENABLE_NFTABLES + {"xtables", xtables_main}, + {"xtables-save", xtables_save_main}, + {"xtables-restore", xtables_restore_main}, + {"xtables-config", xtables_config_main}, + {"xtables-events", xtables_events_main}, + {"xtables-arp", xtables_arp_main}, +#endif {NULL}, }; diff --git a/iptables/xtables-multi.h b/iptables/xtables-multi.h index 615724b1..e706894b 100644 --- a/iptables/xtables-multi.h +++ b/iptables/xtables-multi.h @@ -2,5 +2,16 @@ #define _XTABLES_MULTI_H 1 extern int iptables_xml_main(int, char **); +#ifdef ENABLE_NFTABLES +extern int xtables_ip4_main(int, char **); +extern int xtables_ip4_save_main(int, char **); +extern int xtables_ip4_restore_main(int, char **); +extern int xtables_ip6_main(int, char **); +extern int xtables_ip6_save_main(int, char **); +extern int xtables_ip6_restore_main(int, char **); +extern int xtables_arp_main(int, char **); +extern int xtables_config_main(int, char **); +extern int xtables_events_main(int, char **); +#endif #endif /* _XTABLES_MULTI_H */ diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c new file mode 100644 index 00000000..f7850bb2 --- /dev/null +++ b/iptables/xtables-restore.c @@ -0,0 +1,488 @@ +/* Code to restore the iptables state, from file by iptables-save. + * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org> + * based on previous code from Rusty Russell <rusty@linuxcare.com.au> + * + * This code is distributed under the terms of GNU GPL v2 + */ + +#include <getopt.h> +#include <sys/errno.h> +#include <stdbool.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include "iptables.h" +#include "xtables.h" +#include "libiptc/libiptc.h" +#include "xtables-multi.h" +#include "nft.h" +#include <libnftnl/chain.h> + +#ifdef DEBUG +#define DEBUGP(x, args...) fprintf(stderr, x, ## args) +#else +#define DEBUGP(x, args...) +#endif + +static int binary = 0, counters = 0, verbose = 0, noflush = 0; + +/* Keeping track of external matches and targets. */ +static const struct option options[] = { + {.name = "binary", .has_arg = false, .val = 'b'}, + {.name = "counters", .has_arg = false, .val = 'c'}, + {.name = "verbose", .has_arg = false, .val = 'v'}, + {.name = "test", .has_arg = false, .val = 't'}, + {.name = "help", .has_arg = false, .val = 'h'}, + {.name = "noflush", .has_arg = false, .val = 'n'}, + {.name = "modprobe", .has_arg = true, .val = 'M'}, + {.name = "table", .has_arg = true, .val = 'T'}, + {.name = "ipv4", .has_arg = false, .val = '4'}, + {.name = "ipv6", .has_arg = false, .val = '6'}, + {NULL}, +}; + +static void print_usage(const char *name, const char *version) __attribute__((noreturn)); + +#define prog_name xtables_globals.program_name + +static void print_usage(const char *name, const char *version) +{ + fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-t] [-h]\n" + " [ --binary ]\n" + " [ --counters ]\n" + " [ --verbose ]\n" + " [ --test ]\n" + " [ --help ]\n" + " [ --noflush ]\n" + " [ --table=<TABLE> ]\n" + " [ --modprobe=<command>]\n", name); + + exit(1); +} + +static int parse_counters(char *string, struct xt_counters *ctr) +{ + unsigned long long pcnt, bcnt; + int ret; + + ret = sscanf(string, "[%llu:%llu]", &pcnt, &bcnt); + ctr->pcnt = pcnt; + ctr->bcnt = bcnt; + return ret == 2; +} + +/* global new argv and argc */ +static char *newargv[255]; +static int newargc; + +/* function adding one argument to newargv, updating newargc + * returns true if argument added, false otherwise */ +static int add_argv(char *what) { + DEBUGP("add_argv: %s\n", what); + if (what && newargc + 1 < ARRAY_SIZE(newargv)) { + newargv[newargc] = strdup(what); + newargv[++newargc] = NULL; + return 1; + } else { + xtables_error(PARAMETER_PROBLEM, + "Parser cannot handle more arguments\n"); + return 0; + } +} + +static void free_argv(void) { + int i; + + for (i = 0; i < newargc; i++) + free(newargv[i]); +} + +static void add_param_to_argv(char *parsestart) +{ + int quote_open = 0, escaped = 0, param_len = 0; + char param_buffer[1024], *curchar; + + /* After fighting with strtok enough, here's now + * a 'real' parser. According to Rusty I'm now no + * longer a real hacker, but I can live with that */ + + for (curchar = parsestart; *curchar; curchar++) { + if (quote_open) { + if (escaped) { + param_buffer[param_len++] = *curchar; + escaped = 0; + continue; + } else if (*curchar == '\\') { + escaped = 1; + continue; + } else if (*curchar == '"') { + quote_open = 0; + *curchar = ' '; + } else { + param_buffer[param_len++] = *curchar; + continue; + } + } else { + if (*curchar == '"') { + quote_open = 1; + continue; + } + } + + if (*curchar == ' ' + || *curchar == '\t' + || * curchar == '\n') { + if (!param_len) { + /* two spaces? */ + continue; + } + + param_buffer[param_len] = '\0'; + + /* check if table name specified */ + if (!strncmp(param_buffer, "-t", 2) + || !strncmp(param_buffer, "--table", 8)) { + xtables_error(PARAMETER_PROBLEM, + "The -t option (seen in line %u) cannot be " + "used in xtables-restore.\n", line); + exit(1); + } + + add_argv(param_buffer); + param_len = 0; + } else { + /* regular character, copy to buffer */ + param_buffer[param_len++] = *curchar; + + if (param_len >= sizeof(param_buffer)) + xtables_error(PARAMETER_PROBLEM, + "Parameter too long!"); + } + } +} + +static const struct xtc_ops xtc_ops = { + .strerror = nft_strerror, +}; + +static int +xtables_restore_main(int family, const char *progname, int argc, char *argv[]) +{ + struct nft_handle h = { + .family = family, + .restore = true, + }; + char buffer[10240]; + int c; + char curtable[XT_TABLE_MAXNAMELEN + 1]; + FILE *in; + int in_table = 0, testing = 0; + const char *tablename = NULL; + const struct xtc_ops *ops = &xtc_ops; + struct nft_chain_list *chain_list; + struct nft_chain *chain_obj; + + line = 0; + + xtables_globals.program_name = progname; + c = xtables_init_all(&xtables_globals, family); + if (c < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + xtables_globals.program_name, + xtables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + + if (nft_init(&h, xtables_ipv4) < 0) { + fprintf(stderr, "%s/%s Failed to initialize nft: %s\n", + xtables_globals.program_name, + xtables_globals.program_version, + strerror(errno)); + exit(EXIT_FAILURE); + } + + while ((c = getopt_long(argc, argv, "bcvthnM:T:46", options, NULL)) != -1) { + switch (c) { + case 'b': + binary = 1; + break; + case 'c': + counters = 1; + break; + case 'v': + verbose = 1; + break; + case 't': + testing = 1; + break; + case 'h': + print_usage("xtables-restore", + IPTABLES_VERSION); + break; + case 'n': + noflush = 1; + break; + case 'M': + xtables_modprobe_program = optarg; + break; + case 'T': + tablename = optarg; + break; + case '4': + h.family = AF_INET; + break; + case '6': + h.family = AF_INET6; + xtables_set_nfproto(AF_INET6); + break; + } + } + + if (optind == argc - 1) { + in = fopen(argv[optind], "re"); + if (!in) { + fprintf(stderr, "Can't open %s: %s\n", argv[optind], + strerror(errno)); + exit(1); + } + } + else if (optind < argc) { + fprintf(stderr, "Unknown arguments found on commandline\n"); + exit(1); + } + else in = stdin; + + chain_list = nft_chain_dump(&h); + if (chain_list == NULL) + xtables_error(OTHER_PROBLEM, "cannot retrieve chain list\n"); + + /* Grab standard input. */ + while (fgets(buffer, sizeof(buffer), in)) { + int ret = 0; + + line++; + if (buffer[0] == '\n') + continue; + else if (buffer[0] == '#') { + if (verbose) + fputs(buffer, stdout); + continue; + } else if ((strcmp(buffer, "COMMIT\n") == 0) && (in_table)) { + if (!testing) { + /* Commit per table, although we support + * global commit at once, stick by now to + * the existing behaviour. + */ + DEBUGP("Calling commit\n"); + ret = nft_commit(&h); + } else { + DEBUGP("Not calling commit, testing\n"); + ret = nft_abort(&h); + } + in_table = 0; + + /* Purge out unused chains in this table */ + if (!testing) + nft_table_purge_chains(&h, curtable, chain_list); + + } else if ((buffer[0] == '*') && (!in_table)) { + /* New table */ + char *table; + + table = strtok(buffer+1, " \t\n"); + DEBUGP("line %u, table '%s'\n", line, table); + if (!table) { + xtables_error(PARAMETER_PROBLEM, + "%s: line %u table name invalid\n", + xt_params->program_name, line); + exit(1); + } + strncpy(curtable, table, XT_TABLE_MAXNAMELEN); + curtable[XT_TABLE_MAXNAMELEN] = '\0'; + + if (tablename && (strcmp(tablename, table) != 0)) + continue; + + if (noflush == 0) { + DEBUGP("Cleaning all chains of table '%s'\n", + table); + nft_rule_flush(&h, NULL, table); + } + + ret = 1; + in_table = 1; + + } else if ((buffer[0] == ':') && (in_table)) { + /* New chain. */ + char *policy, *chain = NULL; + struct xt_counters count = {}; + + chain = strtok(buffer+1, " \t\n"); + DEBUGP("line %u, chain '%s'\n", line, chain); + if (!chain) { + xtables_error(PARAMETER_PROBLEM, + "%s: line %u chain name invalid\n", + xt_params->program_name, line); + exit(1); + } + + chain_obj = nft_chain_list_find(chain_list, + curtable, chain); + /* This chain has been found, delete from list. Later + * on, unvisited chains will be purged out. + */ + if (chain_obj != NULL) + nft_chain_list_del(chain_obj); + + if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "Invalid chain name `%s' " + "(%u chars max)", + chain, XT_EXTENSION_MAXNAMELEN - 1); + + policy = strtok(NULL, " \t\n"); + DEBUGP("line %u, policy '%s'\n", line, policy); + if (!policy) { + xtables_error(PARAMETER_PROBLEM, + "%s: line %u policy invalid\n", + xt_params->program_name, line); + exit(1); + } + + if (strcmp(policy, "-") != 0) { + if (counters) { + char *ctrs; + ctrs = strtok(NULL, " \t\n"); + + if (!ctrs || !parse_counters(ctrs, &count)) + xtables_error(PARAMETER_PROBLEM, + "invalid policy counters " + "for chain '%s'\n", chain); + + } + if (nft_chain_set(&h, curtable, chain, policy, &count) < 0) { + xtables_error(OTHER_PROBLEM, + "Can't set policy `%s'" + " on `%s' line %u: %s\n", + policy, chain, line, + ops->strerror(errno)); + } + DEBUGP("Setting policy of chain %s to %s\n", + chain, policy); + ret = 1; + + } else { + if (nft_chain_user_add(&h, chain, curtable) < 0) { + if (errno == EEXIST) + continue; + + xtables_error(PARAMETER_PROBLEM, + "cannot create chain " + "'%s' (%s)\n", chain, + strerror(errno)); + } + continue; + } + + } else if (in_table) { + int a; + char *ptr = buffer; + char *pcnt = NULL; + char *bcnt = NULL; + char *parsestart; + + /* reset the newargv */ + newargc = 0; + + if (buffer[0] == '[') { + /* we have counters in our input */ + ptr = strchr(buffer, ']'); + if (!ptr) + xtables_error(PARAMETER_PROBLEM, + "Bad line %u: need ]\n", + line); + + pcnt = strtok(buffer+1, ":"); + if (!pcnt) + xtables_error(PARAMETER_PROBLEM, + "Bad line %u: need :\n", + line); + + bcnt = strtok(NULL, "]"); + if (!bcnt) + xtables_error(PARAMETER_PROBLEM, + "Bad line %u: need ]\n", + line); + + /* start command parsing after counter */ + parsestart = ptr + 1; + } else { + /* start command parsing at start of line */ + parsestart = buffer; + } + + add_argv(argv[0]); + add_argv("-t"); + add_argv(curtable); + + if (counters && pcnt && bcnt) { + add_argv("--set-counters"); + add_argv((char *) pcnt); + add_argv((char *) bcnt); + } + + add_param_to_argv(parsestart); + + DEBUGP("calling do_command4(%u, argv, &%s, handle):\n", + newargc, curtable); + + for (a = 0; a < newargc; a++) + DEBUGP("argv[%u]: %s\n", a, newargv[a]); + + ret = do_commandx(&h, newargc, newargv, + &newargv[2], true); + if (ret < 0) { + ret = nft_abort(&h); + if (ret < 0) { + fprintf(stderr, "failed to abort " + "commit operation\n"); + } + exit(1); + } + + free_argv(); + fflush(stdout); + } + if (tablename && (strcmp(tablename, curtable) != 0)) + continue; + if (!ret) { + fprintf(stderr, "%s: line %u failed\n", + xt_params->program_name, line); + exit(1); + } + } + if (in_table) { + fprintf(stderr, "%s: COMMIT expected at line %u\n", + xt_params->program_name, line + 1); + exit(1); + } + + fclose(in); + return 0; +} + +int xtables_ip4_restore_main(int argc, char *argv[]) +{ + return xtables_restore_main(NFPROTO_IPV4, "iptables-restore", + argc, argv); +} + +int xtables_ip6_restore_main(int argc, char *argv[]) +{ + return xtables_restore_main(NFPROTO_IPV6, "ip6tables-restore", + argc, argv); +} diff --git a/iptables/xtables-save.c b/iptables/xtables-save.c new file mode 100644 index 00000000..42d29071 --- /dev/null +++ b/iptables/xtables-save.c @@ -0,0 +1,154 @@ +/* Code to save the xtables state, in human readable-form. */ +/* (C) 1999 by Paul 'Rusty' Russell <rusty@rustcorp.com.au> and + * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org> + * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * This code is distributed under the terms of GNU GPL v2 + * + */ +#include <getopt.h> +#include <sys/errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <netdb.h> +#include "libiptc/libiptc.h" +#include "iptables.h" +#include "xtables-multi.h" +#include "nft.h" + +#include <libnftnl/chain.h> + +#ifndef NO_SHARED_LIBS +#include <dlfcn.h> +#endif + +static bool show_counters = false; + +static const struct option options[] = { + {.name = "counters", .has_arg = false, .val = 'c'}, + {.name = "dump", .has_arg = false, .val = 'd'}, + {.name = "table", .has_arg = true, .val = 't'}, + {.name = "modprobe", .has_arg = true, .val = 'M'}, + {.name = "ipv4", .has_arg = false, .val = '4'}, + {.name = "ipv6", .has_arg = false, .val = '6'}, + {NULL}, +}; + +static int +do_output(struct nft_handle *h, const char *tablename, bool counters) +{ + struct nft_chain_list *chain_list; + + if (!tablename) + return nft_for_each_table(h, do_output, counters); + + if (!nft_table_find(h, tablename)) { + printf("Table `%s' does not exist\n", tablename); + return 0; + } + + chain_list = nft_chain_dump(h); + + time_t now = time(NULL); + + printf("# Generated by xtables-save v%s on %s", + IPTABLES_VERSION, ctime(&now)); + printf("*%s\n", tablename); + + /* Dump out chain names first, + * thereby preventing dependency conflicts */ + nft_chain_save(h, chain_list, tablename); + nft_rule_save(h, tablename, counters); + + now = time(NULL); + printf("COMMIT\n"); + printf("# Completed on %s", ctime(&now)); + + return 1; +} + +/* Format: + * :Chain name POLICY packets bytes + * rule + */ +static int +xtables_save_main(int family, const char *progname, int argc, char *argv[]) +{ + const char *tablename = NULL; + bool dump = false; + struct nft_handle h = { + .family = family, + }; + int c; + + xtables_globals.program_name = progname; + c = xtables_init_all(&xtables_globals, family); + if (c < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + xtables_globals.program_name, + xtables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + if (nft_init(&h, xtables_ipv4) < 0) { + fprintf(stderr, "%s/%s Failed to initialize nft: %s\n", + xtables_globals.program_name, + xtables_globals.program_version, + strerror(errno)); + exit(EXIT_FAILURE); + } + + while ((c = getopt_long(argc, argv, "bcdt:46", options, NULL)) != -1) { + switch (c) { + case 'c': + show_counters = true; + break; + + case 't': + /* Select specific table. */ + tablename = optarg; + break; + case 'M': + xtables_modprobe_program = optarg; + break; + case 'd': + dump = true; + break; + case '4': + h.family = AF_INET; + break; + case '6': + h.family = AF_INET6; + xtables_set_nfproto(AF_INET6); + break; + } + } + + if (optind < argc) { + fprintf(stderr, "Unknown arguments found on commandline\n"); + exit(1); + } + + if (dump) { + do_output(&h, tablename, show_counters); + exit(0); + } + + return !do_output(&h, tablename, show_counters); +} + +int xtables_ip4_save_main(int argc, char *argv[]) +{ + return xtables_save_main(NFPROTO_IPV4, "iptables-save", argc, argv); +} + +int xtables_ip6_save_main(int argc, char *argv[]) +{ + return xtables_save_main(NFPROTO_IPV6, "ip6tables-save", argc, argv); +} diff --git a/iptables/xtables-standalone.c b/iptables/xtables-standalone.c new file mode 100644 index 00000000..355a4460 --- /dev/null +++ b/iptables/xtables-standalone.c @@ -0,0 +1,104 @@ +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * Based on the ipchains code by Paul Russell and Michael Neuling + * + * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>: + * Paul 'Rusty' Russell <rusty@rustcorp.com.au> + * Marc Boucher <marc+nf@mbsi.ca> + * James Morris <jmorris@intercode.com.au> + * Harald Welte <laforge@gnumonks.org> + * Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> + * + * iptables -- IP firewall administration for kernels with + * firewall table (aimed for the 2.3 kernels) + * + * See the accompanying manual page iptables(8) for information + * about proper usage of this program. + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <iptables.h> +#include "xtables-multi.h" +#include "nft.h" + +static int +xtables_main(int family, const char *progname, int argc, char *argv[]) +{ + int ret; + char *table = "filter"; + struct nft_handle h = { + .family = family, + }; + + xtables_globals.program_name = progname; + ret = xtables_init_all(&xtables_globals, family); + if (ret < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + xtables_globals.program_name, + xtables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + + if (nft_init(&h, xtables_ipv4) < 0) { + fprintf(stderr, "%s/%s Failed to initialize nft: %s\n", + xtables_globals.program_name, + xtables_globals.program_version, + strerror(errno)); + nft_fini(&h); + exit(EXIT_FAILURE); + } + + ret = do_commandx(&h, argc, argv, &table, false); + if (ret) + ret = nft_commit(&h); + + nft_fini(&h); + + if (!ret) { + if (errno == EINVAL) { + fprintf(stderr, "iptables: %s. " + "Run `dmesg' for more information.\n", + nft_strerror(errno)); + } else { + fprintf(stderr, "iptables: %s.\n", + nft_strerror(errno)); + } + if (errno == EAGAIN) { + exit(RESOURCE_PROBLEM); + } + } + + exit(!ret); +} + +int xtables_ip4_main(int argc, char *argv[]) +{ + return xtables_main(NFPROTO_IPV4, "iptables", argc, argv); +} + +int xtables_ip6_main(int argc, char *argv[]) +{ + return xtables_main(NFPROTO_IPV6, "ip6tables", argc, argv); +} diff --git a/iptables/xtables.c b/iptables/xtables.c new file mode 100644 index 00000000..45a5ac63 --- /dev/null +++ b/iptables/xtables.c @@ -0,0 +1,1261 @@ +/* Code to take an iptables-style command line and do it. */ + +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * (C) 2000-2002 by the netfilter coreteam <coreteam@netfilter.org>: + * Paul 'Rusty' Russell <rusty@rustcorp.com.au> + * Marc Boucher <marc+nf@mbsi.ca> + * James Morris <jmorris@intercode.com.au> + * Harald Welte <laforge@gnumonks.org> + * Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <getopt.h> +#include <string.h> +#include <netdb.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> +#include <limits.h> +#include <unistd.h> +#include <iptables.h> +#include <xtables.h> +#include <fcntl.h> +#include "xshared.h" +#include "nft-shared.h" +#include "nft.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define NUMBER_OF_CMD 16 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'N', 'X', 'P', 'E', 'S', 'Z', 'C' }; + +#define OPT_FRAGMENT 0x00800U +#define NUMBER_OF_OPT ARRAY_SIZE(optflags) +static const char optflags[] += { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c', 'f'}; + +static struct option original_opts[] = { + {.name = "append", .has_arg = 1, .val = 'A'}, + {.name = "delete", .has_arg = 1, .val = 'D'}, + {.name = "check", .has_arg = 1, .val = 'C'}, + {.name = "insert", .has_arg = 1, .val = 'I'}, + {.name = "replace", .has_arg = 1, .val = 'R'}, + {.name = "list", .has_arg = 2, .val = 'L'}, + {.name = "list-rules", .has_arg = 2, .val = 'S'}, + {.name = "flush", .has_arg = 2, .val = 'F'}, + {.name = "zero", .has_arg = 2, .val = 'Z'}, + {.name = "new-chain", .has_arg = 1, .val = 'N'}, + {.name = "delete-chain", .has_arg = 2, .val = 'X'}, + {.name = "rename-chain", .has_arg = 1, .val = 'E'}, + {.name = "policy", .has_arg = 1, .val = 'P'}, + {.name = "source", .has_arg = 1, .val = 's'}, + {.name = "destination", .has_arg = 1, .val = 'd'}, + {.name = "src", .has_arg = 1, .val = 's'}, /* synonym */ + {.name = "dst", .has_arg = 1, .val = 'd'}, /* synonym */ + {.name = "protocol", .has_arg = 1, .val = 'p'}, + {.name = "in-interface", .has_arg = 1, .val = 'i'}, + {.name = "jump", .has_arg = 1, .val = 'j'}, + {.name = "table", .has_arg = 1, .val = 't'}, + {.name = "match", .has_arg = 1, .val = 'm'}, + {.name = "numeric", .has_arg = 0, .val = 'n'}, + {.name = "out-interface", .has_arg = 1, .val = 'o'}, + {.name = "verbose", .has_arg = 0, .val = 'v'}, + {.name = "exact", .has_arg = 0, .val = 'x'}, + {.name = "fragments", .has_arg = 0, .val = 'f'}, + {.name = "version", .has_arg = 0, .val = 'V'}, + {.name = "help", .has_arg = 2, .val = 'h'}, + {.name = "line-numbers", .has_arg = 0, .val = '0'}, + {.name = "modprobe", .has_arg = 1, .val = 'M'}, + {.name = "set-counters", .has_arg = 1, .val = 'c'}, + {.name = "goto", .has_arg = 1, .val = 'g'}, + {.name = "ipv4", .has_arg = 0, .val = '4'}, + {.name = "ipv6", .has_arg = 0, .val = '6'}, + {NULL}, +}; + +void xtables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3))); + +struct xtables_globals xtables_globals = { + .option_offset = 0, + .program_version = IPTABLES_VERSION, + .orig_opts = original_opts, + .exit_err = xtables_exit_error, + .compat_rev = nft_compatible_revision, +}; + +/* Table of legal combinations of commands and options. If any of the + * given commands make an option legal, that option is legal (applies to + * CMD_LIST and CMD_ZERO only). + * Key: + * + compulsory + * x illegal + * optional + */ + +static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = +/* Well, it's better than "Re: Linux vs FreeBSD" */ +{ + /* -n -s -d -p -j -v -x -i -o --line -c -f */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '}, +/*DELETE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '}, +/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*REPLACE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '}, +/*APPEND*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '}, +/*LIST*/ {' ','x','x','x','x',' ',' ','x','x',' ','x','x'}, +/*FLUSH*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*ZERO*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*ZERO_NUM*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x',' ','x'}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*LIST_RULES*/{'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*CHECK*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '}, +}; + +static const int inverse_for_options[NUMBER_OF_OPT] = +{ +/* -n */ 0, +/* -s */ IPT_INV_SRCIP, +/* -d */ IPT_INV_DSTIP, +/* -p */ XT_INV_PROTO, +/* -j */ 0, +/* -v */ 0, +/* -x */ 0, +/* -i */ IPT_INV_VIA_IN, +/* -o */ IPT_INV_VIA_OUT, +/*--line*/ 0, +/* -c */ 0, +/* -f */ IPT_INV_FRAG, +}; + +#define opts xtables_globals.opts +#define prog_name xtables_globals.program_name +#define prog_vers xtables_globals.program_version + +static void __attribute__((noreturn)) +exit_tryhelp(int status) +{ + if (line != -1) + fprintf(stderr, "Error occurred at line: %d\n", line); + fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", + prog_name, prog_name); + xtables_free_opts(1); + exit(status); +} + +static void +exit_printhelp(const struct xtables_rule_match *matches) +{ + printf("%s v%s\n\n" +"Usage: %s -[ACD] chain rule-specification [options]\n" +" %s -I chain [rulenum] rule-specification [options]\n" +" %s -R chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LS] [chain [rulenum]] [options]\n" +" %s -[FZ] [chain] [options]\n" +" %s -[NX] chain\n" +" %s -E old-chain-name new-chain-name\n" +" %s -P chain target [options]\n" +" %s -h (print this help information)\n\n", + prog_name, prog_vers, prog_name, prog_name, + prog_name, prog_name, prog_name, prog_name, + prog_name, prog_name, prog_name, prog_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\n" +" --check -C chain Check for the existence of a rule\n" +" --delete -D chain Delete matching rule from chain\n" +" --delete -D chain rulenum\n" +" Delete rule rulenum (1 = first) from chain\n" +" --insert -I chain [rulenum]\n" +" Insert in chain as rulenum (default 1=first)\n" +" --replace -R chain rulenum\n" +" Replace rule rulenum (1 = first) in chain\n" +" --list -L [chain [rulenum]]\n" +" List the rules in a chain or all chains\n" +" --list-rules -S [chain [rulenum]]\n" +" Print the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain [rulenum]]\n" +" Zero counters in chain or all chains\n" +" --new -N chain Create a new user-defined chain\n" +" --delete-chain\n" +" -X [chain] Delete a user-defined chain\n" +" --policy -P chain target\n" +" Change policy on chain to target\n" +" --rename-chain\n" +" -E old-chain new-chain\n" +" Change chain name, (moving any references)\n" + +"Options:\n" +" --ipv4 -4 Nothing (line is ignored by ip6tables-restore)\n" +" --ipv6 -6 Error (line is ignored by iptables-restore)\n" +"[!] --proto -p proto protocol: by number or name, eg. `tcp'\n" +"[!] --source -s address[/mask][...]\n" +" source specification\n" +"[!] --destination -d address[/mask][...]\n" +" destination specification\n" +"[!] --in-interface -i input name[+]\n" +" network interface name ([+] for wildcard)\n" +" --jump -j target\n" +" target for rule (may load target extension)\n" +#ifdef IPT_F_GOTO +" --goto -g chain\n" +" jump to chain with no return\n" +#endif +" --match -m match\n" +" extended match (may load extension)\n" +" --numeric -n numeric output of addresses and ports\n" +"[!] --out-interface -o output name[+]\n" +" network interface name ([+] for wildcard)\n" +" --table -t table table to manipulate (default: `filter')\n" +" --verbose -v verbose mode\n" +" --line-numbers print line numbers when listing\n" +" --exact -x expand numbers (display exact values)\n" +"[!] --fragment -f match second or further fragments only\n" +" --modprobe=<command> try to insert modules using this command\n" +" --set-counters PKTS BYTES set the counter during insert/append\n" +"[!] --version -V print package version.\n"); + + print_extension_helps(xtables_targets, matches); + exit(0); +} + +void +xtables_exit_error(enum xtables_exittype status, const char *msg, ...) +{ + va_list args; + + va_start(args, msg); + fprintf(stderr, "%s v%s: ", prog_name, prog_vers); + vfprintf(stderr, msg, args); + va_end(args); + fprintf(stderr, "\n"); + if (status == PARAMETER_PROBLEM) + exit_tryhelp(status); + if (status == VERSION_PROBLEM) + fprintf(stderr, + "Perhaps iptables or your kernel needs to be upgraded.\n"); + /* On error paths, make sure that we don't leak memory */ + xtables_free_opts(1); + exit(status); +} + +static void +generic_opt_check(int command, int options) +{ + int i, j, legal = 0; + + /* Check that commands are valid with options. Complicated by the + * fact that if an option is legal with *any* command given, it is + * legal overall (ie. -z and -l). + */ + for (i = 0; i < NUMBER_OF_OPT; i++) { + legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */ + + for (j = 0; j < NUMBER_OF_CMD; j++) { + if (!(command & (1<<j))) + continue; + + if (!(options & (1<<i))) { + if (commands_v_options[j][i] == '+') + xtables_error(PARAMETER_PROBLEM, + "You need to supply the `-%c' " + "option for this command\n", + optflags[i]); + } else { + if (commands_v_options[j][i] != 'x') + legal = 1; + else if (legal == 0) + legal = -1; + } + } + if (legal == -1) + xtables_error(PARAMETER_PROBLEM, + "Illegal option `-%c' with this command\n", + optflags[i]); + } +} + +static char +opt2char(int option) +{ + const char *ptr; + for (ptr = optflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static char +cmd2char(int option) +{ + const char *ptr; + for (ptr = cmdflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static void +add_command(unsigned int *cmd, const int newcmd, const int othercmds, + int invert) +{ + if (invert) + xtables_error(PARAMETER_PROBLEM, "unexpected ! flag"); + if (*cmd & (~othercmds)) + xtables_error(PARAMETER_PROBLEM, "Cannot use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +/* + * All functions starting with "parse" should succeed, otherwise + * the program fails. + * Most routines return pointers to static data that may change + * between calls to the same or other routines with a few exceptions: + * "host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask" + * return global static data. +*/ + +/* Christophe Burki wants `-p 6' to imply `-m tcp'. */ +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + unsigned int rulenum; + + if (!xtables_strtoui(rule, NULL, &rulenum, 1, INT_MAX)) + xtables_error(PARAMETER_PROBLEM, + "Invalid rule number `%s'", rule); + + return rulenum; +} + +static const char * +parse_target(const char *targetname) +{ + const char *ptr; + + if (strlen(targetname) < 1) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%u chars max)", + targetname, XT_EXTENSION_MAXNAMELEN - 1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +static void +set_option(unsigned int *options, unsigned int option, uint8_t *invflg, + int invert) +{ + if (*options & option) + xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed", + opt2char(option)); + *options |= option; + + if (invert) { + unsigned int i; + for (i = 0; 1 << i != option; i++); + + if (!inverse_for_options[i]) + xtables_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +static int +add_entry(const char *chain, + const char *table, + struct iptables_command_state *cs, + int rulenum, int family, + const struct addr_mask s, + const struct addr_mask d, + bool verbose, struct nft_handle *h, bool append) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < s.naddrs; i++) { + if (family == AF_INET) { + cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr; + cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr; + for (j = 0; j < d.naddrs; j++) { + cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr; + cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr; + + if (append) { + ret = nft_rule_append(h, chain, table, + cs, 0, + verbose); + } else { + ret = nft_rule_insert(h, chain, table, + cs, rulenum, + verbose); + } + } + } else if (family == AF_INET6) { + memcpy(&cs->fw6.ipv6.src, + &s.addr.v6[i], sizeof(struct in6_addr)); + memcpy(&cs->fw6.ipv6.smsk, + &s.mask.v6[i], sizeof(struct in6_addr)); + for (j = 0; j < d.naddrs; j++) { + memcpy(&cs->fw6.ipv6.dst, + &d.addr.v6[j], sizeof(struct in6_addr)); + memcpy(&cs->fw6.ipv6.dmsk, + &d.mask.v6[j], sizeof(struct in6_addr)); + if (append) { + ret = nft_rule_append(h, chain, table, + cs, 0, + verbose); + } else { + ret = nft_rule_insert(h, chain, table, + cs, rulenum, + verbose); + } + } + } + } + + return ret; +} + +static int +replace_entry(const char *chain, const char *table, + struct iptables_command_state *cs, + unsigned int rulenum, + int family, + const struct addr_mask s, + const struct addr_mask d, + bool verbose, struct nft_handle *h) +{ + if (family == AF_INET) { + cs->fw.ip.src.s_addr = s.addr.v4->s_addr; + cs->fw.ip.dst.s_addr = d.addr.v4->s_addr; + cs->fw.ip.smsk.s_addr = s.mask.v4->s_addr; + cs->fw.ip.dmsk.s_addr = d.mask.v4->s_addr; + } else if (family == AF_INET6) { + memcpy(&cs->fw6.ipv6.src, s.addr.v6, sizeof(struct in6_addr)); + memcpy(&cs->fw6.ipv6.dst, d.addr.v6, sizeof(struct in6_addr)); + memcpy(&cs->fw6.ipv6.smsk, s.mask.v6, sizeof(struct in6_addr)); + memcpy(&cs->fw6.ipv6.dmsk, d.mask.v6, sizeof(struct in6_addr)); + } else + return 1; + + return nft_rule_replace(h, chain, table, cs, rulenum, verbose); +} + +static int +delete_entry(const char *chain, const char *table, + struct iptables_command_state *cs, + int family, + const struct addr_mask s, + const struct addr_mask d, + bool verbose, + struct nft_handle *h) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < s.naddrs; i++) { + if (family == AF_INET) { + cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr; + cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr; + for (j = 0; j < d.naddrs; j++) { + cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr; + cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr; + ret = nft_rule_delete(h, chain, + table, cs, verbose); + } + } else if (family == AF_INET6) { + memcpy(&cs->fw6.ipv6.src, + &s.addr.v6[i], sizeof(struct in6_addr)); + memcpy(&cs->fw6.ipv6.smsk, + &s.mask.v6[i], sizeof(struct in6_addr)); + for (j = 0; j < d.naddrs; j++) { + memcpy(&cs->fw6.ipv6.dst, + &d.addr.v6[j], sizeof(struct in6_addr)); + memcpy(&cs->fw6.ipv6.dmsk, + &d.mask.v6[j], sizeof(struct in6_addr)); + ret = nft_rule_delete(h, chain, + table, cs, verbose); + } + } + } + + return ret; +} + +static int +check_entry(const char *chain, const char *table, + struct iptables_command_state *cs, + int family, + const struct addr_mask s, + const struct addr_mask d, + bool verbose, struct nft_handle *h) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < s.naddrs; i++) { + if (family == AF_INET) { + cs->fw.ip.src.s_addr = s.addr.v4[i].s_addr; + cs->fw.ip.smsk.s_addr = s.mask.v4[i].s_addr; + for (j = 0; j < d.naddrs; j++) { + cs->fw.ip.dst.s_addr = d.addr.v4[j].s_addr; + cs->fw.ip.dmsk.s_addr = d.mask.v4[j].s_addr; + ret = nft_rule_check(h, chain, + table, cs, verbose); + } + } else if (family == AF_INET6) { + memcpy(&cs->fw6.ipv6.src, + &s.addr.v6[i], sizeof(struct in6_addr)); + memcpy(&cs->fw6.ipv6.smsk, + &s.mask.v6[i], sizeof(struct in6_addr)); + for (j = 0; j < d.naddrs; j++) { + memcpy(&cs->fw6.ipv6.dst, + &d.addr.v6[j], sizeof(struct in6_addr)); + memcpy(&cs->fw6.ipv6.dmsk, + &d.mask.v6[j], sizeof(struct in6_addr)); + ret = nft_rule_check(h, chain, + table, cs, verbose); + } + } + } + + return ret; +} + +static int +list_entries(struct nft_handle *h, const char *chain, const char *table, + int rulenum, int verbose, int numeric, int expanded, + int linenumbers) +{ + unsigned int format; + + format = FMT_OPTIONS; + if (!verbose) + format |= FMT_NOCOUNTS; + else + format |= FMT_VIA; + + if (numeric) + format |= FMT_NUMERIC; + + if (!expanded) + format |= FMT_KILOMEGAGIGA; + + if (linenumbers) + format |= FMT_LINENUMBERS; + + return nft_rule_list(h, chain, table, rulenum, format); +} + +static int +list_rules(struct nft_handle *h, const char *chain, const char *table, + int rulenum, int counters) +{ + if (counters) + counters = -1; /* iptables -c format */ + + nft_rule_list_save(h, chain, table, rulenum, counters); + + /* iptables does not return error if rule number not found */ + return 1; +} + +static void command_jump(struct iptables_command_state *cs) +{ + size_t size; + + set_option(&cs->options, OPT_JUMP, &cs->fw.ip.invflags, cs->invert); + cs->jumpto = parse_target(optarg); + /* TRY_LOAD (may be chain name) */ + cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD); + + if (cs->target == NULL) + return; + + size = XT_ALIGN(sizeof(struct xt_entry_target)) + + cs->target->size; + + cs->target->t = xtables_calloc(1, size); + cs->target->t->u.target_size = size; + if (cs->target->real_name == NULL) { + strcpy(cs->target->t->u.user.name, cs->jumpto); + } else { + /* Alias support for userspace side */ + strcpy(cs->target->t->u.user.name, cs->target->real_name); + if (!(cs->target->ext_flags & XTABLES_EXT_ALIAS)) + fprintf(stderr, "Notice: The %s target is converted into %s target " + "in rule listing and saving.\n", + cs->jumpto, cs->target->real_name); + } + cs->target->t->u.user.revision = cs->target->revision; + xs_init_target(cs->target); + + if (cs->target->x6_options != NULL) + opts = xtables_options_xfrm(xtables_globals.orig_opts, opts, + cs->target->x6_options, + &cs->target->option_offset); + else + opts = xtables_merge_options(xtables_globals.orig_opts, opts, + cs->target->extra_opts, + &cs->target->option_offset); + if (opts == NULL) + xtables_error(OTHER_PROBLEM, "can't alloc memory!"); +} + +static void command_match(struct iptables_command_state *cs) +{ + struct xtables_match *m; + size_t size; + + if (cs->invert) + xtables_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = xtables_find_match(optarg, XTF_LOAD_MUST_SUCCEED, &cs->matches); + size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size; + m->m = xtables_calloc(1, size); + m->m->u.match_size = size; + if (m->real_name == NULL) { + strcpy(m->m->u.user.name, m->name); + } else { + strcpy(m->m->u.user.name, m->real_name); + if (!(m->ext_flags & XTABLES_EXT_ALIAS)) + fprintf(stderr, "Notice: the %s match is converted into %s match " + "in rule listing and saving.\n", m->name, m->real_name); + } + m->m->u.user.revision = m->revision; + xs_init_match(m); + if (m == m->next) + return; + /* Merge options for non-cloned matches */ + if (m->x6_options != NULL) + opts = xtables_options_xfrm(xtables_globals.orig_opts, opts, + m->x6_options, &m->option_offset); + else if (m->extra_opts != NULL) + opts = xtables_merge_options(xtables_globals.orig_opts, opts, + m->extra_opts, &m->option_offset); + if (opts == NULL) + xtables_error(OTHER_PROBLEM, "can't alloc memory!"); +} + +int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table, + bool restore) +{ + struct iptables_command_state cs; + int verbose = 0; + const char *chain = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, command = 0; + int ret = 1; + struct xtables_match *m; + struct xtables_rule_match *matchp; + struct xtables_target *t; + struct xtables_args args = { + .family = h->family, + }; + + memset(&cs, 0, sizeof(cs)); + cs.jumpto = ""; + cs.argv = argv; + + /* re-set optind to 0 in case do_command4 gets called + * a second time */ + optind = 0; + + /* clear mflags in case do_command4 gets called a second time + * (we clear the global list of all matches for security)*/ + for (m = xtables_matches; m; m = m->next) + m->mflags = 0; + + for (t = xtables_targets; t; t = t->next) { + t->tflags = 0; + t->used = 0; + } + + /* Suppress error messages: we may add new options if we + demand-load a protocol. */ + opterr = 0; + + h->ops = nft_family_ops_lookup(h->family); + if (h->ops == NULL) + xtables_error(PARAMETER_PROBLEM, "Unknown family"); + + opts = xt_params->orig_opts; + while ((cs.c = getopt_long(argc, argv, + "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:g:46", + opts, NULL)) != -1) { + switch (cs.c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'C': + add_command(&command, CMD_CHECK, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_DELETE_NUM; + } + break; + + case 'R': + add_command(&command, CMD_REPLACE, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else rulenum = 1; + break; + + case 'L': + add_command(&command, CMD_LIST, + CMD_ZERO | CMD_ZERO_NUM, cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + break; + + case 'S': + add_command(&command, CMD_LIST_RULES, + CMD_ZERO|CMD_ZERO_NUM, cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'Z': + add_command(&command, CMD_ZERO, CMD_LIST|CMD_LIST_RULES, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_ZERO_NUM; + } + break; + + case 'N': + if (optarg && (*optarg == '-' || *optarg == '!')) + xtables_error(PARAMETER_PROBLEM, + "chain name not allowed to start " + "with `%c'\n", *optarg); + if (xtables_find_target(optarg, XTF_TRY_LOAD)) + xtables_error(PARAMETER_PROBLEM, + "chain name may not clash " + "with target name\n"); + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'E': + add_command(&command, CMD_RENAME_CHAIN, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires old-chain-name and " + "new-chain-name", + cmd2char(CMD_RENAME_CHAIN)); + break; + + case 'P': + add_command(&command, CMD_SET_POLICY, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a chain and a policy", + cmd2char(CMD_SET_POLICY)); + break; + + case 'h': + if (!optarg) + optarg = argv[optind]; + + /* iptables -p icmp -h */ + if (!cs.matches && cs.protocol) + xtables_find_match(cs.protocol, + XTF_TRY_LOAD, &cs.matches); + + exit_printhelp(cs.matches); + + /* + * Option selection + */ + case 'p': + set_option(&cs.options, OPT_PROTOCOL, &args.invflags, + cs.invert); + + /* Canonicalize into lower case */ + for (cs.protocol = optarg; *cs.protocol; cs.protocol++) + *cs.protocol = tolower(*cs.protocol); + + cs.protocol = optarg; + args.proto = xtables_parse_protocol(cs.protocol); + + if (args.proto == 0 && (args.invflags & XT_INV_PROTO)) + xtables_error(PARAMETER_PROBLEM, + "rule would never match protocol"); + + /* This needs to happen here to parse extensions */ + h->ops->proto_parse(&cs, &args); + break; + + case 's': + set_option(&cs.options, OPT_SOURCE, &args.invflags, + cs.invert); + args.shostnetworkmask = optarg; + break; + + case 'd': + set_option(&cs.options, OPT_DESTINATION, + &args.invflags, cs.invert); + args.dhostnetworkmask = optarg; + break; + +#ifdef IPT_F_GOTO + case 'g': + set_option(&cs.options, OPT_JUMP, &args.invflags, + cs.invert); + args.goto_set = true; + cs.jumpto = parse_target(optarg); + break; +#endif + + case 'j': + command_jump(&cs); + break; + + + case 'i': + if (*optarg == '\0') + xtables_error(PARAMETER_PROBLEM, + "Empty interface is likely to be " + "undesired"); + set_option(&cs.options, OPT_VIANAMEIN, &args.invflags, + cs.invert); + xtables_parse_interface(optarg, + args.iniface, + args.iniface_mask); + break; + + case 'o': + if (*optarg == '\0') + xtables_error(PARAMETER_PROBLEM, + "Empty interface is likely to be " + "undesired"); + set_option(&cs.options, OPT_VIANAMEOUT, &args.invflags, + cs.invert); + xtables_parse_interface(optarg, + args.outiface, + args.outiface_mask); + break; + + case 'f': + if (args.family == AF_INET6) { + xtables_error(PARAMETER_PROBLEM, + "`-f' is not supported in IPv6, " + "use -m frag instead"); + } + set_option(&cs.options, OPT_FRAGMENT, &args.invflags, + cs.invert); + args.flags |= IPT_F_FRAG; + break; + + case 'v': + if (!verbose) + set_option(&cs.options, OPT_VERBOSE, + &args.invflags, cs.invert); + verbose++; + break; + + case 'm': + command_match(&cs); + break; + + case 'n': + set_option(&cs.options, OPT_NUMERIC, &args.invflags, + cs.invert); + break; + + case 't': + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = optarg; + break; + + case 'x': + set_option(&cs.options, OPT_EXPANDED, &args.invflags, + cs.invert); + break; + + case 'V': + if (cs.invert) + printf("Not %s ;-)\n", prog_vers); + else + printf("%s v%s\n", + prog_name, prog_vers); + exit(0); + + case 'w': + if (restore) { + xtables_error(PARAMETER_PROBLEM, + "You cannot use `-w' from " + "iptables-restore"); + } + break; + + case '0': + set_option(&cs.options, OPT_LINENUMBERS, + &args.invflags, cs.invert); + break; + + case 'M': + xtables_modprobe_program = optarg; + break; + + case 'c': + + set_option(&cs.options, OPT_COUNTERS, &args.invflags, + cs.invert); + args.pcnt = optarg; + args.bcnt = strchr(args.pcnt + 1, ','); + if (args.bcnt) + args.bcnt++; + if (!args.bcnt && optind < argc && + argv[optind][0] != '-' && + argv[optind][0] != '!') + args.bcnt = argv[optind++]; + if (!args.bcnt) + xtables_error(PARAMETER_PROBLEM, + "-%c requires packet and byte counter", + opt2char(OPT_COUNTERS)); + + if (sscanf(args.pcnt, "%llu", &args.pcnt_cnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c packet counter not numeric", + opt2char(OPT_COUNTERS)); + + if (sscanf(args.bcnt, "%llu", &args.bcnt_cnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c byte counter not numeric", + opt2char(OPT_COUNTERS)); + break; + + case '4': + if (args.family != AF_INET) + exit_tryhelp(2); + + h->ops = nft_family_ops_lookup(args.family); + break; + + case '6': + args.family = AF_INET6; + xtables_set_nfproto(AF_INET6); + + h->ops = nft_family_ops_lookup(args.family); + if (h->ops == NULL) + xtables_error(PARAMETER_PROBLEM, + "Unknown family"); + break; + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + cs.invert = TRUE; + optarg[0] = '\0'; + continue; + } + fprintf(stderr, "Bad argument `%s'\n", optarg); + exit_tryhelp(2); + + default: + if (command_default(&cs, &xtables_globals) == 1) + /* cf. ip6tables.c */ + continue; + break; + } + cs.invert = FALSE; + } + + if (strcmp(*table, "nat") == 0 && + ((policy != NULL && strcmp(policy, "DROP") == 0) || + (cs.jumpto != NULL && strcmp(cs.jumpto, "DROP") == 0))) + xtables_error(PARAMETER_PROBLEM, + "\nThe \"nat\" table is not intended for filtering, " + "the use of DROP is therefore inhibited.\n\n"); + + for (matchp = cs.matches; matchp; matchp = matchp->next) + xtables_option_mfcall(matchp->match); + if (cs.target != NULL) + xtables_option_tfcall(cs.target); + + /* Fix me: must put inverse options checking here --MN */ + + if (optind < argc) + xtables_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + xtables_error(PARAMETER_PROBLEM, "no command specified"); + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + /* Set only if required, needed by xtables-restore */ + if (h->family == AF_UNSPEC) + h->family = args.family; + + h->ops->post_parse(command, &cs, &args); + + if (command == CMD_REPLACE && + (args.s.naddrs != 1 || args.d.naddrs != 1)) + xtables_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, cs.options); + + if (chain != NULL && strlen(chain) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %u chars)", + chain, XT_EXTENSION_MAXNAMELEN); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_CHECK + || command == CMD_INSERT + || command == CMD_REPLACE) { + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + /* -o not valid with incoming packets. */ + if (cs.options & OPT_VIANAMEOUT) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEOUT), + chain); + } + + if (strcmp(chain, "POSTROUTING") == 0 + || strcmp(chain, "OUTPUT") == 0) { + /* -i not valid with outgoing packets */ + if (cs.options & OPT_VIANAMEIN) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + /* + * Contrary to what iptables does, we assume that any jumpto + * is a custom chain jumps (if no target is found). Later on, + * nf_table will spot the error if the chain does not exists. + */ + } + + switch (command) { + case CMD_APPEND: + ret = add_entry(chain, *table, &cs, 0, h->family, + args.s, args.d, cs.options&OPT_VERBOSE, + h, true); + break; + case CMD_DELETE: + ret = delete_entry(chain, *table, &cs, h->family, + args.s, args.d, cs.options&OPT_VERBOSE, h); + break; + case CMD_DELETE_NUM: + ret = nft_rule_delete_num(h, chain, *table, rulenum - 1, verbose); + break; + case CMD_CHECK: + ret = check_entry(chain, *table, &cs, h->family, + args.s, args.d, cs.options&OPT_VERBOSE, h); + break; + case CMD_REPLACE: + ret = replace_entry(chain, *table, &cs, rulenum - 1, + h->family, args.s, args.d, + cs.options&OPT_VERBOSE, h); + break; + case CMD_INSERT: + ret = add_entry(chain, *table, &cs, rulenum - 1, h->family, + args.s, args.d, cs.options&OPT_VERBOSE, h, + false); + break; + case CMD_FLUSH: + ret = nft_rule_flush(h, chain, *table); + break; + case CMD_ZERO: + ret = nft_chain_zero_counters(h, chain, *table); + break; + case CMD_ZERO_NUM: + ret = nft_rule_zero_counters(h, chain, *table, rulenum - 1); + break; + case CMD_LIST: + case CMD_LIST|CMD_ZERO: + case CMD_LIST|CMD_ZERO_NUM: + ret = list_entries(h, chain, *table, + rulenum, + cs.options&OPT_VERBOSE, + cs.options&OPT_NUMERIC, + cs.options&OPT_EXPANDED, + cs.options&OPT_LINENUMBERS); + if (ret && (command & CMD_ZERO)) + ret = nft_chain_zero_counters(h, chain, *table); + if (ret && (command & CMD_ZERO_NUM)) + ret = nft_rule_zero_counters(h, chain, *table, + rulenum - 1); + break; + case CMD_LIST_RULES: + case CMD_LIST_RULES|CMD_ZERO: + case CMD_LIST_RULES|CMD_ZERO_NUM: + ret = list_rules(h, chain, *table, rulenum, cs.options&OPT_VERBOSE); + if (ret && (command & CMD_ZERO)) + ret = nft_chain_zero_counters(h, chain, *table); + if (ret && (command & CMD_ZERO_NUM)) + ret = nft_rule_zero_counters(h, chain, *table, + rulenum - 1); + break; + case CMD_NEW_CHAIN: + ret = nft_chain_user_add(h, chain, *table); + break; + case CMD_DELETE_CHAIN: + ret = nft_chain_user_del(h, chain, *table); + break; + case CMD_RENAME_CHAIN: + ret = nft_chain_user_rename(h, chain, *table, newname); + break; + case CMD_SET_POLICY: + ret = nft_chain_set(h, *table, chain, policy, NULL); + if (ret < 0) + xtables_error(PARAMETER_PROBLEM, "Wrong policy `%s'\n", + policy); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + +/* if (verbose > 1) + dump_entries(*handle); */ + + xtables_rule_matches_free(&cs.matches); + + if (h->family == AF_INET) { + free(args.s.addr.v4); + free(args.s.mask.v4); + free(args.d.addr.v4); + free(args.d.mask.v4); + } else if (h->family == AF_INET6) { + free(args.s.addr.v6); + free(args.s.mask.v6); + free(args.d.addr.v6); + free(args.d.mask.v6); + } + xtables_free_opts(1); + + return ret; +} diff --git a/libxtables/xtables.c b/libxtables/xtables.c index fb60c01b..a511c08c 100644 --- a/libxtables/xtables.c +++ b/libxtables/xtables.c @@ -743,7 +743,7 @@ xtables_find_target(const char *name, enum xtables_tryload tryload) return ptr; } -static int compatible_revision(const char *name, uint8_t revision, int opt) +int xtables_compatible_revision(const char *name, uint8_t revision, int opt) { struct xt_get_revision rev; socklen_t s = sizeof(rev); @@ -799,12 +799,12 @@ static int compatible_revision(const char *name, uint8_t revision, int opt) static int compatible_match_revision(const char *name, uint8_t revision) { - return compatible_revision(name, revision, afinfo->so_rev_match); + return xt_params->compat_rev(name, revision, afinfo->so_rev_match); } static int compatible_target_revision(const char *name, uint8_t revision) { - return compatible_revision(name, revision, afinfo->so_rev_target); + return xt_params->compat_rev(name, revision, afinfo->so_rev_target); } static void xtables_check_options(const char *name, const struct option *opt) |