mirror of
git://git.code.sf.net/p/xtables-addons/xtables-addons
synced 2025-09-06 20:55:13 +02:00
1990 lines
51 KiB
C
1990 lines
51 KiB
C
/* 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 <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 <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 */
|
|
|
|
#include <linux/genetlink.h>
|
|
|
|
#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_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_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,
|
|
},
|
|
};
|
|
|
|
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) > IPSET_MAXNAMELEN)
|
|
return MNL_CB_ERROR;
|
|
tb[type] = attr;
|
|
return MNL_CB_OK;
|
|
}
|
|
|
|
static int
|
|
create_attr_cb(const struct nlattr *attr, void *data)
|
|
{
|
|
return generic_data_attr_cb(attr, data,
|
|
IPSET_ATTR_CREATE_MAX, create_attrs);
|
|
}
|
|
|
|
static int
|
|
adt_attr_cb(const struct nlattr *attr, void *data)
|
|
{
|
|
return generic_data_attr_cb(attr, data,
|
|
IPSET_ATTR_ADT_MAX, adt_attrs);
|
|
}
|
|
|
|
static int
|
|
ipaddr_attr_cb(const struct nlattr *attr, void *data)
|
|
{
|
|
return generic_data_attr_cb(attr, data,
|
|
IPSET_ATTR_IPADDR_MAX, ipaddr_attrs);
|
|
}
|
|
|
|
#define FAILURE(format, args...) \
|
|
{ ipset_err(session, format , ## args); return MNL_CB_ERROR; }
|
|
|
|
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;
|
|
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("attr type %u", type);
|
|
if (mnl_attr_parse_nested(nla[type],
|
|
ipaddr_attr_cb, ipattr) < 0)
|
|
FAILURE("Broken kernel message, cannot validate "
|
|
"IP address attribute!");
|
|
|
|
/* Validate by hand */
|
|
switch (family) {
|
|
case AF_INET:
|
|
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 AF_INET6:
|
|
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) {
|
|
switch (attr->type) {
|
|
case MNL_TYPE_U32: {
|
|
uint32_t value;
|
|
|
|
value = ntohl(*(const uint32_t *)d);
|
|
|
|
d = &value;
|
|
break;
|
|
}
|
|
case MNL_TYPE_U16: {
|
|
uint16_t value;
|
|
|
|
value = ntohs(*(const uint16_t *)d);
|
|
|
|
d = &value;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#ifdef IPSET_DEBUG
|
|
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) {
|
|
ipset_err(session,
|
|
"Internal error at printing to output buffer");
|
|
longjmp(printf_failure, 1);
|
|
}
|
|
|
|
if (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) {
|
|
ipset_err(session,
|
|
"Internal error at printing to output buffer");
|
|
longjmp(printf_failure, 1);
|
|
}
|
|
|
|
if (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;
|
|
uint8_t family;
|
|
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;
|
|
family = ipset_data_family(data);
|
|
|
|
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>");
|
|
break;
|
|
case IPSET_LIST_PLAIN:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
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, "</%s>\n",
|
|
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) == AF_INET ? "inet" : \
|
|
(f) == AF_INET6 ? "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\nHeader: ",
|
|
session->printed_set ? "\n" : "",
|
|
ipset_data_setname(data),
|
|
type->name);
|
|
break;
|
|
case IPSET_LIST_XML:
|
|
safe_snprintf(session,
|
|
"<ipset name=\"%s\">\n"
|
|
" <type>%s</type>\n"
|
|
" <header>\n",
|
|
ipset_data_setname(data),
|
|
type->name);
|
|
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:
|
|
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, "</%s>\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, "\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,
|
|
session->envopts & IPSET_ENV_LIST_HEADER ?
|
|
"\n" : "\nMembers:\n");
|
|
break;
|
|
case IPSET_LIST_XML:
|
|
safe_snprintf(session, " <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,
|
|
session->envopts & IPSET_ENV_LIST_HEADER ?
|
|
"</references>\n </header>\n" :
|
|
"</references>\n </header>\n <members>\n");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
session->printed_set++;
|
|
|
|
return MNL_CB_OK;
|
|
}
|
|
|
|
static int
|
|
print_set_done(struct ipset_session *session)
|
|
{
|
|
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;
|
|
}
|
|
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);
|
|
}
|
|
|
|
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]));
|
|
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 genlmsghdr));
|
|
|
|
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 genlmsghdr));
|
|
|
|
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);
|
|
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 == 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;
|
|
}
|
|
}
|
|
|
|
#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 == AF_INET ? 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 == AF_INET ? "INET" :
|
|
family == AF_INET6 ? "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_NUL_STRING:
|
|
alen = strlen((const char *)d) + 1;
|
|
break;
|
|
case MNL_TYPE_U32: {
|
|
uint32_t value = htonl(*(const uint32_t *)d);
|
|
|
|
d = &value;
|
|
break;
|
|
}
|
|
case MNL_TYPE_U16: {
|
|
uint16_t value = htons(*(const uint16_t *)d);
|
|
|
|
d = &value;
|
|
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, AF_INET, 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, AF_INET, 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, AF_INET, cmd_attrs);
|
|
if (ipset_data_test(data, IPSET_OPT_FAMILY))
|
|
ADDATTR(session, 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, 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, AF_INET, 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,
|
|
AF_INET, cmd_attrs);
|
|
else
|
|
/* bitmap:port and list:set types */
|
|
mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, AF_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, AF_INET,
|
|
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;
|
|
}
|
|
|
|
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_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
|