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/session.c | 1782 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1782 insertions(+) create mode 100644 lib/session.c (limited to 'lib/session.c') 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; +} -- cgit v1.2.3