| /* 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.h> /* assert */ |
| #include <endian.h> /* htobe64 */ |
| #include <errno.h> /* errno */ |
| #include <setjmp.h> /* setjmp, longjmp */ |
| #include <stdio.h> /* snprintf */ |
| #include <stdarg.h> /* va_* */ |
| #include <stdlib.h> /* free */ |
| #include <string.h> /* str* */ |
| #include <unistd.h> /* getpagesize */ |
| #include <net/ethernet.h> /* ETH_ALEN */ |
| #include <net/if.h> /* IFNAMSIZ */ |
| |
| #include <libipset/compat.h> /* be64toh() */ |
| #include <libipset/debug.h> /* D() */ |
| #include <libipset/data.h> /* IPSET_OPT_* */ |
| #include <libipset/errcode.h> /* ipset_errcode */ |
| #include <libipset/print.h> /* ipset_print_* */ |
| #include <libipset/types.h> /* struct ipset_type */ |
| #include <libipset/transport.h> /* transport */ |
| #include <libipset/mnl.h> /* default backend */ |
| #include <libipset/utils.h> /* STREQ */ |
| #include <libipset/ui.h> /* IPSET_ENV_* */ |
| #include <libipset/session.h> /* 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 */ |
| uint32_t printed_set; /* Printed sets so far */ |
| char saved_setname[IPSET_MAXNAMELEN]; /* Saved setname */ |
| const struct ipset_type *saved_type; /* Saved type */ |
| 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; |
| void *buffer; |
| }; |
| |
| /* |
| * 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; |
| } |
| |
| /** |
| * ipset_saved_type - return pointer to the saved type |
| * @session: session structure |
| * |
| * Returns the pointer to the saved type from the last ipset_cmd |
| * It is required to decode type-specific error codes in restore mode. |
| */ |
| const struct ipset_type * |
| ipset_saved_type(const struct ipset_session *session) |
| { |
| assert(session); |
| return session->saved_type; |
| } |
| |
| /** |
| * ipset_session_lineno - set session lineno |
| * @session: session structure |
| * |
| * Set session lineno to report parser errors correctly. |
| */ |
| void |
| ipset_session_lineno(struct ipset_session *session, uint32_t lineno) |
| { |
| assert(session); |
| session->lineno = lineno; |
| } |
| |
| /* |
| * 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: |
| case IPSET_ENV_LIST_SETNAME: |
| case IPSET_ENV_LIST_HEADER: |
| 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 (strlen(session->report) < IPSET_ERRORBUFLEN - 1) |
| strcat(session->report, "\n"); |
| |
| 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 */ |
| static 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_FLAGS] = { |
| .type = MNL_TYPE_U32, |
| .opt = IPSET_OPT_FLAGS, |
| }, |
| [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, |
| }, |
| }; |
| |
| static const struct ipset_attr_policy create_attrs[] = { |
| [IPSET_ATTR_IP] = { |
| .type = MNL_TYPE_NESTED, |
| .opt = IPSET_OPT_IP, |
| }, |
| [IPSET_ATTR_IP_TO] = { |
| .type = MNL_TYPE_NESTED, |
| .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_PROTO] = { |
| .type = MNL_TYPE_U8, |
| .opt = IPSET_OPT_PROTO, |
| }, |
| [IPSET_ATTR_CADT_FLAGS] = { |
| .type = MNL_TYPE_U32, |
| .opt = IPSET_OPT_CADT_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_MARKMASK] = { |
| .type = MNL_TYPE_U32, |
| .opt = IPSET_OPT_MARKMASK, |
| }, |
| [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, |
| }, |
| }; |
| |
| static const struct ipset_attr_policy adt_attrs[] = { |
| [IPSET_ATTR_IP] = { |
| .type = MNL_TYPE_NESTED, |
| .opt = IPSET_OPT_IP, |
| }, |
| [IPSET_ATTR_IP_TO] = { |
| .type = MNL_TYPE_NESTED, |
| .opt = IPSET_OPT_IP_TO, |
| }, |
| [IPSET_ATTR_CIDR] = { |
| .type = MNL_TYPE_U8, |
| .opt = IPSET_OPT_CIDR, |
| }, |
| [IPSET_ATTR_MARK] = { |
| .type = MNL_TYPE_U32, |
| .opt = IPSET_OPT_MARK, |
| }, |
| [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_PROTO] = { |
| .type = MNL_TYPE_U8, |
| .opt = IPSET_OPT_PROTO, |
| }, |
| [IPSET_ATTR_TIMEOUT] = { |
| .type = MNL_TYPE_U32, |
| .opt = IPSET_OPT_TIMEOUT, |
| }, |
| [IPSET_ATTR_CADT_FLAGS] = { |
| .type = MNL_TYPE_U32, |
| .opt = IPSET_OPT_CADT_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_NESTED, |
| .opt = IPSET_OPT_IP2, |
| }, |
| [IPSET_ATTR_CIDR2] = { |
| .type = MNL_TYPE_U8, |
| .opt = IPSET_OPT_CIDR2, |
| }, |
| [IPSET_ATTR_IP2_TO] = { |
| .type = MNL_TYPE_NESTED, |
| .opt = IPSET_OPT_IP2_TO, |
| }, |
| [IPSET_ATTR_IFACE] = { |
| .type = MNL_TYPE_NUL_STRING, |
| .opt = IPSET_OPT_IFACE, |
| .len = IFNAMSIZ, |
| }, |
| [IPSET_ATTR_PACKETS] = { |
| .type = MNL_TYPE_U64, |
| .opt = IPSET_OPT_PACKETS, |
| }, |
| [IPSET_ATTR_BYTES] = { |
| .type = MNL_TYPE_U64, |
| .opt = IPSET_OPT_BYTES, |
| }, |
| [IPSET_ATTR_COMMENT] = { |
| .type = MNL_TYPE_NUL_STRING, |
| .opt = IPSET_OPT_ADT_COMMENT, |
| .len = IPSET_MAX_COMMENT_SIZE + 1, |
| }, |
| [IPSET_ATTR_SKBMARK] = { |
| .type = MNL_TYPE_U64, |
| .opt = IPSET_OPT_SKBMARK, |
| }, |
| [IPSET_ATTR_SKBPRIO] = { |
| .type = MNL_TYPE_U32, |
| .opt = IPSET_OPT_SKBPRIO, |
| }, |
| [IPSET_ATTR_SKBQUEUE] = { |
| .type = MNL_TYPE_U16, |
| .opt = IPSET_OPT_SKBQUEUE, |
| }, |
| }; |
| |
| static const struct ipset_attr_policy ipaddr_attrs[] = { |
| [IPSET_ATTR_IPADDR_IPV4] = { |
| .type = MNL_TYPE_U32, |
| }, |
| [IPSET_ATTR_IPADDR_IPV6] = { |
| .type = MNL_TYPE_BINARY, |
| .len = sizeof(union nf_inet_addr), |
| }, |
| }; |
| |
| #ifdef IPSET_DEBUG |
| static int debug = 1; |
| #endif |
| |
| 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 = data; |
| int type = mnl_attr_get_type(attr); |
| |
| IF_D(debug, "attr type: %u, len %u", type, attr->nla_len); |
| if (mnl_attr_type_valid(attr, attr_max) < 0) { |
| IF_D(debug, "attr type: %u INVALID", type); |
| return MNL_CB_ERROR; |
| } |
| if (mnl_attr_validate(attr, policy[type].type) < 0) { |
| IF_D(debug, "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) > policy[type].len) |
| return MNL_CB_ERROR; |
| tb[type] = attr; |
| return MNL_CB_OK; |
| } |
| |
| static int |
| create_attr_cb(const struct nlattr *attr, void *data) |
| { |
| return generic_data_attr_cb(attr, data, |
| IPSET_ATTR_CREATE_MAX, create_attrs); |
| } |
| |
| static int |
| adt_attr_cb(const struct nlattr *attr, void *data) |
| { |
| return generic_data_attr_cb(attr, data, |
| IPSET_ATTR_ADT_MAX, adt_attrs); |
| } |
| |
| static int |
| ipaddr_attr_cb(const struct nlattr *attr, void *data) |
| { |
| return generic_data_attr_cb(attr, data, |
| IPSET_ATTR_IPADDR_MAX, ipaddr_attrs); |
| } |
| |
| #define FAILURE(format, args...) \ |
| { ipset_err(session, format , ## args); return MNL_CB_ERROR; } |
| |
| 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; |
| uint64_t v64; |
| uint32_t v32; |
| uint16_t v16; |
| int ret; |
| |
| attr = &attrs[type]; |
| d = mnl_attr_get_payload(nla[type]); |
| |
| if (attr->type == MNL_TYPE_NESTED && attr->opt) { |
| /* IP addresses */ |
| struct nlattr *ipattr[IPSET_ATTR_IPADDR_MAX+1] = {}; |
| uint8_t family = ipset_data_family(data); |
| int atype; |
| D("IP attr type %u", type); |
| if (mnl_attr_parse_nested(nla[type], |
| ipaddr_attr_cb, ipattr) < 0) |
| FAILURE("Broken kernel message, cannot validate " |
| "IP address attribute!"); |
| |
| /* Validate by hand */ |
| switch (family) { |
| case NFPROTO_IPV4: |
| atype = IPSET_ATTR_IPADDR_IPV4; |
| if (!ipattr[atype]) |
| FAILURE("Broken kernel message: IPv4 address " |
| "expected but not received!"); |
| if (ipattr[atype]->nla_len < sizeof(uint32_t)) |
| FAILURE("Broken kernel message: " |
| "cannot validate IPv4 " |
| "address attribute!"); |
| break; |
| case NFPROTO_IPV6: |
| atype = IPSET_ATTR_IPADDR_IPV6; |
| if (!ipattr[atype]) |
| FAILURE("Broken kernel message: IPv6 address " |
| "expected but not received!"); |
| if (ipattr[atype]->nla_len < sizeof(struct in6_addr)) |
| FAILURE("Broken kernel message: " |
| "cannot validate IPv6 " |
| "address attribute!"); |
| break; |
| default: |
| FAILURE("Broken kernel message: " |
| "IP address attribute but " |
| "family is unspecified!"); |
| } |
| d = mnl_attr_get_payload(ipattr[atype]); |
| } else if (nla[type]->nla_type & NLA_F_NET_BYTEORDER) { |
| D("netorder attr type %u", type); |
| switch (attr->type) { |
| case MNL_TYPE_U64: { |
| uint64_t tmp; |
| /* Ensure data alignment */ |
| memcpy(&tmp, d, sizeof(tmp)); |
| v64 = be64toh(tmp); |
| d = &v64; |
| break; |
| } |
| case MNL_TYPE_U32: { |
| v32 = ntohl(*(const uint32_t *)d); |
| d = &v32; |
| break; |
| } |
| case MNL_TYPE_U16: { |
| v16 = ntohs(*(const uint16_t *)d); |
| d = &v16; |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| #ifdef IPSET_DEBUG |
| else |
| D("hostorder attr type %u", type); |
| if (type == IPSET_ATTR_TYPENAME) |
| D("nla typename %s", (const char *) d); |
| #endif |
| ret = ipset_data_set(data, attr->opt, d); |
| #ifdef IPSET_DEBUG |
| if (type == IPSET_ATTR_TYPENAME) |
| D("nla typename %s", |
| (const char *) ipset_data_get(data, IPSET_OPT_TYPENAME)); |
| #endif |
| return ret; |
| } |
| |
| #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; |
| } |
| |
| /* Handle printing failures */ |
| static jmp_buf printf_failure; |
| |
| 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 = strlen(session->outbuf); |
| D("len: %u, retry %u", len, loop); |
| va_start(args, fmt); |
| ret = vsnprintf(session->outbuf + len, IPSET_OUTBUFLEN - len, |
| fmt, args); |
| va_end(args); |
| |
| if (ret < 0 || ret >= IPSET_OUTBUFLEN - len) { |
| /* Buffer was too small, push it out and retry */ |
| D("print buffer and try again: %u", len); |
| if (loop++) { |
| ipset_err(session, |
| "Internal error at printing, loop detected!"); |
| longjmp(printf_failure, 1); |
| } |
| |
| session->outbuf[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 = strlen(session->outbuf); |
| D("len: %u, retry %u", len, loop); |
| ret = fn(session->outbuf + len, IPSET_OUTBUFLEN - len, |
| session->data, opt, session->envopts); |
| |
| if (ret < 0 || ret >= IPSET_OUTBUFLEN - len) { |
| /* Buffer was too small, push it out and retry */ |
| D("print buffer and try again: %u", len); |
| if (loop++) { |
| ipset_err(session, |
| "Internal error at printing, loop detected!"); |
| longjmp(printf_failure, 1); |
| } |
| |
| session->outbuf[len] = '\0'; |
| if (!call_outfn(session)) |
| goto retry; |
| } |
| return ret; |
| } |
| |
| static int |
| list_adt(struct ipset_session *session, struct nlattr *nla[]) |
| { |
| const struct ipset_data *data = session->data; |
| const struct ipset_type *type; |
| const struct ipset_arg *arg; |
| int i, found = 0; |
| |
| D("enter"); |
| /* 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; |
| |
| for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) |
| if (nla[i]) { |
| found++; |
| ATTR2DATA(session, nla, i, adt_attrs); |
| } |
| D("attr found %u", found); |
| if (!found) |
| return MNL_CB_OK; |
| |
| switch (session->mode) { |
| case IPSET_LIST_SAVE: |
| safe_snprintf(session, "add %s ", ipset_data_setname(data)); |
| break; |
| case IPSET_LIST_XML: |
| safe_snprintf(session, "<member><elem>"); |
| break; |
| case IPSET_LIST_PLAIN: |
| default: |
| break; |
| } |
| |
| safe_dprintf(session, ipset_print_elem, IPSET_OPT_ELEM); |
| if (session->mode == IPSET_LIST_XML) |
| safe_snprintf(session, "</elem>"); |
| |
| for (arg = type->args[IPSET_ADD]; arg != NULL && arg->opt; arg++) { |
| D("print arg opt %u %s", arg->opt, |
| ipset_data_test(data, arg->opt) ? "(yes)" : "(missing)"); |
| if (!(arg->print && ipset_data_test(data, arg->opt))) |
| continue; |
| switch (session->mode) { |
| case IPSET_LIST_SAVE: |
| case IPSET_LIST_PLAIN: |
| if (arg->has_arg == IPSET_NO_ARG) { |
| safe_snprintf(session, " %s", arg->name[0]); |
| break; |
| } |
| safe_snprintf(session, " %s ", arg->name[0]); |
| safe_dprintf(session, arg->print, arg->opt); |
| break; |
| case IPSET_LIST_XML: |
| if (arg->has_arg == IPSET_NO_ARG) { |
| safe_snprintf(session, |
| "<%s/>", arg->name[0]); |
| break; |
| } |
| safe_snprintf(session, "<%s>", arg->name[0]); |
| safe_dprintf(session, arg->print, arg->opt); |
| safe_snprintf(session, "</%s>", arg->name[0]); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| if (session->mode == IPSET_LIST_XML) |
| safe_snprintf(session, "</member>\n"); |
| else |
| safe_snprintf(session, "\n"); |
| |
| return MNL_CB_OK; |
| } |
| |
| #define FAMILY_TO_STR(f) \ |
| ((f) == NFPROTO_IPV4 ? "inet" : \ |
| (f) == NFPROTO_IPV6 ? "inet6" : "any") |
| |
| static int |
| list_create(struct ipset_session *session, struct nlattr *nla[]) |
| { |
| const 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); |
| break; |
| case IPSET_LIST_PLAIN: |
| safe_snprintf(session, "%sName: %s\n" |
| "Type: %s\nRevision: %u\nHeader:", |
| session->printed_set ? "\n" : "", |
| ipset_data_setname(data), |
| type->name, type->revision); |
| break; |
| case IPSET_LIST_XML: |
| safe_snprintf(session, |
| "<ipset name=\"%s\">\n" |
| "<type>%s</type>\n" |
| "<revision>%u</revision>\n" |
| "<header>", |
| ipset_data_setname(data), |
| type->name, type->revision); |
| break; |
| default: |
| break; |
| } |
| |
| for (arg = type->args[IPSET_CREATE]; arg != NULL && arg->opt; arg++) { |
| if (!arg->print || |
| !ipset_data_test(data, arg->opt) || |
| (arg->opt == IPSET_OPT_FAMILY && |
| family == type->family)) |
| continue; |
| switch (session->mode) { |
| case IPSET_LIST_SAVE: |
| case IPSET_LIST_PLAIN: |
| if (arg->has_arg == IPSET_NO_ARG) { |
| safe_snprintf(session, " %s", arg->name[0]); |
| break; |
| } |
| safe_snprintf(session, " %s ", arg->name[0]); |
| safe_dprintf(session, arg->print, arg->opt); |
| break; |
| case IPSET_LIST_XML: |
| if (arg->has_arg == IPSET_NO_ARG) { |
| safe_snprintf(session, |
| "<%s/>", arg->name[0]); |
| break; |
| } |
| safe_snprintf(session, "<%s>", arg->name[0]); |
| safe_dprintf(session, arg->print, arg->opt); |
| safe_snprintf(session, "</%s>", 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, "\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); |
| if (ipset_data_test(data, IPSET_OPT_ELEMENTS)) { |
| safe_snprintf(session, "\nNumber of entries: "); |
| safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); |
| } |
| safe_snprintf(session, |
| session->envopts & IPSET_ENV_LIST_HEADER ? |
| "\n" : "\nMembers:\n"); |
| break; |
| case IPSET_LIST_XML: |
| safe_snprintf(session, "\n<memsize>"); |
| safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); |
| safe_snprintf(session, "</memsize>\n<references>"); |
| safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES); |
| safe_snprintf(session, "</references>\n"); |
| if (ipset_data_test(data, IPSET_OPT_ELEMENTS)) { |
| safe_snprintf(session, "<numentries>"); |
| safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); |
| safe_snprintf(session, "</numentries>\n"); |
| } |
| safe_snprintf(session, |
| session->envopts & IPSET_ENV_LIST_HEADER ? |
| "</header>\n" : |
| "</header>\n<members>\n"); |
| break; |
| default: |
| break; |
| } |
| session->printed_set++; |
| |
| return MNL_CB_OK; |
| } |
| |
| static int |
| print_set_done(struct ipset_session *session, bool callback_done) |
| { |
| D("called for %s", session->saved_setname[0] == '\0' |
| ? "NONE" : session->saved_setname); |
| switch (session->mode) { |
| case IPSET_LIST_XML: |
| if (session->envopts & IPSET_ENV_LIST_SETNAME) |
| break; |
| if (session->envopts & IPSET_ENV_LIST_HEADER) { |
| if (session->saved_setname[0] != '\0') |
| safe_snprintf(session, "</ipset>\n"); |
| break; |
| } |
| if (session->saved_setname[0] != '\0') |
| safe_snprintf(session, "</members>\n</ipset>\n"); |
| break; |
| default: |
| break; |
| } |
| if (callback_done && session->mode == IPSET_LIST_XML) |
| safe_snprintf(session, "</ipsets>\n"); |
| return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_STOP; |
| } |
| |
| static int |
| callback_list(struct ipset_session *session, struct nlattr *nla[], |
| enum ipset_cmd cmd) |
| { |
| struct ipset_data *data = session->data; |
| |
| if (setjmp(printf_failure)) { |
| session->saved_setname[0] = '\0'; |
| session->printed_set = 0; |
| return MNL_CB_ERROR; |
| } |
| |
| if (!nla[IPSET_ATTR_SETNAME]) |
| FAILURE("Broken %s kernel message: missing setname!", |
| cmd2name[cmd]); |
| |
| ATTR2DATA(session, nla, IPSET_ATTR_SETNAME, cmd_attrs); |
| D("setname %s", ipset_data_setname(data)); |
| if (session->envopts & IPSET_ENV_LIST_SETNAME && |
| session->mode != IPSET_LIST_SAVE) { |
| if (session->mode == IPSET_LIST_XML) |
| safe_snprintf(session, "<ipset name=\"%s\"/>\n", |
| ipset_data_setname(data)); |
| else |
| safe_snprintf(session, "%s\n", |
| ipset_data_setname(data)); |
| return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_OK; |
| } |
| |
| 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, false); |
| } |
| |
| 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"); |
| |
| /* Reset CREATE specific flags */ |
| ipset_data_flags_unset(data, IPSET_CREATE_FLAGS); |
| D("nla typename %s", |
| (char *) mnl_attr_get_payload(nla[IPSET_ATTR_TYPENAME])); |
| |
| ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); |
| ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); |
| ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); |
| D("head: family %u, typename %s", |
| ipset_data_family(data), |
| (const char *) ipset_data_get(data, IPSET_OPT_TYPENAME)); |
| 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]) { |
| D("ADT attributes for %s", ipset_data_setname(data)); |
| 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; |
| const 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); |
| D("got family: %u", ipset_data_family(session->data)); |
| |
| return MNL_CB_STOP; |
| } |
| |
| static int |
| callback_type(struct ipset_session *session, struct nlattr *nla[]) |
| { |
| const 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, false); |
| 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, true); |
| |
| 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), |
| ipset_data_family(data)); |
| 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, true); |
| 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 int |
| open_nested(struct ipset_session *session, struct nlmsghdr *nlh, int attr) |
| { |
| if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > session->bufsize) |
| return 1; |
| session->nested[session->nestid++] = mnl_attr_nest_start(nlh, attr); |
| return 0; |
| } |
| |
| static inline void |
| close_nested(struct ipset_session *session, struct nlmsghdr *nlh) |
| { |
| mnl_attr_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_NESTED: |
| if (attr->len) |
| return attr->len; |
| |
| *flags = NLA_F_NET_BYTEORDER; |
| return family == NFPROTO_IPV4 ? sizeof(uint32_t) |
| : sizeof(struct in6_addr); |
| case MNL_TYPE_U64: |
| *flags = NLA_F_NET_BYTEORDER; |
| return sizeof(uint64_t); |
| 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; |
| } |
| } |
| |
| #define BUFFER_FULL(bufsize, nlmsg_len, nestlen, attrlen) \ |
| (nlmsg_len + nestlen + MNL_ATTR_HDRLEN + MNL_ALIGN(alen) + \ |
| MNL_ALIGN(sizeof(struct nlmsgerr)) > bufsize) |
| |
| static int |
| rawdata2attr(struct ipset_session *session, 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]; |
| if (attr->type == MNL_TYPE_NESTED) { |
| /* IP addresses */ |
| struct nlattr *nested; |
| int atype = family == NFPROTO_IPV4 ? IPSET_ATTR_IPADDR_IPV4 |
| : IPSET_ATTR_IPADDR_IPV6; |
| |
| alen = attr_len(attr, family, &flags); |
| if (BUFFER_FULL(session->bufsize, nlh->nlmsg_len, |
| MNL_ATTR_HDRLEN, alen)) |
| return 1; |
| nested = mnl_attr_nest_start(nlh, type); |
| D("family: %s", family == NFPROTO_IPV4 ? "INET" : |
| family == NFPROTO_IPV6 ? "INET6" : "UNSPEC"); |
| mnl_attr_put(nlh, atype | flags, alen, d); |
| mnl_attr_nest_end(nlh, nested); |
| |
| return 0; |
| } |
| |
| alen = attr_len(attr, family, &flags); |
| if (BUFFER_FULL(session->bufsize, nlh->nlmsg_len, 0, alen)) |
| return 1; |
| |
| switch (attr->type) { |
| case MNL_TYPE_U64: { |
| uint64_t value = htobe64(*(const uint64_t *)d); |
| |
| mnl_attr_put(nlh, type | flags, alen, &value); |
| return 0; |
| } |
| case MNL_TYPE_U32: { |
| uint32_t value = htonl(*(const uint32_t *)d); |
| |
| mnl_attr_put(nlh, type | flags, alen, &value); |
| return 0; |
| } |
| case MNL_TYPE_U16: { |
| uint16_t value = htons(*(const uint16_t *)d); |
| |
| mnl_attr_put(nlh, type | flags, alen, &value); |
| return 0; |
| } |
| case MNL_TYPE_NUL_STRING: |
| alen = strlen((const char *)d) + 1; |
| break; |
| default: |
| break; |
| } |
| |
| mnl_attr_put(nlh, type | flags, alen, d); |
| |
| return 0; |
| } |
| |
| static int |
| data2attr(struct ipset_session *session, 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(session, 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(session, nlh, data, type, family, attrs) \ |
| data2attr(session, nlh, data, type, family, attrs) |
| |
| #define ADDATTR_SETNAME(session, nlh, data) \ |
| data2attr(session, nlh, data, IPSET_ATTR_SETNAME, NFPROTO_IPV4, \ |
| cmd_attrs) |
| |
| #define ADDATTR_IF(session, nlh, data, type, family, attrs) \ |
| ipset_data_test(data, attrs[type].opt) ? \ |
| data2attr(session, nlh, data, type, family, attrs) : 0 |
| |
| #define ADDATTR_RAW(session, nlh, data, type, attrs) \ |
| rawdata2attr(session, nlh, data, type, NFPROTO_IPV4, attrs) |
| |
| static void |
| addattr_create(struct ipset_session *session, |
| 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(session, nlh, data, i, family, create_attrs); |
| } |
| |
| static int |
| addattr_adt(struct ipset_session *session, |
| struct nlmsghdr *nlh, struct ipset_data *data, uint8_t family) |
| { |
| int i; |
| |
| for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) |
| if (ADDATTR_IF(session, nlh, data, i, family, adt_attrs)) |
| return 1; |
| return 0; |
| } |
| |
| #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 = (void *)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(session, 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(session, nlh, data, IPSET_ATTR_TYPENAME, |
| NFPROTO_IPV4, cmd_attrs); |
| if (ipset_data_test(data, IPSET_OPT_FAMILY)) |
| ADDATTR(session, nlh, data, IPSET_ATTR_FAMILY, |
| NFPROTO_IPV4, cmd_attrs); |
| else |
| /* bitmap:port and list:set types */ |
| mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, NFPROTO_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, enum ipset_cmd cmd) |
| { |
| return session->lineno != 0 && |
| (cmd == IPSET_CMD_ADD || cmd == IPSET_CMD_DEL) && |
| cmd == session->cmd && |
| STREQ(ipset_data_setname(session->data), session->saved_setname); |
| } |
| |
| static int |
| build_msg(struct ipset_session *session, bool aggregate) |
| { |
| struct nlmsghdr *nlh = 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(session, nlh, data); |
| ADDATTR(session, nlh, data, IPSET_ATTR_TYPENAME, |
| NFPROTO_IPV4, cmd_attrs); |
| ADDATTR_RAW(session, nlh, &type->revision, |
| IPSET_ATTR_REVISION, cmd_attrs); |
| D("family: %u, type family %u", |
| ipset_data_family(data), type->family); |
| if (ipset_data_test(data, IPSET_OPT_FAMILY)) |
| ADDATTR(session, nlh, data, IPSET_ATTR_FAMILY, |
| NFPROTO_IPV4, cmd_attrs); |
| else |
| /* bitmap:port and list:set types */ |
| mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, NFPROTO_UNSPEC); |
| |
| /* Type-specific create attributes */ |
| D("call open_nested"); |
| open_nested(session, nlh, IPSET_ATTR_DATA); |
| addattr_create(session, nlh, data, type->family); |
| D("call close_nested"); |
| close_nested(session, nlh); |
| break; |
| } |
| case IPSET_CMD_DESTROY: |
| case IPSET_CMD_FLUSH: |
| case IPSET_CMD_SAVE: |
| if (ipset_data_test(data, IPSET_SETNAME)) |
| ADDATTR_SETNAME(session, nlh, data); |
| break; |
| case IPSET_CMD_LIST: { |
| uint32_t flags = 0; |
| |
| if (session->envopts & IPSET_ENV_LIST_SETNAME) |
| flags |= IPSET_FLAG_LIST_SETNAME; |
| if (session->envopts & IPSET_ENV_LIST_HEADER) |
| flags |= IPSET_FLAG_LIST_HEADER; |
| if (ipset_data_test(data, IPSET_SETNAME)) |
| ADDATTR_SETNAME(session, nlh, data); |
| if (flags && session->mode != IPSET_LIST_SAVE) { |
| ipset_data_set(data, IPSET_OPT_FLAGS, &flags); |
| ADDATTR(session, nlh, data, IPSET_ATTR_FLAGS, |
| NFPROTO_IPV4, cmd_attrs); |
| } |
| 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(session, nlh, data); |
| ADDATTR_RAW(session, 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(session, nlh, data); |
| if (session->lineno != 0) { |
| /* Restore mode */ |
| ADDATTR_RAW(session, nlh, &session->lineno, |
| IPSET_ATTR_LINENO, cmd_attrs); |
| open_nested(session, nlh, IPSET_ATTR_ADT); |
| } |
| } |
| type = ipset_data_get(data, IPSET_OPT_TYPE); |
| D("family: %u, type family %u", |
| ipset_data_family(data), type->family); |
| if (open_nested(session, nlh, IPSET_ATTR_DATA)) { |
| D("open_nested failed"); |
| return 1; |
| } |
| if (addattr_adt(session, nlh, data, ipset_data_family(data)) || |
| ADDATTR_RAW(session, nlh, &session->lineno, |
| IPSET_ATTR_LINENO, cmd_attrs)) { |
| /* Cancel last, unfinished nested attribute */ |
| mnl_attr_nest_cancel(nlh, |
| session->nested[session->nestid-1]); |
| session->nested[--session->nestid] = NULL; |
| return 1; |
| } |
| 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); |
| D("family: %u, type family %u", |
| ipset_data_family(data), type->family); |
| ADDATTR_SETNAME(session, nlh, data); |
| open_nested(session, nlh, IPSET_ATTR_DATA); |
| addattr_adt(session, nlh, data, ipset_data_family(data)); |
| 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 = 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 saved data and nested state */ |
| session->saved_setname[0] = '\0'; |
| session->printed_set = 0; |
| 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 aggregatable commands */ |
| aggregate = may_aggregate_ad(session, cmd); |
| if (!aggregate) { |
| /* Flush possible aggregated commands */ |
| ret = ipset_commit(session); |
| if (ret < 0) |
| return ret; |
| } |
| |
| /* 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; |
| } |
| /* Start the root element in XML mode */ |
| if ((cmd == IPSET_CMD_LIST || cmd == IPSET_CMD_SAVE) && |
| session->mode == IPSET_LIST_XML) |
| safe_snprintf(session, "<ipsets>\n"); |
| |
| D("next: build_msg"); |
| /* Build new message or append buffered commands */ |
| ret = build_msg(session, aggregate); |
| D("build_msg returned %u", ret); |
| if (ret > 0) { |
| /* Buffer is full, send buffered commands */ |
| ret = ipset_commit(session); |
| if (ret < 0) |
| goto cleanup; |
| ret = build_msg(session, false); |
| D("build_msg 2 returned %u", ret); |
| } |
| if (ret < 0) |
| goto cleanup; |
| D("past: build_msg"); |
| |
| /* We have to save the type for error handling */ |
| session->saved_type = ipset_data_get(data, IPSET_OPT_TYPE); |
| if (session->lineno != 0 && |
| (cmd == IPSET_CMD_ADD || cmd == IPSET_CMD_DEL)) { |
| /* Save setname for the next possible aggregated restore line */ |
| strcpy(session->saved_setname, ipset_data_setname(data)); |
| ipset_data_reset(data); |
| /* Don't commit: we may aggregate next command */ |
| ret = 0; |
| goto cleanup; |
| } |
| |
| D("call commit"); |
| ret = ipset_commit(session); |
| |
| cleanup: |
| D("reset data"); |
| ipset_data_reset(data); |
| return ret; |
| } |
| |
| /** |
| * ipset_session_outfn - set session output printing function |
| * |
| * Set the session printing function. |
| * |
| */ |
| int |
| ipset_session_outfn(struct ipset_session *session, ipset_outfn outfn) |
| { |
| session->outfn = outfn ? outfn : printf; |
| return 0; |
| } |
| |
| /** |
| * 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; |
| session->buffer = session + 1; |
| |
| /* 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_cache_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_cache_fini(); |
| free(session); |
| return 0; |
| } |
| |
| #ifdef IPSET_DEBUG |
| #include "debug.c" |
| #endif |