diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 1 | ||||
-rw-r--r-- | lib/args.c | 8 | ||||
-rw-r--r-- | lib/data.c | 12 | ||||
-rw-r--r-- | lib/debug.c | 1 | ||||
-rw-r--r-- | lib/errcode.c | 4 | ||||
-rw-r--r-- | lib/ipset.c | 608 | ||||
-rw-r--r-- | lib/ipset_hash_ip.c | 86 | ||||
-rw-r--r-- | lib/ipset_hash_ipport.c | 108 | ||||
-rw-r--r-- | lib/ipset_hash_netnet.c | 101 | ||||
-rw-r--r-- | lib/libipset.map | 9 | ||||
-rw-r--r-- | lib/parse.c | 121 | ||||
-rw-r--r-- | lib/print.c | 22 | ||||
-rw-r--r-- | lib/session.c | 133 |
13 files changed, 1138 insertions, 76 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 3a82417..a9edf95 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -46,7 +46,6 @@ EXTRA_libipset_la_SOURCES = \ EXTRA_DIST = $(IPSET_SETTYPE_LIST) libipset.map -pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libipset.pc dist_man_MANS = libipset.3 @@ -300,6 +300,14 @@ static const struct ipset_arg ipset_args[] = { .print = ipset_print_hexnumber, .help = "[initval VALUE]", }, + [IPSET_ARG_BITMASK] = { + .name = { "bitmask", NULL }, + .has_arg = IPSET_MANDATORY_ARG, + .opt = IPSET_OPT_BITMASK, + .parse = ipset_parse_bitmask, + .print = ipset_print_ip, + .help = "[bitmask bitmask]", + }, }; const struct ipset_arg * @@ -53,6 +53,7 @@ struct ipset_data { uint8_t bucketsize; uint8_t resize; uint8_t netmask; + union nf_inet_addr bitmask; uint32_t hashsize; uint32_t maxelem; uint32_t markmask; @@ -110,7 +111,7 @@ ipset_strlcpy(char *dst, const char *src, size_t len) assert(dst); assert(src); - strncpy(dst, src, len); + memcpy(dst, src, len); dst[len - 1] = '\0'; } @@ -301,6 +302,12 @@ ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value) case IPSET_OPT_NETMASK: data->create.netmask = *(const uint8_t *) value; break; + case IPSET_OPT_BITMASK: + if (!(data->family == NFPROTO_IPV4 || + data->family == NFPROTO_IPV6)) + return -1; + copy_addr(data->family, &data->create.bitmask, value); + break; case IPSET_OPT_BUCKETSIZE: data->create.bucketsize = *(const uint8_t *) value; break; @@ -508,6 +515,8 @@ ipset_data_get(const struct ipset_data *data, enum ipset_opt opt) return &data->create.markmask; case IPSET_OPT_NETMASK: return &data->create.netmask; + case IPSET_OPT_BITMASK: + return &data->create.bitmask; case IPSET_OPT_BUCKETSIZE: return &data->create.bucketsize; case IPSET_OPT_RESIZE: @@ -594,6 +603,7 @@ ipset_data_sizeof(enum ipset_opt opt, uint8_t family) case IPSET_OPT_IP_TO: case IPSET_OPT_IP2: case IPSET_OPT_IP2_TO: + case IPSET_OPT_BITMASK: return family == NFPROTO_IPV4 ? sizeof(uint32_t) : sizeof(struct in6_addr); case IPSET_OPT_MARK: diff --git a/lib/debug.c b/lib/debug.c index bf57a41..dbc5cfb 100644 --- a/lib/debug.c +++ b/lib/debug.c @@ -40,6 +40,7 @@ static const struct ipset_attrname createattr2name[] = { [IPSET_ATTR_MAXELEM] = { .name = "MAXELEM" }, [IPSET_ATTR_MARKMASK] = { .name = "MARKMASK" }, [IPSET_ATTR_NETMASK] = { .name = "NETMASK" }, + [IPSET_ATTR_BITMASK] = { .name = "BITMASK" }, [IPSET_ATTR_BUCKETSIZE] = { .name = "BUCKETSIZE" }, [IPSET_ATTR_RESIZE] = { .name = "RESIZE" }, [IPSET_ATTR_SIZE] = { .name = "SIZE" }, diff --git a/lib/errcode.c b/lib/errcode.c index b38f95e..49c97a1 100644 --- a/lib/errcode.c +++ b/lib/errcode.c @@ -25,6 +25,8 @@ static const struct ipset_errcode_table core_errcode_table[] = { "The set with the given name does not exist" }, { EMSGSIZE, 0, "Kernel error received: message could not be created" }, + { ERANGE, 0, + "The specified range is too large, split it up into smaller ranges" }, { IPSET_ERR_PROTOCOL, 0, "Kernel error received: ipset protocol error" }, @@ -42,6 +44,8 @@ static const struct ipset_errcode_table core_errcode_table[] = { "The value of the markmask parameter is invalid" }, { IPSET_ERR_INVALID_FAMILY, 0, "Protocol family not supported by the set type" }, + { IPSET_ERR_BITMASK_NETMASK_EXCL, 0, + "netmask and bitmask options are mutually exclusive, provide only one" }, /* DESTROY specific error codes */ { IPSET_ERR_BUSY, IPSET_CMD_DESTROY, diff --git a/lib/ipset.c b/lib/ipset.c index 8633491..c910d88 100644 --- a/lib/ipset.c +++ b/lib/ipset.c @@ -13,6 +13,7 @@ #include <stdio.h> /* printf */ #include <stdlib.h> /* exit */ #include <string.h> /* str* */ +#include <inttypes.h> /* PRIu64 */ #include <config.h> @@ -27,6 +28,9 @@ #include <libipset/print.h> /* ipset_print_family */ #include <libipset/utils.h> /* STREQ */ #include <libipset/ipset.h> /* prototypes */ +#include <libipset/ip_set_compiler.h> /* compiler attributes */ +#include <libipset/list_sort.h> /* lists */ +#include <libipset/xlate.h> /* ipset_xlate_argv */ static char program_name[] = PACKAGE; static char program_version[] = PACKAGE_VERSION; @@ -49,6 +53,17 @@ struct ipset { char *newargv[MAX_ARGS]; int newargc; const char *filename; /* Input/output filename */ + bool xlate; + struct list_head xlate_sets; +}; + +struct ipset_xlate_set { + struct list_head list; + char name[IPSET_MAXNAMELEN]; + uint8_t netmask; + uint8_t family; + bool interval; + const struct ipset_type *type; }; /* Commands and environment options */ @@ -220,7 +235,7 @@ const struct ipset_envopts ipset_envopts[] = { { .name = { "-o", "-output" }, .has_arg = IPSET_MANDATORY_ARG, .flag = IPSET_OPT_MAX, .parse = ipset_parse_output, - .help = "plain|save|xml\n" + .help = "plain|save|xml|json\n" " Specify output mode for listing sets.\n" " Default value for \"list\" command is mode \"plain\"\n" " and for \"save\" command is mode \"save\".", @@ -414,6 +429,8 @@ ipset_parse_output(struct ipset *ipset, return ipset_session_output(session, IPSET_LIST_PLAIN); else if (STREQ(str, "xml")) return ipset_session_output(session, IPSET_LIST_XML); + else if (STREQ(str, "json")) + return ipset_session_output(session, IPSET_LIST_JSON); else if (STREQ(str, "save")) return ipset_session_output(session, IPSET_LIST_SAVE); @@ -922,20 +939,21 @@ static const char *cmd_prefix[] = { [IPSET_TEST] = "test SETNAME", }; -/* Workhorses */ +static struct ipset_xlate_set * +ipset_xlate_set_get(struct ipset *ipset, const char *name) +{ + struct ipset_xlate_set *set; -/** - * ipset_parse_argv - parse and argv array and execute the command - * @ipset: ipset structure - * @argc: length of the array - * @argv: array of strings - * - * Parse an array of strings and execute the ipset command. - * - * Returns 0 on success or a negative error code. - */ -int -ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[]) + list_for_each_entry(set, &ipset->xlate_sets, list) { + if (!strcmp(set->name, name)) + return set; + } + + return NULL; +} + +static int +ipset_parser(struct ipset *ipset, int oargc, char *oargv[]) { int ret = 0; enum ipset_cmd cmd = IPSET_CMD_NONE; @@ -943,12 +961,17 @@ ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[]) char *arg0 = NULL, *arg1 = NULL; const struct ipset_envopts *opt; const struct ipset_commands *command; - const struct ipset_type *type; + const struct ipset_type *type = NULL; struct ipset_session *session = ipset->session; void *p = ipset_session_printf_private(session); int argc = oargc; char *argv[MAX_ARGS] = {}; + if (argc > MAX_ARGS) + return ipset->custom_error(ipset, + p, IPSET_PARAMETER_PROBLEM, + "Line is too long to parse."); + /* We need a local copy because of ipset_shift_argv */ memcpy(argv, oargv, sizeof(char *) * argc); @@ -1107,6 +1130,7 @@ ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[]) if (arg0) { const struct ipset_arg *arg; int k; + enum ipset_adt c; /* Type-specific help, without kernel checking */ type = type_find(arg0); @@ -1116,11 +1140,11 @@ ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[]) "Unknown settype: `%s'", arg0); printf("\n%s type specific options:\n\n", type->name); for (i = 0; cmd_help_order[i] != IPSET_CADT_MAX; i++) { - cmd = cmd_help_order[i]; + c = cmd_help_order[i]; printf("%s %s %s\n", - cmd_prefix[cmd], type->name, type->cmd[cmd].help); - for (k = 0; type->cmd[cmd].args[k] != IPSET_ARG_NONE; k++) { - arg = ipset_keyword(type->cmd[cmd].args[k]); + cmd_prefix[c], type->name, type->cmd[c].help); + for (k = 0; type->cmd[c].args[k] != IPSET_ARG_NONE; k++) { + arg = ipset_keyword(type->cmd[c].args[k]); if (!arg->help || arg->help[0] == '\0') continue; printf(" %s\n", arg->help); @@ -1208,6 +1232,7 @@ ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[]) return ret; } /* Fall through to parse optional setname */ + fallthrough; case IPSET_CMD_DESTROY: case IPSET_CMD_FLUSH: /* Args: [setname] */ @@ -1236,7 +1261,7 @@ ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[]) return ipset->custom_error(ipset, p, IPSET_PARAMETER_PROBLEM, "Unknown argument %s", argv[1]); - return restore(ipset); + return IPSET_CMD_RESTORE; case IPSET_CMD_ADD: case IPSET_CMD_DEL: case IPSET_CMD_TEST: @@ -1246,7 +1271,20 @@ ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[]) if (ret < 0) return ipset->standard_error(ipset, p); - type = ipset_type_get(session, cmd); + if (!ipset->xlate) { + type = ipset_type_get(session, cmd); + } else { + const struct ipset_xlate_set *xlate_set; + + xlate_set = ipset_xlate_set_get(ipset, arg0); + if (xlate_set) { + ipset_session_data_set(session, IPSET_OPT_TYPE, + xlate_set->type); + ipset_session_data_set(session, IPSET_OPT_FAMILY, + &xlate_set->family); + type = xlate_set->type; + } + } if (type == NULL) return ipset->standard_error(ipset, p); @@ -1273,6 +1311,37 @@ ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[]) if (argc > 1) return ipset->custom_error(ipset, p, IPSET_PARAMETER_PROBLEM, "Unknown argument %s", argv[1]); + + return cmd; +} + +/* Workhorses */ + +/** + * ipset_parse_argv - parse and argv array and execute the command + * @ipset: ipset structure + * @argc: length of the array + * @argv: array of strings + * + * Parse an array of strings and execute the ipset command. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_argv(struct ipset *ipset, int oargc, char *oargv[]) +{ + struct ipset_session *session = ipset->session; + void *p = ipset_session_printf_private(session); + enum ipset_cmd cmd; + int ret; + + cmd = ipset_parser(ipset, oargc, oargv); + if (cmd < 0) + return cmd; + + if (cmd == IPSET_CMD_RESTORE) + return restore(ipset); + ret = ipset_cmd(session, cmd, ipset->restore_line); D("ret %d", ret); /* In the case of warning, the return code is success */ @@ -1448,6 +1517,9 @@ ipset_init(void) return NULL; } ipset_custom_printf(ipset, NULL, NULL, NULL, NULL); + + INIT_LIST_HEAD(&ipset->xlate_sets); + return ipset; } @@ -1462,6 +1534,8 @@ ipset_init(void) int ipset_fini(struct ipset *ipset) { + struct ipset_xlate_set *xlate_set, *next; + assert(ipset); if (ipset->session) @@ -1470,6 +1544,498 @@ ipset_fini(struct ipset *ipset) if (ipset->newargv[0]) free(ipset->newargv[0]); + list_for_each_entry_safe(xlate_set, next, &ipset->xlate_sets, list) + free(xlate_set); + free(ipset); return 0; } + +/* Ignore the set family, use inet. */ +static const char *ipset_xlate_family(uint8_t family UNUSED) +{ + return "inet"; +} + +enum ipset_xlate_set_type { + IPSET_XLATE_TYPE_UNKNOWN = 0, + IPSET_XLATE_TYPE_HASH_MAC, + IPSET_XLATE_TYPE_HASH_IP, + IPSET_XLATE_TYPE_HASH_IP_MAC, + IPSET_XLATE_TYPE_HASH_NET_IFACE, + IPSET_XLATE_TYPE_HASH_NET_PORT, + IPSET_XLATE_TYPE_HASH_NET_PORT_NET, + IPSET_XLATE_TYPE_HASH_NET_NET, + IPSET_XLATE_TYPE_HASH_NET, + IPSET_XLATE_TYPE_HASH_IP_PORT_NET, + IPSET_XLATE_TYPE_HASH_IP_PORT_IP, + IPSET_XLATE_TYPE_HASH_IP_MARK, + IPSET_XLATE_TYPE_HASH_IP_PORT, + IPSET_XLATE_TYPE_BITMAP_PORT, + IPSET_XLATE_TYPE_BITMAP_IP_MAC, + IPSET_XLATE_TYPE_BITMAP_IP, +}; + +static enum ipset_xlate_set_type ipset_xlate_set_type(const char *typename) +{ + if (!strcmp(typename, "hash:mac")) + return IPSET_XLATE_TYPE_HASH_MAC; + else if (!strcmp(typename, "hash:ip")) + return IPSET_XLATE_TYPE_HASH_IP; + else if (!strcmp(typename, "hash:ip,mac")) + return IPSET_XLATE_TYPE_HASH_IP_MAC; + else if (!strcmp(typename, "hash:net,iface")) + return IPSET_XLATE_TYPE_HASH_NET_IFACE; + else if (!strcmp(typename, "hash:net,port")) + return IPSET_XLATE_TYPE_HASH_NET_PORT; + else if (!strcmp(typename, "hash:net,port,net")) + return IPSET_XLATE_TYPE_HASH_NET_PORT_NET; + else if (!strcmp(typename, "hash:net,net")) + return IPSET_XLATE_TYPE_HASH_NET_NET; + else if (!strcmp(typename, "hash:net")) + return IPSET_XLATE_TYPE_HASH_NET; + else if (!strcmp(typename, "hash:ip,port,net")) + return IPSET_XLATE_TYPE_HASH_IP_PORT_NET; + else if (!strcmp(typename, "hash:ip,port,ip")) + return IPSET_XLATE_TYPE_HASH_IP_PORT_IP; + else if (!strcmp(typename, "hash:ip,mark")) + return IPSET_XLATE_TYPE_HASH_IP_MARK; + else if (!strcmp(typename, "hash:ip,port")) + return IPSET_XLATE_TYPE_HASH_IP_PORT; + else if (!strcmp(typename, "hash:ip")) + return IPSET_XLATE_TYPE_HASH_IP; + else if (!strcmp(typename, "bitmap:port")) + return IPSET_XLATE_TYPE_BITMAP_PORT; + else if (!strcmp(typename, "bitmap:ip,mac")) + return IPSET_XLATE_TYPE_BITMAP_IP_MAC; + else if (!strcmp(typename, "bitmap:ip")) + return IPSET_XLATE_TYPE_BITMAP_IP; + + return IPSET_XLATE_TYPE_UNKNOWN; +} + +#define NFT_SET_INTERVAL (1 << 0) + +static const char * +ipset_xlate_type_to_nftables(int family, enum ipset_xlate_set_type type, + uint32_t *flags) +{ + switch (type) { + case IPSET_XLATE_TYPE_HASH_MAC: + return "ether_addr"; + case IPSET_XLATE_TYPE_HASH_IP: + if (family == AF_INET) + return "ipv4_addr"; + else if (family == AF_INET6) + return "ipv6_addr"; + break; + case IPSET_XLATE_TYPE_HASH_IP_MAC: + if (family == AF_INET) + return "ipv4_addr . ether_addr"; + else if (family == AF_INET6) + return "ipv6_addr . ether_addr"; + break; + case IPSET_XLATE_TYPE_HASH_NET_IFACE: + *flags |= NFT_SET_INTERVAL; + if (family == AF_INET) + return "ipv4_addr . ifname"; + else if (family == AF_INET6) + return "ipv6_addr . ifname"; + break; + case IPSET_XLATE_TYPE_HASH_NET_PORT: + *flags |= NFT_SET_INTERVAL; + if (family == AF_INET) + return "ipv4_addr . inet_proto . inet_service"; + else if (family == AF_INET6) + return "ipv6_addr . inet_proto . inet_service"; + break; + case IPSET_XLATE_TYPE_HASH_NET_PORT_NET: + *flags |= NFT_SET_INTERVAL; + if (family == AF_INET) + return "ipv4_addr . inet_proto . inet_service . ipv4_addr"; + else if (family == AF_INET6) + return "ipv6_addr . inet_proto . inet_service . ipv6_addr"; + break; + case IPSET_XLATE_TYPE_HASH_NET_NET: + *flags |= NFT_SET_INTERVAL; + if (family == AF_INET) + return "ipv4_addr . ipv4_addr"; + else if (family == AF_INET6) + return "ipv6_addr . ipv6_addr"; + break; + case IPSET_XLATE_TYPE_HASH_NET: + *flags |= NFT_SET_INTERVAL; + if (family == AF_INET) + return "ipv4_addr"; + else if (family == AF_INET6) + return "ipv6_addr"; + break; + case IPSET_XLATE_TYPE_HASH_IP_PORT_NET: + *flags |= NFT_SET_INTERVAL; + if (family == AF_INET) + return "ipv4_addr . inet_proto . inet_service . ipv4_addr"; + else if (family == AF_INET6) + return "ipv6_addr . inet_proto . inet_service . ipv6_addr"; + break; + case IPSET_XLATE_TYPE_HASH_IP_PORT_IP: + if (family == AF_INET) + return "ipv4_addr . inet_proto . inet_service . ipv4_addr"; + else if (family == AF_INET6) + return "ipv6_addr . inet_proto . inet_service . ipv6_addr"; + break; + case IPSET_XLATE_TYPE_HASH_IP_MARK: + if (family == AF_INET) + return "ipv4_addr . mark"; + else if (family == AF_INET6) + return "ipv6_addr . mark"; + break; + case IPSET_XLATE_TYPE_HASH_IP_PORT: + if (family == AF_INET) + return "ipv4_addr . inet_proto . inet_service"; + else if (family == AF_INET6) + return "ipv6_addr . inet_proto . inet_service"; + break; + case IPSET_XLATE_TYPE_BITMAP_PORT: + return "inet_service"; + case IPSET_XLATE_TYPE_BITMAP_IP_MAC: + if (family == AF_INET) + return "ipv4_addr . ether_addr"; + else if (family == AF_INET6) + return "ipv6_addr . ether_addr"; + break; + case IPSET_XLATE_TYPE_BITMAP_IP: + if (family == AF_INET) + return "ipv4_addr"; + else if (family == AF_INET6) + return "ipv6_addr"; + break; + case IPSET_XLATE_TYPE_UNKNOWN: + break; + default: + break; + } + /* This should not ever happen. */ + return "unknown"; +} + +static int ipset_xlate(struct ipset *ipset, enum ipset_cmd cmd, + const char *table) +{ + const char *set, *typename, *nft_type; + const struct ipset_type *ipset_type; + struct ipset_xlate_set *xlate_set; + enum ipset_xlate_set_type type; + struct ipset_session *session; + const uint32_t *cadt_flags; + const uint32_t *timeout; + const uint32_t *maxelem; + struct ipset_data *data; + const uint8_t *netmask; + const char *comment; + uint32_t flags = 0; + uint8_t family; + char buf[64]; + bool concat; + char *term; + + session = ipset_session(ipset); + data = ipset_session_data(session); + + set = ipset_data_get(data, IPSET_SETNAME); + family = ipset_data_family(data); + + switch (cmd) { + case IPSET_CMD_CREATE: + /* Not supported. */ + if (ipset_data_test(data, IPSET_OPT_MARKMASK)) { + printf("# %s", ipset->cmdline); + break; + } + cadt_flags = ipset_data_get(data, IPSET_OPT_CADT_FLAGS); + + /* Ignore: + * - IPSET_FLAG_WITH_COMMENT + * - IPSET_FLAG_WITH_FORCEADD + */ + if (cadt_flags && + (*cadt_flags & (IPSET_FLAG_BEFORE | + IPSET_FLAG_PHYSDEV | + IPSET_FLAG_NOMATCH | + IPSET_FLAG_WITH_SKBINFO | + IPSET_FLAG_IFACE_WILDCARD))) { + printf("# %s", ipset->cmdline); + break; + } + + typename = ipset_data_get(data, IPSET_OPT_TYPENAME); + type = ipset_xlate_set_type(typename); + nft_type = ipset_xlate_type_to_nftables(family, type, &flags); + + printf("add set %s %s %s { type %s; ", + ipset_xlate_family(family), table, set, nft_type); + if (cadt_flags) { + if (*cadt_flags & IPSET_FLAG_WITH_COUNTERS) + printf("counter; "); + } + timeout = ipset_data_get(data, IPSET_OPT_TIMEOUT); + if (timeout) + printf("timeout %us; ", *timeout); + maxelem = ipset_data_get(data, IPSET_OPT_MAXELEM); + if (maxelem) + printf("size %u; ", *maxelem); + + netmask = ipset_data_get(data, IPSET_OPT_NETMASK); + if (netmask && + ((family == AF_INET && *netmask < 32) || + (family == AF_INET6 && *netmask < 128))) + flags |= NFT_SET_INTERVAL; + + if (flags & NFT_SET_INTERVAL) + printf("flags interval; "); + + /* These create-specific options are safe to be ignored: + * - IPSET_OPT_GC + * - IPSET_OPT_HASHSIZE + * - IPSET_OPT_PROBES + * - IPSET_OPT_RESIZE + * - IPSET_OPT_SIZE + * - IPSET_OPT_FORCEADD + * + * Ranges and CIDR are safe to be ignored too: + * - IPSET_OPT_IP_FROM + * - IPSET_OPT_IP_TO + * - IPSET_OPT_PORT_FROM + * - IPSET_OPT_PORT_TO + */ + + printf("}\n"); + + xlate_set = calloc(1, sizeof(*xlate_set)); + if (!xlate_set) + return -1; + + snprintf(xlate_set->name, sizeof(xlate_set->name), "%s", set); + ipset_type = ipset_types(); + while (ipset_type) { + if (!strcmp(ipset_type->name, typename)) + break; + ipset_type = ipset_type->next; + } + + xlate_set->family = family; + xlate_set->type = ipset_type; + if (netmask) { + xlate_set->netmask = *netmask; + xlate_set->interval = true; + } + list_add_tail(&xlate_set->list, &ipset->xlate_sets); + break; + case IPSET_CMD_DESTROY: + printf("del set %s %s %s\n", + ipset_xlate_family(family), table, set); + break; + case IPSET_CMD_FLUSH: + if (!set) { + printf("# %s", ipset->cmdline); + } else { + printf("flush set %s %s %s\n", + ipset_xlate_family(family), table, set); + } + break; + case IPSET_CMD_RENAME: + printf("# %s", ipset->cmdline); + return -1; + case IPSET_CMD_SWAP: + printf("# %s", ipset->cmdline); + return -1; + case IPSET_CMD_LIST: + if (!set) { + printf("list sets %s %s\n", + ipset_xlate_family(family), table); + } else { + printf("list set %s %s %s\n", + ipset_xlate_family(family), table, set); + } + break; + case IPSET_CMD_SAVE: + printf("# %s", ipset->cmdline); + return -1; + case IPSET_CMD_ADD: + case IPSET_CMD_DEL: + case IPSET_CMD_TEST: + /* Not supported. */ + if (ipset_data_test(data, IPSET_OPT_NOMATCH) || + ipset_data_test(data, IPSET_OPT_SKBINFO) || + ipset_data_test(data, IPSET_OPT_SKBMARK) || + ipset_data_test(data, IPSET_OPT_SKBPRIO) || + ipset_data_test(data, IPSET_OPT_SKBQUEUE) || + ipset_data_test(data, IPSET_OPT_IFACE_WILDCARD)) { + printf("# %s", ipset->cmdline); + break; + } + printf("%s element %s %s %s { ", + cmd == IPSET_CMD_ADD ? "add" : + cmd == IPSET_CMD_DEL ? "delete" : "get", + ipset_xlate_family(family), table, set); + + xlate_set = (struct ipset_xlate_set *) + ipset_xlate_set_get(ipset, set); + if (xlate_set && xlate_set->interval) + netmask = &xlate_set->netmask; + else + netmask = NULL; + + concat = false; + if (ipset_data_test(data, IPSET_OPT_IP)) { + ipset_print_data(buf, sizeof(buf), data, IPSET_OPT_IP, 0); + printf("%s", buf); + if (netmask) + printf("/%u ", *netmask); + else + printf(" "); + + concat = true; + } + if (ipset_data_test(data, IPSET_OPT_MARK)) { + ipset_print_mark(buf, sizeof(buf), data, IPSET_OPT_MARK, 0); + printf("%s%s ", concat ? ". " : "", buf); + } + if (ipset_data_test(data, IPSET_OPT_IFACE)) { + ipset_print_data(buf, sizeof(buf), data, IPSET_OPT_IFACE, 0); + printf("%s%s ", concat ? ". " : "", buf); + } + if (ipset_data_test(data, IPSET_OPT_ETHER)) { + ipset_print_ether(buf, sizeof(buf), data, IPSET_OPT_ETHER, 0); + size_t i; + + for (i = 0; i < strlen(buf); i++) + buf[i] = tolower(buf[i]); + + printf("%s%s ", concat ? ". " : "", buf); + concat = true; + } + if (ipset_data_test(data, IPSET_OPT_PORT)) { + ipset_print_proto_port(buf, sizeof(buf), data, IPSET_OPT_PORT, 0); + term = strchr(buf, ':'); + if (term) { + *term = '\0'; + printf("%s%s ", concat ? ". " : "", buf); + } + ipset_print_data(buf, sizeof(buf), data, IPSET_OPT_PORT, 0); + printf("%s%s ", concat ? ". " : "", buf); + } + if (ipset_data_test(data, IPSET_OPT_IP2)) { + ipset_print_ip(buf, sizeof(buf), data, IPSET_OPT_IP2, 0); + printf("%s%s", concat ? ". " : "", buf); + if (netmask) + printf("/%u ", *netmask); + else + printf(" "); + } + if (ipset_data_test(data, IPSET_OPT_PACKETS) && + ipset_data_test(data, IPSET_OPT_BYTES)) { + const uint64_t *pkts, *bytes; + + pkts = ipset_data_get(data, IPSET_OPT_PACKETS); + bytes = ipset_data_get(data, IPSET_OPT_BYTES); + + printf("counter packets %" PRIu64 " bytes %" PRIu64 " ", + *pkts, *bytes); + } + timeout = ipset_data_get(data, IPSET_OPT_TIMEOUT); + if (timeout) + printf("timeout %us ", *timeout); + + comment = ipset_data_get(data, IPSET_OPT_ADT_COMMENT); + if (comment) + printf("comment \"%s\" ", comment); + + printf("}\n"); + break; + case IPSET_CMD_GET_BYNAME: + printf("# %s", ipset->cmdline); + return -1; + case IPSET_CMD_GET_BYINDEX: + printf("# %s", ipset->cmdline); + return -1; + default: + break; + } + + return 0; +} + +static int ipset_xlate_restore(struct ipset *ipset) +{ + struct ipset_session *session = ipset_session(ipset); + struct ipset_data *data = ipset_session_data(session); + void *p = ipset_session_printf_private(session); + enum ipset_cmd cmd; + FILE *f = stdin; + int ret = 0; + char *c; + + if (ipset->filename) { + f = fopen(ipset->filename, "r"); + if (!f) { + fprintf(stderr, "cannot open file `%s'\n", ipset->filename); + return -1; + } + } + + /* TODO: Allow to specify the table name other than 'global'. */ + printf("add table inet global\n"); + + while (fgets(ipset->cmdline, sizeof(ipset->cmdline), f)) { + ipset->restore_line++; + c = ipset->cmdline; + while (isspace(c[0])) + c++; + if (c[0] == '\0' || c[0] == '#') + continue; + else if (STREQ(c, "COMMIT\n") || STREQ(c, "COMMIT\r\n")) + continue; + + ret = build_argv(ipset, c); + if (ret < 0) + break; + + cmd = ipset_parser(ipset, ipset->newargc, ipset->newargv); + if (cmd < 0) + ipset->standard_error(ipset, p); + + /* TODO: Allow to specify the table name other than 'global'. */ + ret = ipset_xlate(ipset, cmd, "global"); + if (ret < 0) + break; + + ipset_data_reset(data); + } + + if (ipset->filename) + fclose(f); + + return ret; +} + +int ipset_xlate_argv(struct ipset *ipset, int argc, char *argv[]) +{ + enum ipset_cmd cmd; + int ret; + + ipset->xlate = true; + + cmd = ipset_parser(ipset, argc, argv); + if (cmd < 0) + return cmd; + + if (cmd == IPSET_CMD_RESTORE) { + ret = ipset_xlate_restore(ipset); + } else { + fprintf(stderr, "This command is not supported, " + "use `ipset-translate restore < file'\n"); + ret = -1; + } + + return ret; +} diff --git a/lib/ipset_hash_ip.c b/lib/ipset_hash_ip.c index ea85700..4f96ebb 100644 --- a/lib/ipset_hash_ip.c +++ b/lib/ipset_hash_ip.c @@ -477,6 +477,91 @@ static struct ipset_type ipset_hash_ip5 = { .description = "bucketsize, initval support", }; +/* bitmask support */ +static struct ipset_type ipset_hash_ip6 = { + .name = "hash:ip", + .alias = { "iphash", NULL }, + .revision = 6, + .family = NFPROTO_IPSET_IPV46, + .dimension = IPSET_DIM_ONE, + .elem = { + [IPSET_DIM_ONE - 1] = { + .parse = ipset_parse_ip4_single6, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + }, + .cmd = { + [IPSET_CREATE] = { + .args = { + IPSET_ARG_FAMILY, + /* Aliases */ + IPSET_ARG_INET, + IPSET_ARG_INET6, + IPSET_ARG_HASHSIZE, + IPSET_ARG_MAXELEM, + IPSET_ARG_NETMASK, + IPSET_ARG_BITMASK, + IPSET_ARG_TIMEOUT, + IPSET_ARG_COUNTERS, + IPSET_ARG_COMMENT, + IPSET_ARG_FORCEADD, + IPSET_ARG_SKBINFO, + IPSET_ARG_BUCKETSIZE, + IPSET_ARG_INITVAL, + /* Ignored options: backward compatibilty */ + IPSET_ARG_PROBES, + IPSET_ARG_RESIZE, + IPSET_ARG_GC, + IPSET_ARG_NONE, + }, + .need = 0, + .full = 0, + .help = "", + }, + [IPSET_ADD] = { + .args = { + IPSET_ARG_TIMEOUT, + IPSET_ARG_PACKETS, + IPSET_ARG_BYTES, + IPSET_ARG_ADT_COMMENT, + IPSET_ARG_SKBMARK, + IPSET_ARG_SKBPRIO, + IPSET_ARG_SKBQUEUE, + IPSET_ARG_NONE, + }, + .need = IPSET_FLAG(IPSET_OPT_IP), + .full = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO), + .help = "IP", + }, + [IPSET_DEL] = { + .args = { + IPSET_ARG_NONE, + }, + .need = IPSET_FLAG(IPSET_OPT_IP), + .full = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO), + .help = "IP", + }, + [IPSET_TEST] = { + .args = { + IPSET_ARG_NONE, + }, + .need = IPSET_FLAG(IPSET_OPT_IP), + .full = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO), + .help = "IP", + }, + }, + .usage = "where depending on the INET family\n" + " IP is a valid IPv4 or IPv6 address (or hostname),\n" + " CIDR is a valid IPv4 or IPv6 CIDR prefix.\n" + " Adding/deleting multiple elements in IP/CIDR or FROM-TO form\n" + " is supported for IPv4.", + .description = "bitmask support", +}; + void _init(void); void _init(void) { @@ -486,4 +571,5 @@ void _init(void) ipset_type_add(&ipset_hash_ip3); ipset_type_add(&ipset_hash_ip4); ipset_type_add(&ipset_hash_ip5); + ipset_type_add(&ipset_hash_ip6); } diff --git a/lib/ipset_hash_ipport.c b/lib/ipset_hash_ipport.c index 288be10..2fa8abd 100644 --- a/lib/ipset_hash_ipport.c +++ b/lib/ipset_hash_ipport.c @@ -604,6 +604,113 @@ static struct ipset_type ipset_hash_ipport6 = { .description = "bucketsize, initval support", }; +/* bitmask support */ +static struct ipset_type ipset_hash_ipport7 = { + .name = "hash:ip,port", + .alias = { "ipporthash", NULL }, + .revision = 7, + .family = NFPROTO_IPSET_IPV46, + .dimension = IPSET_DIM_TWO, + .elem = { + [IPSET_DIM_ONE - 1] = { + .parse = ipset_parse_ip4_single6, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + [IPSET_DIM_TWO - 1] = { + .parse = ipset_parse_proto_port, + .print = ipset_print_proto_port, + .opt = IPSET_OPT_PORT + }, + }, + .cmd = { + [IPSET_CREATE] = { + .args = { + IPSET_ARG_FAMILY, + /* Aliases */ + IPSET_ARG_INET, + IPSET_ARG_INET6, + IPSET_ARG_HASHSIZE, + IPSET_ARG_MAXELEM, + IPSET_ARG_TIMEOUT, + IPSET_ARG_COUNTERS, + IPSET_ARG_COMMENT, + IPSET_ARG_FORCEADD, + IPSET_ARG_SKBINFO, + IPSET_ARG_BUCKETSIZE, + IPSET_ARG_INITVAL, + IPSET_ARG_NETMASK, + IPSET_ARG_BITMASK, + /* Ignored options: backward compatibilty */ + IPSET_ARG_PROBES, + IPSET_ARG_RESIZE, + IPSET_ARG_IGNORED_FROM, + IPSET_ARG_IGNORED_TO, + IPSET_ARG_IGNORED_NETWORK, + IPSET_ARG_NONE, + }, + .need = 0, + .full = 0, + .help = "", + }, + [IPSET_ADD] = { + .args = { + IPSET_ARG_TIMEOUT, + IPSET_ARG_PACKETS, + IPSET_ARG_BYTES, + IPSET_ARG_ADT_COMMENT, + IPSET_ARG_SKBMARK, + IPSET_ARG_SKBPRIO, + IPSET_ARG_SKBQUEUE, + IPSET_ARG_NONE, + }, + .need = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + .full = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO), + .help = "IP,[PROTO:]PORT", + }, + [IPSET_DEL] = { + .args = { + IPSET_ARG_NONE, + }, + .need = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + .full = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO), + .help = "IP,[PROTO:]PORT", + }, + [IPSET_TEST] = { + .args = { + IPSET_ARG_NONE, + }, + .need = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + .full = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + .help = "IP,[PROTO:]PORT", + }, + }, + .usage = "where depending on the INET family\n" + " IP is a valid IPv4 or IPv6 address (or hostname).\n" + " Adding/deleting multiple elements in IP/CIDR or FROM-TO form\n" + " is supported for IPv4.\n" + " Adding/deleting multiple elements with TCP/SCTP/UDP/UDPLITE\n" + " port range is supported both for IPv4 and IPv6.", + .usagefn = ipset_port_usage, + .description = "netmask and bitmask support", +}; + void _init(void); void _init(void) { @@ -613,4 +720,5 @@ void _init(void) ipset_type_add(&ipset_hash_ipport4); ipset_type_add(&ipset_hash_ipport5); ipset_type_add(&ipset_hash_ipport6); + ipset_type_add(&ipset_hash_ipport7); } diff --git a/lib/ipset_hash_netnet.c b/lib/ipset_hash_netnet.c index df993b8..0e176e3 100644 --- a/lib/ipset_hash_netnet.c +++ b/lib/ipset_hash_netnet.c @@ -387,6 +387,106 @@ static struct ipset_type ipset_hash_netnet3 = { .description = "bucketsize, initval support", }; +/* bitmask support */ +static struct ipset_type ipset_hash_netnet4 = { + .name = "hash:net,net", + .alias = { "netnethash", NULL }, + .revision = 4, + .family = NFPROTO_IPSET_IPV46, + .dimension = IPSET_DIM_TWO, + .elem = { + [IPSET_DIM_ONE - 1] = { + .parse = ipset_parse_ip4_net6, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + [IPSET_DIM_TWO - 1] = { + .parse = ipset_parse_ip4_net6, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP2 + }, + }, + .cmd = { + [IPSET_CREATE] = { + .args = { + IPSET_ARG_FAMILY, + /* Aliases */ + IPSET_ARG_INET, + IPSET_ARG_INET6, + IPSET_ARG_HASHSIZE, + IPSET_ARG_MAXELEM, + IPSET_ARG_TIMEOUT, + IPSET_ARG_COUNTERS, + IPSET_ARG_COMMENT, + IPSET_ARG_FORCEADD, + IPSET_ARG_SKBINFO, + IPSET_ARG_BUCKETSIZE, + IPSET_ARG_INITVAL, + IPSET_ARG_BITMASK, + IPSET_ARG_NETMASK, + IPSET_ARG_NONE, + }, + .need = 0, + .full = 0, + .help = "", + }, + [IPSET_ADD] = { + .args = { + IPSET_ARG_TIMEOUT, + IPSET_ARG_NOMATCH, + IPSET_ARG_PACKETS, + IPSET_ARG_BYTES, + IPSET_ARG_ADT_COMMENT, + IPSET_ARG_SKBMARK, + IPSET_ARG_SKBPRIO, + IPSET_ARG_SKBQUEUE, + IPSET_ARG_NONE, + }, + .need = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP2), + .full = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_CIDR) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_IP2) + | IPSET_FLAG(IPSET_OPT_CIDR2) + | IPSET_FLAG(IPSET_OPT_IP2_TO), + .help = "IP[/CIDR]|FROM-TO,IP[/CIDR]|FROM-TO", + }, + [IPSET_DEL] = { + .args = { + IPSET_ARG_NONE, + }, + .need = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP2), + .full = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_CIDR) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_IP2) + | IPSET_FLAG(IPSET_OPT_CIDR2) + | IPSET_FLAG(IPSET_OPT_IP2_TO), + .help = "IP[/CIDR]|FROM-TO,IP[/CIDR]|FROM-TO", + }, + [IPSET_TEST] = { + .args = { + IPSET_ARG_NOMATCH, + IPSET_ARG_NONE, + }, + .need = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP2), + .full = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_CIDR) + | IPSET_FLAG(IPSET_OPT_IP2) + | IPSET_FLAG(IPSET_OPT_CIDR2), + .help = "IP[/CIDR],IP[/CIDR]", + }, + }, + .usage = "where depending on the INET family\n" + " IP is an IPv4 or IPv6 address (or hostname),\n" + " CIDR is a valid IPv4 or IPv6 CIDR prefix.\n" + " IP range is not supported with IPv6.", + .description = "netmask and bitmask support", +}; + void _init(void); void _init(void) { @@ -394,4 +494,5 @@ void _init(void) ipset_type_add(&ipset_hash_netnet1); ipset_type_add(&ipset_hash_netnet2); ipset_type_add(&ipset_hash_netnet3); + ipset_type_add(&ipset_hash_netnet4); } diff --git a/lib/libipset.map b/lib/libipset.map index 12d16a4..c69b738 100644 --- a/lib/libipset.map +++ b/lib/libipset.map @@ -208,3 +208,12 @@ global: ipset_print_hexnumber; } LIBIPSET_4.9; +LIBIPSET_4.11 { +global: + ipset_xlate_argv; +} LIBIPSET_4.10; + +LIBIPSET_4.12 { +global: + ipset_parse_bitmask; +} LIBIPSET_4.10; diff --git a/lib/parse.c b/lib/parse.c index 31a619d..4d2d8b3 100644 --- a/lib/parse.c +++ b/lib/parse.c @@ -41,6 +41,9 @@ #define syntax_err(fmt, args...) \ ipset_err(session, "Syntax error: " fmt , ## args) +#define syntax_err_ll(errtype, fmt, args...) \ + ipset_session_report(session, errtype, "Syntax error: " fmt , ## args) + static char * ipset_strchr(const char *str, const char *sep) { @@ -87,7 +90,8 @@ string_to_number_ll(struct ipset_session *session, const char *str, unsigned long long min, unsigned long long max, - unsigned long long *ret) + unsigned long long *ret, + enum ipset_err_type errtype) { unsigned long long number = 0; char *end; @@ -104,23 +108,24 @@ string_to_number_ll(struct ipset_session *session, errno = ERANGE; } if (errno == ERANGE && max) - return syntax_err("'%s' is out of range %llu-%llu", - str, min, max); + return syntax_err_ll(errtype, "'%s' is out of range %llu-%llu", + str, min, max); else if (errno == ERANGE) - return syntax_err("'%s' is out of range %llu-%llu", - str, min, ULLONG_MAX); + return syntax_err_ll(errtype, "'%s' is out of range %llu-%llu", + str, min, ULLONG_MAX); else - return syntax_err("'%s' is invalid as number", str); + return syntax_err_ll(errtype, "'%s' is invalid as number", str); } static int string_to_u8(struct ipset_session *session, - const char *str, uint8_t *ret) + const char *str, uint8_t *ret, + enum ipset_err_type errtype) { int err; unsigned long long num = 0; - err = string_to_number_ll(session, str, 0, 255, &num); + err = string_to_number_ll(session, str, 0, 255, &num, errtype); *ret = num; return err; @@ -130,7 +135,7 @@ static int string_to_cidr(struct ipset_session *session, const char *str, uint8_t min, uint8_t max, uint8_t *ret) { - int err = string_to_u8(session, str, ret); + int err = string_to_u8(session, str, ret, IPSET_ERROR); if (!err && (*ret < min || *ret > max)) return syntax_err("'%s' is out of range %u-%u", @@ -141,12 +146,13 @@ string_to_cidr(struct ipset_session *session, static int string_to_u16(struct ipset_session *session, - const char *str, uint16_t *ret) + const char *str, uint16_t *ret, + enum ipset_err_type errtype) { int err; unsigned long long num = 0; - err = string_to_number_ll(session, str, 0, USHRT_MAX, &num); + err = string_to_number_ll(session, str, 0, USHRT_MAX, &num, errtype); *ret = num; return err; @@ -159,7 +165,8 @@ string_to_u32(struct ipset_session *session, int err; unsigned long long num = 0; - err = string_to_number_ll(session, str, 0, UINT_MAX, &num); + err = string_to_number_ll(session, str, 0, UINT_MAX, &num, + IPSET_ERROR); *ret = num; return err; @@ -274,7 +281,10 @@ parse_portname(struct ipset_session *session, const char *str, uint16_t *port, const char *proto) { char *saved, *tmp; + const char *protoname; + const struct protoent *protoent; struct servent *service; + uint8_t protonum = 0; saved = tmp = ipset_strdup(session, str); if (tmp == NULL) @@ -283,7 +293,15 @@ parse_portname(struct ipset_session *session, const char *str, if (tmp == NULL) goto error; - service = getservbyname(tmp, proto); + protoname = proto; + if (string_to_u8(session, proto, &protonum, IPSET_WARNING) == 0) { + protoent = getprotobynumber(protonum); + if (protoent == NULL) + goto error; + protoname = protoent->p_name; + } + + service = getservbyname(tmp, protoname); if (service != NULL) { *port = ntohs((uint16_t) service->s_port); free(saved); @@ -319,11 +337,11 @@ ipset_parse_port(struct ipset_session *session, assert(opt == IPSET_OPT_PORT || opt == IPSET_OPT_PORT_TO); assert(str); - if (parse_portname(session, str, &port, proto) == 0) { + if (string_to_u16(session, str, &port, IPSET_WARNING) == 0) { return ipset_session_data_set(session, opt, &port); } /* Error is stored as warning in session report */ - if (string_to_u16(session, str, &port) == 0) { + if (parse_portname(session, str, &port, proto) == 0) { /* No error, so reset false error messages */ ipset_session_report_reset(session); return ipset_session_data_set(session, opt, &port); @@ -469,21 +487,23 @@ ipset_parse_proto(struct ipset_session *session, { const struct protoent *protoent; uint8_t proto = 0; + uint8_t protonum = 0; assert(session); assert(opt == IPSET_OPT_PROTO); assert(str); + if (string_to_u8(session, str, &protonum, IPSET_WARNING) == 0) + return ipset_session_data_set(session, opt, &protonum); + + /* No error, so reset false error messages */ + ipset_session_report_reset(session); protoent = getprotobyname(strcasecmp(str, "icmpv6") == 0 ? "ipv6-icmp" : str); - if (protoent == NULL) { - uint8_t protonum = 0; - if (string_to_u8(session, str, &protonum) || - (protoent = getprotobynumber(protonum)) == NULL) - return syntax_err("cannot parse '%s' " - "as a protocol", str); - } + if (protoent == NULL) + return syntax_err("cannot parse '%s' " + "as a protocol", str); proto = protoent->p_proto; if (!proto) return syntax_err("Unsupported protocol '%s'", str); @@ -513,8 +533,8 @@ parse_icmp_typecode(struct ipset_session *session, str, family); } *a++ = '\0'; - if ((err = string_to_u8(session, tmp, &type)) != 0 || - (err = string_to_u8(session, a, &code)) != 0) + if ((err = string_to_u8(session, tmp, &type, IPSET_ERROR)) != 0 || + (err = string_to_u8(session, a, &code, IPSET_ERROR)) != 0) goto error; typecode = (type << 8) | code; @@ -1335,7 +1355,8 @@ ipset_parse_timeout(struct ipset_session *session, assert(opt == IPSET_OPT_TIMEOUT); assert(str); - err = string_to_number_ll(session, str, 0, (UINT_MAX>>1)/1000, &llnum); + err = string_to_number_ll(session, str, 0, (UINT_MAX>>1)/1000, &llnum, + IPSET_ERROR); if (err == 0) { /* Timeout is expected to be 32bits wide, so we have to convert it here */ @@ -1579,7 +1600,8 @@ ipset_parse_uint64(struct ipset_session *session, assert(session); assert(str); - err = string_to_number_ll(session, str, 0, ULLONG_MAX - 1, &value); + err = string_to_number_ll(session, str, 0, ULLONG_MAX - 1, &value, + IPSET_ERROR); if (err) return err; @@ -1623,7 +1645,7 @@ ipset_parse_uint16(struct ipset_session *session, assert(session); assert(str); - err = string_to_u16(session, str, &value); + err = string_to_u16(session, str, &value, IPSET_ERROR); if (err == 0) return ipset_session_data_set(session, opt, &value); @@ -1651,7 +1673,7 @@ ipset_parse_uint8(struct ipset_session *session, assert(session); assert(str); - if ((err = string_to_u8(session, str, &value)) == 0) + if ((err = string_to_u8(session, str, &value, IPSET_ERROR)) == 0) return ipset_session_data_set(session, opt, &value); return err; @@ -1682,6 +1704,9 @@ ipset_parse_netmask(struct ipset_session *session, assert(str); data = ipset_session_data(session); + if (ipset_data_test(data, IPSET_OPT_BITMASK)) + return syntax_err("bitmask and netmask are mutually exclusive, provide only one"); + family = ipset_data_family(data); if (family == NFPROTO_UNSPEC) { family = NFPROTO_IPV4; @@ -1701,6 +1726,46 @@ ipset_parse_netmask(struct ipset_session *session, } /** + * ipset_parse_bitmask - parse string as a bitmask + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a bitmask value, depending on family type. + * If family is not set yet, INET is assumed. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_bitmask(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + uint8_t family; + struct ipset_data *data; + + assert(session); + assert(opt == IPSET_OPT_BITMASK); + assert(str); + + data = ipset_session_data(session); + if (ipset_data_test(data, IPSET_OPT_NETMASK)) + return syntax_err("bitmask and netmask are mutually exclusive, provide only one"); + + family = ipset_data_family(data); + if (family == NFPROTO_UNSPEC) { + family = NFPROTO_IPV4; + ipset_data_set(data, IPSET_OPT_FAMILY, &family); + } + + if (parse_ipaddr(session, opt, str, family)) + return syntax_err("bitmask is not valid for family = %s", + family == NFPROTO_IPV4 ? "inet" : "inet6"); + + return 0; +} + +/** * ipset_parse_flag - "parse" option flags * @session: session structure * @opt: option kind of the data diff --git a/lib/print.c b/lib/print.c index 0d86a98..6ea79cb 100644 --- a/lib/print.c +++ b/lib/print.c @@ -265,7 +265,7 @@ ipset_print_ip(char *buf, unsigned int len, assert(buf); assert(len > 0); assert(data); - assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2 || opt == IPSET_OPT_BITMASK); D("len: %u", len); family = ipset_data_family(data); @@ -411,10 +411,11 @@ ipset_print_number(char *buf, unsigned int len, int ipset_print_hexnumber(char *buf, unsigned int len, const struct ipset_data *data, enum ipset_opt opt, - uint8_t env UNUSED) + uint8_t env) { size_t maxsize; const void *number; + const char *quoted = env & IPSET_ENV_QUOTED ? "\"" : ""; assert(buf); assert(len > 0); @@ -424,17 +425,17 @@ ipset_print_hexnumber(char *buf, unsigned int len, maxsize = ipset_data_sizeof(opt, AF_INET); D("opt: %u, maxsize %zu", opt, maxsize); if (maxsize == sizeof(uint8_t)) - return snprintf(buf, len, "0x%02"PRIx8, - *(const uint8_t *) number); + return snprintf(buf, len, "%s0x%02"PRIx8"%s", + quoted, *(const uint8_t *) number, quoted); else if (maxsize == sizeof(uint16_t)) - return snprintf(buf, len, "0x%04"PRIx16, - *(const uint16_t *) number); + return snprintf(buf, len, "%s0x%04"PRIx16"%s", + quoted, *(const uint16_t *) number, quoted); else if (maxsize == sizeof(uint32_t)) - return snprintf(buf, len, "0x%08"PRIx32, - (long unsigned) *(const uint32_t *) number); + return snprintf(buf, len, "%s0x%08"PRIx32"%s", + quoted, *(const uint32_t *) number, quoted); else if (maxsize == sizeof(uint64_t)) - return snprintf(buf, len, "0x016lx", - (long long unsigned) *(const uint64_t *) number); + return snprintf(buf, len, "%s0x%016"PRIx64"%s", + quoted, *(const uint64_t *) number, quoted); else assert(0); return 0; @@ -976,6 +977,7 @@ ipset_print_data(char *buf, unsigned int len, size = ipset_print_elem(buf, len, data, opt, env); break; case IPSET_OPT_IP: + case IPSET_OPT_BITMASK: size = ipset_print_ip(buf, len, data, opt, env); break; case IPSET_OPT_PORT: diff --git a/lib/session.c b/lib/session.c index b088bc8..f822288 100644 --- a/lib/session.c +++ b/lib/session.c @@ -462,6 +462,10 @@ static const struct ipset_attr_policy create_attrs[] = { .type = MNL_TYPE_U32, .opt = IPSET_OPT_MEMSIZE, }, + [IPSET_ATTR_BITMASK] = { + .type = MNL_TYPE_NESTED, + .opt = IPSET_OPT_BITMASK, + }, }; static const struct ipset_attr_policy adt_attrs[] = { @@ -856,6 +860,7 @@ list_adt(struct ipset_session *session, struct nlattr *nla[]) const struct ipset_arg *arg; size_t offset = 0; int i, found = 0; + static char last_setname[IPSET_MAXNAMELEN] = ""; D("enter"); /* Check and load type, family */ @@ -890,6 +895,13 @@ list_adt(struct ipset_session *session, struct nlattr *nla[]) case IPSET_LIST_XML: safe_snprintf(session, "<member><elem>"); break; + case IPSET_LIST_JSON: + /* print separator if a member for this set was printed before */ + if (STREQ(ipset_data_setname(data), last_setname)) + safe_snprintf(session, ","); + strcpy(last_setname, ipset_data_setname(data)); + safe_snprintf(session, "\n {\n \"elem\" : \""); + break; case IPSET_LIST_PLAIN: default: break; @@ -898,6 +910,8 @@ list_adt(struct ipset_session *session, struct nlattr *nla[]) safe_dprintf(session, ipset_print_elem, IPSET_OPT_ELEM); if (session->mode == IPSET_LIST_XML) safe_snprintf(session, "</elem>"); + if (session->mode == IPSET_LIST_JSON) + safe_snprintf(session, "\""); for (i = 0; type->cmd[IPSET_ADD].args[i] != IPSET_ARG_NONE; i++) { arg = ipset_keyword(type->cmd[IPSET_ADD].args[i]); @@ -925,6 +939,15 @@ list_adt(struct ipset_session *session, struct nlattr *nla[]) safe_dprintf(session, arg->print, arg->opt); safe_snprintf(session, "</%s>", arg->name[0]); break; + case IPSET_LIST_JSON: + if (arg->has_arg == IPSET_NO_ARG) { + safe_snprintf(session, + ",\n \"%s\" : true", arg->name[0]); + break; + } + safe_snprintf(session, ",\n \"%s\" : ", arg->name[0]); + safe_dprintf(session, arg->print, arg->opt); + break; default: break; } @@ -932,6 +955,8 @@ list_adt(struct ipset_session *session, struct nlattr *nla[]) if (session->mode == IPSET_LIST_XML) safe_snprintf(session, "</member>\n"); + else if (session->mode == IPSET_LIST_JSON) + safe_snprintf(session, "\n }"); else safe_snprintf(session, "\n"); @@ -968,6 +993,7 @@ list_create(struct ipset_session *session, struct nlattr *nla[]) const struct ipset_arg *arg; uint8_t family; int i; + static bool firstipset = true; for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_CREATE_MAX; i++) if (nla[i]) { @@ -1003,6 +1029,19 @@ list_create(struct ipset_session *session, struct nlattr *nla[]) ipset_data_setname(data), type->name, type->revision); break; + case IPSET_LIST_JSON: + if (!firstipset) + safe_snprintf(session, ",\n"); + firstipset = false; + safe_snprintf(session, + " \{\n" + " \"name\" : \"%s\",\n" + " \"type\" : \"%s\",\n" + " \"revision\" : %u,\n" + " \"header\" : \{\n", + ipset_data_setname(data), + type->name, type->revision); + break; default: break; } @@ -1038,6 +1077,22 @@ list_create(struct ipset_session *session, struct nlattr *nla[]) safe_dprintf(session, arg->print, arg->opt); safe_snprintf(session, "</%s>", arg->name[0]); break; + case IPSET_LIST_JSON: + if (arg->has_arg == IPSET_NO_ARG) { + safe_snprintf(session, + " \"%s\" : true,\n", arg->name[0]); + break; + } + if (arg->opt == IPSET_OPT_FAMILY) { + safe_snprintf(session, " \"%s\" : \"", arg->name[0]); + safe_dprintf(session, arg->print, arg->opt); + safe_snprintf(session, "\",\n"); + break; + } + safe_snprintf(session, " \"%s\" : ", arg->name[0]); + safe_dprintf(session, arg->print, arg->opt); + safe_snprintf(session, ",\n"); + break; default: break; } @@ -1075,6 +1130,21 @@ list_create(struct ipset_session *session, struct nlattr *nla[]) "</header>\n" : "</header>\n<members>\n"); break; + case IPSET_LIST_JSON: + safe_snprintf(session, " \"memsize\" : "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); + safe_snprintf(session, ",\n \"references\" : "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES); + if (ipset_data_test(data, IPSET_OPT_ELEMENTS)) { + safe_snprintf(session, ",\n \"numentries\" : "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); + } + safe_snprintf(session, "\n"); + safe_snprintf(session, + session->envopts & IPSET_ENV_LIST_HEADER ? + " },\n" : + " },\n \"members\" : ["); + break; default: break; } @@ -1210,11 +1280,24 @@ print_set_done(struct ipset_session *session, bool callback_done) if (session->saved_setname[0] != '\0') safe_snprintf(session, "</members>\n</ipset>\n"); break; + case IPSET_LIST_JSON: + if (session->envopts & IPSET_ENV_LIST_SETNAME) + break; + if (session->envopts & IPSET_ENV_LIST_HEADER) { + if (session->saved_setname[0] != '\0') + safe_snprintf(session, " }"); + break; + } + if (session->saved_setname[0] != '\0') + safe_snprintf(session, "\n ]\n }"); + break; default: break; } if (callback_done && session->mode == IPSET_LIST_XML) safe_snprintf(session, "</ipsets>\n"); + if (callback_done && session->mode == IPSET_LIST_JSON) + safe_snprintf(session, "\n]\n"); return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_STOP; } @@ -1223,6 +1306,7 @@ callback_list(struct ipset_session *session, struct nlattr *nla[], enum ipset_cmd cmd) { struct ipset_data *data = session->data; + static bool firstipset = true; if (setjmp(printf_failure)) { session->saved_setname[0] = '\0'; @@ -1241,7 +1325,13 @@ callback_list(struct ipset_session *session, struct nlattr *nla[], if (session->mode == IPSET_LIST_XML) safe_snprintf(session, "<ipset name=\"%s\"/>\n", ipset_data_setname(data)); - else + else if (session->mode == IPSET_LIST_JSON) { + if (!firstipset) + safe_snprintf(session, ",\n"); + firstipset = false; + safe_snprintf(session, " { \"name\" : \"%s\" }", + ipset_data_setname(data)); + } else safe_snprintf(session, "%s\n", ipset_data_setname(data)); return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_OK; @@ -1721,6 +1811,10 @@ rawdata2attr(struct ipset_session *session, struct nlmsghdr *nlh, if (attr->type == MNL_TYPE_NESTED) { /* IP addresses */ struct nlattr *nested; + + if (type == IPSET_ATTR_BITMASK) + family = ipset_data_family(session->data); + int atype = family == NFPROTO_IPV4 ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6; @@ -1982,7 +2076,7 @@ build_msg(struct ipset_session *session, bool aggregate) break; case IPSET_CMD_ADD: case IPSET_CMD_DEL: { - const struct ipset_type *type; + DD(const struct ipset_type *type); if (!aggregate) { /* Setname, type not checked/added yet */ @@ -2007,7 +2101,7 @@ build_msg(struct ipset_session *session, bool aggregate) open_nested(session, nlh, IPSET_ATTR_ADT); } } - type = ipset_data_get(data, IPSET_OPT_TYPE); + DD(type = ipset_data_get(data, IPSET_OPT_TYPE)); D("family: %u, type family %u", ipset_data_family(data), type->family); if (open_nested(session, nlh, IPSET_ATTR_DATA)) { @@ -2027,7 +2121,7 @@ build_msg(struct ipset_session *session, bool aggregate) break; } case IPSET_CMD_TEST: { - const struct ipset_type *type; + DD(const struct ipset_type *type); /* Return codes are not aggregated, so tests cannot be either */ /* Setname, type not checked/added yet */ @@ -2040,7 +2134,7 @@ build_msg(struct ipset_session *session, bool aggregate) return ipset_err(session, "Invalid test command: missing settype"); - type = ipset_data_get(data, IPSET_OPT_TYPE); + DD(type = ipset_data_get(data, IPSET_OPT_TYPE)); D("family: %u, type family %u", ipset_data_family(data), type->family); ADDATTR_SETNAME(session, nlh, data); @@ -2187,18 +2281,27 @@ ipset_cmd(struct ipset_session *session, enum ipset_cmd cmd, uint32_t lineno) session->cmd = cmd; session->lineno = lineno; - /* Set default output mode */ - if (cmd == IPSET_CMD_LIST) { - if (session->mode == IPSET_LIST_NONE) - session->mode = IPSET_LIST_PLAIN; - } else if (cmd == IPSET_CMD_SAVE) { + if (cmd == IPSET_CMD_LIST || cmd == IPSET_CMD_SAVE) { + /* Set default output mode */ if (session->mode == IPSET_LIST_NONE) - session->mode = IPSET_LIST_SAVE; + session->mode = cmd == IPSET_CMD_LIST ? + IPSET_LIST_PLAIN : IPSET_LIST_SAVE; + /* Reset just in case there are multiple modes in a session */ + ipset_envopt_unset(session, IPSET_ENV_QUOTED); + switch (session->mode) { + case IPSET_LIST_XML: + /* Start the root element in XML mode */ + safe_snprintf(session, "<ipsets>\n"); + break; + case IPSET_LIST_JSON: + /* Start the root element in json mode */ + ipset_envopt_set(session, IPSET_ENV_QUOTED); + safe_snprintf(session, "[\n"); + break; + default: + break; + } } - /* Start the root element in XML mode */ - if ((cmd == IPSET_CMD_LIST || cmd == IPSET_CMD_SAVE) && - session->mode == IPSET_LIST_XML) - safe_snprintf(session, "<ipsets>\n"); D("next: build_msg"); /* Build new message or append buffered commands */ |