From 8e0608d31d988333ff04f3faaa6e851c0ecdbc6e Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Thu, 22 Apr 2010 16:52:29 +0200 Subject: Fourth stage to ipset-5 Add new userspace files: include/, lib/ and plus new files in src/. --- lib/Makefile.am | 20 + lib/PROTOCOL | 84 +++ lib/data.c | 505 ++++++++++++++++ lib/mnl.c | 157 +++++ lib/parse.c | 963 ++++++++++++++++++++++++++++++ lib/print.c | 577 ++++++++++++++++++ lib/session.c | 1782 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/types.c | 566 ++++++++++++++++++ lib/utils.c | 103 ++++ 9 files changed, 4757 insertions(+) create mode 100644 lib/Makefile.am create mode 100644 lib/PROTOCOL create mode 100644 lib/data.c create mode 100644 lib/mnl.c create mode 100644 lib/parse.c create mode 100644 lib/print.c create mode 100644 lib/session.c create mode 100644 lib/types.c create mode 100644 lib/utils.c (limited to 'lib') diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..74b6651 --- /dev/null +++ b/lib/Makefile.am @@ -0,0 +1,20 @@ +include $(top_srcdir)/Make_global.am + +AM_CFLAGS += -fPIC +LIBS = + +lib_LTLIBRARIES = libipset.la + +libipset_la_LDFLAGS = -version-info $(LIBVERSION) +libipset_la_SOURCES = \ + data.c \ + mnl.c \ + parse.c \ + print.c \ + session.c \ + types.c \ + utils.c + + +#%.o: %.c +# ${AM_VERBOSE_CC} ${CC} ${AM_DEPFLAGS} ${AM_CFLAGS} ${CFLAGS} -o $@ -c $< diff --git a/lib/PROTOCOL b/lib/PROTOCOL new file mode 100644 index 0000000..e1a139e --- /dev/null +++ b/lib/PROTOCOL @@ -0,0 +1,84 @@ +req: msg: IPSET_CMD_PROTOCOL + attr: IPSET_ATTR_PROTOCOL + +resp: attr: IPSET_ATTR_PROTOCOL (protocol max) + IPSET_ATTR_PROTOCOL_MIN (protocol min, optional) + +req: msg: IPSET_CMD_CREATE + attr: IPSET_ATTR_PROTOCOL + IPSET_ATTR_SETNAME + IPSET_ATTR_TYPENAME + IPSET_ATTR_REVISION + IPSET_ATTR_FAMILY + IPSET_ATTR_FLAGS + IPSET_ATTR_DATA + create-specific-data + +resp: success/error + +req: msg: IPSET_CMD_DESTROY|IPSET_CMD_FLUSH + attr: IPSET_ATTR_PROTOCOL + IPSET_ATTR_SETNAME (optional) + +resp: success/error + +req: msg: IPSET_CMD_SWAP|IPSET_CMD_RENAME + attr: IPSET_ATTR_PROTOCOL + IPSET_ATTR_SETNAME + IPSET_ATTR_SETNAME2 + +resp: success/error + +req: msg: IPSET_CMD_LIST|SAVE + attr: IPSET_ATTR_PROTOCOL + IPSET_ATTR_SETNAME (optional) + +resp: attr: IPSET_ATTR_DATA + create-specific-data + IPSET_ATTR_ADT + IPSET_ATTR_DATA + adt-specific-data + ... + +req: msg: IPSET_CMD_ADD|DEL + attr: IPSET_ATTR_PROTOCOL + IPSET_ATTR_SETNAME + IPSET_ATTR_FLAGS + IPSET_ATTR_LINENO (for reporting error line back too) + IPSET_ATTR_DATA + adt-specific-data + + or + + IPSET_ATTR_ADT + IPSET_ATTR_DATA + adt-specific-data + ... + +req: msg: IPSET_CMD_TEST + attr: IPSET_ATTR_PROTOCOL + IPSET_ATTR_SETNAME + IPSET_ATTR_FLAGS + IPSET_ATTR_DATA + adt-specific-data + +resp: success/error + +req: msg: IPSET_CMD_HEADER + attr: IPSET_ATTR_PROTOCOL + IPSET_ATTR_SETNAME + +resp: attr: IPSET_ATTR_SETNAME + IPSET_ATTR_TYPENAME + IPSET_ATTR_REVISION + IPSET_ATTR_FAMILY + +req: msg: IPSET_CMD_TYPE + attr: IPSET_ATTR_PROTOCOL + IPSET_ATTR_TYPENAME + IPSET_ATTR_FAMILY + +resp: attr: IPSET_ATTR_TYPENAME + IPSET_ATTR_FAMILY + IPSET_ATTR_REVISION (version max) + IPSET_ATTR_REVISION_MIN (version min, optional) diff --git a/lib/data.c b/lib/data.c new file mode 100644 index 0000000..0de91a1 --- /dev/null +++ b/lib/data.c @@ -0,0 +1,505 @@ +/* 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 /* assert */ +#include /* ntoh* */ +#include /* ETH_ALEN */ +#include /* AF_ */ +#include /* malloc, free */ +#include /* memset */ + +#include /* IPSET_MAXNAMELEN */ +#include /* struct ipset_type */ +#include /* inXcpy */ +#include /* prototypes */ + +/* Internal data structure to hold + * a) input data entered by the user or + * b) data received from kernel + * + * We always store the data in host order, *except* IP addresses. + */ + +struct ipset_data { + /* Option bits: which fields are set */ + uint64_t bits; + /* Setname */ + char setname[IPSET_MAXNAMELEN]; + const struct ipset_type *type; + /* Common CADT options */ + uint8_t cidr; + uint8_t family; + uint32_t flags; + uint32_t timeout; + union nf_inet_addr ip; + union nf_inet_addr ip_to; + uint16_t port; + uint16_t port_to; + union { + /* RENAME/SWAP */ + char setname2[IPSET_MAXNAMELEN]; + /* CREATE/LIST/SAVE */ + struct { + uint8_t probes; + uint8_t resize; + uint8_t netmask; + uint32_t hashsize; + uint32_t maxelem; + uint32_t gc; + uint32_t size; + /* Filled out by kernel */ + uint32_t references; + uint32_t elements; + uint32_t memsize; + char typename[IPSET_MAXNAMELEN]; + uint8_t revision_min; + uint8_t revision; + } create; + /* ADT/LIST/SAVE */ + struct { + union nf_inet_addr ip2; + uint8_t cidr2; + char ether[ETH_ALEN]; + char name[IPSET_MAXNAMELEN]; + char nameref[IPSET_MAXNAMELEN]; + } adt; + } u; +}; + +static void +copy_addr(uint8_t family, union nf_inet_addr *ip, const void *value) +{ + if (family == AF_INET) + in4cpy(&ip->in, (const struct in_addr *)value); + else + in6cpy(&ip->in6, (const struct in6_addr *)value); +} + +/** + * ipset_data_flags_test - test option bits in the data blob + * @data: data blob + * @flags: the option flags to test + * + * Returns true if the options are already set in the data blob. + */ +bool +ipset_data_flags_test(const struct ipset_data *data, uint64_t flags) +{ + assert(data); + return !!(data->bits & flags); +} + +/** + * ipset_data_flags_set - set option bits in the data blob + * @data: data blob + * @flags: the option flags to set + * + * The function sets the flags in the data blob so that + * the corresponding fields are regarded as if filled with proper data. + */ +void +ipset_data_flags_set(struct ipset_data *data, uint64_t flags) +{ + assert(data); + data->bits |= flags; +} + +/** + * ipset_data_flags_unset - unset option bits in the data blob + * @data: data blob + * @flags: the option flags to unset + * + * The function unsets the flags in the data blob. + * This is the quick way to clear specific fields. + */ +void +ipset_data_flags_unset(struct ipset_data *data, uint64_t flags) +{ + assert(data); + data->bits &= ~flags; +} + +#define flag_type_attr(data, opt, flag) \ +do { \ + data->flags |= (1 << flag); \ + opt = IPSET_OPT_FLAGS; \ +} while (0) + +/** + * ipset_data_set - put data into the data blob + * @data: data blob + * @opt: the option kind of the data + * @value: the value of the data + * + * Put a given kind of data into the data blob and mark the + * option kind as already set in the blob. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value) +{ + assert(data); + assert(opt != IPSET_OPT_NONE); + assert(value); + + switch (opt) { + /* Common ones */ + case IPSET_SETNAME: + ipset_strncpy(data->setname, value, IPSET_MAXNAMELEN); + break; + case IPSET_OPT_TYPE: + data->type = value; + break; + case IPSET_OPT_FAMILY: + data->family = *(const uint8_t *) value; + break; + /* CADT options */ + case IPSET_OPT_IP: + if (!(data->family == AF_INET || data->family == AF_INET6)) + return -1; + copy_addr(data->family, &data->ip, value); + break; + case IPSET_OPT_IP_TO: + if (!(data->family == AF_INET || data->family == AF_INET6)) + return -1; + copy_addr(data->family, &data->ip_to, value); + break; + case IPSET_OPT_CIDR: + data->cidr = *(const uint8_t *) value; + break; + case IPSET_OPT_PORT: + data->port = *(const uint16_t *) value; + break; + case IPSET_OPT_PORT_TO: + data->port_to = *(const uint16_t *) value; + break; + case IPSET_OPT_TIMEOUT: + data->timeout = *(const uint32_t *) value; + break; + /* Create-specific options */ + case IPSET_OPT_GC: + data->u.create.gc = *(const uint32_t *) value; + break; + case IPSET_OPT_HASHSIZE: + data->u.create.hashsize = *(const uint32_t *) value; + break; + case IPSET_OPT_MAXELEM: + data->u.create.maxelem = *(const uint32_t *) value; + break; + case IPSET_OPT_NETMASK: + data->u.create.netmask = *(const uint8_t *) value; + break; + case IPSET_OPT_PROBES: + data->u.create.probes = *(const uint8_t *) value; + break; + case IPSET_OPT_RESIZE: + data->u.create.resize = *(const uint8_t *) value; + break; + case IPSET_OPT_SIZE: + data->u.create.size = *(const uint32_t *) value; + break; + /* Create-specific options, filled out by the kernel */ + case IPSET_OPT_ELEMENTS: + data->u.create.elements = *(const uint32_t *) value; + break; + case IPSET_OPT_REFERENCES: + data->u.create.references = *(const uint32_t *) value; + break; + case IPSET_OPT_MEMSIZE: + data->u.create.memsize = *(const uint32_t *) value; + break; + /* Create-specific options, type */ + case IPSET_OPT_TYPENAME: + ipset_strncpy(data->u.create.typename, value, IPSET_MAXNAMELEN); + break; + case IPSET_OPT_REVISION: + data->u.create.revision = *(const uint8_t *) value; + break; + case IPSET_OPT_REVISION_MIN: + data->u.create.revision_min = *(const uint8_t *) value; + break; + /* ADT-specific options */ + case IPSET_OPT_ETHER: + memcpy(data->u.adt.ether, value, ETH_ALEN); + break; + case IPSET_OPT_NAME: + ipset_strncpy(data->u.adt.name, value, IPSET_MAXNAMELEN); + break; + case IPSET_OPT_NAMEREF: + ipset_strncpy(data->u.adt.nameref, value, IPSET_MAXNAMELEN); + break; + case IPSET_OPT_IP2: + if (!(data->family == AF_INET || data->family == AF_INET6)) + return -1; + copy_addr(data->family, &data->u.adt.ip2, value); + break; + case IPSET_OPT_CIDR2: + data->u.adt.cidr2 = *(const uint8_t *) value; + break; + /* Swap/rename */ + case IPSET_OPT_SETNAME2: + ipset_strncpy(data->u.setname2, value, IPSET_MAXNAMELEN); + break; + /* flags */ + case IPSET_OPT_EXIST: + flag_type_attr(data, opt, IPSET_FLAG_EXIST); + break; + case IPSET_OPT_BEFORE: + flag_type_attr(data, opt, IPSET_FLAG_BEFORE); + break; + case IPSET_OPT_FLAGS: + data->flags = *(const uint32_t *)value; + break; + default: + return -1; + }; + + ipset_data_flags_set(data, IPSET_FLAG(opt)); + return 0; +} + +/** + * ipset_data_get - get data from the data blob + * @data: data blob + * @opt: option kind of the requested data + * + * Returns the pointer to the requested kind of data from the data blob + * if it is set. If the option kind is not set or is an unkown type, + * NULL is returned. + */ +const void * +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)) + return NULL; + + switch (opt) { + /* Common ones */ + case IPSET_SETNAME: + return data->setname; + case IPSET_OPT_TYPE: + return data->type; + case IPSET_OPT_TYPENAME: + if (ipset_data_test(data, IPSET_OPT_TYPE)) + return data->type->name; + else if (ipset_data_test(data, IPSET_OPT_TYPENAME)) + return data->u.create.typename; + return NULL; + case IPSET_OPT_FAMILY: + return &data->family; + /* CADT options */ + case IPSET_OPT_IP: + return &data->ip; + case IPSET_OPT_IP_TO: + return &data->ip_to; + case IPSET_OPT_CIDR: + return &data->cidr; + case IPSET_OPT_PORT: + return &data->port; + case IPSET_OPT_PORT_TO: + return &data->port_to; + case IPSET_OPT_TIMEOUT: + return &data->timeout; + /* Create-specific options */ + case IPSET_OPT_GC: + return &data->u.create.gc; + case IPSET_OPT_HASHSIZE: + return &data->u.create.hashsize; + case IPSET_OPT_MAXELEM: + return &data->u.create.maxelem; + case IPSET_OPT_NETMASK: + return &data->u.create.netmask; + case IPSET_OPT_PROBES: + return &data->u.create.probes; + case IPSET_OPT_RESIZE: + return &data->u.create.resize; + case IPSET_OPT_SIZE: + return &data->u.create.size; + /* Create-specific options, filled out by the kernel */ + case IPSET_OPT_ELEMENTS: + return &data->u.create.elements; + case IPSET_OPT_REFERENCES: + return &data->u.create.references; + case IPSET_OPT_MEMSIZE: + return &data->u.create.memsize; + /* Create-specific options, TYPE */ + case IPSET_OPT_REVISION: + return &data->u.create.revision; + case IPSET_OPT_REVISION_MIN: + return &data->u.create.revision_min; + /* ADT-specific options */ + case IPSET_OPT_ETHER: + return data->u.adt.ether; + case IPSET_OPT_NAME: + return data->u.adt.name; + case IPSET_OPT_NAMEREF: + return data->u.adt.nameref; + case IPSET_OPT_IP2: + return &data->u.adt.ip2; + case IPSET_OPT_CIDR2: + return &data->u.adt.cidr2; + /* Swap/rename */ + case IPSET_OPT_SETNAME2: + return data->u.setname2; + /* flags */ + case IPSET_OPT_FLAGS: + case IPSET_OPT_EXIST: + case IPSET_OPT_BEFORE: + return &data->flags; + default: + return NULL; + } +} + +/** + * ipset_data_sizeof - calculates the size for the type of data + * @opt: option kind of the data + * @family: INET family + * + * Returns the size required to store the given option kind. + */ +size_t +ipset_data_sizeof(enum ipset_opt opt, uint8_t family) +{ + assert(opt != IPSET_OPT_NONE); + + switch (opt) { + case IPSET_OPT_IP: + case IPSET_OPT_IP_TO: + case IPSET_OPT_IP2: + return family == AF_INET ? sizeof(uint32_t) + : sizeof(struct in6_addr); + case IPSET_OPT_PORT: + case IPSET_OPT_PORT_TO: + return sizeof(uint16_t); + case IPSET_SETNAME: + case IPSET_OPT_NAME: + case IPSET_OPT_NAMEREF: + return IPSET_MAXNAMELEN; + case IPSET_OPT_TIMEOUT: + case IPSET_OPT_GC: + case IPSET_OPT_HASHSIZE: + case IPSET_OPT_MAXELEM: + case IPSET_OPT_SIZE: + case IPSET_OPT_ELEMENTS: + case IPSET_OPT_REFERENCES: + case IPSET_OPT_MEMSIZE: + return sizeof(uint32_t); + case IPSET_OPT_CIDR: + case IPSET_OPT_CIDR2: + case IPSET_OPT_NETMASK: + case IPSET_OPT_PROBES: + case IPSET_OPT_RESIZE: + return sizeof(uint8_t); + case IPSET_OPT_ETHER: + return ETH_ALEN; + /* Flags counted once */ + case IPSET_OPT_BEFORE: + return sizeof(uint32_t); + default: + return 0; + }; +} + +/** + * ipset_setname - return the name of the set from the data blob + * @data: data blob + * + * Return the name of the set from the data blob or NULL if the + * name not set yet. + */ +const char * +ipset_data_setname(const struct ipset_data *data) +{ + assert(data); + return ipset_data_test(data, IPSET_SETNAME) ? data->setname : NULL; +} + +/** + * ipset_family - return the INET family of the set from the data blob + * @data: data blob + * + * Return the INET family supported by the set from the data blob. + * If the family is not set yet, AF_UNSPEC is returned. + */ +uint8_t +ipset_data_family(const struct ipset_data *data) +{ + assert(data); + return ipset_data_test(data, IPSET_OPT_FAMILY) + ? data->family : AF_UNSPEC; +} + +/** + * ipset_data_cidr - return the value of IPSET_OPT_CIDR + * @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 + * the default one according to the family type or zero. + */ +uint8_t +ipset_data_cidr(const struct ipset_data *data) +{ + assert(data); + return ipset_data_test(data, IPSET_OPT_CIDR) ? data->cidr : + data->family == AF_INET ? 32 : + data->family == AF_INET6 ? 128 : 0; +} + +/** + * ipset_flags - return which fields are set in the data blob + * @data: data blob + * + * Returns the value of the bit field which elements are set. + */ +uint64_t +ipset_data_flags(const struct ipset_data *data) +{ + assert(data); + return data->bits; +} + +/** + * ipset_data_reset - reset the data blob to unset + * @data: data blob + * + * Resets the data blob to the unset state for every field. + */ +void +ipset_data_reset(struct ipset_data *data) +{ + assert(data); + memset(data, 0, sizeof(*data)); +} + +/** + * ipset_data_init - create a new data blob + * + * Return the new data blob initialized to empty. In case of + * an error, NULL is retured. + */ +struct ipset_data * +ipset_data_init(void) +{ + return calloc(1, sizeof(struct ipset_data)); +} + +/** + * ipset_data_fini - release a data blob created by ipset_data_init + * + * Release the data blob created by ipset_data_init previously. + */ +void +ipset_data_fini(struct ipset_data *data) +{ + assert(data); + free(data); +} diff --git a/lib/mnl.c b/lib/mnl.c new file mode 100644 index 0000000..5662a47 --- /dev/null +++ b/lib/mnl.c @@ -0,0 +1,157 @@ +/* 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 /* assert */ +#include /* errno */ +#include /* calloc, free */ +#include /* time */ + +#include /* enum ipset_cmd */ +#include /* ipset_session_handle */ +#include /* IPSET_ENV_EXIST */ +#include /* UNUSED */ +#include /* prototypes */ + +#ifndef NFNL_SUBSYS_IPSET +#define NFNL_SUBSYS_IPSET 6 +#endif + +struct ipset_handle { + struct mnl_socket *h; + unsigned int seq; + unsigned int portid; + mnl_cb_t *cb_ctl; + void *data; +}; + +/* Netlink flags of the commands */ +static uint16_t cmdflags[] = { + [IPSET_CMD_CREATE-1] = NLM_F_REQUEST|NLM_F_ACK|NLM_F_CREATE|NLM_F_EXCL, + [IPSET_CMD_DESTROY-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_FLUSH-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_RENAME-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_SWAP-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_LIST-1] = NLM_F_REQUEST|NLM_F_ROOT|NLM_F_MATCH|NLM_F_DUMP, + [IPSET_CMD_SAVE-1] = NLM_F_REQUEST|NLM_F_ROOT|NLM_F_MATCH|NLM_F_DUMP, + [IPSET_CMD_ADD-1] = NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL, + [IPSET_CMD_DEL-1] = NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL, + [IPSET_CMD_TEST-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_HEADER-1] = NLM_F_REQUEST, + [IPSET_CMD_TYPE-1] = NLM_F_REQUEST, + [IPSET_CMD_PROTOCOL-1] = NLM_F_REQUEST, +}; + +int +ipset_get_nlmsg_type(const struct nlmsghdr *nlh) +{ + return nlh->nlmsg_type & ~(NFNL_SUBSYS_IPSET << 8); +} + +static void +ipset_mnl_fill_hdr(struct ipset_handle *handle, enum ipset_cmd cmd, + void *buffer, size_t len UNUSED, uint8_t envflags) +{ + struct nlmsghdr *nlh; + struct nfgenmsg *nfg; + + assert(handle); + assert(buffer); + assert(cmd > IPSET_CMD_NONE && cmd < IPSET_MSG_MAX); + + nlh = mnl_nlmsg_put_header(buffer); + nlh->nlmsg_type = cmd | (NFNL_SUBSYS_IPSET << 8); + nlh->nlmsg_flags = cmdflags[cmd - 1]; + if (envflags & IPSET_ENV_EXIST) + nlh->nlmsg_flags &= ~NLM_F_EXCL; + nlh->nlmsg_seq = handle->seq = time(NULL); + + nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); + nfg->nfgen_family = AF_INET; + nfg->version = NFNETLINK_V0; + nfg->res_id = htons(0); +} + +static int +ipset_mnl_query(struct ipset_handle *handle, void *buffer, size_t len) +{ + struct nlmsghdr *nlh = buffer; + int ret; + + assert(handle); + assert(buffer); + + if (mnl_socket_sendto(handle->h, nlh, nlh->nlmsg_len) < 0) + return -ECOMM; + + D("message sent"); + ret = mnl_socket_recvfrom(handle->h, buffer, len); + D("message received, ret: %d", ret); + while (ret > 0) { + ret = mnl_cb_run2(buffer, ret, + handle->seq, handle->portid, + handle->cb_ctl[NLMSG_MIN_TYPE], + handle->data, + handle->cb_ctl, NLMSG_MIN_TYPE); + D("nfln_cb_run2, ret: %d", ret); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(handle->h, buffer, len); + D("message received, ret: %d", ret); + } + return ret > 0 ? 0 : ret; +} + +static struct ipset_handle * +ipset_mnl_init(mnl_cb_t *cb_ctl, void *data) +{ + struct ipset_handle *handle; + + assert(cb_ctl); + assert(data); + + handle = calloc(1, sizeof(*handle)); + if (!handle) + return NULL; + + handle->h = mnl_socket_open(NETLINK_NETFILTER); + if (!handle->h) + goto free_handle; + + if (mnl_socket_bind(handle->h, 0, MNL_SOCKET_AUTOPID) < 0) + goto close_nl; + + handle->portid = mnl_socket_get_portid(handle->h); + handle->cb_ctl = cb_ctl; + handle->data = data; + + return handle; + +close_nl: + mnl_socket_close(handle->h); +free_handle: + free(handle); + + return NULL; +} + +static int +ipset_mnl_fini(struct ipset_handle *handle) +{ + assert(handle); + + if (handle->h) + mnl_socket_close(handle->h); + + free(handle); + return 0; +} + +const struct ipset_transport ipset_mnl_transport = { + .init = ipset_mnl_init, + .fini = ipset_mnl_fini, + .fill_hdr = ipset_mnl_fill_hdr, + .query = ipset_mnl_query, +}; diff --git a/lib/parse.c b/lib/parse.c new file mode 100644 index 0000000..0e0e7f1 --- /dev/null +++ b/lib/parse.c @@ -0,0 +1,963 @@ +/* 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 /* assert */ +#include /* errno */ +#include /* ULLONG_MAX */ +#include /* getservbyname, getaddrinfo */ +#include /* strtoull, etc. */ +#include /* getaddrinfo */ +#include /* getaddrinfo, AF_ */ +#include /* ETH_ALEN */ + +#include /* IPSET_OPT_* */ +#include /* prefixlen_netmask_map */ +#include /* ipset_err */ +#include /* ipset_type_get */ +#include /* string utilities */ +#include /* prototypes */ + +/* Parse input data */ + +#define ipset_cidr_separator(str) ipset_strchr(str, IPSET_CIDR_SEPARATOR) +#define ipset_range_separator(str) ipset_strchr(str, IPSET_RANGE_SEPARATOR) +#define ipset_elem_separator(str) ipset_strchr(str, IPSET_ELEM_SEPARATOR) +#define ipset_name_separator(str) ipset_strchr(str, IPSET_NAME_SEPARATOR) + +#define syntax_err(fmt, args...) \ + ipset_err(session, "Syntax error: " fmt , ## args) + +/* + * Parser functions, shamelessly taken from iptables.c, ip6tables.c + * and parser.c from libnetfilter_conntrack. + */ + +/* + * Parse numbers + */ +static int +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 number = 0; + char *end; + + /* Handle hex, octal, etc. */ + errno = 0; + number = strtoull(str, &end, 0); + if (*end == '\0' && end != str && errno != ERANGE) { + /* we parsed a number, let's see if we want this */ + if (min <= number && (!max || number <= max)) { + *ret = number; + return 0; + } else + errno = ERANGE; + } + if (errno == ERANGE && max) + return syntax_err("'%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); + else + return syntax_err("'%s' is invalid as number", str); +} + +static int +string_to_number_l(struct ipset_session *session, + const char *str, + unsigned long min, + unsigned long max, + unsigned long *ret) +{ + int err; + unsigned long long number = 0; + + err = string_to_number_ll(session, str, min, max, &number); + *ret = (unsigned long) number; + + return err; +} + +static int +string_to_number(struct ipset_session *session, + const char *str, + unsigned int min, + unsigned int max, + unsigned int *ret) +{ + int err; + unsigned long number = 0; + + err = string_to_number_l(session, str, min, max, &number); + *ret = (unsigned int) number; + + return err; +} + +/** + * ipset_parse_ether - parse ethernet address + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an ethernet address. The parsed ethernet + * address is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_ether(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + unsigned int i = 0; + unsigned char ether[ETH_ALEN]; + + assert(session); + assert(opt == IPSET_OPT_ETHER); + assert(str); + + if (strlen(str) != ETH_ALEN * 3 - 1) + goto error; + + for (i = 0; i < ETH_ALEN; i++) { + long number; + char *end; + + number = strtol(str + i * 3, &end, 16); + + if (end == str + i * 3 + 2 + && (*end == ':' || *end == '\0') + && number >= 0 && number <= 255) + ether[i] = number; + else + goto error; + } + return ipset_session_data_set(session, opt, ether); + +error: + return syntax_err("cannot parse '%s' as ethernet address", str); +} + +/* + * Parse TCP service names or port numbers + */ +static int +parse_portname(struct ipset_session *session, const char *str, uint16_t *port) +{ + struct servent *service; + + if ((service = getservbyname(str, "tcp")) != NULL) { + *port = ntohs((uint16_t) service->s_port); + return 0; + } + + return syntax_err("cannot parse '%s' as a (TCP) port", str); +} + +static int +parse_portnum(struct ipset_session *session, const char *str, uint16_t *port) +{ + return string_to_number(session, str, 0, 65535, (unsigned int *)port); +} + +/** + * ipset_parse_single_port - parse a single (TCP) port number or name + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a single (TCP) 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) +{ + uint16_t port; + int err; + + assert(session); + assert(opt == IPSET_OPT_PORT || opt == IPSET_OPT_PORT_TO); + assert(str); + + if ((err = parse_portnum(session, str, &port)) == 0 + || (err = parse_portname(session, str, &port)) == 0) + err = ipset_session_data_set(session, opt, &port); + + if (!err) + /* No error, so reset session messages! */ + ipset_session_report_reset(session); + + return err; +} + +/** + * ipset_parse_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. + * 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) +{ + char *a, *saved, *tmp; + int err = 0; + + assert(session); + assert(opt == IPSET_OPT_PORT); + assert(str); + + saved = tmp = strdup(str); + if (tmp == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + + a = ipset_range_separator(tmp); + if (a != NULL) { + /* port-port */ + *a++ = '\0'; + err = ipset_parse_single_port(session, IPSET_OPT_PORT_TO, a); + if (err) + goto error; + } + err = ipset_parse_single_port(session, opt, tmp); + +error: + free(saved); + return err; +} + +/** + * ipset_parse_family - parse INET|INET6 family names + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an INET|INET6 family name. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_family(struct ipset_session *session, int opt, const char *str) +{ + uint8_t family; + + assert(session); + assert(opt == IPSET_OPT_FAMILY); + assert(str); + + if (STREQ(str, "inet") || STREQ(str, "ipv4") || STREQ(str, "-4")) + family = AF_INET; + else if (STREQ(str, "inet6") || STREQ(str, "ipv6") || STREQ(str, "-6")) + family = AF_INET6; + else if (STREQ(str, "any") || STREQ(str, "unspec")) + family = AF_UNSPEC; + else + return syntax_err("unknown INET family %s", str); + + return ipset_session_data_set(session, opt, &family); +} + +/* + * 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) +{ + struct addrinfo hints; + struct addrinfo *res; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + hints.ai_family = family; + hints.ai_socktype = SOCK_RAW; + hints.ai_protocol = 0; + hints.ai_next = NULL; + + if ((err = getaddrinfo(str, NULL, &hints, &res)) != 0) { + syntax_err("cannot resolve '%s' to an %s address: %s", + str, family == AF_INET6 ? "IPv6" : "IPv4", + gai_strerror(err)); + return NULL; + } else + 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: resolving " \ + IP " failed", str); \ + return EINVAL; \ + } \ + \ + for (i = *info, found = 0; i != NULL; i = i->ai_next) { \ + if (i->ai_family != family) \ + continue; \ + if (found == 0) { \ + saddr = (struct sockaddr_in##n *)i->ai_addr; \ + *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; \ +} + +#define PARSE_IP(mask, f, n) \ +static int \ +parse_ipv##f(struct ipset_session *session, \ + enum ipset_opt opt, const char *str) \ +{ \ + unsigned int 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 = ipset_cidr_separator(tmp)) != NULL) { \ + /* IP/mask */ \ + *a++ = '\0'; \ + \ + if ((err = string_to_number(session, a, 0, m, &m)) != 0 \ + || (err = ipset_data_set(data, copt, &m)) != 0) \ + goto out; \ + } else if ((a = ipset_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; \ +} + +GET_ADDRINFO(AF_INET, "IPv4", 4, ) +PARSE_IP(32, 4, ) + +GET_ADDRINFO(AF_INET6, "IPv6", 6, 6) +PARSE_IP(128, 6, 6) + +enum ipaddr_type { + IPADDR_ANY, + IPADDR_PLAIN, + IPADDR_NET, + IPADDR_RANGE, +}; + +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); + + if (family == AF_UNSPEC) { + family = AF_INET; + ipset_data_set(data, IPSET_OPT_FAMILY, &family); + } + + switch (addrtype) { + case IPADDR_PLAIN: + if (ipset_range_separator(str) || ipset_cidr_separator(str)) + return syntax_err("plain IP address must be supplied: %s", + str); + break; + case IPADDR_NET: + if (!ipset_cidr_separator(str) || ipset_range_separator(str)) + return syntax_err("IP/netblock must be supplied: %s", + str); + break; + case IPADDR_RANGE: + if (!ipset_range_separator(str) || ipset_cidr_separator(str)) + return syntax_err("IP-IP range must supplied: %s", + str); + break; + case IPADDR_ANY: + default: + break; + } + + if (family == AF_INET) + err = parse_ipv4(session, opt, str); + else + err = parse_ipv6(session, opt, str); + + return err; +} + +/** + * ipset_parse_ip - parse IPv4|IPv6 address, range or netblock + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address or address range + * or netblock. Hostnames are resolved. If family is not set + * yet in the data blob, INET is assumed. + * The values are stored in the data blob of the session. + * + * FIXME: if the hostname resolves to multiple addresses, + * the first one is used only. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_ip(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(str); + + return parse_ip(session, opt, str, IPADDR_ANY); +} + +/** + * ipset_parse_single_ip - parse a single IPv4|IPv6 address + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address or hostname. If family + * is not set yet in the data blob, 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_single_ip(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP + || opt == IPSET_OPT_IP_TO + || opt == IPSET_OPT_IP2); + assert(str); + + return parse_ip(session, opt, str, IPADDR_PLAIN); +} + +/** + * ipset_parse_net - parse IPv4|IPv6 address/cidr + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address/cidr pattern. If family + * is not set yet in the data blob, 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_net(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(str); + + return parse_ip(session, opt, str, IPADDR_NET); +} + +/** + * ipset_parse_range - parse IPv4|IPv6 ranges + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 range separated by a dash. If family + * is not set yet in the data blob, INET is assumed. + * The values are stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_range(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP); + assert(str); + + return parse_ip(session, IPSET_OPT_IP, str, IPADDR_RANGE); +} + +/** + * ipset_parse_netrange - parse IPv4|IPv6 address/cidr or range + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address/cidr pattern or a range + * of addresses separated by a dash. If family is not set yet in + * the data blob, 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_netrange(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP); + assert(str); + + if (!(ipset_range_separator(str) || ipset_cidr_separator(str))) + return syntax_err("IP/net or IP-IP range must be specified: %s", + str); + return parse_ip(session, opt, str, IPADDR_ANY); +} + +#define check_setname(str, saved) \ +do { \ + if (strlen(str) > IPSET_MAXNAMELEN - 1) { \ + if (saved != NULL) \ + free(saved); \ + return syntax_err("setname '%s' is longer than %u characters", \ + str, IPSET_MAXNAMELEN - 1); \ + } \ +} while (0) + + +/** + * ipset_parse_name - parse setname as element + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a setname or a setname element to add to a set. + * The pattern "setname,before|after,setname" is recognized and + * parsed. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_name(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + char *saved; + char *a = NULL, *b = NULL, *tmp; + int err, before = 0; + const char *sep = IPSET_ELEM_SEPARATOR; + struct ipset_data *data; + + assert(session); + assert(opt == IPSET_OPT_NAME || opt == IPSET_OPT_SETNAME2); + assert(str); + + data = ipset_session_data(session); + if (opt == IPSET_OPT_SETNAME2) { + check_setname(str, NULL); + + return ipset_data_set(data, opt, str); + } + + tmp = saved = strdup(str); + if (saved == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + if ((a = ipset_elem_separator(tmp)) != NULL) { + /* setname,[before|after,setname */ + *a++ = '\0'; + if ((b = ipset_elem_separator(a)) != NULL) + *b++ = '\0'; + if (b == NULL + || !(STREQ(a, "before") || STREQ(a, "after"))) { + err = ipset_err(session, "you must specify elements " + "as setname%s[before|after]%ssetname", + sep, sep); + goto out; + } + before = STREQ(a, "before"); + } + check_setname(tmp, saved); + if ((err = ipset_data_set(data, opt, tmp)) != 0 || b == NULL) + goto out; + + check_setname(b, saved); + if ((err = ipset_data_set(data, + IPSET_OPT_NAMEREF, b)) != 0) + goto out; + + if (before) + err = ipset_data_set(data, IPSET_OPT_BEFORE, &before); + +out: + free(saved); + return err; +} + +/** + * ipset_parse_setname - parse name as the name of the (current) set + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as the name of the (current) set. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_setname(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_SETNAME); + assert(str); + + check_setname(str, NULL); + + return ipset_session_data_set(session, opt, str); +} + +/** + * ipset_parse_uint32 - parse string as an unsigned integer + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an unsigned integer number. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_uint32(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + uint32_t value; + int err; + + assert(session); + assert(str); + + if ((err = string_to_number(session, str, 0, 0, &value)) == 0) + return ipset_session_data_set(session, opt, &value); + + return err; +} + +/** + * ipset_parse_uint8 - parse string as an unsigned short integer + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an unsigned short integer number. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_uint8(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + unsigned int value; + int err; + + assert(session); + assert(str); + + if ((err = string_to_number(session, str, 0, 255, &value)) == 0) + return ipset_session_data_set(session, opt, &value); + + return err; +} + +/** + * ipset_parse_netmask - parse string as a CIDR netmask value + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a CIDR netmask 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_netmask(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + unsigned int family, cidr; + struct ipset_data *data; + int err = 0; + + assert(session); + assert(opt == IPSET_OPT_NETMASK); + assert(str); + + data = ipset_session_data(session); + family = ipset_data_family(data); + if (family == AF_UNSPEC) { + family = AF_INET; + ipset_data_set(data, IPSET_OPT_FAMILY, &family); + } + + err = string_to_number(session, str, + family == AF_INET ? 1 : 4, + family == AF_INET ? 31 : 124, + &cidr); + + if (err) + return syntax_err("netmask is out of the inclusive range " + "of %u-%u", + family == AF_INET ? 1 : 4, + family == AF_INET ? 31 : 124); + + return ipset_data_set(data, opt, &cidr); +} + +/** + * ipset_parse_flag - "parse" option flags + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse option flags :-) + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_flag(struct ipset_session *session, + enum ipset_opt opt, const char *str UNUSED) +{ + assert(session); + + return ipset_session_data_set(session, opt, NULL); +} + +/** + * ipset_parse_type - parse ipset type name + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse ipset module type: supports both old and new formats. + * The type name is looked up and the type found is stored + * in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_typename(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + const struct ipset_type *type; + const char *typename; + + assert(session); + assert(opt == IPSET_OPT_TYPENAME); + assert(str); + + if (strlen(str) > IPSET_MAXNAMELEN - 1) + return syntax_err("typename '%s' is longer than %u characters", + str, IPSET_MAXNAMELEN - 1); + + /* Find the corresponding type */ + typename = ipset_typename_resolve(str); + if (typename == NULL) + return syntax_err("typename '%s' is unkown", str); + ipset_session_data_set(session, IPSET_OPT_TYPENAME, typename); + type = ipset_type_get(session, IPSET_CMD_CREATE); + + if (type == NULL) + return -1; + + return ipset_session_data_set(session, IPSET_OPT_TYPE, type); +} + +/** + * ipset_parse_output - parse output format name + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse output format names and set session mode. + * The value is stored in the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_output(struct ipset_session *session, + int opt UNUSED, const char *str) +{ + assert(session); + assert(str); + + if (STREQ(str, "plain")) + 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, "save")) + return ipset_session_output(session, IPSET_LIST_SAVE); + + return syntax_err("unkown output mode '%s'", str); +} + +#define parse_elem(s, t, d, str) \ +do { \ + if (!t->elem[d].parse) \ + goto internal; \ + err = t->elem[d].parse(s, t->elem[d].opt, str); \ + if (err) \ + goto out; \ +} while (0) + +/** + * ipset_parse_elem - parse ADT elem, depending on settype + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a (multipart) element according to the settype. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_elem(struct ipset_session *session, + enum ipset_opt optional, const char *str) +{ + const struct ipset_type *type; + char *a = NULL, *b = NULL, *tmp, *saved; + int err; + + assert(session); + assert(str); + + type = ipset_session_data_get(session, IPSET_OPT_TYPE); + if (!type) + return ipset_err(session, + "Internal error: set type is unknown!"); + + saved = tmp = strdup(str); + if (tmp == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + + a = ipset_elem_separator(tmp); + if (type->dimension > IPSET_DIM_ONE) { + if (a != NULL) { + /* elem,elem */ + *a++ = '\0'; + } else if (type->dimension > IPSET_DIM_TWO && !optional) { + free(tmp); + return syntax_err("Second element is missing from %s.", + str); + } + } else if (a != NULL) + return syntax_err("Elem separator in %s, " + "but settype %s supports none.", + str, type->name); + + if (a) + b = ipset_elem_separator(a); + if (type->dimension > IPSET_DIM_TWO) { + if (b != NULL) { + /* elem,elem,elem */ + *b++ = '\0'; + } else if (!optional) { + free(tmp); + return syntax_err("Third element is missing from %s.", + str); + } + } else if (b != NULL) + return syntax_err("Two elem separators in %s, " + "but settype %s supports one.", + str, type->name); + if (b != NULL && ipset_elem_separator(b)) + return syntax_err("Three elem separators in %s, " + "but settype %s supports two.", + str, type->name); + + D("parse elem part one: %s", tmp); + parse_elem(session, type, IPSET_DIM_ONE, tmp); + + if (type->dimension > IPSET_DIM_ONE && a != NULL) { + D("parse elem part two: %s", a); + parse_elem(session, type, IPSET_DIM_TWO, a); + } + if (type->dimension > IPSET_DIM_TWO && b != NULL) + parse_elem(session, type, IPSET_DIM_THREE, b); + + goto out; + +internal: + err = ipset_err(session, + "Internal error: missing parser function for %s", + type->name); +out: + free(saved); + return err; +} diff --git a/lib/print.c b/lib/print.c new file mode 100644 index 0000000..4df0905 --- /dev/null +++ b/lib/print.c @@ -0,0 +1,577 @@ +/* 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 /* assert */ +#include /* errno */ +#include /* snprintf */ +#include /* getservbyport */ +#include /* inet_ntop */ +#include /* inet_ntop */ +#include /* inet_ntop */ +#include /* ETH_ALEN */ + +#include /* ipset_data_* */ +#include /* IPSET_*_SEPARATOR */ +#include /* ipset set types */ +#include /* IPSET_FLAG_ */ +#include /* UNUSED */ +#include /* IPSET_ENV_* */ +#include /* prototypes */ + +/* Print data (to output buffer). All function must follow snprintf. */ + +#define SNPRINTF_FAILURE(size, len, offset) \ +do { \ + if (size < 0 || (unsigned int) size >= len) \ + return size; \ + offset += size; \ + len -= size; \ +} while (0) + +/** + * ipset_print_ether - print ethernet address to string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print Ethernet address to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_ether(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const unsigned char *ether; + int i, size, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_ETHER); + + if (len < ETH_ALEN*3) + return -1; + + ether = ipset_data_get(data, opt); + assert(ether); + + size = snprintf(buf, len, "%02X", ether[0]); + SNPRINTF_FAILURE(size, len, offset); + for (i = 1; i < ETH_ALEN; i++) { + size = snprintf(buf + offset, len, ":%02X", ether[i]); + SNPRINTF_FAILURE(size, len, offset); + } + + return offset; +} + +/** + * ipset_print_family - print INET family + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print INET family string to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_family(char *buf, unsigned int len, + const struct ipset_data *data, int opt, + uint8_t env UNUSED) +{ + uint8_t family; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_FAMILY); + + if (len < strlen("inet6") + 1) + return -1; + + family = ipset_data_family(data); + + return snprintf(buf, len, "%s", + family == AF_INET ? "inet" : + family == AF_INET6 ? "inet6" : "any"); +} + +/** + * ipset_print_type - print ipset type string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print ipset module string identifier to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_type(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const struct ipset_type *type; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_TYPE); + + type = ipset_data_get(data, opt); + assert(type); + if (len < strlen(type->name) + 1) + return -1; + + return snprintf(buf, len, "%s", type->name); +} + +#define GETNAMEINFO(family, f, n) \ +static inline int \ +__getnameinfo##f(char *buf, unsigned int len, \ + int flags, const union nf_inet_addr *addr) \ +{ \ + struct sockaddr_in##n saddr; \ + int err; \ + \ + memset(&saddr, 0, sizeof(saddr)); \ + in##f##cpy(&saddr.sin##n##_addr, &addr->in##n); \ + saddr.sin##n##_family = family; \ + \ + err = getnameinfo((const struct sockaddr *)&saddr, \ + sizeof(saddr), \ + buf, len, NULL, 0, flags); \ + \ + if (err == EAI_AGAIN && !(flags & NI_NUMERICHOST)) \ + err = getnameinfo((const struct sockaddr *)&saddr, \ + sizeof(saddr), \ + buf, len, NULL, 0, \ + flags | NI_NUMERICHOST); \ + return (err != 0 ? -1 : (int)strlen(buf)); \ +} + +#define SNPRINTF_IP(mask, f) \ +static int \ +snprintf_ipv##f(char *buf, unsigned int len, int flags, \ + const union nf_inet_addr *ip, uint8_t cidr) \ +{ \ + int size, offset = 0; \ + \ + size = __getnameinfo##f(buf, len, flags, ip); \ + SNPRINTF_FAILURE(size, len, offset); \ + \ + if (cidr == mask) \ + return offset; \ + if ((unsigned int)(size + 5) < len) \ + return -1; \ + size = snprintf(buf + offset, len, \ + "%s%u", IPSET_CIDR_SEPARATOR, cidr); \ + SNPRINTF_FAILURE(size, len, offset); \ + return offset; \ +} + +GETNAMEINFO(AF_INET, 4, ) +SNPRINTF_IP(32, 4) + +GETNAMEINFO(AF_INET6, 6, 6) +SNPRINTF_IP(128, 6) + +/** + * ipset_print_ip - print IPv4|IPv6 address to string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print IPv4|IPv6 address, address/cidr or address range to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_ip(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env) +{ + const union nf_inet_addr *ip; + uint8_t family, cidr; + int flags, size, offset = 0; + enum ipset_opt cidropt; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + + D("len: %u", len); + family = ipset_data_family(data); + cidropt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR : IPSET_OPT_CIDR2; + if (ipset_data_test(data, cidropt)) + cidr = *(uint8_t *) ipset_data_get(data, cidropt); + else + cidr = family == AF_INET6 ? 128 : 32; + flags = env & (1 << IPSET_ENV_RESOLVE) ? 0 : NI_NUMERICHOST; + + ip = ipset_data_get(data, opt); + assert(ip); + if (family == AF_INET) + size = snprintf_ipv4(buf, len, flags, ip, cidr); + else if (family == AF_INET6) + size = snprintf_ipv6(buf, len, flags, ip, cidr); + else + return -1; + SNPRINTF_FAILURE(size, len, offset); + + D("len: %u, offset %u", len, offset); + if (!ipset_data_test(data, IPSET_OPT_IP_TO)) + return offset; + + size = snprintf(buf + offset, len, "%s", IPSET_RANGE_SEPARATOR); + SNPRINTF_FAILURE(size, len, offset); + + ip = ipset_data_get(data, IPSET_OPT_IP_TO); + if (family == AF_INET) + size = snprintf_ipv4(buf + offset, len, flags, ip, cidr); + else if (family == AF_INET6) + size = snprintf_ipv6(buf + offset, len, flags, ip, cidr); + else + return -1; + + SNPRINTF_FAILURE(size, len, offset); + return offset; +} + +/** + * ipset_print_ipaddr - print IPv4|IPv6 address to string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print IPv4|IPv6 address or address/cidr to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_ipaddr(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env) +{ + const union nf_inet_addr *ip; + uint8_t family, cidr; + enum ipset_opt cidropt; + int flags; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_IP + || opt == IPSET_OPT_IP_TO + || opt == IPSET_OPT_IP2); + + family = ipset_data_family(data); + cidropt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR : IPSET_OPT_CIDR2; + if (ipset_data_test(data, cidropt)) + cidr = *(uint8_t *) ipset_data_get(data, cidropt); + else + cidr = family == AF_INET6 ? 128 : 32; + flags = env & (1 << IPSET_ENV_RESOLVE) ? 0 : NI_NUMERICHOST; + + ip = ipset_data_get(data, opt); + assert(ip); + if (family == AF_INET) + return snprintf_ipv4(buf, len, flags, ip, cidr); + else if (family == AF_INET6) + return snprintf_ipv6(buf, len, flags, ip, cidr); + + return -1; +} + +/** + * ipset_print_number - print number to string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print number to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_number(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + size_t maxsize; + const void *number; + + assert(buf); + assert(len > 0); + assert(data); + + number = ipset_data_get(data, opt); + maxsize = ipset_data_sizeof(opt, AF_INET); + D("opt: %u, maxsize %zu", opt, maxsize); + if (maxsize == sizeof(uint8_t)) + return snprintf(buf, len, "%u", *(uint8_t *) number); + else if (maxsize == sizeof(uint16_t)) + return snprintf(buf, len, "%u", *(uint16_t *) number); + else if (maxsize == sizeof(uint32_t)) + return snprintf(buf, len, "%lu", + (long unsigned) *(uint32_t *) number); + else + assert(0); + return 0; +} + +/** + * ipset_print_name - print setname element string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print setname element string to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_name(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const char *name; + int size, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_NAME); + + if (len < 2*IPSET_MAXNAMELEN + 2 + strlen("before")) + return -1; + + name = ipset_data_get(data, opt); + assert(name); + size = snprintf(buf, len, "%s", name); + SNPRINTF_FAILURE(size, len, offset); + + if (ipset_data_test(data, IPSET_OPT_NAMEREF)) { + bool before = ipset_data_test(data, IPSET_OPT_BEFORE); + size = snprintf(buf + offset, len, + "%s%s%s%s", IPSET_ELEM_SEPARATOR, + before ? "before" : "after", + IPSET_ELEM_SEPARATOR, + (const char *) ipset_data_get(data, + IPSET_OPT_NAMEREF)); + SNPRINTF_FAILURE(size, len, offset); + } + + return offset; +} + +/** + * ipset_print_port - print port or port range + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print port or port range to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_port(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const uint16_t *port; + int size, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_PORT); + + if (len < 2*strlen("65535") + 2) + return -1; + + port = ipset_data_get(data, IPSET_OPT_PORT); + assert(port); + size = snprintf(buf, len, "%u", *port); + SNPRINTF_FAILURE(size, len, offset); + + if (ipset_data_test(data, IPSET_OPT_PORT_TO)) { + port = ipset_data_get(data, IPSET_OPT_PORT_TO); + size = snprintf(buf + offset, len, + "%s%u", + IPSET_RANGE_SEPARATOR, *port); + SNPRINTF_FAILURE(size, len, offset); + } + + return offset; +} + +#define print_second(data) \ +ipset_data_flags_test(data, \ + IPSET_FLAG(IPSET_OPT_PORT)|IPSET_FLAG(IPSET_OPT_ETHER)) + +#define print_third(data) \ +ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_IP2)) + +/** + * ipset_print_elem - print ADT elem according to settype + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print (multipart) element according to settype + * + * Return lenght of printed string or error size. + */ +int +ipset_print_elem(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt UNUSED, + uint8_t env) +{ + const struct ipset_type *type; + int size, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + + type = ipset_data_get(data, IPSET_OPT_TYPE); + if (!type) + return -1; + + size = type->elem[IPSET_DIM_ONE].print(buf, len, data, + type->elem[IPSET_DIM_ONE].opt, env); + SNPRINTF_FAILURE(size, len, offset); + if (ipset_data_test(data, type->elem[IPSET_DIM_TWO].opt)) + D("print second elem"); + if (type->dimension == IPSET_DIM_ONE + || (type->last_elem_optional + && !ipset_data_test(data, type->elem[IPSET_DIM_TWO].opt))) + return offset; + + size = snprintf(buf + offset, len, IPSET_ELEM_SEPARATOR); + SNPRINTF_FAILURE(size, len, offset); + size = type->elem[IPSET_DIM_TWO].print(buf + offset, len, data, + type->elem[IPSET_DIM_TWO].opt, env); + SNPRINTF_FAILURE(size, len, offset); + if (type->dimension == IPSET_DIM_TWO + || (type->last_elem_optional + && !ipset_data_test(data, type->elem[IPSET_DIM_THREE].opt))) + return offset; + + size = snprintf(buf + offset, len, IPSET_ELEM_SEPARATOR); + SNPRINTF_FAILURE(size, len, offset); + size = type->elem[IPSET_DIM_THREE].print(buf + offset, len, data, + type->elem[IPSET_DIM_THREE].opt, env); + SNPRINTF_FAILURE(size, len, offset); + + return offset; +} + +/** + * ipset_print_flag - print a flag + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print a flag, i.e. option without value + * + * Return lenght of printed string or error size. + */ +int +ipset_print_flag(char *buf UNUSED, unsigned int len UNUSED, + const struct ipset_data *data UNUSED, + enum ipset_opt opt UNUSED, uint8_t env UNUSED) +{ + return 0; +} + +/** + * ipset_print_data - print data, generic fuction + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Generic wrapper of the printing functions. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_data(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env) +{ + int size = 0, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + + switch (opt) { + case IPSET_OPT_FAMILY: + size = ipset_print_family(buf, len, data, opt, env); + break; + case IPSET_OPT_TYPE: + size = ipset_print_type(buf, len, data, opt, env); + break; + case IPSET_SETNAME: + size = snprintf(buf, len, "%s", ipset_data_setname(data)); + break; + case IPSET_OPT_ELEM: + size = ipset_print_elem(buf, len, data, opt, env); + break; + case IPSET_OPT_IP: + size = ipset_print_ip(buf, len, data, opt, env); + break; + case IPSET_OPT_PORT: + size = ipset_print_port(buf, len, data, opt, env); + break; + case IPSET_OPT_GC: + case IPSET_OPT_HASHSIZE: + case IPSET_OPT_MAXELEM: + case IPSET_OPT_NETMASK: + case IPSET_OPT_PROBES: + case IPSET_OPT_RESIZE: + case IPSET_OPT_TIMEOUT: + case IPSET_OPT_REFERENCES: + case IPSET_OPT_ELEMENTS: + case IPSET_OPT_SIZE: + size = ipset_print_number(buf, len, data, opt, env); + break; + default: + return -1; + } + SNPRINTF_FAILURE(size, len, offset); + + return offset; +} diff --git a/lib/session.c b/lib/session.c new file mode 100644 index 0000000..2c4e39a --- /dev/null +++ b/lib/session.c @@ -0,0 +1,1782 @@ +/* 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 /* assert */ +#include /* errno */ +#include /* snprintf */ +#include /* va_* */ +#include /* free */ +#include /* str* */ +#include /* getpagesize */ +#include /* ETH_ALEN */ + +#include /* IPSET_OPT_* */ +#include /* ipset_errcode */ +#include /* ipset_print_* */ +#include /* struct ipset_type */ +#include /* transport */ +#include /* default backend */ +#include /* STREQ */ +#include /* IPSET_ENV_* */ +#include /* prototypes */ + +#define IPSET_NEST_MAX 4 + +/* The session structure */ +struct ipset_session { + const struct ipset_transport *transport;/* Transport protocol */ + struct ipset_handle *handle; /* Transport handler */ + struct ipset_data *data; /* Input/output data */ + /* Command state */ + enum ipset_cmd cmd; /* Current command */ + uint32_t lineno; /* Current lineno in restore mode */ + char saved_setname[IPSET_MAXNAMELEN]; /* Saved setname */ + struct nlattr *nested[IPSET_NEST_MAX]; /* Pointer to nest levels */ + uint8_t nestid; /* Current nest level */ + bool version_checked; /* Version checked */ + /* Output buffer */ + char outbuf[IPSET_OUTBUFLEN]; /* Output buffer */ + enum ipset_output_mode mode; /* Output mode */ + ipset_outfn outfn; /* Output function */ + /* Error/warning reporting */ + char report[IPSET_ERRORBUFLEN]; /* Error/report buffer */ + char *errmsg; + char *warnmsg; + uint8_t envopts; /* Session env opts */ + /* Kernel message buffer */ + size_t bufsize; + char buffer[0]; +}; + +/* + * Glue functions + */ + +/** + * ipset_session_data - return pointer to the data + * @session: session structure + * + * Returns the pointer to the data structure of the session. + */ +struct ipset_data * +ipset_session_data(const struct ipset_session *session) +{ + assert(session); + return session->data; +} + +/** + * ipset_session_handle - return pointer to the handle + * @session: session structure + * + * Returns the pointer to the transport handle structure of the session. + */ +struct ipset_handle * +ipset_session_handle(const struct ipset_session *session) +{ + assert(session); + return session->handle; +} + +/* + * Environment options + */ + +/** + * ipset_envopt_parse - parse/set environment option + * @session: session structure + * @opt: environment option + * @arg: option argument (unused) + * + * Parse and set an environment option. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_envopt_parse(struct ipset_session *session, int opt, + const char *arg UNUSED) +{ + assert(session); + + switch (opt) { + case IPSET_ENV_SORTED: + case IPSET_ENV_QUIET: + case IPSET_ENV_RESOLVE: + case IPSET_ENV_EXIST: + session->envopts |= opt; + return 0; + default: + break; + } + return -1; +} + +/** + * ipset_envopt_test - test environment option + * @session: session structure + * @opt: environment option + * + * Test whether the environment option is set in the session. + * + * Returns true or false. + */ +bool +ipset_envopt_test(struct ipset_session *session, enum ipset_envopt opt) +{ + assert(session); + return session->envopts & opt; +} + +/** + * ipset_session_output - set the session output mode + * @session: session structure + * @mode: output mode + * + * Set the output mode for the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_session_output(struct ipset_session *session, + enum ipset_output_mode mode) +{ + assert(session); + session->mode = mode; + return 0; +} + +/* + * Error and warning reporting + */ + +/** + * ipset_session_report - fill the report buffer + * @session: session structure + * @type: report type + * @fmt: message format + * + * Fill the report buffer with an error or warning message. + * Depending on the report type, set the error or warning + * message pointer. + * + * Returns -1. + */ +int __attribute__((format(printf,3,4))) +ipset_session_report(struct ipset_session *session, + enum ipset_err_type type, + const char *fmt, ...) +{ + int len, offset = 0; + va_list args; + + assert(session); + assert(fmt); + + if (session->lineno != 0 && type == IPSET_ERROR) { + sprintf(session->report, "Error in line %u: ", + session->lineno); + } + offset = strlen(session->report); + + va_start(args, fmt); + len = vsnprintf(session->report + offset, + IPSET_ERRORBUFLEN - 1 - offset, + fmt, args); + va_end(args); + + if (len >= IPSET_ERRORBUFLEN - 1 - offset) + session->report[IPSET_ERRORBUFLEN - 1] = '\0'; + + if (type == IPSET_ERROR) { + session->errmsg = session->report; + session->warnmsg = NULL; + } else { + session->errmsg = NULL; + session->warnmsg = session->report; + } + return -1; +} + +/** + * ipset_session_reset - reset the report buffer + * @session: session structure + * + * Reset the report buffer, the error and warning pointers. + */ +void +ipset_session_report_reset(struct ipset_session *session) +{ + assert(session); + session->report[0] = '\0'; + session->errmsg = session->warnmsg = NULL; +} + +/** + * ipset_session_error - return the report buffer as error + * @session: session structure + * + * Return the pointer to the report buffer as an error report. + * If there is no error message in the buffer, NULL returned. + */ +const char * +ipset_session_error(const struct ipset_session *session) +{ + assert(session); + + return session->errmsg; +} + +/** + * ipset_session_warning - return the report buffer as warning + * @session: session structure + * + * Return the pointer to the report buffer as a warning report. + * If there is no warning message in the buffer, NULL returned. + */ +const char * +ipset_session_warning(const struct ipset_session *session) +{ + assert(session); + + return session->warnmsg; +} + +/* + * Receive data from the kernel + */ + +struct ipset_attr_policy { + uint16_t type; + uint16_t len; + enum ipset_opt opt; +}; + +/* Attribute policies and mapping to options */ +const struct ipset_attr_policy cmd_attrs[] = { + [IPSET_ATTR_PROTOCOL] = { + .type = MNL_TYPE_U8, + }, + [IPSET_ATTR_SETNAME] = { + .type = MNL_TYPE_NUL_STRING, + .opt = IPSET_SETNAME, + .len = IPSET_MAXNAMELEN, + }, + [IPSET_ATTR_TYPENAME] = { + .type = MNL_TYPE_NUL_STRING, + .opt = IPSET_OPT_TYPENAME, + .len = IPSET_MAXNAMELEN, + }, + /* IPSET_ATTR_SETNAME2 is an alias for IPSET_ATTR_TYPENAME */ + [IPSET_ATTR_REVISION] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_REVISION, + }, + [IPSET_ATTR_FAMILY] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_FAMILY, + }, + [IPSET_ATTR_DATA] = { + .type = MNL_TYPE_NESTED, + }, + [IPSET_ATTR_ADT] = { + .type = MNL_TYPE_NESTED, + }, + [IPSET_ATTR_REVISION_MIN] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_REVISION_MIN, + }, + /* IPSET_ATTR_PROTOCOL_MIN is an alias for IPSET_ATTR_REVISION_MIN */ + [IPSET_ATTR_LINENO] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_LINENO, + }, +}; + +const struct ipset_attr_policy create_attrs[] = { + [IPSET_ATTR_IP] = { + .type = MNL_TYPE_BINARY, + .opt = IPSET_OPT_IP, + }, + [IPSET_ATTR_IP_TO] = { + .type = MNL_TYPE_BINARY, + .opt = IPSET_OPT_IP_TO, + }, + [IPSET_ATTR_CIDR] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_CIDR, + }, + [IPSET_ATTR_PORT] = { + .type = MNL_TYPE_U16, + .opt = IPSET_OPT_PORT, + }, + [IPSET_ATTR_PORT_TO] = { + .type = MNL_TYPE_U16, + .opt = IPSET_OPT_PORT_TO, + }, + [IPSET_ATTR_TIMEOUT] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_TIMEOUT, + }, + [IPSET_ATTR_FLAGS] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_FLAGS, + }, + [IPSET_ATTR_GC] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_GC, + }, + [IPSET_ATTR_HASHSIZE] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_HASHSIZE, + }, + [IPSET_ATTR_MAXELEM] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_MAXELEM, + }, + [IPSET_ATTR_NETMASK] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_NETMASK, + }, + [IPSET_ATTR_PROBES] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_PROBES, + }, + [IPSET_ATTR_RESIZE] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_RESIZE, + }, + [IPSET_ATTR_SIZE] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_SIZE, + }, + [IPSET_ATTR_ELEMENTS] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_ELEMENTS, + }, + [IPSET_ATTR_REFERENCES] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_REFERENCES, + }, + [IPSET_ATTR_MEMSIZE] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_MEMSIZE, + }, +}; + +const struct ipset_attr_policy adt_attrs[] = { + [IPSET_ATTR_IP] = { + .type = MNL_TYPE_BINARY, + .opt = IPSET_OPT_IP, + }, + [IPSET_ATTR_IP_TO] = { + .type = MNL_TYPE_BINARY, + .opt = IPSET_OPT_IP_TO, + }, + [IPSET_ATTR_CIDR] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_CIDR, + }, + [IPSET_ATTR_PORT] = { + .type = MNL_TYPE_U16, + .opt = IPSET_OPT_PORT, + }, + [IPSET_ATTR_PORT_TO] = { + .type = MNL_TYPE_U16, + .opt = IPSET_OPT_PORT_TO, + }, + [IPSET_ATTR_TIMEOUT] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_TIMEOUT, + }, + [IPSET_ATTR_FLAGS] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_FLAGS, + }, + [IPSET_ATTR_LINENO] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_LINENO, + }, + [IPSET_ATTR_ETHER] = { + .type = MNL_TYPE_BINARY, + .opt = IPSET_OPT_ETHER, + .len = ETH_ALEN, + }, + [IPSET_ATTR_NAME] = { + .type = MNL_TYPE_NUL_STRING, + .opt = IPSET_OPT_NAME, + .len = IPSET_MAXNAMELEN, + }, + [IPSET_ATTR_NAMEREF] = { + .type = MNL_TYPE_NUL_STRING, + .opt = IPSET_OPT_NAMEREF, + .len = IPSET_MAXNAMELEN, + }, + [IPSET_ATTR_IP2] = { + .type = MNL_TYPE_BINARY, + .opt = IPSET_OPT_IP2, + }, + [IPSET_ATTR_CIDR2] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_CIDR2, + }, +}; + +#define FAILURE(format, args...) \ + { ipset_err(session, format , ## args); return MNL_CB_ERROR; } + +static int +attr2data(struct ipset_session *session, struct nlattr *nla[], + int type, const struct ipset_attr_policy attrs[]) +{ + struct ipset_data *data = session->data; + const struct ipset_attr_policy *attr; + const void *d; + + attr = &attrs[type]; + d = mnl_attr_get_payload(nla[type]); + + if (attr->type == MNL_TYPE_BINARY && !attr->len) { + uint8_t family = ipset_data_family(data); + + /* Validate by hand */ + switch (family) { + case AF_INET: + if (nla[type]->nla_len < sizeof(uint32_t)) + FAILURE("Broken kernel message: " + "cannot validate IPv4 address attribute!"); + break; + case AF_INET6: + if (nla[type]->nla_len < sizeof(struct in6_addr)) + FAILURE("Broken kernel message: " + "cannot validate IPv6 address attribute!"); + break; + default: + FAILURE("Broken kernel message: " + "IP address attribute but " + "family is unspecified!"); + } + } else if (nla[type]->nla_type & NLA_F_NET_BYTEORDER) { + switch (attr->type) { + case MNL_TYPE_U32: { + uint32_t value; + + value = ntohl(*(uint32_t *)d); + + d = &value; + break; + } + case MNL_TYPE_U16: { + uint16_t value; + + value = ntohs(*(uint16_t *)d); + + d = &value; + break; + } + default: + break; + } + } + return ipset_data_set(data, attr->opt, d); +} + +#define ATTR2DATA(session, nla, type, attrs) \ + if (attr2data(session, nla, type, attrs) < 0) \ + return MNL_CB_ERROR + +static const char cmd2name[][9] = { + [IPSET_CMD_NONE] = "NONE", + [IPSET_CMD_CREATE] = "CREATE", + [IPSET_CMD_DESTROY] = "DESTROY", + [IPSET_CMD_FLUSH] = "FLUSH", + [IPSET_CMD_RENAME] = "RENAME", + [IPSET_CMD_SWAP] = "SWAP", + [IPSET_CMD_LIST] = "LIST", + [IPSET_CMD_SAVE] = "SAVE", + [IPSET_CMD_ADD] = "ADD", + [IPSET_CMD_DEL] = "DEL", + [IPSET_CMD_TEST] = "TEST", + [IPSET_CMD_HEADER] = "HEADER", + [IPSET_CMD_TYPE] = "TYPE", + [IPSET_CMD_PROTOCOL] = "PROTOCOL", +}; + +static inline int +call_outfn(struct ipset_session *session) +{ + int ret = session->outfn("%s", session->outbuf); + + session->outbuf[0] = '\0'; + + return ret < 0 ? ret : 0; +} + +static int __attribute__((format(printf,2,3))) +safe_snprintf(struct ipset_session *session, const char *fmt, ...) +{ + va_list args; + int len, ret, loop = 0; + +retry: + len = IPSET_OUTBUFLEN - strlen(session->outbuf); + va_start(args, fmt); + ret = vsnprintf(session->outbuf + IPSET_OUTBUFLEN - len, len, + fmt, args); + va_end(args); + + if (ret < 0) + return ipset_err(session, + "Internal error at printing to output buffer"); + + if (ret >= len) { + /* Buffer was too small, push it out and retry */ + if (loop++) + return ipset_err(session, + "Internal error at printing, loop detected!"); + + session->outbuf[IPSET_OUTBUFLEN - len] = '\0'; + if (!call_outfn(session)) + goto retry; + } + return ret; +} + +static int +safe_dprintf(struct ipset_session *session, ipset_printfn fn, + enum ipset_opt opt) +{ + int len, ret, loop = 0; + +retry: + len = IPSET_OUTBUFLEN - strlen(session->outbuf); + ret = fn(session->outbuf + IPSET_OUTBUFLEN - len, len, + session->data, opt, session->envopts); + + if (ret < 0) + return ipset_err(session, + "Internal error at printing to output buffer"); + + if (ret >= len) { + /* Buffer was too small, push it out and retry */ + if (loop++) + return ipset_err(session, + "Internal error at printing, loop detected!"); + + session->outbuf[IPSET_OUTBUFLEN - len] = '\0'; + if (!call_outfn(session)) + goto retry; + } + return ret; +} + +static int +list_adt(struct ipset_session *session, struct nlattr *nla[]) +{ + struct ipset_data *data = session->data; + const struct ipset_type *type; + const struct ipset_arg *arg; + uint8_t family; + int i; + + /* Check and load type, family */ + if (!ipset_data_test(data, IPSET_OPT_TYPE)) + type = ipset_type_get(session, IPSET_CMD_ADD); + else + type = ipset_data_get(data, IPSET_OPT_TYPE); + + if (type == NULL) + return MNL_CB_ERROR; + family = ipset_data_family(data); + + switch (session->mode) { + case IPSET_LIST_SAVE: + safe_snprintf(session, "add %s ", ipset_data_setname(data)); + break; + case IPSET_LIST_XML: + safe_snprintf(session, " "); + break; + case IPSET_LIST_PLAIN: + default: + break; + } + + for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) + if (nla[i]) + ATTR2DATA(session, nla, i, adt_attrs); + + safe_dprintf(session, ipset_print_elem, IPSET_OPT_ELEM); + + for (arg = type->args[IPSET_ADD]; arg != NULL && arg->print; arg++) { + if (!ipset_data_test(data, arg->opt)) + continue; + switch (session->mode) { + case IPSET_LIST_SAVE: + case IPSET_LIST_PLAIN: + safe_snprintf(session, " %s ", arg->name[0]); + if (arg->has_arg == IPSET_NO_ARG) + break; + safe_dprintf(session, arg->print, arg->opt); + break; + case IPSET_LIST_XML: + if (arg->has_arg == IPSET_NO_ARG) { + safe_snprintf(session, + " <%s/>\n", + arg->name[0]); + break; + } + safe_snprintf(session, " <%s>", + arg->name[0]); + safe_dprintf(session, arg->print, arg->opt); + safe_snprintf(session, "\n", + arg->name[0]); + break; + default: + break; + } + } + + if (session->mode == IPSET_LIST_XML) + safe_snprintf(session, "\n"); + else + safe_snprintf(session, "\n"); + + return MNL_CB_OK; +} + +static int +list_create(struct ipset_session *session, struct nlattr *nla[]) +{ + struct ipset_data *data = session->data; + const struct ipset_type *type; + const struct ipset_arg *arg; + uint8_t family; + int i; + + for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_CREATE_MAX; i++) + if (nla[i]) { + D("add attr %u, opt %u", i, create_attrs[i].opt); + ATTR2DATA(session, nla, i, create_attrs); + } + + type = ipset_type_check(session); + if (type == NULL) + return MNL_CB_ERROR; + family = ipset_data_family(data); + + switch (session->mode) { + case IPSET_LIST_SAVE: + safe_snprintf(session, "create %s %s ", + ipset_data_setname(data), + type->name); + if (family == AF_INET6) + sprintf(session->outbuf, "family inet6 "); + break; + case IPSET_LIST_PLAIN: + safe_snprintf(session, "Name: %s\nType: %s\n", + ipset_data_setname(data), + type->name); + if (family == AF_INET6) + safe_snprintf(session, "Family: INET6\n"); + safe_snprintf(session, "Header: "); + break; + case IPSET_LIST_XML: + safe_snprintf(session, + "\n" + " %s\n", + ipset_data_setname(data), + type->name); + if (family == AF_INET6) + safe_snprintf(session, " INET6\n"); + safe_snprintf(session, "
\n"); + break; + default: + break; + } + + for (arg = type->args[IPSET_CREATE]; arg != NULL && arg->print; arg++) { + if (!ipset_data_test(data, arg->opt)) + continue; + switch (session->mode) { + case IPSET_LIST_SAVE: + case IPSET_LIST_PLAIN: + safe_snprintf(session, "%s ", arg->name[0]); + if (arg->has_arg == IPSET_NO_ARG) + break; + safe_dprintf(session, arg->print, arg->opt); + safe_snprintf(session, " "); + break; + case IPSET_LIST_XML: + if (arg->has_arg == IPSET_NO_ARG) { + safe_snprintf(session, + " <%s/>\n", + arg->name[0]); + break; + } + safe_snprintf(session, " <%s>", + arg->name[0]); + safe_dprintf(session, arg->print, arg->opt); + safe_snprintf(session, "\n", + arg->name[0]); + break; + default: + break; + } + } + switch (session->mode) { + case IPSET_LIST_SAVE: + safe_snprintf(session, "\n"); + break; + case IPSET_LIST_PLAIN: + safe_snprintf(session, "\nElements: "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); + safe_snprintf(session, "\nSize in memory: "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); + safe_snprintf(session, "\nReferences: "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES); + safe_snprintf(session, "\nMembers:\n"); + break; + case IPSET_LIST_XML: + safe_snprintf(session, " "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); + safe_snprintf(session, "\n "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); + safe_snprintf(session, "\n "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES); + safe_snprintf(session, "\n
\n \n"); + break; + default: + break; + } + + return MNL_CB_OK; +} + +static int +print_set_done(struct ipset_session *session) +{ + D("called"); + switch (session->mode) { + case IPSET_LIST_XML: + if (session->saved_setname[0] == '\0') + safe_snprintf(session, "\n"); + else + safe_snprintf(session, " \n
\n"); + break; + case IPSET_LIST_SAVE: + /* No empty lines between the sets */ + break; + default: + safe_snprintf(session, "\n"); + break; + } + return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_STOP; +} + +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) +{ + struct ipset_data *data = session->data; + + if (!nla[IPSET_ATTR_SETNAME]) + FAILURE("Broken %s kernel message: missing setname!", + cmd2name[cmd]); + + ATTR2DATA(session, nla, IPSET_ATTR_SETNAME, cmd_attrs); + if (STREQ(ipset_data_setname(data), session->saved_setname)) { + /* Header part already seen */ + if (ipset_data_test(data, IPSET_OPT_TYPE) + && nla[IPSET_ATTR_DATA] != NULL) + FAILURE("Broken %s kernel message: " + "extra DATA received!", cmd2name[cmd]); + } else { + if (nla[IPSET_ATTR_DATA] == NULL) + FAILURE("Broken %s kernel message: " + "missing DATA part!", cmd2name[cmd]); + + /* Close previous set printing */ + if (session->saved_setname[0] != '\0') + print_set_done(session); + } + + if (nla[IPSET_ATTR_DATA] != NULL) { + struct nlattr *cattr[IPSET_ATTR_CREATE_MAX+1] = {}; + + if (!(nla[IPSET_ATTR_TYPENAME] + && nla[IPSET_ATTR_FAMILY] + && nla[IPSET_ATTR_REVISION])) + FAILURE("Broken %s kernel message: missing %s!", + cmd2name[cmd], + !nla[IPSET_ATTR_TYPENAME] ? "typename" : + !nla[IPSET_ATTR_FAMILY] ? "family" : "revision"); + + ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); + + /* Reset CREATE specific flags */ + ipset_data_flags_unset(data, IPSET_CREATE_FLAGS); + if (mnl_attr_parse_nested(nla[IPSET_ATTR_DATA], + create_attr_cb, cattr) < 0) + FAILURE("Broken %s kernel message: " + "cannot validate DATA attributes!", + cmd2name[cmd]); + if (list_create(session, cattr) != MNL_CB_OK) + return MNL_CB_ERROR; + strcpy(session->saved_setname, ipset_data_setname(data)); + } + + if (nla[IPSET_ATTR_ADT] != NULL) { + struct nlattr *tb, *adt[IPSET_ATTR_ADT_MAX+1]; + + mnl_attr_for_each_nested(tb, nla[IPSET_ATTR_ADT]) { + memset(adt, 0, sizeof(adt)); + /* Reset ADT specific flags */ + ipset_data_flags_unset(data, IPSET_ADT_FLAGS); + if (mnl_attr_parse_nested(tb, adt_attr_cb, adt) < 0) + FAILURE("Broken %s kernel message: " + "cannot validate ADT attributes!", + cmd2name[cmd]); + if (list_adt(session, adt) != MNL_CB_OK) + return MNL_CB_ERROR; + } + } + return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_OK; +} + +#ifndef IPSET_PROTOCOL_MIN +#define IPSET_PROTOCOL_MIN IPSET_PROTOCOL +#endif + +#ifndef IPSET_PROTOCOL_MAX +#define IPSET_PROTOCOL_MAX IPSET_PROTOCOL +#endif + +static int +callback_version(struct ipset_session *session, struct nlattr *nla[]) +{ + uint8_t min, max; + + min = max = mnl_attr_get_u8(nla[IPSET_ATTR_PROTOCOL]); + + if (nla[IPSET_ATTR_PROTOCOL_MIN]) { + min = mnl_attr_get_u8(nla[IPSET_ATTR_PROTOCOL_MIN]); + D("min: %u", min); + } + + if (min > IPSET_PROTOCOL_MAX || max < IPSET_PROTOCOL_MIN) + FAILURE("Cannot communicate with kernel: " + "Kernel support protocol versions %u-%u " + "while userspace supports protocol versions %u-%u", + min, max, IPSET_PROTOCOL_MIN, IPSET_PROTOCOL_MAX); + + if (!(session->envopts & IPSET_ENV_QUIET) + && max != IPSET_PROTOCOL_MAX) + ipset_warn(session, + "Kernel support protocol versions %u-%u " + "while userspace supports protocol versions %u-%u", + min, max, IPSET_PROTOCOL_MIN, IPSET_PROTOCOL_MAX); + + session->version_checked = true; + + return MNL_CB_STOP; +} + +static int +callback_header(struct ipset_session *session, struct nlattr *nla[]) +{ + const char *setname; + struct ipset_data *data = session->data; + + if (!nla[IPSET_ATTR_SETNAME]) + FAILURE("Broken HEADER kernel message: missing setname!"); + + setname = mnl_attr_get_str(nla[IPSET_ATTR_SETNAME]); + if (!STREQ(setname, ipset_data_setname(data))) + FAILURE("Broken HEADER kernel message: sent setname `%s' " + "does not match with received one `%s'!", + ipset_data_setname(data), setname); + + if (!(nla[IPSET_ATTR_TYPENAME] + && nla[IPSET_ATTR_REVISION] + && nla[IPSET_ATTR_FAMILY])) + FAILURE("Broken HEADER kernel message: " + "missing attribute '%s'!", + !nla[IPSET_ATTR_TYPENAME] ? "typename" : + !nla[IPSET_ATTR_REVISION] ? "revision" : + "family"); + + ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); + + return MNL_CB_STOP; +} + +static int +callback_type(struct ipset_session *session, struct nlattr *nla[]) +{ + struct ipset_data *data = session->data; + const char *typename, *orig; + + if (!(nla[IPSET_ATTR_TYPENAME] + && nla[IPSET_ATTR_REVISION] + && nla[IPSET_ATTR_FAMILY])) + FAILURE("Broken TYPE kernel message: " + "missing attribute '%s'!", + !nla[IPSET_ATTR_TYPENAME] ? "typename" : + !nla[IPSET_ATTR_REVISION] ? "revision" : + "family"); + + typename = mnl_attr_get_str(nla[IPSET_ATTR_TYPENAME]); + orig = ipset_data_get(data, IPSET_OPT_TYPENAME); + if (!STREQ(typename, orig)) + FAILURE("Broken TYPE kernel message: sent typename `%s' " + "does not match with received one `%s'!", + orig, typename); + + ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); + if (nla[IPSET_ATTR_REVISION_MIN]) + ATTR2DATA(session, nla, IPSET_ATTR_REVISION_MIN, cmd_attrs); + + return MNL_CB_STOP; +} + +static int +cmd_attr_cb(const struct nlattr *attr, void *data) +{ + return generic_data_attr_cb(attr, data, IPSET_ATTR_CMD_MAX, cmd_attrs); +} + +#if 0 +static int +mnl_attr_parse_dbg(const struct nlmsghdr *nlh, int offset, + mnl_attr_cb_t cb, void *data) +{ + int ret = MNL_CB_OK; + struct nlattr *attr = mnl_nlmsg_get_payload_offset(nlh, offset); + int len = nlh->nlmsg_len - MNL_NLMSG_HDRLEN - MNL_ALIGN(offset); + + while (mnl_attr_ok(attr, len)) { + D("attr: type %u, attrlen %u, len %u", + mnl_attr_get_type(attr), attr->nla_len, len); + if (cb && (ret = cb(attr, data)) <= MNL_CB_STOP) + return ret; + attr = mnl_attr_next(attr, &len); + } + return ret; +} +#endif + +static int +callback_data(const struct nlmsghdr *nlh, void *data) +{ + struct ipset_session *session = data; + struct nlattr *nla[IPSET_ATTR_CMD_MAX+1] = {}; + uint8_t proto, cmd; + int ret = MNL_CB_OK, nfmsglen = MNL_ALIGN(sizeof(struct nfgenmsg)); + + D("called, nlmsg_len %u", nlh->nlmsg_len); + cmd = ipset_get_nlmsg_type(nlh); + if (cmd == IPSET_CMD_LIST && session->cmd == IPSET_CMD_SAVE) + /* Kernel always send IPSET_CMD_LIST */ + cmd = IPSET_CMD_SAVE; + + if (cmd != session->cmd) + FAILURE("Protocol error, we sent command %s " + "and received %s[%u]", + cmd2name[session->cmd], + cmd < IPSET_MSG_MAX ? cmd2name[cmd] : "unknown", cmd); + + if (mnl_attr_parse(nlh, nfmsglen, cmd_attr_cb, nla) < MNL_CB_STOP) + FAILURE("Broken %s kernel message: " + "cannot validate and parse attributes", + cmd2name[cmd]); + + if (!nla[IPSET_ATTR_PROTOCOL]) + FAILURE("Sad, sad day: kernel message %s " + "does not carry the protocol version.", + cmd2name[cmd]); + + proto = mnl_attr_get_u8(nla[IPSET_ATTR_PROTOCOL]); + + /* Check protocol */ + if (cmd != IPSET_CMD_PROTOCOL && proto != IPSET_PROTOCOL) + FAILURE("Giving up: kernel protocol version %u " + "does not match our protocol version %u", + proto, IPSET_PROTOCOL); + + D("Message: %s", cmd2name[cmd]); + switch (cmd) { + case IPSET_CMD_LIST: + case IPSET_CMD_SAVE: + ret = callback_list(session, nla, cmd); + D("flag multi: %u", nlh->nlmsg_flags & NLM_F_MULTI); + if (ret >= MNL_CB_STOP && !(nlh->nlmsg_flags & NLM_F_MULTI)) + ret = print_set_done(session); + break; + case IPSET_CMD_PROTOCOL: + if (!session->version_checked) + ret = callback_version(session, nla); + break; + case IPSET_CMD_HEADER: + ret = callback_header(session, nla); + break; + case IPSET_CMD_TYPE: + ret = callback_type(session, nla); + break; + default: + FAILURE("Data message received when not expected at %s", + cmd2name[session->cmd]); + } + D("return code: %s", ret == MNL_CB_STOP ? "stop" : + ret == MNL_CB_OK ? "ok" : "error"); + return ret; +} + +static int +callback_done(const struct nlmsghdr *nlh UNUSED, void *data) +{ + struct ipset_session *session = data; + + D(" called"); + if (session->cmd == IPSET_CMD_LIST || session->cmd == IPSET_CMD_SAVE) + return print_set_done(session); + + FAILURE("Invalid message received in non LIST or SAVE state."); +} + +static int +decode_errmsg(struct ipset_session *session, const struct nlmsghdr *nlh) +{ + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + const struct nlmsghdr *msg = &err->msg; + struct nlattr *nla[IPSET_ATTR_CMD_MAX+1] = {}; + enum ipset_cmd cmd; + int nfmsglen = MNL_ALIGN(sizeof(struct nfgenmsg)); + + if (nlh->nlmsg_len < (uint32_t) MNL_ALIGN(sizeof(struct nlmsgerr)) + || nlh->nlmsg_len < MNL_ALIGN(sizeof(struct nlmsgerr)) + + msg->nlmsg_len) + FAILURE("Broken error report message received."); + + cmd = ipset_get_nlmsg_type(msg); + D("nlsmg_len: %u", msg->nlmsg_len); + if (cmd != session->cmd) + FAILURE("Protocol error, we sent command %s " + "and received error report for %s[%u]", + cmd2name[session->cmd], + cmd < IPSET_MSG_MAX ? cmd2name[cmd] : "unknown", cmd); + + if (mnl_attr_parse(msg, nfmsglen, cmd_attr_cb, nla) < MNL_CB_STOP) + FAILURE("Broken %s error report message: " + "cannot validate attributes", + cmd2name[cmd]); + + if (!nla[IPSET_ATTR_PROTOCOL]) + FAILURE("Broken %s error report message: " + "missing protocol attribute", + cmd2name[cmd]); + + if (nla[IPSET_ATTR_LINENO]) { + session->lineno = mnl_attr_get_u32(nla[IPSET_ATTR_LINENO]); + if (nla[IPSET_ATTR_LINENO]->nla_type & NLA_F_NET_BYTEORDER) + session->lineno = ntohl(session->lineno); + } + + return ipset_errcode(session, cmd, -err->error); +} + +static int +callback_error(const struct nlmsghdr *nlh, void *cbdata) +{ + struct ipset_session *session = cbdata; + struct ipset_data *data = session->data; + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + int ret = MNL_CB_ERROR; + + D(" called, cmd %s", cmd2name[session->cmd]); + if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) + FAILURE("Broken error message received."); + + if (err->error == 0) { + /* ACK */ + ret = MNL_CB_STOP; + + switch (session->cmd) { + case IPSET_CMD_CREATE: + /* Add successfully created set to the cache */ + ipset_cache_add(ipset_data_setname(data), + ipset_data_get(data, IPSET_OPT_TYPE)); + break; + case IPSET_CMD_DESTROY: + /* Delete destroyed sets from the cache */ + ipset_cache_del(ipset_data_setname(data)); + /* Fall through */ + case IPSET_CMD_FLUSH: + break; + case IPSET_CMD_RENAME: + ipset_cache_rename(ipset_data_setname(data), + ipset_data_get(data, IPSET_OPT_SETNAME2)); + break; + case IPSET_CMD_SWAP: + ipset_cache_swap(ipset_data_setname(data), + ipset_data_get(data, IPSET_OPT_SETNAME2)); + break; + case IPSET_CMD_TEST: + if (!(session->envopts & IPSET_ENV_QUIET)) { + ipset_print_elem(session->report, IPSET_ERRORBUFLEN, + session->data, IPSET_OPT_NONE, 0); + ipset_warn(session, " is in set %s.", + ipset_data_setname(data)); + } + /* Fall through */ + case IPSET_CMD_ADD: + case IPSET_CMD_DEL: + break; + case IPSET_CMD_LIST: + case IPSET_CMD_SAVE: + /* No set in kernel */ + print_set_done(session); + break; + default: + FAILURE("ACK message received to command %s[%u], which is not expected", + session->cmd < IPSET_MSG_MAX + ? cmd2name[session->cmd] : "unknown", + session->cmd); + } + return ret; + } + D("nlmsgerr error: %u", -err->error); + + /* Error messages */ + + /* Special case for IPSET_CMD_TEST */ + if (session->cmd == IPSET_CMD_TEST + && err->error == -IPSET_ERR_EXIST) { + if (!(session->envopts & IPSET_ENV_QUIET)) { + ipset_print_elem(session->report, IPSET_ERRORBUFLEN, + session->data, IPSET_OPT_NONE, 0); + ipset_warn(session, " is NOT in set %s.", + ipset_data_setname(data)); + } + return ret; + } + + decode_errmsg(session, nlh); + + return ret; +} + +static int +callback_noop(const struct nlmsghdr *nlh UNUSED, void *data UNUSED) +{ + return MNL_CB_OK; +} +/* + * Build and send messages + */ + +static inline struct nlattr * +nla_nest_start(struct nlmsghdr *nlh, int attr) +{ + struct nlattr *start = mnl_nlmsg_get_payload_tail(nlh); + + start->nla_type = attr | NLA_F_NESTED; + start->nla_len = MNL_ALIGN(sizeof(struct nlattr)); + + nlh->nlmsg_len += start->nla_len; + + return start; +} + +static inline void +nla_nest_end(struct nlmsghdr *nlh, struct nlattr *start) +{ + start->nla_len = (void *) mnl_nlmsg_get_payload_tail(nlh) + - (void *) start; +} + +static inline void +open_nested(struct ipset_session *session, struct nlmsghdr *nlh, int attr) +{ + session->nested[session->nestid++] = nla_nest_start(nlh, attr); +} + +static inline void +close_nested(struct ipset_session *session, struct nlmsghdr *nlh) +{ + nla_nest_end(nlh, session->nested[session->nestid-1]); + session->nested[--session->nestid] = NULL; +} + +static size_t +attr_len(const struct ipset_attr_policy *attr, uint8_t family, uint16_t *flags) +{ + switch (attr->type) { + case MNL_TYPE_BINARY: + if (attr->len) + return attr->len; + + *flags = NLA_F_NET_BYTEORDER; + return family == AF_INET ? sizeof(uint32_t) + : sizeof(struct in6_addr); + case MNL_TYPE_U32: + *flags = NLA_F_NET_BYTEORDER; + return sizeof(uint32_t); + case MNL_TYPE_U16: + *flags = NLA_F_NET_BYTEORDER; + return sizeof(uint16_t); + case MNL_TYPE_U8: + return sizeof(uint8_t); + default: + return attr->len; + } +} + +static int +rawdata2attr(struct nlmsghdr *nlh, + const void *d, int type, uint8_t family, + const struct ipset_attr_policy attrs[]) +{ + const struct ipset_attr_policy *attr; + int alen; + uint16_t flags = 0; + + + attr = &attrs[type]; + alen = attr_len(attr, family, &flags); + + switch (attr->type) { + case MNL_TYPE_U32: { + uint32_t value = htonl(*(uint32_t *)d); + + d = &value; + break; + } + case MNL_TYPE_U16: { + uint16_t value = htons(*(uint16_t *)d); + + d = &value; + break; + } + default: + break; + } + + mnl_attr_put(nlh, type | flags, alen, d); + + return 0; +} + +static int +data2attr(struct nlmsghdr *nlh, + struct ipset_data *data, int type, uint8_t family, + const struct ipset_attr_policy attrs[]) +{ + const struct ipset_attr_policy *attr = &attrs[type]; + + return rawdata2attr(nlh, ipset_data_get(data, attr->opt), + type, family, attrs); +} + +#define ADDATTR_PROTOCOL(nlh) \ + mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL) + +#define ADDATTR(nlh, data, type, family, attrs) \ + data2attr(nlh, data, type, family, attrs) + +#define ADDATTR_SETNAME(nlh, data) \ + data2attr(nlh, data, IPSET_ATTR_SETNAME, AF_INET, cmd_attrs) + +#define ADDATTR_IF(nlh, data, type, family, attrs) \ + if (ipset_data_test(data, attrs[type].opt)) { \ + D("addattr if: %u", attrs[type].opt); \ + data2attr(nlh, data, type, family, attrs); } + +#define ADDATTR_RAW(nlh, data, type, attrs) \ + rawdata2attr(nlh, data, type, AF_INET, attrs) + +static void +addattr_create(struct nlmsghdr *nlh, struct ipset_data *data, uint8_t family) +{ + int i; + + for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_CREATE_MAX; i++) + ADDATTR_IF(nlh, data, i, family, create_attrs); +} + +static void +addattr_adt(struct nlmsghdr *nlh, struct ipset_data *data, uint8_t family) +{ + int i; + + for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) + ADDATTR_IF(nlh, data, i, family, adt_attrs); +} + +static inline bool +buffer_full(struct ipset_session *session) +{ + struct nlmsghdr *nlh; + const struct ipset_type *type; + int sizeid; + + nlh = (struct nlmsghdr *) session->buffer; + type = ipset_data_get(session->data, IPSET_OPT_TYPE); + sizeid = !!(ipset_data_family(session->data) != AF_INET); + + /* Current size + ADD/DEL max size + nlsmgerr must fit into buffer */ + return session->bufsize < nlh->nlmsg_len + + type->maxsize[sizeid] + + MNL_ALIGN(sizeof(struct nlmsgerr)); +} + +#define PRIVATE_MSG_BUFLEN 256 + +static int +build_send_private_msg(struct ipset_session *session, enum ipset_cmd cmd) +{ + char buffer[PRIVATE_MSG_BUFLEN] __attribute__ ((aligned)); + struct nlmsghdr *nlh = (struct nlmsghdr *) buffer; + struct ipset_data *data = session->data; + int len = PRIVATE_MSG_BUFLEN, ret; + enum ipset_cmd saved = session->cmd; + + /* Initialize header */ + session->transport->fill_hdr(session->handle, cmd, buffer, len, 0); + + ADDATTR_PROTOCOL(nlh); + + switch (cmd) { + case IPSET_CMD_PROTOCOL: + break; + case IPSET_CMD_HEADER: + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid internal HEADER command: " + "missing setname"); + ADDATTR_SETNAME(nlh, data); + break; + case IPSET_CMD_TYPE: + if (!ipset_data_test(data, IPSET_OPT_TYPENAME)) + return ipset_err(session, + "Invalid internal TYPE command: " + "missing settype"); + ADDATTR(nlh, data, IPSET_ATTR_TYPENAME, AF_INET, cmd_attrs); + if (ipset_data_test(data, IPSET_OPT_FAMILY)) + ADDATTR(nlh, data, IPSET_ATTR_FAMILY, AF_INET, cmd_attrs); + else + /* bitmap:port and list:set types */ + mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, AF_UNSPEC); + break; + default: + return ipset_err(session, "Internal error: " + "unknown private command %u", cmd); + } + + /* Backup, then restore real command */ + session->cmd = cmd; + ret = session->transport->query(session->handle, buffer, len); + session->cmd = saved; + + return ret; +} + +static inline bool +may_aggregate_ad(struct ipset_session *session, struct ipset_data *data, + enum ipset_cmd cmd) +{ + return (cmd == IPSET_CMD_ADD || cmd == IPSET_CMD_DEL) + && cmd == session->cmd + && STREQ(ipset_data_setname(data), session->saved_setname); +} + +static int +build_msg(struct ipset_session *session, bool aggregate) +{ + struct nlmsghdr *nlh = (struct nlmsghdr *) session->buffer; + struct ipset_data *data = session->data; + + /* Public commands */ + D("cmd %s, nlmsg_len: %u", cmd2name[session->cmd], nlh->nlmsg_len); + if (nlh->nlmsg_len == 0) { + /* Initialize header */ + aggregate = false; + session->transport->fill_hdr(session->handle, + session->cmd, + session->buffer, + session->bufsize, + session->envopts); + ADDATTR_PROTOCOL(nlh); + } + D("Protocol added, aggregate %s", aggregate ? "yes" : "no"); + switch (session->cmd) { + case IPSET_CMD_CREATE: { + const struct ipset_type *type; + + /* Sanity checkings */ + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid create command: missing setname"); + if (!ipset_data_test(data, IPSET_OPT_TYPE)) + return ipset_err(session, + "Invalid create command: missing settype"); + + type = ipset_data_get(data, IPSET_OPT_TYPE); + /* Core attributes: + * setname, typename, revision, family, flags (optional) */ + ADDATTR_SETNAME(nlh, data); + 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)); + ADDATTR(nlh, data, IPSET_ATTR_FAMILY, AF_INET, cmd_attrs); + + /* Type-specific create attributes */ + D("call open_nested"); + open_nested(session, nlh, IPSET_ATTR_DATA); + addattr_create(nlh, data, type->family); + D("call close_nested"); + close_nested(session, nlh); + break; + } + case IPSET_CMD_DESTROY: + case IPSET_CMD_FLUSH: + case IPSET_CMD_LIST: + case IPSET_CMD_SAVE: + if (ipset_data_test(data, IPSET_SETNAME)) + ADDATTR_SETNAME(nlh, data); + break; + case IPSET_CMD_RENAME: + case IPSET_CMD_SWAP: + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid %s command: missing from-setname", + session->cmd == IPSET_CMD_SWAP ? "swap" : "rename"); + if (!ipset_data_test(data, IPSET_OPT_SETNAME2)) + return ipset_err(session, + "Invalid %s command: missing to-setname", + session->cmd == IPSET_CMD_SWAP ? "swap" : "rename"); + ADDATTR_SETNAME(nlh, data); + ADDATTR_RAW(nlh, ipset_data_get(data, IPSET_OPT_SETNAME2), + IPSET_ATTR_SETNAME2, cmd_attrs); + break; + case IPSET_CMD_ADD: + case IPSET_CMD_DEL: { + const struct ipset_type *type; + + if (!aggregate) { + /* Setname, type not checked/added yet */ + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid %s command: missing setname", + session->cmd == IPSET_CMD_ADD ? "add" : "del"); + + if (!ipset_data_test(data, IPSET_OPT_TYPE)) + return ipset_err(session, + "Invalid %s command: missing settype", + session->cmd == IPSET_CMD_ADD ? "add" : "del"); + + /* Core options: setname */ + ADDATTR_SETNAME(nlh, data); + if (session->lineno != 0) { + /* Restore mode */ + ADDATTR_RAW(nlh, &session->lineno, + IPSET_ATTR_LINENO, cmd_attrs); + open_nested(session, nlh, IPSET_ATTR_ADT); + } + } + type = ipset_data_get(data, IPSET_OPT_TYPE); + open_nested(session, nlh, IPSET_ATTR_DATA); + addattr_adt(nlh, data, type->family); + ADDATTR_RAW(nlh, &session->lineno, + IPSET_ATTR_LINENO, cmd_attrs); + close_nested(session, nlh); + break; + } + case IPSET_CMD_TEST: { + const struct ipset_type *type; + /* Return codes are not aggregated, so tests cannot be either */ + + /* Setname, type not checked/added yet */ + + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid test command: missing setname"); + + if (!ipset_data_test(data, IPSET_OPT_TYPE)) + return ipset_err(session, + "Invalid test command: missing settype"); + + type = ipset_data_get(data, IPSET_OPT_TYPE); + ADDATTR_SETNAME(nlh, data); + open_nested(session, nlh, IPSET_ATTR_DATA); + addattr_adt(nlh, data, type->family); + close_nested(session, nlh); + break; + } + default: + return ipset_err(session, "Internal error: unknown command %u", + session->cmd); + } + return 0; +} + +/** + * ipset_commit - commit buffered commands + * @session: session structure + * + * Commit buffered commands, if there are any. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_commit(struct ipset_session *session) +{ + struct nlmsghdr *nlh; + int ret = 0, i; + + assert(session); + + nlh = (struct nlmsghdr *) session->buffer; + D("send buffer: len %u, cmd %s", nlh->nlmsg_len, cmd2name[session->cmd]); + if (nlh->nlmsg_len == 0) + /* Nothing to do */ + return 0; + + /* Close nested data blocks */ + for (i = session->nestid - 1; i >= 0; i--) + close_nested(session, nlh); + + /* Send buffer */ + ret = session->transport->query(session->handle, + session->buffer, + session->bufsize); + + /* Reset data block and nested state */ + for (i = session->nestid - 1; i >= 0; i--) + session->nested[i] = NULL; + session->nestid = 0; + nlh->nlmsg_len = 0; + + D("ret: %d", ret); + + if (ret < 0) { + if (session->report[0] != '\0') + return -1; + else + return ipset_err(session, + "Internal protocol error"); + } + return 0; +} + +static mnl_cb_t cb_ctl[] = { + [NLMSG_NOOP] = callback_noop, + [NLMSG_ERROR] = callback_error, + [NLMSG_DONE] = callback_done, + [NLMSG_OVERRUN] = callback_noop, + [NLMSG_MIN_TYPE] = callback_data, +}; + +static inline struct ipset_handle * +init_transport(struct ipset_session *session) +{ + session->handle = session->transport->init(cb_ctl, session); + + return session->handle; +} + +/** + * ipset_cmd - execute a command + * @session: session structure + * @cmd: command to execute + * @lineno: command line number in restore mode + * + * Execute - or prepare/buffer in restore mode - a command. + * It is the caller responsibility that the data field be filled out + * with all required parameters for a successful execution. + * The data field is cleared after this function call for the public + * commands. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cmd(struct ipset_session *session, enum ipset_cmd cmd, uint32_t lineno) +{ + struct ipset_data *data; + bool aggregate = false; + int ret = -1; + + assert(session); + + if (cmd <= IPSET_CMD_NONE || cmd >= IPSET_MSG_MAX) + return 0; + + /* Initialize transport method if not done yet */ + if (session->handle == NULL && init_transport(session) == NULL) + return ipset_err(session, + "Cannot open session to kernel."); + + data = session->data; + + /* Check protocol version once */ + if (!session->version_checked) { + if (build_send_private_msg(session, IPSET_CMD_PROTOCOL) < 0) + return -1; + } + + /* Private commands */ + if (cmd == IPSET_CMD_TYPE || cmd == IPSET_CMD_HEADER) + return build_send_private_msg(session, cmd); + + /* Check buffer and aggregatable commands */ + if (session->lineno != 0) { + if (may_aggregate_ad(session, data, cmd)) + aggregate = true; + if (!aggregate || buffer_full(session)) { + if (ipset_commit(session) < 0) + goto cleanup; + } + } + + /* Real command: update lineno too */ + 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 (session->mode == IPSET_LIST_NONE) + session->mode = IPSET_LIST_SAVE; + } + + D("next: build_msg"); + /* Build new message or append buffered commands */ + if (build_msg(session, aggregate) < 0) + goto cleanup; + D("past: build_msg"); + + /* Save setname for the next possible aggregated restore line */ + if (session->lineno != 0 + && (cmd == IPSET_CMD_ADD || cmd == IPSET_CMD_DEL)) { + strcpy(session->saved_setname, ipset_data_setname(data)); + ret = 0; + /* Don't commit: we may aggregate next command */ + } else { + D("call commit"); + ret = ipset_commit(session); + } +cleanup: + D("reset data"); + ipset_data_reset(data); + return ret; +} + +/** + * ipset_session_init - initialize an ipset session + * + * Initialize an ipset session by allocating a session structure + * and filling out with the initialization data. + * + * Returns the created session sctructure on success or NULL. + */ +struct ipset_session * +ipset_session_init(ipset_outfn outfn) +{ + struct ipset_session *session; + size_t bufsize = getpagesize(); + + /* Create session object */ + session = calloc(1, sizeof(struct ipset_session) + bufsize); + if (session == NULL) + return NULL; + session->bufsize = bufsize; + + /* The single transport method yet */ + session->transport = &ipset_mnl_transport; + + /* Output function */ + session->outfn = outfn; + + /* Initialize data structures */ + session->data = ipset_data_init(); + if (session->data == NULL) + goto free_session; + + ipset_types_init(); + return session; + +free_session: + free(session); + return NULL; +} + +/** + * ipset_session_fini - destroy an ipset session + * @session: session structure + * + * Destroy an ipset session: release the created structures. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_session_fini(struct ipset_session *session) +{ + assert(session); + + if (session->handle) + session->transport->fini(session->handle); + if (session->data) + ipset_data_fini(session->data); + + ipset_types_fini(); + free(session); + return 0; +} diff --git a/lib/types.c b/lib/types.c new file mode 100644 index 0000000..a6476ea --- /dev/null +++ b/lib/types.c @@ -0,0 +1,566 @@ +/* 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 /* assert */ +#include /* errno */ +#include /* ETH_ALEN */ +#include /* struct in6_addr */ +#include /* AF_ */ +#include /* NULL */ +#include /* malloc, free */ +#include /* FIXME: debug */ + +#include /* ipset_data_* */ +#include /* ipset_cmd */ +#include /* STREQ */ +#include /* prototypes */ + +/* Userspace cache of sets which exists in the kernel */ + +struct ipset { + char name[IPSET_MAXNAMELEN]; /* set name */ + const struct ipset_type *type; /* set type */ + struct ipset *next; +}; + +static struct ipset_type *typelist = NULL; /* registered set types */ +static struct ipset *setlist = NULL; /* cached sets */ + +/** + * ipset_cache_add - add a set to the cache + * @name: set name + * @type: set type structure + * + * Add the named set to the internal cache with the specified + * set type. The set name must be unique. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cache_add(const char *name, const struct ipset_type *type) +{ + struct ipset *s, *n; + + assert(name); + assert(type); + + n = malloc(sizeof(*n)); + if (n == NULL) + return -ENOMEM; + + ipset_strncpy(n->name, name, IPSET_MAXNAMELEN); + n->type = type; + n->next = NULL; + + if (setlist == NULL) { + setlist = n; + return 0; + } + for (s = setlist; s->next == NULL; s = s->next) { + if (STREQ(name, s->name)) { + free(n); + return -EEXIST; + } + } + s->next = n; + + return 0; +} + +/** + * ipset_cache_del - delete set from the cache + * @name: set name + * + * Delete the named set from the internal cache. If NULL is + * specified as setname, the whole cache is emptied. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cache_del(const char *name) +{ + struct ipset *s, *match = NULL, *prev = NULL; + + if (!name) { + for (s = setlist; s != NULL; ) { + prev = s; + s = s->next; + free(prev); + } + setlist = NULL; + return 0; + } + for (s = setlist; s != NULL && match == NULL; s = s->next) { + if (STREQ(s->name, name)) { + match = s; + if (prev == NULL) + setlist = match->next; + else + prev->next = match->next; + } + prev = s; + } + if (match == NULL) + return -EEXIST; + + free(match); + return 0; +} + +/** + * ipset_cache_rename - rename a set in the cache + * @from: the set to rename + * @to: the new name of the set + * + * Rename the given set in the cache. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cache_rename(const char *from, const char *to) +{ + struct ipset *s; + + assert(from); + assert(to); + + for (s = setlist; s != NULL; s = s->next) { + if (STREQ(s->name, from)) { + ipset_strncpy(s->name, to, IPSET_MAXNAMELEN); + return 0; + } + } + return -EEXIST; +} + +/** + * ipset_cache_swap - swap two sets in the cache + * @from: the first set + * @to: the second set + * + * Swap two existing sets in the cache. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cache_swap(const char *from, const char *to) +{ + struct ipset *s, *a = NULL, *b = NULL; + + assert(from); + assert(to); + + for (s = setlist; s != NULL && (a == NULL || b == NULL); s = s->next) { + if (a == NULL && STREQ(s->name, from)) + a = s; + if (b == NULL && STREQ(s->name, to)) + b = s; + } + if (a != NULL && b != NULL) { + ipset_strncpy(a->name, to, IPSET_MAXNAMELEN); + ipset_strncpy(b->name, from, IPSET_MAXNAMELEN); + return 0; + } + + return -EEXIST; +} + +#define MATCH_FAMILY(type, f) \ + (f == AF_UNSPEC || type->family == f || type->family == AF_INET46) + +static inline const struct ipset_type * +create_type_get(struct ipset_session *session) +{ + struct ipset_type *t, *match = NULL; + struct ipset_data *data; + const char *typename; + uint8_t family, tmin = 0, tmax = 0; + const uint8_t *kmin, *kmax; + int ret; + + data = ipset_session_data(session); + assert(data); + typename = ipset_data_get(data, IPSET_OPT_TYPENAME); + assert(typename); + family = ipset_data_family(data); + + /* Check registered types in userspace */ + for (t = typelist; t != NULL; t = t->next) { + /* Skip revisions which are unsupported by the kernel */ + if (t->kernel_check == IPSET_KERNEL_MISMATCH) + continue; + if ((STREQ(typename, t->name) || STREQ(typename, t->alias)) + && MATCH_FAMILY(t, family)) { + if (match == NULL) { + match = t; + tmax = t->revision; + } else if (t->family == match->family) + tmin = t->revision; + } + } + if (!match) + return ipset_errptr(session, + "Syntax error: unknown settype %s", + typename); + + /* Family is unspecified yet: set from matching set type */ + if (family == AF_UNSPEC && match->family != AF_UNSPEC) { + family = match->family == AF_INET46 ? AF_INET : match->family; + ipset_data_set(data, IPSET_OPT_FAMILY, &family); + } + + if (match->kernel_check == IPSET_KERNEL_OK) + goto found; + + /* Check kernel */ + ret = ipset_cmd(session, IPSET_CMD_TYPE, 0); + if (ret != 0) + return NULL; + + kmax = ipset_data_get(data, IPSET_OPT_REVISION); + if (ipset_data_test(data, IPSET_OPT_REVISION_MIN)) + kmin = ipset_data_get(data, IPSET_OPT_REVISION_MIN); + else + kmin = kmax; + if (MAX(tmin, *kmin) > MIN(tmax, *kmax)) { + if (*kmin > tmax) + return ipset_errptr(session, + "Kernel supports %s type with family %s " + "in minimal revision %u while ipset library " + "in maximal revision %u. " + "You need to upgrade your ipset library.", + typename, + family == AF_INET ? "INET" : + family == AF_INET6 ? "INET6" : "UNSPEC", + *kmin, tmax); + else + return ipset_errptr(session, + "Kernel supports %s type with family %s " + "in maximal revision %u while ipset library " + "in minimal revision %u. " + "You need to upgrade your kernel.", + typename, + family == AF_INET ? "INET" : + family == AF_INET6 ? "INET6" : "UNSPEC", + *kmax, tmin); + } + + match->kernel_check = IPSET_KERNEL_OK; +found: + ipset_data_set(data, IPSET_OPT_TYPE, match); + + return match; +} + +#define set_family_and_type(data, match, family) do { \ + if (family == AF_UNSPEC && match->family != AF_UNSPEC) { \ + family = match->family == AF_INET46 ? AF_INET : match->family;\ + ipset_data_set(data, IPSET_OPT_FAMILY, &family);\ + } \ + ipset_data_set(data, IPSET_OPT_TYPE, match); \ +} while (0) + + +static inline const struct ipset_type * +adt_type_get(struct ipset_session *session) +{ + struct ipset_data *data; + struct ipset *s; + struct ipset_type *t; + const struct ipset_type *match; + const char *setname, *typename; + const uint8_t *revision; + uint8_t family; + int ret; + + data = ipset_session_data(session); + assert(data); + setname = ipset_data_setname(data); + assert(setname); + + /* Check existing sets in cache */ + for (s = setlist; s != NULL; s = s->next) { + if (STREQ(setname, s->name)) { + match = s->type; + goto found; + } + } + + /* Check kernel */ + ret = ipset_cmd(session, IPSET_CMD_HEADER, 0); + if (ret != 0) + return NULL; + + typename = ipset_data_get(data, IPSET_OPT_TYPENAME); + revision = ipset_data_get(data, IPSET_OPT_REVISION); + family = ipset_data_family(data); + + /* Check registered types */ + for (t = typelist, match = NULL; + t != NULL && match == NULL; t = t->next) { + if (t->kernel_check == IPSET_KERNEL_MISMATCH) + continue; + if (STREQ(typename, t->name) + && MATCH_FAMILY(t, family) + && *revision == t->revision) { + t->kernel_check = IPSET_KERNEL_OK; + match = t; + } + } + if (!match) + return ipset_errptr(session, + "Kernel-library incompatibility: " + "set %s in kernel has got settype %s " + "with family %s and revision %u while " + "ipset library does not support the " + "settype with that family and revision.", + setname, typename, + family == AF_INET ? "inet" : + family == AF_INET6 ? "inet6" : "unspec", + *revision); + +found: + set_family_and_type(data, match, family); + + return match; +} + +/** + * ipset_type_get - get a set type from the kernel + * @session: session structure + * @cmd: the command which needs the set type + * + * Build up and send a private message to the kernel in order to + * get the set type. When creating the set, we send the typename + * and family and get the supported revisions of the given set type. + * When adding/deleting/testing an entry, we send the setname and + * receive the typename, family and revision. + * + * Returns the set type for success and NULL for failure. + */ +const struct ipset_type * +ipset_type_get(struct ipset_session *session, enum ipset_cmd cmd) +{ + assert(session); + + switch (cmd) { + case IPSET_CMD_CREATE: + return create_type_get(session); + case IPSET_CMD_ADD: + case IPSET_CMD_DEL: + case IPSET_CMD_TEST: + return adt_type_get(session); + default: + break; + } + + assert(cmd == IPSET_CMD_NONE); + return NULL; +} + +/** + * ipset_type_check - check the set type received from kernel + * @session: session structure + * + * Check the set type received from the kernel (typename, revision, + * family) against the userspace types looking for a matching type. + * + * Returns the set type for success and NULL for failure. + */ +const struct ipset_type * +ipset_type_check(struct ipset_session *session) +{ + struct ipset_type *t, *match = NULL; + struct ipset_data *data; + const char *typename; + uint8_t family, revision; + + assert(session); + data = ipset_session_data(session); + assert(data); + + typename = ipset_data_get(data, IPSET_OPT_TYPENAME); + family = ipset_data_family(data); + revision = *(uint8_t *) ipset_data_get(data, IPSET_OPT_REVISION); + + /* Check registered types */ + for (t = typelist; t != NULL && match == NULL; t = t->next) { + if (t->kernel_check == IPSET_KERNEL_MISMATCH) + continue; + if ((STREQ(typename, t->name) || STREQ(typename, t->alias)) + && MATCH_FAMILY(t, family) && t->revision == revision) + match = t; + } + if (!match) + return ipset_errptr(session, + "Kernel and userspace incompatible: " + "settype %s with revision %u not supported ", + "by userspace.", typename, revision); + + set_family_and_type(data, match, family); + + return match; +} + +static void +type_max_size(struct ipset_type *type, uint8_t family) +{ + int sizeid; + enum ipset_opt opt; + size_t max = 0; + + sizeid = family == AF_INET ? IPSET_MAXSIZE_INET : IPSET_MAXSIZE_INET6; + for (opt = IPSET_OPT_NONE + 1; opt < IPSET_OPT_MAX; opt++) { + if (!(IPSET_FLAG(opt) & IPSET_ADT_FLAGS)) + continue; + if (!(IPSET_FLAG(opt) & type->full[IPSET_ADD])) + continue; + max += ipset_data_sizeof(opt, family); + } + type->maxsize[sizeid] = max; +} + +/** + * ipset_type_add - add (register) a userspace set type + * @type: set type structure + * + * Add the given set type to the type list. The types + * are added sorted, in descending revision number. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_type_add(struct ipset_type *type) +{ + struct ipset_type *t, *prev; + + assert(type); + + /* Fill out max sizes */ + switch (type->family) { + case AF_UNSPEC: + case AF_INET: + type_max_size(type, AF_INET); + break; + case AF_INET6: + type_max_size(type, AF_INET6); + break; + case AF_INET46: + type_max_size(type, AF_INET); + type_max_size(type, AF_INET6); + break; + default: + return -1; + } + + /* Add to the list: higher revision numbers first */ + for (t = typelist, prev = NULL; t != NULL; t = t->next) { + if (STREQ(t->name, type->name)) { + if (t->revision == type->revision) { + errno = EEXIST; + return -1; + } else if (t->revision < type->revision) { + type->next = t; + if (prev) + prev->next = type; + else + typelist = type; + return 0; + } + } + if (t->next != NULL && STREQ(t->next->name, type->name)) { + if (t->next->revision == type->revision) { + errno = EEXIST; + return -1; + } else if (t->next->revision < type->revision) { + type->next = t->next; + t->next = type; + return 0; + } + } + prev = t; + } + type->next = typelist; + typelist = type; + return 0; +} + +/** + * ipset_typename_resolve - resolve typename alias + * @str: typename or alias + * + * Check the typenames (and aliases) and return the + * preferred name of the set type. + * + * Returns the name of the matching set type or NULL. + */ +const char * +ipset_typename_resolve(const char *str) +{ + const struct ipset_type *t; + + for (t = typelist; t != NULL; t = t->next) + if (STREQ(str, t->name) || STREQ(str, t->alias)) + return t->name; + return NULL; +} + +/** + * ipset_types - return the list of the set types + * + * The types can be unchecked with respect of the running kernel. + * Only useful for type specific help. + * + * Returns the list of the set types. + */ +const struct ipset_type * +ipset_types(void) +{ + return typelist; +} + +/** + * ipset_types_init - initialize known set types + * + * Initialize the type list with the known, supported set types. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_types_init(void) +{ + if (typelist != NULL) + return 0; + + ipset_type_add(&ipset_bitmap_ip0); + ipset_type_add(&ipset_bitmap_ipmac0); + ipset_type_add(&ipset_bitmap_port0); + ipset_type_add(&ipset_hash_ip0); + ipset_type_add(&ipset_hash_net0); + ipset_type_add(&ipset_hash_ipport0); + ipset_type_add(&ipset_hash_ipportip0); + ipset_type_add(&ipset_hash_ipportnet0); + ipset_type_add(&ipset_tree_ip0); + ipset_type_add(&ipset_list_set0); + return 0; +} + +/** + * ipset_types_fini - release initialized known set types + * + * Release initialized known set types and remove the set cache. + */ +void +ipset_types_fini(void) +{ + struct ipset *set; + + while (setlist) { + set = setlist; + setlist = setlist->next; + free(set); + } +} diff --git a/lib/utils.c b/lib/utils.c new file mode 100644 index 0000000..bddeb87 --- /dev/null +++ b/lib/utils.c @@ -0,0 +1,103 @@ +/* Copyright 2007-20010 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 /* assert */ +#include /* bool */ +#include /* malloc, free */ +#include /* memset, str* */ + +#include /* ipset_err */ +#include /* prototypes */ + +/** + * ipset_strchr - locate character(s) in string + * @str: string to locate the character(s) in + * @sep: string of characters to locate + * + * Return a pointer to the first occurence of any of the + * characters to be located in the string. NULL is returned + * if no character is found. + */ +char * +ipset_strchr(const char *str, const char *sep) +{ + char *match; + + assert(str); + assert(sep); + + for (; *sep != '\0'; sep++) + if ((match = strchr(str, (int)sep[0])) != NULL) + return match; + + return NULL; +} + +/** + * ipset_name_match - match a string against an array of strings + * @arg: string + * @name: array of strings, last one is a NULL pointer + * + * Return true if arg matches any of the strings in the array. + */ +bool +ipset_name_match(const char *arg, const char * const name[]) +{ + int i = 0; + + assert(arg); + assert(name); + + while (name[i]) { + if (STREQ(arg, name[i])) + return true; + i++; + } + + return false; +} + +/** + * ipset_shift_argv - shift off an argument + * @arc: argument count + * @argv: array of argument strings + * @from: from where shift off an argument + * + * Shift off the argument at "from" from the array of + * arguments argv of size argc. + */ +void +ipset_shift_argv(int *argc, char *argv[], int from) +{ + int i; + + assert(*argc >= from + 1); + + for (i = from + 1; i <= *argc; i++) { + argv[i-1] = argv[i]; + } + (*argc)--; + return; +} + +/** + * ipset_strncpy - copy the string from src to dst + * @dst: the target string buffer + * @src: the source string buffer + * @len: the length of bytes to copy, including the terminating null byte. + * + * Copy the string from src to destination, but at most len bytes are + * copied. The target is unconditionally terminated by the null byte. + */ +void +ipset_strncpy(char *dst, const char *src, size_t len) +{ + assert(dst); + assert(src); + + strncpy(dst, src, len); + dst[len - 1] = '\0'; +} -- cgit v1.2.3