diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile.am | 2 | ||||
-rw-r--r-- | lib/data.c | 15 | ||||
-rw-r--r-- | lib/icmp.c | 79 | ||||
-rw-r--r-- | lib/icmpv6.c | 66 | ||||
-rw-r--r-- | lib/mnl.c | 17 | ||||
-rw-r--r-- | lib/parse.c | 413 | ||||
-rw-r--r-- | lib/print.c | 95 | ||||
-rw-r--r-- | lib/session.c | 161 | ||||
-rw-r--r-- | lib/types.c | 16 |
9 files changed, 662 insertions, 202 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 79b81db..7913877 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -8,6 +8,8 @@ libipset_la_LDFLAGS = -version-info $(LIBVERSION) libipset_la_LIBADD = ${libmnl_LIBS} libipset_la_SOURCES = \ data.c \ + icmp.c \ + icmpv6.c \ mnl.c \ parse.c \ print.c \ @@ -31,6 +31,7 @@ struct ipset_data { uint64_t ignored; /* Setname */ char setname[IPSET_MAXNAMELEN]; + /* Set type */ const struct ipset_type *type; /* Common CADT options */ uint8_t cidr; @@ -161,7 +162,7 @@ do { \ /** * ipset_data_ignored - test and set ignored bits in the data blob * @data: data blob - * @flags: the option flags which is ignored + * @flags: the option flag to be ignored * * Returns true if the option was not already ignored. */ @@ -205,6 +206,7 @@ ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value) break; case IPSET_OPT_FAMILY: data->family = *(const uint8_t *) value; + D("family set to %u", data->family); break; /* CADT options */ case IPSET_OPT_IP: @@ -263,7 +265,8 @@ ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value) break; /* Create-specific options, type */ case IPSET_OPT_TYPENAME: - ipset_strncpy(data->u.create.typename, value, IPSET_MAXNAMELEN); + ipset_strncpy(data->u.create.typename, value, + IPSET_MAXNAMELEN); break; case IPSET_OPT_REVISION: data->u.create.revision = *(const uint8_t *) value; @@ -332,7 +335,7 @@ ipset_data_get(const struct ipset_data *data, enum ipset_opt opt) assert(data); assert(opt != IPSET_OPT_NONE); - if (opt != IPSET_OPT_TYPENAME && !ipset_data_test(data, opt)) + if (!(opt == IPSET_OPT_TYPENAME || ipset_data_test(data, opt))) return NULL; switch (opt) { @@ -418,11 +421,11 @@ ipset_data_get(const struct ipset_data *data, enum ipset_opt opt) } /** - * ipset_data_sizeof - calculates the size for the type of data + * ipset_data_sizeof - calculates the size of the data type * @opt: option kind of the data * @family: INET family * - * Returns the size required to store the given option kind. + * Returns the size required to store the given data type. */ size_t ipset_data_sizeof(enum ipset_opt opt, uint8_t family) @@ -502,7 +505,7 @@ ipset_data_family(const struct ipset_data *data) * @data: data blob * * Return the value of IPSET_OPT_CIDR stored in the data blob. - * If it is not set, the the returned value corresponds to + * If it is not set, then the returned value corresponds to * the default one according to the family type or zero. */ uint8_t diff --git a/lib/icmp.c b/lib/icmp.c new file mode 100644 index 0000000..93276e2 --- /dev/null +++ b/lib/icmp.c @@ -0,0 +1,79 @@ +/* Copyright 2007-2010 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 version 2 as + * published by the Free Software Foundation. + */ +#include <libipset/utils.h> /* STRNEQ */ +#include <libipset/icmp.h> /* prototypes */ + +struct icmp_names { + const char *name; + uint8_t type, code; +}; + +static const struct icmp_names icmp_typecodes[] = { + { "echo-reply", 0, 0 }, + { "pong", 0, 0 }, + { "network-unreachable", 3, 0 }, + { "host-unreachable", 3, 1 }, + { "protocol-unreachable", 3, 2 }, + { "port-unreachable", 3, 3 }, + { "fragmentation-needed", 3, 4 }, + { "source-route-failed", 3, 5 }, + { "network-unknown", 3, 6 }, + { "host-unknown", 3, 7 }, + { "network-prohibited", 3, 9 }, + { "host-prohibited", 3, 10 }, + { "TOS-network-unreachable", 3, 11 }, + { "TOS-host-unreachable", 3, 12 }, + { "communication-prohibited", 3, 13 }, + { "host-precedence-violation", 3, 14 }, + { "precedence-cutoff", 3, 15 }, + { "source-quench", 4, 0 }, + { "network-redirect", 5, 0 }, + { "host-redirect", 5, 1 }, + { "TOS-network-redirect", 5, 2 }, + { "TOS-host-redirect", 5, 3 }, + { "echo-request", 8, 0 }, + { "ping", 8, 0 }, + { "router-advertisement", 9, 0 }, + { "router-solicitation", 10, 0 }, + { "ttl-zero-during-transit", 11, 0 }, + { "ttl-zero-during-reassembly", 11, 1 }, + { "ip-header-bad", 12, 0 }, + { "required-option-missing", 12, 1 }, + { "timestamp-request", 13, 0 }, + { "timestamp-reply", 14, 0 }, + { "address-mask-request", 17, 0 }, + { "address-mask-reply", 18, 0 }, +}; + +const char * id_to_icmp(uint8_t id) +{ + return id < ARRAY_SIZE(icmp_typecodes) ? icmp_typecodes[id].name : NULL; +} + +const char * icmp_to_name(uint8_t type, uint8_t code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(icmp_typecodes); i++) + if (icmp_typecodes[i].type == type && icmp_typecodes[i].code == code) + return icmp_typecodes[i].name; + + return NULL; +} + +int name_to_icmp(const char *str, uint16_t *typecode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(icmp_typecodes); i++) + if (STRNEQ(icmp_typecodes[i].name, str, strlen(str))) { + *typecode = (icmp_typecodes[i].type << 8) | icmp_typecodes[i].code; + return 0; + } + + return -1; +} diff --git a/lib/icmpv6.c b/lib/icmpv6.c new file mode 100644 index 0000000..c32a6a4 --- /dev/null +++ b/lib/icmpv6.c @@ -0,0 +1,66 @@ +/* Copyright 2007-2010 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 version 2 as + * published by the Free Software Foundation. + */ +#include <libipset/utils.h> /* STRNEQ */ +#include <libipset/icmpv6.h> /* prototypes */ + +struct icmpv6_names { + const char *name; + uint8_t type, code; +}; + +static const struct icmpv6_names icmpv6_typecodes[] = { + { "no-route", 1, 0 }, + { "communication-prohibited", 1, 1 }, + { "address-unreachable", 1, 3 }, + { "port-unreachable", 1, 4 }, + { "packet-too-big", 2, 0 }, + { "ttl-zero-during-transit", 3, 0 }, + { "ttl-zero-during-reassembly", 3, 1 }, + { "bad-header", 4, 0 }, + { "unknown-header-type", 4, 1 }, + { "unknown-option", 4, 2 }, + { "echo-request", 128, 0 }, + { "ping", 128, 0 }, + { "echo-reply", 129, 0 }, + { "pong", 129, 0 }, + { "router-solicitation", 133, 0 }, + { "router-advertisement", 134, 0 }, + { "neighbour-solicitation", 135, 0 }, + { "neigbour-solicitation", 135, 0 }, + { "neighbour-advertisement", 136, 0 }, + { "neigbour-advertisement", 136, 0 }, + { "redirect", 137, 0 }, +}; + +const char * id_to_icmpv6(uint8_t id) +{ + return id < ARRAY_SIZE(icmpv6_typecodes) ? icmpv6_typecodes[id].name : NULL; +} + +const char * icmpv6_to_name(uint8_t type, uint8_t code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(icmpv6_typecodes); i++) + if (icmpv6_typecodes[i].type == type && icmpv6_typecodes[i].code == code) + return icmpv6_typecodes[i].name; + + return NULL; +} + +int name_to_icmpv6(const char *str, uint16_t *typecode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(icmpv6_typecodes); i++) + if (STRNEQ(icmpv6_typecodes[i].name, str, strlen(str))) { + *typecode = (icmpv6_typecodes[i].type << 8) | icmpv6_typecodes[i].code; + return 0; + } + + return -1; +} @@ -21,12 +21,13 @@ #define NFNL_SUBSYS_IPSET 6 #endif +/* Internal data structure for the kernel-userspace communication parameters */ struct ipset_handle { - struct mnl_socket *h; - unsigned int seq; - unsigned int portid; - mnl_cb_t *cb_ctl; - void *data; + struct mnl_socket *h; /* the mnl socket */ + unsigned int seq; /* netlink message sequence number */ + unsigned int portid; /* the socket port identifier */ + mnl_cb_t *cb_ctl; /* control block callbacks */ + void *data; /* data pointer */ }; /* Netlink flags of the commands */ @@ -46,6 +47,12 @@ static uint16_t cmdflags[] = { [IPSET_CMD_PROTOCOL-1] = NLM_F_REQUEST, }; +/** + * ipset_get_nlmsg_type - get ipset netlink message type + * @nlh: pointer to the netlink message header + * + * Returns the ipset netlink message type, i.e. the ipset command. + */ int ipset_get_nlmsg_type(const struct nlmsghdr *nlh) { diff --git a/lib/parse.c b/lib/parse.c index 84b6a3f..28192d8 100644 --- a/lib/parse.c +++ b/lib/parse.c @@ -12,9 +12,12 @@ #include <sys/types.h> /* getaddrinfo */ #include <sys/socket.h> /* getaddrinfo, AF_ */ #include <net/ethernet.h> /* ETH_ALEN */ +#include <netinet/in.h> /* IPPROTO_ */ #include <libipset/debug.h> /* D() */ #include <libipset/data.h> /* IPSET_OPT_* */ +#include <libipset/icmp.h> /* name_to_icmp */ +#include <libipset/icmpv6.h> /* name_to_icmpv6 */ #include <libipset/pfxlen.h> /* prefixlen_netmask_map */ #include <libipset/session.h> /* ipset_err */ #include <libipset/types.h> /* ipset_type_get */ @@ -192,32 +195,35 @@ error: * Parse TCP service names or port numbers */ static int -parse_portname(struct ipset_session *session, const char *str, uint16_t *port) +parse_portname(struct ipset_session *session, const char *str, + uint16_t *port, const char *proto) { struct servent *service; - if ((service = getservbyname(str, "tcp")) != NULL) { + if ((service = getservbyname(str, proto)) != NULL) { *port = ntohs((uint16_t) service->s_port); return 0; } - return syntax_err("cannot parse '%s' as a (TCP) port", str); + return syntax_err("cannot parse '%s' as a %s port", str, proto); } /** - * ipset_parse_single_port - parse a single (TCP) port number or name + * ipset_parse_single_port - parse a single port number or name * @session: session structure * @opt: option kind of the data * @str: string to parse + * @proto: protocol * - * Parse string as a single (TCP) port number or name. The parsed port + * Parse string as a single port number or name. The parsed port * number is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int -ipset_parse_single_port(struct ipset_session *session, - enum ipset_opt opt, const char *str) +ipset_parse_port(struct ipset_session *session, + enum ipset_opt opt, const char *str, + const char *proto) { uint16_t port; int err; @@ -227,31 +233,31 @@ ipset_parse_single_port(struct ipset_session *session, assert(str); if ((err = string_to_u16(session, str, &port)) == 0 - || (err = parse_portname(session, str, &port)) == 0) + || (err = parse_portname(session, str, &port, proto)) == 0) err = ipset_session_data_set(session, opt, &port); if (!err) - /* No error, so reset session messages! */ + /* No error, so reset false error messages! */ ipset_session_report_reset(session); return err; } /** - * ipset_parse_port - parse (TCP) port name, number, or range of them + * ipset_parse_tcp_port - parse TCP port name, number, or range of them * @session: session structure * @opt: option kind of the data * @str: string to parse * - * Parse string as a TCP port name or number or range of them. + * Parse string as a TCP port name or number or range of them * separated by a dash. The parsed port numbers are stored * in the data blob of the session. * * Returns 0 on success or a negative error code. */ int -ipset_parse_port(struct ipset_session *session, - enum ipset_opt opt, const char *str) +ipset_parse_tcp_port(struct ipset_session *session, + enum ipset_opt opt, const char *str) { char *a, *saved, *tmp; int err = 0; @@ -270,11 +276,11 @@ ipset_parse_port(struct ipset_session *session, if (a != NULL) { /* port-port */ *a++ = '\0'; - err = ipset_parse_single_port(session, IPSET_OPT_PORT_TO, a); + err = ipset_parse_port(session, IPSET_OPT_PORT_TO, a, "TCP"); if (err) goto error; } - err = ipset_parse_single_port(session, opt, tmp); + err = ipset_parse_port(session, opt, tmp, "TCP"); error: free(saved); @@ -282,14 +288,36 @@ error: } /** + * ipset_parse_single_tcp_port - parse TCP port name or number + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a single TCP port name or number. + * The parsed port number is stored + * in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_single_tcp_port(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_PORT || opt == IPSET_OPT_PORT_TO); + assert(str); + + return ipset_parse_port(session, opt, str, "TCP"); +} + +/** * ipset_parse_proto - parse protocol name * @session: session structure * @opt: option kind of the data * @str: string to parse * - * Parse string as a protocol name. "any" is supported - * as a special protocol name for ipset itself. - * The parsed protocol are stored in the data blob of the session. + * Parse string as a protocol name. + * The parsed protocol is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ @@ -297,26 +325,114 @@ int ipset_parse_proto(struct ipset_session *session, enum ipset_opt opt, const char *str) { + struct protoent *protoent; uint8_t proto = 0; assert(session); assert(opt == IPSET_OPT_PROTO); assert(str); - if (STREQ(str, "any")) - proto = IPSET_IPPROTO_ANY; - else { - struct protoent *protoent = getprotobyname(str); - if (protoent == NULL) - return syntax_err("cannot parse '%s' as a protocol name", str); - proto = protoent->p_proto; - } - if (!proto || proto == IPSET_IPPROTO_TCPUDP) - return syntax_err("invalid protocol '%s'", str); + protoent = getprotobyname(strcasecmp(str, "icmpv6") == 0 + ? "ipv6-icmp" : str); + if (protoent == NULL) + return syntax_err("cannot parse '%s' " + "as a protocol name", str); + proto = protoent->p_proto; + if (!proto) + return syntax_err("Unsupported protocol '%s'", str); return ipset_session_data_set(session, opt, &proto); } +/* Parse ICMP and ICMPv6 type/code */ +static int +parse_icmp_typecode(struct ipset_session *session, + enum ipset_opt opt, const char *str, + const char *family) +{ + uint16_t typecode; + uint8_t type, code; + char *a, *saved, *tmp; + int err; + + saved = tmp = strdup(str); + if (tmp == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + a = cidr_separator(tmp); + if (a == NULL) { + free(saved); + return ipset_err(session, + "Cannot parse %s as an %s type/code.", str, family); + } + *a++ = '\0'; + if ((err = string_to_u8(session, a, &type)) != 0 + || (err = string_to_u8(session, tmp, &code)) != 0) + goto error; + + typecode = (type << 8) | code; + err = ipset_session_data_set(session, opt, &typecode); + +error: + free(saved); + return err; +} + +/** + * ipset_parse_icmp - parse an ICMP name or type/code + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an ICMP name or type/code numbers. + * The parsed ICMP type/code is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_icmp(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + uint16_t typecode; + + assert(session); + assert(opt == IPSET_OPT_PORT); + assert(str); + + if (name_to_icmp(str, &typecode) < 0) + return parse_icmp_typecode(session, opt, str, "ICMP"); + + return ipset_session_data_set(session, opt, &typecode); +} + +/** + * ipset_parse_icmpv6 - parse an ICMPv6 name or type/code + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an ICMPv6 name or type/code numbers. + * The parsed ICMPv6 type/code is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_icmpv6(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + uint16_t typecode; + + assert(session); + assert(opt == IPSET_OPT_PORT); + assert(str); + + if (name_to_icmpv6(str, &typecode) < 0) + return parse_icmp_typecode(session, opt, str, "ICMPv6"); + + return ipset_session_data_set(session, opt, &typecode); +} + /** * ipset_parse_proto_port - parse (optional) protocol and a single port * @session: session structure @@ -334,13 +450,17 @@ int ipset_parse_proto_port(struct ipset_session *session, enum ipset_opt opt, const char *str) { + struct ipset_data *data; char *a, *saved, *tmp; + const char *proto; + uint8_t p = IPPROTO_TCP; int err = 0; assert(session); assert(opt == IPSET_OPT_PORT); assert(str); + data = ipset_session_data(session); saved = tmp = strdup(str); if (tmp == NULL) return ipset_err(session, @@ -349,14 +469,54 @@ ipset_parse_proto_port(struct ipset_session *session, a = proto_separator(tmp); if (a != NULL) { + uint8_t family = ipset_data_family(data); + /* proto:port */ *a++ = '\0'; err = ipset_parse_proto(session, IPSET_OPT_PROTO, tmp); if (err) goto error; - tmp = a; + + p = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_PROTO); + switch (p) { + case IPPROTO_TCP: + proto = tmp; + tmp = a; + goto parse_port; + case IPPROTO_UDP: + proto = tmp; + tmp = a; + goto parse_port; + case IPPROTO_ICMP: + if (family != AF_INET) { + syntax_err("Protocol ICMP can be used with family INET only"); + goto error; + } + err = ipset_parse_icmp(session, opt, a); + break; + case IPPROTO_ICMPV6: + if (family != AF_INET6) { + syntax_err("Protocol ICMPv6 can be used with family INET6 only"); + goto error; + } + err = ipset_parse_icmpv6(session, opt, a); + break; + default: + if (!STREQ(a, "0")) { + syntax_err("Protocol %s can be used with pseudo port value 0 only."); + goto error; + } + ipset_data_flags_set(data, IPSET_FLAG(opt)); + } + goto error; + } else { + proto = "TCP"; + err = ipset_data_set(data, IPSET_OPT_PROTO, &p); + if (err) + goto error; } - err = ipset_parse_single_port(session, opt, tmp); +parse_port: + err = ipset_parse_port(session, opt, tmp, proto); error: free(saved); @@ -387,7 +547,8 @@ ipset_parse_family(struct ipset_session *session, data = ipset_session_data(session); if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_FAMILY))) - syntax_err("protocol family may not be specified multiple times"); + syntax_err("protocol family may not be specified " + "multiple times"); if (STREQ(str, "inet") || STREQ(str, "ipv4") || STREQ(str, "-4")) family = AF_INET; @@ -402,12 +563,13 @@ ipset_parse_family(struct ipset_session *session, } /* - * Parse IPv4/IPv6 addresses, networks and ranges + * Parse IPv4/IPv6 addresses, networks and ranges. * We resolve hostnames but just the first IP address is used. */ static struct addrinfo * -get_addrinfo(struct ipset_session *session, const char *str, uint8_t family) +call_getaddrinfo(struct ipset_session *session, const char *str, + uint8_t family) { struct addrinfo hints; struct addrinfo *res; @@ -429,101 +591,96 @@ get_addrinfo(struct ipset_session *session, const char *str, uint8_t family) return res; } -#define GET_ADDRINFO(family, IP, f, n) \ -static int \ -get_addrinfo##f(struct ipset_session *session, \ - const char *str, \ - struct addrinfo **info, \ - struct in##n##_addr **inaddr) \ -{ \ - struct addrinfo *i; \ - struct sockaddr_in##n saddr; \ - int found; \ - \ - if ((*info = get_addrinfo(session, str, family)) == NULL) { \ - syntax_err("cannot parse %s: " IP " resolving failed", \ - str); \ - return EINVAL; \ - } \ - \ - for (i = *info, found = 0; i != NULL; i = i->ai_next) { \ - if (i->ai_family != family \ - || i->ai_addrlen != sizeof(saddr)) \ - continue; \ - if (found == 0) { \ - /* Workaround: can't cast on Sparc */ \ - memcpy(&saddr, i->ai_addr, sizeof(saddr)); \ - *inaddr = &saddr.sin##n##_addr; \ - } else if (found == 1) { \ - ipset_warn(session, \ - "%s resolves to multiple addresses: " \ - "using only the first one returned by the resolver", \ - str); \ - } \ - found++; \ - } \ - if (found == 0) \ - return syntax_err("cannot parse %s: " \ - IP "address could not be resolved", \ - str); \ - return 0; \ +static int +get_addrinfo(struct ipset_session *session, + enum ipset_opt opt, + const char *str, + struct addrinfo **info, + uint8_t family) +{ + struct addrinfo *i; + size_t addrlen = family == AF_INET ? sizeof(struct sockaddr_in) + : sizeof(struct sockaddr_in6); + int found, err = 0; + + if ((*info = call_getaddrinfo(session, str, family)) == NULL) { + syntax_err("cannot parse %s: resolving to %s address failed", + str, family == AF_INET ? "IPv4" : "IPv6"); + return EINVAL; + } + + for (i = *info, found = 0; i != NULL; i = i->ai_next) { + if (i->ai_family != family || i->ai_addrlen != addrlen) + continue; + if (found == 0) { + if (family == AF_INET) { + err = ipset_session_data_set(session, opt, + &((const struct sockaddr_in *)i->ai_addr)->sin_addr); + } else { + err = ipset_session_data_set(session, opt, + &((const struct sockaddr_in6 *)i->ai_addr)->sin6_addr); + } + } else if (found == 1) { + ipset_warn(session, + "%s resolves to multiple addresses: " + "using only the first one returned " + "by the resolver", + str); + } + found++; + } + if (found == 0) + return syntax_err("cannot parse %s: " + "%s address could not be resolved", + str, family == AF_INET ? "IPv4" : "IPv6"); + return err; } -#define PARSE_IP(mask, f, n) \ -static int \ -parse_ipv##f(struct ipset_session *session, \ - enum ipset_opt opt, const char *str) \ -{ \ - uint8_t m = mask; \ - int aerr = EINVAL, err = 0, range = 0; \ - char *saved = strdup(str); \ - char *a, *tmp = saved; \ - struct addrinfo *info; \ - struct in##n##_addr *inaddr; \ - struct ipset_data *data = ipset_session_data(session); \ - enum ipset_opt copt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR \ - : IPSET_OPT_CIDR2; \ - \ - if (tmp == NULL) \ - return ipset_err(session, \ - "Cannot allocate memory to duplicate %s.",\ - str); \ - if ((a = cidr_separator(tmp)) != NULL) { \ - /* IP/mask */ \ - *a++ = '\0'; \ - \ - if ((err = string_to_cidr(session, a, 0, m, &m)) != 0 \ - || (err = ipset_data_set(data, copt, &m)) != 0) \ - goto out; \ - } else if ((a = range_separator(tmp)) != NULL) { \ - /* IP-IP */ \ - *a++ = '\0'; \ - D("range %s", a); \ - range++; \ - } \ - if ((aerr = get_addrinfo##f(session, tmp, &info, &inaddr)) != 0 \ - || (err = ipset_data_set(data, opt, inaddr)) != 0 \ - || !range) \ - goto out; \ - freeaddrinfo(info); \ - if ((aerr = get_addrinfo##f(session, a, &info, &inaddr)) == 0) \ - err = ipset_data_set(data, IPSET_OPT_IP_TO, inaddr); \ - \ -out: \ - if (aerr != EINVAL) \ - /* getaddrinfo not failed */ \ - freeaddrinfo(info); \ - else if (aerr) \ - err = -1; \ - free(saved); \ - return err; \ -} +static int +parse_ipaddr(struct ipset_session *session, + enum ipset_opt opt, const char *str, + uint8_t family) +{ + uint8_t m = family == AF_INET ? 32 : 128; + int aerr = EINVAL, err = 0, range = 0; + char *saved = strdup(str); + char *a, *tmp = saved; + struct addrinfo *info; + enum ipset_opt copt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR + : IPSET_OPT_CIDR2; -GET_ADDRINFO(AF_INET, "IPv4", 4, ) -PARSE_IP(32, 4, ) + if (tmp == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + if ((a = cidr_separator(tmp)) != NULL) { + /* IP/mask */ + *a++ = '\0'; -GET_ADDRINFO(AF_INET6, "IPv6", 6, 6) -PARSE_IP(128, 6, 6) + if ((err = string_to_cidr(session, a, 0, m, &m)) != 0 + || (err = ipset_session_data_set(session, copt, &m)) != 0) + goto out; + } else if ((a = range_separator(tmp)) != NULL) { + /* IP-IP */ + *a++ = '\0'; + D("range %s", a); + range++; + } + if ((aerr = get_addrinfo(session, opt, tmp, &info, family)) != 0 + || !range) + goto out; + freeaddrinfo(info); + aerr = get_addrinfo(session, IPSET_OPT_IP_TO, a, &info, family); + +out: + if (aerr != EINVAL) + /* getaddrinfo not failed */ + freeaddrinfo(info); + else if (aerr) + err = -1; + free(saved); + return err; +} enum ipaddr_type { IPADDR_ANY, @@ -536,7 +693,6 @@ static int parse_ip(struct ipset_session *session, enum ipset_opt opt, const char *str, enum ipaddr_type addrtype) { - int err = 0; struct ipset_data *data = ipset_session_data(session); uint8_t family = ipset_data_family(data); @@ -566,12 +722,7 @@ parse_ip(struct ipset_session *session, break; } - if (family == AF_INET) - err = parse_ipv4(session, opt, str); - else - err = parse_ipv6(session, opt, str); - - return err; + return parse_ipaddr(session, opt, str, family); } /** diff --git a/lib/print.c b/lib/print.c index 77c283a..87a9f2b 100644 --- a/lib/print.c +++ b/lib/print.c @@ -15,6 +15,8 @@ #include <libipset/debug.h> /* D() */ #include <libipset/data.h> /* ipset_data_* */ +#include <libipset/icmp.h> /* icmp_to_name */ +#include <libipset/icmpv6.h> /* icmpv6_to_name */ #include <libipset/parse.h> /* IPSET_*_SEPARATOR */ #include <libipset/types.h> /* ipset set types */ #include <libipset/session.h> /* IPSET_FLAG_ */ @@ -463,8 +465,6 @@ ipset_print_proto(char *buf, unsigned int len, proto = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_PROTO); assert(proto); - if (proto == IPSET_IPPROTO_ANY) - return snprintf(buf, len, "any"); protoent = getprotobynumber(proto); if (protoent) return snprintf(buf, len, "%s", protoent->p_name); @@ -474,6 +474,72 @@ ipset_print_proto(char *buf, unsigned int len, } /** + * ipset_print_icmp - print ICMP code name or type/code + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print ICMP code name or type/code name to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_icmp(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const char *name; + uint16_t typecode; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_PORT); + + typecode = *(const uint16_t *) ipset_data_get(data, IPSET_OPT_PORT); + name = icmp_to_name(typecode >> 8, typecode & 0xFF); + if (name != NULL) + return snprintf(buf, len, "%s", name); + else + return snprintf(buf, len, "%u/%u", typecode >> 8, typecode & 0xFF); +} + +/** + * ipset_print_icmpv6 - print ICMPv6 code name or type/code + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print ICMPv6 code name or type/code name to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_icmpv6(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const char *name; + uint16_t typecode; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_PORT); + + typecode = *(const uint16_t *) ipset_data_get(data, IPSET_OPT_PORT); + name = icmpv6_to_name(typecode >> 8, typecode & 0xFF); + if (name != NULL) + return snprintf(buf, len, "%s", name); + else + return snprintf(buf, len, "%u/%u", typecode >> 8, typecode & 0xFF); +} + +/** * ipset_print_proto_port - print proto:port * @buf: printing buffer * @len: length of available buffer space @@ -498,14 +564,33 @@ ipset_print_proto_port(char *buf, unsigned int len, assert(opt == IPSET_OPT_PORT); if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_PROTO))) { + uint8_t proto = *(const uint8_t *) ipset_data_get(data, + IPSET_OPT_PROTO); size = ipset_print_proto(buf, len, data, IPSET_OPT_PROTO, env); SNPRINTF_FAILURE(size, len, offset); if (len < 2) return -ENOSPC; - strcat(buf, ":"); - SNPRINTF_FAILURE(1, len, offset); + size = snprintf(buf + offset, len, IPSET_PROTO_SEPARATOR); + SNPRINTF_FAILURE(size, len, offset); + + switch (proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + break; + case IPPROTO_ICMP: + return ipset_print_icmp(buf + offset, len, data, + IPSET_OPT_PORT, env); + case IPPROTO_ICMPV6: + return ipset_print_icmpv6(buf + offset, len, data, + IPSET_OPT_PORT, env); + default: + break; + } } - return ipset_print_port(buf + offset, len, data, IPSET_OPT_PORT, env); + size = ipset_print_port(buf + offset, len, data, IPSET_OPT_PORT, env); + SNPRINTF_FAILURE(size, len, offset); + + return offset; } #define print_second(data) \ diff --git a/lib/session.c b/lib/session.c index 8a0493a..ba4e458 100644 --- a/lib/session.c +++ b/lib/session.c @@ -319,11 +319,11 @@ const struct ipset_attr_policy cmd_attrs[] = { const struct ipset_attr_policy create_attrs[] = { [IPSET_ATTR_IP] = { - .type = MNL_TYPE_BINARY, + .type = MNL_TYPE_NESTED, .opt = IPSET_OPT_IP, }, [IPSET_ATTR_IP_TO] = { - .type = MNL_TYPE_BINARY, + .type = MNL_TYPE_NESTED, .opt = IPSET_OPT_IP_TO, }, [IPSET_ATTR_CIDR] = { @@ -394,11 +394,11 @@ const struct ipset_attr_policy create_attrs[] = { const struct ipset_attr_policy adt_attrs[] = { [IPSET_ATTR_IP] = { - .type = MNL_TYPE_BINARY, + .type = MNL_TYPE_NESTED, .opt = IPSET_OPT_IP, }, [IPSET_ATTR_IP_TO] = { - .type = MNL_TYPE_BINARY, + .type = MNL_TYPE_NESTED, .opt = IPSET_OPT_IP_TO, }, [IPSET_ATTR_CIDR] = { @@ -445,7 +445,7 @@ const struct ipset_attr_policy adt_attrs[] = { .len = IPSET_MAXNAMELEN, }, [IPSET_ATTR_IP2] = { - .type = MNL_TYPE_BINARY, + .type = MNL_TYPE_NESTED, .opt = IPSET_OPT_IP2, }, [IPSET_ATTR_CIDR2] = { @@ -454,6 +454,61 @@ const struct ipset_attr_policy adt_attrs[] = { }, }; +const struct ipset_attr_policy ipaddr_attrs[] = { + [IPSET_ATTR_IPADDR_IPV4] = { + .type = MNL_TYPE_U32, + }, + [IPSET_ATTR_IPADDR_IPV6] = { + .type = MNL_TYPE_BINARY, + .len = sizeof(union nf_inet_addr), + }, +}; + +static int +generic_data_attr_cb(const struct nlattr *attr, void *data, + int attr_max, const struct ipset_attr_policy *policy) +{ + const struct nlattr **tb = (const struct nlattr **)data; + int type = mnl_attr_get_type(attr); + + D("attr type: %u, len %u", type, attr->nla_len); + if (mnl_attr_type_valid(attr, attr_max) < 0) { + D("attr type: %u INVALID", type); + return MNL_CB_ERROR; + } + if (mnl_attr_validate(attr, policy[type].type) < 0) { + D("attr type: %u POLICY, attrlen %u", type, + mnl_attr_get_payload_len(attr)); + return MNL_CB_ERROR; + } + if (policy[type].type == MNL_TYPE_NUL_STRING + && mnl_attr_get_payload_len(attr) > IPSET_MAXNAMELEN) + return MNL_CB_ERROR; + tb[type] = attr; + return MNL_CB_OK; +} + +static int +create_attr_cb(const struct nlattr *attr, void *data) +{ + return generic_data_attr_cb(attr, data, + IPSET_ATTR_CREATE_MAX, create_attrs); +} + +static int +adt_attr_cb(const struct nlattr *attr, void *data) +{ + return generic_data_attr_cb(attr, data, + IPSET_ATTR_ADT_MAX, adt_attrs); +} + +static int +ipaddr_attr_cb(const struct nlattr *attr, void *data) +{ + return generic_data_attr_cb(attr, data, + IPSET_ATTR_IPADDR_MAX, ipaddr_attrs); +} + #define FAILURE(format, args...) \ { ipset_err(session, format , ## args); return MNL_CB_ERROR; } @@ -469,26 +524,45 @@ attr2data(struct ipset_session *session, struct nlattr *nla[], attr = &attrs[type]; d = mnl_attr_get_payload(nla[type]); - if (attr->type == MNL_TYPE_BINARY && !attr->len) { + if (attr->type == MNL_TYPE_NESTED && attr->opt) { + /* IP addresses */ + struct nlattr *ipattr[IPSET_ATTR_IPADDR_MAX+1] = {}; uint8_t family = ipset_data_family(data); + int atype; + D("attr type %u", type); + if (mnl_attr_parse_nested(nla[type], + ipaddr_attr_cb, ipattr) < 0) + FAILURE("Broken kernel message, cannot validate " + "IP address attribute!"); /* Validate by hand */ switch (family) { case AF_INET: - if (nla[type]->nla_len < sizeof(uint32_t)) + atype = IPSET_ATTR_IPADDR_IPV4; + if (!ipattr[atype]) + FAILURE("Broken kernel message: IPv4 address " + "expected but not received!"); + if (ipattr[atype]->nla_len < sizeof(uint32_t)) FAILURE("Broken kernel message: " - "cannot validate IPv4 address attribute!"); + "cannot validate IPv4 " + "address attribute!"); break; case AF_INET6: - if (nla[type]->nla_len < sizeof(struct in6_addr)) + atype = IPSET_ATTR_IPADDR_IPV6; + if (!ipattr[atype]) + FAILURE("Broken kernel message: IPv6 address " + "expected but not received!"); + if (ipattr[atype]->nla_len < sizeof(struct in6_addr)) FAILURE("Broken kernel message: " - "cannot validate IPv6 address attribute!"); + "cannot validate IPv6 " + "address attribute!"); break; default: FAILURE("Broken kernel message: " "IP address attribute but " "family is unspecified!"); } + d = mnl_attr_get_payload(ipattr[atype]); } else if (nla[type]->nla_type & NLA_F_NET_BYTEORDER) { switch (attr->type) { case MNL_TYPE_U32: { @@ -744,7 +818,8 @@ list_create(struct ipset_session *session, struct nlattr *nla[]) for (arg = type->args[IPSET_CREATE]; arg != NULL && arg->opt; arg++) { if (!arg->print || !ipset_data_test(data, arg->opt) - || (arg->opt == IPSET_OPT_FAMILY && family == type->family)) + || (arg->opt == IPSET_OPT_FAMILY + && family == type->family)) continue; switch (session->mode) { case IPSET_LIST_SAVE: @@ -820,44 +895,6 @@ print_set_done(struct ipset_session *session) } static int -generic_data_attr_cb(const struct nlattr *attr, void *data, - int attr_max, const struct ipset_attr_policy *policy) -{ - const struct nlattr **tb = (const struct nlattr **)data; - int type = mnl_attr_get_type(attr); - - D("attr type: %u, len %u", type, attr->nla_len); - if (mnl_attr_type_valid(attr, attr_max) < 0) { - D("attr type: %u INVALID", type); - return MNL_CB_ERROR; - } - if (mnl_attr_validate(attr, policy[type].type) < 0) { - D("attr type: %u POLICY, attrlen %u", type, - mnl_attr_get_payload_len(attr)); - return MNL_CB_ERROR; - } - if (policy[type].type == MNL_TYPE_NUL_STRING - && mnl_attr_get_payload_len(attr) > IPSET_MAXNAMELEN) - return MNL_CB_ERROR; - tb[type] = attr; - return MNL_CB_OK; -} - -static int -create_attr_cb(const struct nlattr *attr, void *data) -{ - return generic_data_attr_cb(attr, data, - IPSET_ATTR_CREATE_MAX, create_attrs); -} - -static int -adt_attr_cb(const struct nlattr *attr, void *data) -{ - return generic_data_attr_cb(attr, data, - IPSET_ATTR_ADT_MAX, adt_attrs); -} - -static int callback_list(struct ipset_session *session, struct nlattr *nla[], enum ipset_cmd cmd) { @@ -1291,7 +1328,7 @@ static size_t attr_len(const struct ipset_attr_policy *attr, uint8_t family, uint16_t *flags) { switch (attr->type) { - case MNL_TYPE_BINARY: + case MNL_TYPE_NESTED: if (attr->len) return attr->len; @@ -1325,6 +1362,19 @@ rawdata2attr(struct nlmsghdr *nlh, alen = attr_len(attr, family, &flags); switch (attr->type) { + case MNL_TYPE_NESTED: { + /* IP addresses */ + struct nlattr *nested = mnl_attr_nest_start(nlh, type); + int atype = family == AF_INET ? IPSET_ATTR_IPADDR_IPV4 + : IPSET_ATTR_IPADDR_IPV6; + + D("family: %s", family == AF_INET ? "INET" : + family == AF_INET6 ? "INET6" : "UNSPEC"); + mnl_attr_put(nlh, atype | flags, alen, d); + mnl_attr_nest_end(nlh, nested); + + return 0; + } case MNL_TYPE_U32: { uint32_t value = htonl(*(const uint32_t *)d); @@ -1510,7 +1560,8 @@ build_msg(struct ipset_session *session, bool aggregate) ADDATTR(nlh, data, IPSET_ATTR_TYPENAME, AF_INET, cmd_attrs); ADDATTR_RAW(nlh, &type->revision, IPSET_ATTR_REVISION, cmd_attrs); - D("family: %u", ipset_data_family(data)); + D("family: %u, type family %u", + ipset_data_family(data), type->family); ADDATTR(nlh, data, IPSET_ATTR_FAMILY, AF_INET, cmd_attrs); /* Type-specific create attributes */ @@ -1568,8 +1619,10 @@ build_msg(struct ipset_session *session, bool aggregate) } } type = ipset_data_get(data, IPSET_OPT_TYPE); + D("family: %u, type family %u", + ipset_data_family(data), type->family); open_nested(session, nlh, IPSET_ATTR_DATA); - addattr_adt(nlh, data, type->family); + addattr_adt(nlh, data, ipset_data_family(data)); ADDATTR_RAW(nlh, &session->lineno, IPSET_ATTR_LINENO, cmd_attrs); close_nested(session, nlh); @@ -1590,9 +1643,11 @@ build_msg(struct ipset_session *session, bool aggregate) "Invalid test command: missing settype"); type = ipset_data_get(data, IPSET_OPT_TYPE); + D("family: %u, type family %u", + ipset_data_family(data), type->family); ADDATTR_SETNAME(nlh, data); open_nested(session, nlh, IPSET_ATTR_DATA); - addattr_adt(nlh, data, type->family); + addattr_adt(nlh, data, ipset_data_family(data)); close_nested(session, nlh); break; } diff --git a/lib/types.c b/lib/types.c index e3cad35..3d9b034 100644 --- a/lib/types.c +++ b/lib/types.c @@ -11,6 +11,7 @@ #include <sys/socket.h> /* AF_ */ #include <stdlib.h> /* malloc, free */ #include <stdio.h> /* FIXME: debug */ +#include <libmnl/libmnl.h> /* MNL_ALIGN */ #include <libipset/debug.h> /* D() */ #include <libipset/data.h> /* ipset_data_* */ @@ -438,14 +439,25 @@ type_max_size(struct ipset_type *type, uint8_t family) continue; if (!(IPSET_FLAG(opt) & type->full[IPSET_ADD])) continue; - max += ipset_data_sizeof(opt, family); + max += MNL_ALIGN(ipset_data_sizeof(opt, family)) + + MNL_ATTR_HDRLEN; + switch (opt) { + case IPSET_OPT_IP: + case IPSET_OPT_IP_TO: + case IPSET_OPT_IP2: + /* Nested attributes */ + max += MNL_ATTR_HDRLEN; + break; + default: + break; + } } type->maxsize[sizeid] = max; } /** * ipset_type_add - add (register) a userspace set type - * @type: set type structure + * @type: pointer to the set type structure * * Add the given set type to the type list. The types * are added sorted, in descending revision number. |