ipset: import ipset 5.2+GENL

This requires Linux 2.6.35 or newer to build, so it is deactivated by
default in the "mconfig" file.
This commit is contained in:
Jan Engelhardt
2011-01-04 03:57:21 +01:00
parent 75212f3972
commit 7d8ffffd85
75 changed files with 21736 additions and 1 deletions

View File

@@ -0,0 +1,568 @@
/* 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 <arpa/inet.h> /* ntoh* */
#include <net/ethernet.h> /* ETH_ALEN */
#include <sys/socket.h> /* AF_ */
#include <stdlib.h> /* malloc, free */
#include <string.h> /* memset */
#include <libipset/linux_ip_set.h> /* IPSET_MAXNAMELEN */
#include <libipset/debug.h> /* D() */
#include <libipset/types.h> /* struct ipset_type */
#include <libipset/utils.h> /* inXcpy */
#include <libipset/data.h> /* prototypes */
/* Internal data structure to hold
* a) input data entered by the user or
* b) data received from kernel
*
* We always store the data in host order, *except* IP addresses.
*/
struct ipset_data {
/* Option bits: which fields are set */
uint64_t bits;
/* Option bits: which options are ignored */
uint64_t ignored;
/* Setname */
char setname[IPSET_MAXNAMELEN];
/* Set type */
const struct ipset_type *type;
/* Common CADT options */
uint8_t cidr;
uint8_t family;
uint32_t flags; /* command level flags */
uint32_t cadt_flags; /* data level flags */
uint32_t timeout;
union nf_inet_addr ip;
union nf_inet_addr ip_to;
uint16_t port;
uint16_t port_to;
union {
/* RENAME/SWAP */
char setname2[IPSET_MAXNAMELEN];
/* CREATE/LIST/SAVE */
struct {
uint8_t probes;
uint8_t resize;
uint8_t netmask;
uint32_t hashsize;
uint32_t maxelem;
uint32_t gc;
uint32_t size;
/* Filled out by kernel */
uint32_t references;
uint32_t elements;
uint32_t memsize;
char typename[IPSET_MAXNAMELEN];
uint8_t revision_min;
uint8_t revision;
} create;
/* ADT/LIST/SAVE */
struct {
union nf_inet_addr ip2;
uint8_t cidr2;
uint8_t proto;
char ether[ETH_ALEN];
char name[IPSET_MAXNAMELEN];
char nameref[IPSET_MAXNAMELEN];
} adt;
};
};
static void
copy_addr(uint8_t family, union nf_inet_addr *ip, const void *value)
{
if (family == AF_INET)
in4cpy(&ip->in, value);
else
in6cpy(&ip->in6, value);
}
/**
* ipset_strlcpy - copy the string from src to dst
* @dst: the target string buffer
* @src: the source string buffer
* @len: the length of bytes to copy, including the terminating null byte.
*
* Copy the string from src to destination, but at most len bytes are
* copied. The target is unconditionally terminated by the null byte.
*/
void
ipset_strlcpy(char *dst, const char *src, size_t len)
{
assert(dst);
assert(src);
strncpy(dst, src, len);
dst[len - 1] = '\0';
}
/**
* ipset_data_flags_test - test option bits in the data blob
* @data: data blob
* @flags: the option flags to test
*
* Returns true if the options are already set in the data blob.
*/
bool
ipset_data_flags_test(const struct ipset_data *data, uint64_t flags)
{
assert(data);
return !!(data->bits & flags);
}
/**
* ipset_data_flags_set - set option bits in the data blob
* @data: data blob
* @flags: the option flags to set
*
* The function sets the flags in the data blob so that
* the corresponding fields are regarded as if filled with proper data.
*/
void
ipset_data_flags_set(struct ipset_data *data, uint64_t flags)
{
assert(data);
data->bits |= flags;
}
/**
* ipset_data_flags_unset - unset option bits in the data blob
* @data: data blob
* @flags: the option flags to unset
*
* The function unsets the flags in the data blob.
* This is the quick way to clear specific fields.
*/
void
ipset_data_flags_unset(struct ipset_data *data, uint64_t flags)
{
assert(data);
data->bits &= ~flags;
}
#define flag_type_attr(data, opt, flag) \
do { \
data->flags |= flag; \
opt = IPSET_OPT_FLAGS; \
} while (0)
#define cadt_flag_type_attr(data, opt, flag) \
do { \
data->cadt_flags |= flag; \
opt = IPSET_OPT_CADT_FLAGS; \
} while (0)
/**
* ipset_data_ignored - test and set ignored bits in the data blob
* @data: data blob
* @flags: the option flag to be ignored
*
* Returns true if the option was not already ignored.
*/
bool
ipset_data_ignored(struct ipset_data *data, enum ipset_opt opt)
{
bool ignored;
assert(data);
ignored = data->ignored & IPSET_FLAG(opt);
data->ignored |= IPSET_FLAG(opt);
return ignored;
}
/**
* ipset_data_set - put data into the data blob
* @data: data blob
* @opt: the option kind of the data
* @value: the value of the data
*
* Put a given kind of data into the data blob and mark the
* option kind as already set in the blob.
*
* Returns 0 on success or a negative error code.
*/
int
ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value)
{
assert(data);
assert(opt != IPSET_OPT_NONE);
assert(value);
switch (opt) {
/* Common ones */
case IPSET_SETNAME:
ipset_strlcpy(data->setname, value, IPSET_MAXNAMELEN);
break;
case IPSET_OPT_TYPE:
data->type = value;
break;
case IPSET_OPT_FAMILY:
data->family = *(const uint8_t *) value;
D("family set to %u", data->family);
break;
/* CADT options */
case IPSET_OPT_IP:
if (!(data->family == AF_INET || data->family == AF_INET6))
return -1;
copy_addr(data->family, &data->ip, value);
break;
case IPSET_OPT_IP_TO:
if (!(data->family == AF_INET || data->family == AF_INET6))
return -1;
copy_addr(data->family, &data->ip_to, value);
break;
case IPSET_OPT_CIDR:
data->cidr = *(const uint8_t *) value;
break;
case IPSET_OPT_PORT:
data->port = *(const uint16_t *) value;
break;
case IPSET_OPT_PORT_TO:
data->port_to = *(const uint16_t *) value;
break;
case IPSET_OPT_TIMEOUT:
data->timeout = *(const uint32_t *) value;
break;
/* Create-specific options */
case IPSET_OPT_GC:
data->create.gc = *(const uint32_t *) value;
break;
case IPSET_OPT_HASHSIZE:
data->create.hashsize = *(const uint32_t *) value;
break;
case IPSET_OPT_MAXELEM:
data->create.maxelem = *(const uint32_t *) value;
break;
case IPSET_OPT_NETMASK:
data->create.netmask = *(const uint8_t *) value;
break;
case IPSET_OPT_PROBES:
data->create.probes = *(const uint8_t *) value;
break;
case IPSET_OPT_RESIZE:
data->create.resize = *(const uint8_t *) value;
break;
case IPSET_OPT_SIZE:
data->create.size = *(const uint32_t *) value;
break;
/* Create-specific options, filled out by the kernel */
case IPSET_OPT_ELEMENTS:
data->create.elements = *(const uint32_t *) value;
break;
case IPSET_OPT_REFERENCES:
data->create.references = *(const uint32_t *) value;
break;
case IPSET_OPT_MEMSIZE:
data->create.memsize = *(const uint32_t *) value;
break;
/* Create-specific options, type */
case IPSET_OPT_TYPENAME:
ipset_strlcpy(data->create.typename, value,
IPSET_MAXNAMELEN);
break;
case IPSET_OPT_REVISION:
data->create.revision = *(const uint8_t *) value;
break;
case IPSET_OPT_REVISION_MIN:
data->create.revision_min = *(const uint8_t *) value;
break;
/* ADT-specific options */
case IPSET_OPT_ETHER:
memcpy(data->adt.ether, value, ETH_ALEN);
break;
case IPSET_OPT_NAME:
ipset_strlcpy(data->adt.name, value, IPSET_MAXNAMELEN);
break;
case IPSET_OPT_NAMEREF:
ipset_strlcpy(data->adt.nameref, value, IPSET_MAXNAMELEN);
break;
case IPSET_OPT_IP2:
if (!(data->family == AF_INET || data->family == AF_INET6))
return -1;
copy_addr(data->family, &data->adt.ip2, value);
break;
case IPSET_OPT_CIDR2:
data->adt.cidr2 = *(const uint8_t *) value;
break;
case IPSET_OPT_PROTO:
data->adt.proto = *(const uint8_t *) value;
break;
/* Swap/rename */
case IPSET_OPT_SETNAME2:
ipset_strlcpy(data->setname2, value, IPSET_MAXNAMELEN);
break;
/* flags */
case IPSET_OPT_EXIST:
flag_type_attr(data, opt, IPSET_FLAG_EXIST);
break;
case IPSET_OPT_BEFORE:
cadt_flag_type_attr(data, opt, IPSET_FLAG_BEFORE);
break;
case IPSET_OPT_FLAGS:
data->flags = *(const uint32_t *)value;
break;
case IPSET_OPT_CADT_FLAGS:
data->cadt_flags = *(const uint32_t *)value;
break;
default:
return -1;
};
ipset_data_flags_set(data, IPSET_FLAG(opt));
return 0;
}
/**
* ipset_data_get - get data from the data blob
* @data: data blob
* @opt: option kind of the requested data
*
* Returns the pointer to the requested kind of data from the data blob
* if it is set. If the option kind is not set or is an unkown type,
* NULL is returned.
*/
const void *
ipset_data_get(const struct ipset_data *data, enum ipset_opt opt)
{
assert(data);
assert(opt != IPSET_OPT_NONE);
if (!(opt == IPSET_OPT_TYPENAME || ipset_data_test(data, opt)))
return NULL;
switch (opt) {
/* Common ones */
case IPSET_SETNAME:
return data->setname;
case IPSET_OPT_TYPE:
return data->type;
case IPSET_OPT_TYPENAME:
if (ipset_data_test(data, IPSET_OPT_TYPE))
return data->type->name;
else if (ipset_data_test(data, IPSET_OPT_TYPENAME))
return data->create.typename;
return NULL;
case IPSET_OPT_FAMILY:
return &data->family;
/* CADT options */
case IPSET_OPT_IP:
return &data->ip;
case IPSET_OPT_IP_TO:
return &data->ip_to;
case IPSET_OPT_CIDR:
return &data->cidr;
case IPSET_OPT_PORT:
return &data->port;
case IPSET_OPT_PORT_TO:
return &data->port_to;
case IPSET_OPT_TIMEOUT:
return &data->timeout;
/* Create-specific options */
case IPSET_OPT_GC:
return &data->create.gc;
case IPSET_OPT_HASHSIZE:
return &data->create.hashsize;
case IPSET_OPT_MAXELEM:
return &data->create.maxelem;
case IPSET_OPT_NETMASK:
return &data->create.netmask;
case IPSET_OPT_PROBES:
return &data->create.probes;
case IPSET_OPT_RESIZE:
return &data->create.resize;
case IPSET_OPT_SIZE:
return &data->create.size;
/* Create-specific options, filled out by the kernel */
case IPSET_OPT_ELEMENTS:
return &data->create.elements;
case IPSET_OPT_REFERENCES:
return &data->create.references;
case IPSET_OPT_MEMSIZE:
return &data->create.memsize;
/* Create-specific options, TYPE */
case IPSET_OPT_REVISION:
return &data->create.revision;
case IPSET_OPT_REVISION_MIN:
return &data->create.revision_min;
/* ADT-specific options */
case IPSET_OPT_ETHER:
return data->adt.ether;
case IPSET_OPT_NAME:
return data->adt.name;
case IPSET_OPT_NAMEREF:
return data->adt.nameref;
case IPSET_OPT_IP2:
return &data->adt.ip2;
case IPSET_OPT_CIDR2:
return &data->adt.cidr2;
case IPSET_OPT_PROTO:
return &data->adt.proto;
/* Swap/rename */
case IPSET_OPT_SETNAME2:
return data->setname2;
/* flags */
case IPSET_OPT_FLAGS:
case IPSET_OPT_EXIST:
return &data->flags;
case IPSET_OPT_CADT_FLAGS:
case IPSET_OPT_BEFORE:
return &data->cadt_flags;
default:
return NULL;
}
}
/**
* ipset_data_sizeof - calculates the size of the data type
* @opt: option kind of the data
* @family: INET family
*
* Returns the size required to store the given data type.
*/
size_t
ipset_data_sizeof(enum ipset_opt opt, uint8_t family)
{
assert(opt != IPSET_OPT_NONE);
switch (opt) {
case IPSET_OPT_IP:
case IPSET_OPT_IP_TO:
case IPSET_OPT_IP2:
return family == AF_INET ? sizeof(uint32_t)
: sizeof(struct in6_addr);
case IPSET_OPT_PORT:
case IPSET_OPT_PORT_TO:
return sizeof(uint16_t);
case IPSET_SETNAME:
case IPSET_OPT_NAME:
case IPSET_OPT_NAMEREF:
return IPSET_MAXNAMELEN;
case IPSET_OPT_TIMEOUT:
case IPSET_OPT_GC:
case IPSET_OPT_HASHSIZE:
case IPSET_OPT_MAXELEM:
case IPSET_OPT_SIZE:
case IPSET_OPT_ELEMENTS:
case IPSET_OPT_REFERENCES:
case IPSET_OPT_MEMSIZE:
return sizeof(uint32_t);
case IPSET_OPT_CIDR:
case IPSET_OPT_CIDR2:
case IPSET_OPT_NETMASK:
case IPSET_OPT_PROBES:
case IPSET_OPT_RESIZE:
case IPSET_OPT_PROTO:
return sizeof(uint8_t);
case IPSET_OPT_ETHER:
return ETH_ALEN;
/* Flags counted once */
case IPSET_OPT_BEFORE:
return sizeof(uint32_t);
default:
return 0;
};
}
/**
* ipset_setname - return the name of the set from the data blob
* @data: data blob
*
* Return the name of the set from the data blob or NULL if the
* name not set yet.
*/
const char *
ipset_data_setname(const struct ipset_data *data)
{
assert(data);
return ipset_data_test(data, IPSET_SETNAME) ? data->setname : NULL;
}
/**
* ipset_family - return the INET family of the set from the data blob
* @data: data blob
*
* Return the INET family supported by the set from the data blob.
* If the family is not set yet, AF_UNSPEC is returned.
*/
uint8_t
ipset_data_family(const struct ipset_data *data)
{
assert(data);
return ipset_data_test(data, IPSET_OPT_FAMILY)
? data->family : AF_UNSPEC;
}
/**
* ipset_data_cidr - return the value of IPSET_OPT_CIDR
* @data: data blob
*
* Return the value of IPSET_OPT_CIDR stored in the data blob.
* If it is not set, then the returned value corresponds to
* the default one according to the family type or zero.
*/
uint8_t
ipset_data_cidr(const struct ipset_data *data)
{
assert(data);
return ipset_data_test(data, IPSET_OPT_CIDR) ? data->cidr :
data->family == AF_INET ? 32 :
data->family == AF_INET6 ? 128 : 0;
}
/**
* ipset_flags - return which fields are set in the data blob
* @data: data blob
*
* Returns the value of the bit field which elements are set.
*/
uint64_t
ipset_data_flags(const struct ipset_data *data)
{
assert(data);
return data->bits;
}
/**
* ipset_data_reset - reset the data blob to unset
* @data: data blob
*
* Resets the data blob to the unset state for every field.
*/
void
ipset_data_reset(struct ipset_data *data)
{
assert(data);
memset(data, 0, sizeof(*data));
}
/**
* ipset_data_init - create a new data blob
*
* Return the new data blob initialized to empty. In case of
* an error, NULL is retured.
*/
struct ipset_data *
ipset_data_init(void)
{
return calloc(1, sizeof(struct ipset_data));
}
/**
* ipset_data_fini - release a data blob created by ipset_data_init
*
* Release the data blob created by ipset_data_init previously.
*/
void
ipset_data_fini(struct ipset_data *data)
{
assert(data);
free(data);
}

View File

@@ -0,0 +1,79 @@
/* Copyright 2007-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <libipset/utils.h> /* STRNEQ */
#include <libipset/icmp.h> /* prototypes */
struct icmp_names {
const char *name;
uint8_t type, code;
};
static const struct icmp_names icmp_typecodes[] = {
{ "echo-reply", 0, 0 },
{ "pong", 0, 0 },
{ "network-unreachable", 3, 0 },
{ "host-unreachable", 3, 1 },
{ "protocol-unreachable", 3, 2 },
{ "port-unreachable", 3, 3 },
{ "fragmentation-needed", 3, 4 },
{ "source-route-failed", 3, 5 },
{ "network-unknown", 3, 6 },
{ "host-unknown", 3, 7 },
{ "network-prohibited", 3, 9 },
{ "host-prohibited", 3, 10 },
{ "TOS-network-unreachable", 3, 11 },
{ "TOS-host-unreachable", 3, 12 },
{ "communication-prohibited", 3, 13 },
{ "host-precedence-violation", 3, 14 },
{ "precedence-cutoff", 3, 15 },
{ "source-quench", 4, 0 },
{ "network-redirect", 5, 0 },
{ "host-redirect", 5, 1 },
{ "TOS-network-redirect", 5, 2 },
{ "TOS-host-redirect", 5, 3 },
{ "echo-request", 8, 0 },
{ "ping", 8, 0 },
{ "router-advertisement", 9, 0 },
{ "router-solicitation", 10, 0 },
{ "ttl-zero-during-transit", 11, 0 },
{ "ttl-zero-during-reassembly", 11, 1 },
{ "ip-header-bad", 12, 0 },
{ "required-option-missing", 12, 1 },
{ "timestamp-request", 13, 0 },
{ "timestamp-reply", 14, 0 },
{ "address-mask-request", 17, 0 },
{ "address-mask-reply", 18, 0 },
};
const char * id_to_icmp(uint8_t id)
{
return id < ARRAY_SIZE(icmp_typecodes) ? icmp_typecodes[id].name : NULL;
}
const char * icmp_to_name(uint8_t type, uint8_t code)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(icmp_typecodes); i++)
if (icmp_typecodes[i].type == type && icmp_typecodes[i].code == code)
return icmp_typecodes[i].name;
return NULL;
}
int name_to_icmp(const char *str, uint16_t *typecode)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(icmp_typecodes); i++)
if (STRNCASEQ(icmp_typecodes[i].name, str, strlen(str))) {
*typecode = (icmp_typecodes[i].type << 8) | icmp_typecodes[i].code;
return 0;
}
return -1;
}

View File

@@ -0,0 +1,66 @@
/* Copyright 2007-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <libipset/utils.h> /* STRNEQ */
#include <libipset/icmpv6.h> /* prototypes */
struct icmpv6_names {
const char *name;
uint8_t type, code;
};
static const struct icmpv6_names icmpv6_typecodes[] = {
{ "no-route", 1, 0 },
{ "communication-prohibited", 1, 1 },
{ "address-unreachable", 1, 3 },
{ "port-unreachable", 1, 4 },
{ "packet-too-big", 2, 0 },
{ "ttl-zero-during-transit", 3, 0 },
{ "ttl-zero-during-reassembly", 3, 1 },
{ "bad-header", 4, 0 },
{ "unknown-header-type", 4, 1 },
{ "unknown-option", 4, 2 },
{ "echo-request", 128, 0 },
{ "ping", 128, 0 },
{ "echo-reply", 129, 0 },
{ "pong", 129, 0 },
{ "router-solicitation", 133, 0 },
{ "router-advertisement", 134, 0 },
{ "neighbour-solicitation", 135, 0 },
{ "neigbour-solicitation", 135, 0 },
{ "neighbour-advertisement", 136, 0 },
{ "neigbour-advertisement", 136, 0 },
{ "redirect", 137, 0 },
};
const char * id_to_icmpv6(uint8_t id)
{
return id < ARRAY_SIZE(icmpv6_typecodes) ? icmpv6_typecodes[id].name : NULL;
}
const char * icmpv6_to_name(uint8_t type, uint8_t code)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(icmpv6_typecodes); i++)
if (icmpv6_typecodes[i].type == type && icmpv6_typecodes[i].code == code)
return icmpv6_typecodes[i].name;
return NULL;
}
int name_to_icmpv6(const char *str, uint16_t *typecode)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(icmpv6_typecodes); i++)
if (STRNCASEQ(icmpv6_typecodes[i].name, str, strlen(str))) {
*typecode = (icmpv6_typecodes[i].type << 8) | icmpv6_typecodes[i].code;
return 0;
}
return -1;
}

View File

@@ -0,0 +1,258 @@
/* 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 <stdint.h>
#include <stdlib.h> /* calloc, free */
#include <time.h> /* time */
#include <arpa/inet.h> /* hto* */
#include <libipset/linux_ip_set.h> /* enum ipset_cmd */
#include <libipset/debug.h> /* D() */
#include <libipset/session.h> /* ipset_session_handle */
#include <libipset/ui.h> /* IPSET_ENV_EXIST */
#include <libipset/utils.h> /* UNUSED */
#include <libipset/mnl.h> /* prototypes */
#include <libmnl/libmnl.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#ifndef NFNL_SUBSYS_IPSET
#define NFNL_SUBSYS_IPSET 6
#endif
/* Internal data structure for the kernel-userspace communication parameters */
struct ipset_handle {
struct mnl_socket *h; /* the mnl socket */
unsigned int seq; /* netlink message sequence number */
unsigned int portid; /* the socket port identifier */
mnl_cb_t *cb_ctl; /* control block callbacks */
void *data; /* data pointer */
unsigned int genl_id; /* genetlink ID of ip_set */
};
/* Netlink flags of the commands */
static const uint16_t cmdflags[] = {
[IPSET_CMD_CREATE-1] = NLM_F_REQUEST|NLM_F_ACK|NLM_F_CREATE|NLM_F_EXCL,
[IPSET_CMD_DESTROY-1] = NLM_F_REQUEST|NLM_F_ACK,
[IPSET_CMD_FLUSH-1] = NLM_F_REQUEST|NLM_F_ACK,
[IPSET_CMD_RENAME-1] = NLM_F_REQUEST|NLM_F_ACK,
[IPSET_CMD_SWAP-1] = NLM_F_REQUEST|NLM_F_ACK,
[IPSET_CMD_LIST-1] = NLM_F_REQUEST,
[IPSET_CMD_SAVE-1] = NLM_F_REQUEST,
[IPSET_CMD_ADD-1] = NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL,
[IPSET_CMD_DEL-1] = NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL,
[IPSET_CMD_TEST-1] = NLM_F_REQUEST|NLM_F_ACK,
[IPSET_CMD_HEADER-1] = NLM_F_REQUEST,
[IPSET_CMD_TYPE-1] = NLM_F_REQUEST,
[IPSET_CMD_PROTOCOL-1] = NLM_F_REQUEST,
};
/**
* ipset_get_nlmsg_type - get ipset netlink message type
* @nlh: pointer to the netlink message header
*
* Returns the ipset netlink message type, i.e. the ipset command.
*/
int
ipset_get_nlmsg_type(const struct nlmsghdr *nlh)
{
const struct genlmsghdr *ghdr = mnl_nlmsg_get_payload(nlh);
return ghdr->cmd;
}
static void
ipset_mnl_fill_hdr(struct ipset_handle *handle, enum ipset_cmd cmd,
void *buffer, size_t len UNUSED, uint8_t envflags)
{
struct nlmsghdr *nlh;
struct genlmsghdr *ghdr;
assert(handle);
assert(buffer);
assert(cmd > IPSET_CMD_NONE && cmd < IPSET_MSG_MAX);
nlh = mnl_nlmsg_put_header(buffer);
nlh->nlmsg_type = handle->genl_id;
nlh->nlmsg_flags = NLM_F_REQUEST;
if (cmdflags[cmd-1] & NLM_F_ACK)
nlh->nlmsg_flags |= NLM_F_ACK;
nlh->nlmsg_seq = handle->seq = time(NULL);
ghdr = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
ghdr->cmd = cmd;
/* (ge)netlink is dumb, NLM_F_CREATE and NLM_F_DUMP overlap, get misinterpreted. */
ghdr->reserved = cmdflags[cmd-1];
if (envflags & IPSET_ENV_EXIST)
ghdr->reserved &= ~NLM_F_EXCL;
}
static int
ipset_mnl_query(struct ipset_handle *handle, void *buffer, size_t len)
{
struct nlmsghdr *nlh = buffer;
int ret;
assert(handle);
assert(buffer);
if (mnl_socket_sendto(handle->h, nlh, nlh->nlmsg_len) < 0)
return -ECOMM;
D("message sent");
ret = mnl_socket_recvfrom(handle->h, buffer, len);
D("message received, ret: %d", ret);
while (ret > 0) {
ret = mnl_cb_run2(buffer, ret,
handle->seq, handle->portid,
handle->cb_ctl[NLMSG_MIN_TYPE],
handle->data,
handle->cb_ctl, NLMSG_MIN_TYPE);
D("nfln_cb_run2, ret: %d", ret);
if (ret <= 0)
break;
ret = mnl_socket_recvfrom(handle->h, buffer, len);
D("message received, ret: %d", ret);
}
return ret > 0 ? 0 : ret;
}
static int ipset_mnl_getid_acb(const struct nlattr *attr, void *datap)
{
const struct nlattr **tb = datap;
uint16_t type = mnl_attr_get_type(attr);
if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
return MNL_CB_OK;
tb[type] = attr;
return MNL_CB_OK;
}
static int ipset_mnl_getid_cb(const struct nlmsghdr *nlhdr, void *datap)
{
struct ipset_handle *h = datap;
const struct nlattr *tb[CTRL_ATTR_MAX+1] = {0};
const struct genlmsghdr *ghdr = mnl_nlmsg_get_payload(nlhdr);
int ret;
ret = mnl_attr_parse(nlhdr, sizeof(*ghdr), ipset_mnl_getid_acb, tb);
if (ret != MNL_CB_OK)
return ret;
if (tb[CTRL_ATTR_FAMILY_ID] != NULL)
h->genl_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]);
return MNL_CB_OK;
}
/**
* Look up the GENL identifier for the ip_set subsystem, and store it in
* @h->genl_id. On success, 0 is returned, otherwise error encoded as
* negative number.
*/
static int ipset_mnl_getid(struct ipset_handle *h, bool modprobe)
{
size_t buf_size = 8192; //MNL_SOCKET_BUFFER_SIZE;
struct nlmsghdr *nlhdr;
struct genlmsghdr *ghdr;
char *buf;
int ret = -ENOENT;
h->genl_id = 0;
if (modprobe) {
/* genetlink has no autoloading like nfnetlink... */
system("/sbin/modprobe -q ip_set");
}
buf = malloc(buf_size);
if (buf == NULL)
return -errno;
nlhdr = mnl_nlmsg_put_header(buf);
nlhdr->nlmsg_type = GENL_ID_CTRL;
nlhdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
ghdr = mnl_nlmsg_put_extra_header(nlhdr, sizeof(struct genlmsghdr));
ghdr->cmd = CTRL_CMD_GETFAMILY;
ghdr->version = 2;
if (!mnl_attr_put_strz_check(nlhdr, buf_size,
CTRL_ATTR_FAMILY_NAME, "ip_set"))
goto out;
ret = mnl_socket_sendto(h->h, buf, nlhdr->nlmsg_len);
if (ret < 0)
goto out;
ret = mnl_socket_recvfrom(h->h, buf, buf_size);
while (ret > 0) {
ret = mnl_cb_run(buf, ret, 0, 0, ipset_mnl_getid_cb, h);
if (ret <= 0)
break;
ret = mnl_socket_recvfrom(h->h, buf, buf_size);
}
if (h->genl_id == 0 && !modprobe)
/* Redo, with modprobe this time. */
ret = ipset_mnl_getid(h, true);
if (h->genl_id > 0)
ret = 0;
out:
free(buf);
return ret;
}
static struct ipset_handle *
ipset_mnl_init(mnl_cb_t *cb_ctl, void *data)
{
struct ipset_handle *handle;
assert(cb_ctl);
assert(data);
handle = calloc(1, sizeof(*handle));
if (!handle)
return NULL;
handle->h = mnl_socket_open(NETLINK_GENERIC);
if (!handle->h)
goto free_handle;
if (mnl_socket_bind(handle->h, 0, MNL_SOCKET_AUTOPID) < 0)
goto close_nl;
handle->portid = mnl_socket_get_portid(handle->h);
handle->cb_ctl = cb_ctl;
handle->data = data;
if (ipset_mnl_getid(handle, false) < 0)
goto close_nl;
return handle;
close_nl:
mnl_socket_close(handle->h);
free_handle:
free(handle);
return NULL;
}
static int
ipset_mnl_fini(struct ipset_handle *handle)
{
assert(handle);
if (handle->h)
mnl_socket_close(handle->h);
free(handle);
return 0;
}
const struct ipset_transport ipset_mnl_transport = {
.init = ipset_mnl_init,
.fini = ipset_mnl_fini,
.fill_hdr = ipset_mnl_fill_hdr,
.query = ipset_mnl_query,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,743 @@
/* 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 <stdio.h> /* snprintf */
#include <netdb.h> /* getservbyport */
#include <sys/types.h> /* inet_ntop */
#include <sys/socket.h> /* inet_ntop */
#include <arpa/inet.h> /* inet_ntop */
#include <net/ethernet.h> /* ETH_ALEN */
#include <libipset/debug.h> /* D() */
#include <libipset/data.h> /* ipset_data_* */
#include <libipset/icmp.h> /* icmp_to_name */
#include <libipset/icmpv6.h> /* icmpv6_to_name */
#include <libipset/parse.h> /* IPSET_*_SEPARATOR */
#include <libipset/types.h> /* ipset set types */
#include <libipset/session.h> /* IPSET_FLAG_ */
#include <libipset/utils.h> /* UNUSED */
#include <libipset/ui.h> /* IPSET_ENV_* */
#include <libipset/print.h> /* prototypes */
/* Print data (to output buffer). All function must follow snprintf. */
#define SNPRINTF_FAILURE(size, len, offset) \
do { \
if (size < 0 || (unsigned int) size >= len) \
return size; \
offset += size; \
len -= size; \
} while (0)
/**
* ipset_print_ether - print ethernet address to string
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print Ethernet address to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_ether(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
const unsigned char *ether;
int i, size, offset = 0;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_ETHER);
if (len < ETH_ALEN*3)
return -1;
ether = ipset_data_get(data, opt);
assert(ether);
size = snprintf(buf, len, "%02X", ether[0]);
SNPRINTF_FAILURE(size, len, offset);
for (i = 1; i < ETH_ALEN; i++) {
size = snprintf(buf + offset, len, ":%02X", ether[i]);
SNPRINTF_FAILURE(size, len, offset);
}
return offset;
}
/**
* ipset_print_family - print INET family
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print INET family string to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_family(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
uint8_t family;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_FAMILY);
if (len < strlen("inet6") + 1)
return -1;
family = ipset_data_family(data);
return snprintf(buf, len, "%s",
family == AF_INET ? "inet" :
family == AF_INET6 ? "inet6" : "any");
}
/**
* ipset_print_type - print ipset type string
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print ipset module string identifier to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_type(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
const struct ipset_type *type;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_TYPE);
type = ipset_data_get(data, opt);
assert(type);
if (len < strlen(type->name) + 1)
return -1;
return snprintf(buf, len, "%s", type->name);
}
#define GETNAMEINFO(family, f, n) \
static inline int \
__getnameinfo##f(char *buf, unsigned int len, \
int flags, const union nf_inet_addr *addr) \
{ \
struct sockaddr_in##n saddr; \
int err; \
\
memset(&saddr, 0, sizeof(saddr)); \
in##f##cpy(&saddr.sin##n##_addr, &addr->in##n); \
saddr.sin##n##_family = family; \
\
err = getnameinfo((const struct sockaddr *)&saddr, \
sizeof(saddr), \
buf, len, NULL, 0, flags); \
\
if (err == EAI_AGAIN && !(flags & NI_NUMERICHOST)) \
err = getnameinfo((const struct sockaddr *)&saddr, \
sizeof(saddr), \
buf, len, NULL, 0, \
flags | NI_NUMERICHOST); \
D("getnameinfo err: %i, errno %i", err, errno); \
return (err == 0 ? (int)strlen(buf) : \
(err == EAI_OVERFLOW || err == EAI_SYSTEM) ? (int)len : -1);\
}
#define SNPRINTF_IP(mask, f) \
static int \
snprintf_ipv##f(char *buf, unsigned int len, int flags, \
const union nf_inet_addr *ip, uint8_t cidr) \
{ \
int size, offset = 0; \
\
size = __getnameinfo##f(buf, len, flags, ip); \
SNPRINTF_FAILURE(size, len, offset); \
\
D("cidr %u mask %u", cidr, mask); \
if (cidr == mask) \
return offset; \
D("print cidr"); \
size = snprintf(buf + offset, len, \
"%s%u", IPSET_CIDR_SEPARATOR, cidr); \
SNPRINTF_FAILURE(size, len, offset); \
return offset; \
}
GETNAMEINFO(AF_INET, 4, )
SNPRINTF_IP(32, 4)
GETNAMEINFO(AF_INET6, 6, 6)
SNPRINTF_IP(128, 6)
/**
* ipset_print_ip - print IPv4|IPv6 address to string
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print IPv4|IPv6 address, address/cidr or address range to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_ip(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env)
{
const union nf_inet_addr *ip;
uint8_t family, cidr;
int flags, size, offset = 0;
enum ipset_opt cidropt;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2);
D("len: %u", len);
family = ipset_data_family(data);
cidropt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR : IPSET_OPT_CIDR2;
if (ipset_data_test(data, cidropt)) {
cidr = *(const uint8_t *) ipset_data_get(data, cidropt);
D("CIDR: %u", cidr);
} else
cidr = family == AF_INET6 ? 128 : 32;
flags = env & (1 << IPSET_ENV_RESOLVE) ? 0 : NI_NUMERICHOST;
ip = ipset_data_get(data, opt);
assert(ip);
if (family == AF_INET)
size = snprintf_ipv4(buf, len, flags, ip, cidr);
else if (family == AF_INET6)
size = snprintf_ipv6(buf, len, flags, ip, cidr);
else
return -1;
D("size %i, len %u", size, len);
SNPRINTF_FAILURE(size, len, offset);
D("len: %u, offset %u", len, offset);
if (!ipset_data_test(data, IPSET_OPT_IP_TO))
return offset;
size = snprintf(buf + offset, len, "%s", IPSET_RANGE_SEPARATOR);
SNPRINTF_FAILURE(size, len, offset);
ip = ipset_data_get(data, IPSET_OPT_IP_TO);
if (family == AF_INET)
size = snprintf_ipv4(buf + offset, len, flags, ip, cidr);
else if (family == AF_INET6)
size = snprintf_ipv6(buf + offset, len, flags, ip, cidr);
else
return -1;
SNPRINTF_FAILURE(size, len, offset);
return offset;
}
/**
* ipset_print_ipaddr - print IPv4|IPv6 address to string
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print IPv4|IPv6 address or address/cidr to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_ipaddr(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env)
{
const union nf_inet_addr *ip;
uint8_t family, cidr;
enum ipset_opt cidropt;
int flags;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_IP
|| opt == IPSET_OPT_IP_TO
|| opt == IPSET_OPT_IP2);
family = ipset_data_family(data);
cidropt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR : IPSET_OPT_CIDR2;
if (ipset_data_test(data, cidropt))
cidr = *(const uint8_t *) ipset_data_get(data, cidropt);
else
cidr = family == AF_INET6 ? 128 : 32;
flags = env & (1 << IPSET_ENV_RESOLVE) ? 0 : NI_NUMERICHOST;
ip = ipset_data_get(data, opt);
assert(ip);
if (family == AF_INET)
return snprintf_ipv4(buf, len, flags, ip, cidr);
else if (family == AF_INET6)
return snprintf_ipv6(buf, len, flags, ip, cidr);
return -1;
}
/**
* ipset_print_number - print number to string
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print number to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_number(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
size_t maxsize;
const void *number;
assert(buf);
assert(len > 0);
assert(data);
number = ipset_data_get(data, opt);
maxsize = ipset_data_sizeof(opt, AF_INET);
D("opt: %u, maxsize %zu", opt, maxsize);
if (maxsize == sizeof(uint8_t))
return snprintf(buf, len, "%u", *(const uint8_t *) number);
else if (maxsize == sizeof(uint16_t))
return snprintf(buf, len, "%u", *(const uint16_t *) number);
else if (maxsize == sizeof(uint32_t))
return snprintf(buf, len, "%lu",
(long unsigned) *(const uint32_t *) number);
else
assert(0);
return 0;
}
/**
* ipset_print_name - print setname element string
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print setname element string to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_name(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
const char *name;
int size, offset = 0;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_NAME);
if (len < 2*IPSET_MAXNAMELEN + 2 + strlen("before"))
return -1;
name = ipset_data_get(data, opt);
assert(name);
size = snprintf(buf, len, "%s", name);
SNPRINTF_FAILURE(size, len, offset);
if (ipset_data_test(data, IPSET_OPT_NAMEREF)) {
bool before = false;
if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_FLAGS))) {
const uint32_t *flags =
ipset_data_get(data, IPSET_OPT_FLAGS);
before = (*flags) & IPSET_FLAG_BEFORE;
}
size = snprintf(buf + offset, len,
" %s %s", before ? "before" : "after",
(const char *) ipset_data_get(data,
IPSET_OPT_NAMEREF));
SNPRINTF_FAILURE(size, len, offset);
}
return offset;
}
/**
* ipset_print_port - print port or port range
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print port or port range to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_port(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
const uint16_t *port;
int size, offset = 0;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_PORT);
if (len < 2*strlen("65535") + 2)
return -1;
port = ipset_data_get(data, IPSET_OPT_PORT);
assert(port);
size = snprintf(buf, len, "%u", *port);
SNPRINTF_FAILURE(size, len, offset);
if (ipset_data_test(data, IPSET_OPT_PORT_TO)) {
port = ipset_data_get(data, IPSET_OPT_PORT_TO);
size = snprintf(buf + offset, len,
"%s%u",
IPSET_RANGE_SEPARATOR, *port);
SNPRINTF_FAILURE(size, len, offset);
}
return offset;
}
/**
* ipset_print_proto - print protocol name
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print protocol name to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_proto(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
const struct protoent *protoent;
uint8_t proto;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_PROTO);
proto = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_PROTO);
assert(proto);
protoent = getprotobynumber(proto);
if (protoent)
return snprintf(buf, len, "%s", protoent->p_name);
/* Should not happen */
return snprintf(buf, len, "%u", proto);
}
/**
* ipset_print_icmp - print ICMP code name or type/code
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print ICMP code name or type/code name to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_icmp(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
const char *name;
uint16_t typecode;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_PORT);
typecode = *(const uint16_t *) ipset_data_get(data, IPSET_OPT_PORT);
name = icmp_to_name(typecode >> 8, typecode & 0xFF);
if (name != NULL)
return snprintf(buf, len, "%s", name);
else
return snprintf(buf, len, "%u/%u", typecode >> 8, typecode & 0xFF);
}
/**
* ipset_print_icmpv6 - print ICMPv6 code name or type/code
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print ICMPv6 code name or type/code name to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_icmpv6(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
const char *name;
uint16_t typecode;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_PORT);
typecode = *(const uint16_t *) ipset_data_get(data, IPSET_OPT_PORT);
name = icmpv6_to_name(typecode >> 8, typecode & 0xFF);
if (name != NULL)
return snprintf(buf, len, "%s", name);
else
return snprintf(buf, len, "%u/%u", typecode >> 8, typecode & 0xFF);
}
/**
* ipset_print_proto_port - print proto:port
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print protocol and port to output buffer.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_proto_port(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env UNUSED)
{
int size, offset = 0;
assert(buf);
assert(len > 0);
assert(data);
assert(opt == IPSET_OPT_PORT);
if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_PROTO))) {
uint8_t proto = *(const uint8_t *) ipset_data_get(data,
IPSET_OPT_PROTO);
size = ipset_print_proto(buf, len, data, IPSET_OPT_PROTO, env);
SNPRINTF_FAILURE(size, len, offset);
if (len < 2)
return -ENOSPC;
size = snprintf(buf + offset, len, IPSET_PROTO_SEPARATOR);
SNPRINTF_FAILURE(size, len, offset);
switch (proto) {
case IPPROTO_TCP:
case IPPROTO_UDP:
break;
case IPPROTO_ICMP:
return ipset_print_icmp(buf + offset, len, data,
IPSET_OPT_PORT, env);
case IPPROTO_ICMPV6:
return ipset_print_icmpv6(buf + offset, len, data,
IPSET_OPT_PORT, env);
default:
break;
}
}
size = ipset_print_port(buf + offset, len, data, IPSET_OPT_PORT, env);
SNPRINTF_FAILURE(size, len, offset);
return offset;
}
#define print_second(data) \
ipset_data_flags_test(data, \
IPSET_FLAG(IPSET_OPT_PORT)|IPSET_FLAG(IPSET_OPT_ETHER))
#define print_third(data) \
ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_IP2))
/**
* ipset_print_elem - print ADT elem according to settype
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print (multipart) element according to settype
*
* Return lenght of printed string or error size.
*/
int
ipset_print_elem(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt UNUSED,
uint8_t env)
{
const struct ipset_type *type;
int size, offset = 0;
assert(buf);
assert(len > 0);
assert(data);
type = ipset_data_get(data, IPSET_OPT_TYPE);
if (!type)
return -1;
size = type->elem[IPSET_DIM_ONE].print(buf, len, data,
type->elem[IPSET_DIM_ONE].opt, env);
SNPRINTF_FAILURE(size, len, offset);
IF_D(ipset_data_test(data, type->elem[IPSET_DIM_TWO].opt),
"print second elem");
if (type->dimension == IPSET_DIM_ONE
|| (type->last_elem_optional
&& !ipset_data_test(data, type->elem[IPSET_DIM_TWO].opt)))
return offset;
size = snprintf(buf + offset, len, IPSET_ELEM_SEPARATOR);
SNPRINTF_FAILURE(size, len, offset);
size = type->elem[IPSET_DIM_TWO].print(buf + offset, len, data,
type->elem[IPSET_DIM_TWO].opt, env);
SNPRINTF_FAILURE(size, len, offset);
if (type->dimension == IPSET_DIM_TWO
|| (type->last_elem_optional
&& !ipset_data_test(data, type->elem[IPSET_DIM_THREE].opt)))
return offset;
size = snprintf(buf + offset, len, IPSET_ELEM_SEPARATOR);
SNPRINTF_FAILURE(size, len, offset);
size = type->elem[IPSET_DIM_THREE].print(buf + offset, len, data,
type->elem[IPSET_DIM_THREE].opt, env);
SNPRINTF_FAILURE(size, len, offset);
return offset;
}
/**
* ipset_print_flag - print a flag
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Print a flag, i.e. option without value
*
* Return lenght of printed string or error size.
*/
int
ipset_print_flag(char *buf UNUSED, unsigned int len UNUSED,
const struct ipset_data *data UNUSED,
enum ipset_opt opt UNUSED, uint8_t env UNUSED)
{
return 0;
}
/**
* ipset_print_data - print data, generic fuction
* @buf: printing buffer
* @len: length of available buffer space
* @data: data blob
* @opt: the option kind
* @env: environment flags
*
* Generic wrapper of the printing functions.
*
* Return lenght of printed string or error size.
*/
int
ipset_print_data(char *buf, unsigned int len,
const struct ipset_data *data, enum ipset_opt opt,
uint8_t env)
{
int size = 0, offset = 0;
assert(buf);
assert(len > 0);
assert(data);
switch (opt) {
case IPSET_OPT_FAMILY:
size = ipset_print_family(buf, len, data, opt, env);
break;
case IPSET_OPT_TYPE:
size = ipset_print_type(buf, len, data, opt, env);
break;
case IPSET_SETNAME:
size = snprintf(buf, len, "%s", ipset_data_setname(data));
break;
case IPSET_OPT_ELEM:
size = ipset_print_elem(buf, len, data, opt, env);
break;
case IPSET_OPT_IP:
size = ipset_print_ip(buf, len, data, opt, env);
break;
case IPSET_OPT_PORT:
size = ipset_print_port(buf, len, data, opt, env);
break;
case IPSET_OPT_GC:
case IPSET_OPT_HASHSIZE:
case IPSET_OPT_MAXELEM:
case IPSET_OPT_NETMASK:
case IPSET_OPT_PROBES:
case IPSET_OPT_RESIZE:
case IPSET_OPT_TIMEOUT:
case IPSET_OPT_REFERENCES:
case IPSET_OPT_ELEMENTS:
case IPSET_OPT_SIZE:
size = ipset_print_number(buf, len, data, opt, env);
break;
default:
return -1;
}
SNPRINTF_FAILURE(size, len, offset);
return offset;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,538 @@
/* 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 <net/ethernet.h> /* ETH_ALEN */
#include <netinet/in.h> /* struct in6_addr */
#include <sys/socket.h> /* AF_ */
#include <stdlib.h> /* malloc, free */
#include <stdio.h> /* FIXME: debug */
#include <libmnl/libmnl.h> /* MNL_ALIGN */
#include <libipset/debug.h> /* D() */
#include <libipset/data.h> /* ipset_data_* */
#include <libipset/session.h> /* ipset_cmd */
#include <libipset/utils.h> /* STREQ */
#include <libipset/types.h> /* prototypes */
/* Userspace cache of sets which exists in the kernel */
struct ipset {
char name[IPSET_MAXNAMELEN]; /* set name */
const struct ipset_type *type; /* set type */
uint8_t family; /* family */
struct ipset *next;
};
static struct ipset_type *typelist = NULL; /* registered set types */
static struct ipset *setlist = NULL; /* cached sets */
/**
* ipset_cache_add - add a set to the cache
* @name: set name
* @type: set type structure
*
* Add the named set to the internal cache with the specified
* set type. The set name must be unique.
*
* Returns 0 on success or a negative error code.
*/
int
ipset_cache_add(const char *name, const struct ipset_type *type,
uint8_t family)
{
struct ipset *s, *n;
assert(name);
assert(type);
n = malloc(sizeof(*n));
if (n == NULL)
return -ENOMEM;
ipset_strlcpy(n->name, name, IPSET_MAXNAMELEN);
n->type = type;
n->family = family;
n->next = NULL;
if (setlist == NULL) {
setlist = n;
return 0;
}
for (s = setlist; s->next != NULL; s = s->next) {
if (STREQ(name, s->name)) {
free(n);
return -EEXIST;
}
}
s->next = n;
return 0;
}
/**
* ipset_cache_del - delete set from the cache
* @name: set name
*
* Delete the named set from the internal cache. If NULL is
* specified as setname, the whole cache is emptied.
*
* Returns 0 on success or a negative error code.
*/
int
ipset_cache_del(const char *name)
{
struct ipset *s, *match = NULL, *prev = NULL;
if (!name) {
for (s = setlist; s != NULL; ) {
prev = s;
s = s->next;
free(prev);
}
setlist = NULL;
return 0;
}
for (s = setlist; s != NULL && match == NULL; s = s->next) {
if (STREQ(s->name, name)) {
match = s;
if (prev == NULL)
setlist = match->next;
else
prev->next = match->next;
}
prev = s;
}
if (match == NULL)
return -EEXIST;
free(match);
return 0;
}
/**
* ipset_cache_rename - rename a set in the cache
* @from: the set to rename
* @to: the new name of the set
*
* Rename the given set in the cache.
*
* Returns 0 on success or a negative error code.
*/
int
ipset_cache_rename(const char *from, const char *to)
{
struct ipset *s;
assert(from);
assert(to);
for (s = setlist; s != NULL; s = s->next) {
if (STREQ(s->name, from)) {
ipset_strlcpy(s->name, to, IPSET_MAXNAMELEN);
return 0;
}
}
return -EEXIST;
}
/**
* ipset_cache_swap - swap two sets in the cache
* @from: the first set
* @to: the second set
*
* Swap two existing sets in the cache.
*
* Returns 0 on success or a negative error code.
*/
int
ipset_cache_swap(const char *from, const char *to)
{
struct ipset *s, *a = NULL, *b = NULL;
assert(from);
assert(to);
for (s = setlist; s != NULL && (a == NULL || b == NULL); s = s->next) {
if (a == NULL && STREQ(s->name, from))
a = s;
if (b == NULL && STREQ(s->name, to))
b = s;
}
if (a != NULL && b != NULL) {
ipset_strlcpy(a->name, to, IPSET_MAXNAMELEN);
ipset_strlcpy(b->name, from, IPSET_MAXNAMELEN);
return 0;
}
return -EEXIST;
}
#define MATCH_FAMILY(type, f) \
(f == AF_UNSPEC || type->family == f || type->family == AF_INET46)
bool
ipset_match_typename(const char *name, const struct ipset_type *type)
{
const char * const * alias = type->alias;
if (STREQ(name, type->name))
return true;
while (alias[0]) {
if (STREQ(name, alias[0]))
return true;
alias++;
}
return false;
}
static inline const struct ipset_type *
create_type_get(struct ipset_session *session)
{
struct ipset_type *t, *match = NULL;
struct ipset_data *data;
const char *typename;
uint8_t family, tmin = 0, tmax = 0;
const uint8_t *kmin, *kmax;
int ret;
data = ipset_session_data(session);
assert(data);
typename = ipset_data_get(data, IPSET_OPT_TYPENAME);
assert(typename);
family = ipset_data_family(data);
/* Check registered types in userspace */
for (t = typelist; t != NULL; t = t->next) {
/* Skip revisions which are unsupported by the kernel */
if (t->kernel_check == IPSET_KERNEL_MISMATCH)
continue;
if (ipset_match_typename(typename, t)
&& MATCH_FAMILY(t, family)) {
if (match == NULL) {
match = t;
tmax = t->revision;
} else if (t->family == match->family)
tmin = t->revision;
}
}
if (!match)
return ipset_errptr(session,
"Syntax error: unknown settype %s",
typename);
/* Family is unspecified yet: set from matching set type */
if (family == AF_UNSPEC && match->family != AF_UNSPEC) {
family = match->family == AF_INET46 ? AF_INET : match->family;
ipset_data_set(data, IPSET_OPT_FAMILY, &family);
}
if (match->kernel_check == IPSET_KERNEL_OK)
goto found;
/* Check kernel */
ret = ipset_cmd(session, IPSET_CMD_TYPE, 0);
if (ret != 0)
return NULL;
kmax = ipset_data_get(data, IPSET_OPT_REVISION);
if (ipset_data_test(data, IPSET_OPT_REVISION_MIN))
kmin = ipset_data_get(data, IPSET_OPT_REVISION_MIN);
else
kmin = kmax;
if (MAX(tmin, *kmin) > MIN(tmax, *kmax)) {
if (*kmin > tmax)
return ipset_errptr(session,
"Kernel supports %s type with family %s "
"in minimal revision %u while ipset library "
"in maximal revision %u. "
"You need to upgrade your ipset library.",
typename,
family == AF_INET ? "INET" :
family == AF_INET6 ? "INET6" : "UNSPEC",
*kmin, tmax);
else
return ipset_errptr(session,
"Kernel supports %s type with family %s "
"in maximal revision %u while ipset library "
"in minimal revision %u. "
"You need to upgrade your kernel.",
typename,
family == AF_INET ? "INET" :
family == AF_INET6 ? "INET6" : "UNSPEC",
*kmax, tmin);
}
match->kernel_check = IPSET_KERNEL_OK;
found:
ipset_data_set(data, IPSET_OPT_TYPE, match);
return match;
}
#define set_family_and_type(data, match, family) do { \
if (family == AF_UNSPEC && match->family != AF_UNSPEC) \
family = match->family == AF_INET46 ? AF_INET : match->family;\
ipset_data_set(data, IPSET_OPT_FAMILY, &family); \
ipset_data_set(data, IPSET_OPT_TYPE, match); \
} while (0)
static inline const struct ipset_type *
adt_type_get(struct ipset_session *session)
{
struct ipset_data *data;
struct ipset *s;
struct ipset_type *t;
const struct ipset_type *match;
const char *setname, *typename;
const uint8_t *revision;
uint8_t family = AF_UNSPEC;
int ret;
data = ipset_session_data(session);
assert(data);
setname = ipset_data_setname(data);
assert(setname);
/* Check existing sets in cache */
for (s = setlist; s != NULL; s = s->next) {
if (STREQ(setname, s->name)) {
ipset_data_set(data, IPSET_OPT_FAMILY, &s->family);
ipset_data_set(data, IPSET_OPT_TYPE, s->type);
return s->type;
}
}
/* Check kernel */
ret = ipset_cmd(session, IPSET_CMD_HEADER, 0);
if (ret != 0)
return NULL;
typename = ipset_data_get(data, IPSET_OPT_TYPENAME);
revision = ipset_data_get(data, IPSET_OPT_REVISION);
family = ipset_data_family(data);
/* Check registered types */
for (t = typelist, match = NULL;
t != NULL && match == NULL; t = t->next) {
if (t->kernel_check == IPSET_KERNEL_MISMATCH)
continue;
if (STREQ(typename, t->name)
&& MATCH_FAMILY(t, family)
&& *revision == t->revision) {
t->kernel_check = IPSET_KERNEL_OK;
match = t;
}
}
if (!match)
return ipset_errptr(session,
"Kernel-library incompatibility: "
"set %s in kernel has got settype %s "
"with family %s and revision %u while "
"ipset library does not support the "
"settype with that family and revision.",
setname, typename,
family == AF_INET ? "inet" :
family == AF_INET6 ? "inet6" : "unspec",
*revision);
set_family_and_type(data, match, family);
return match;
}
/**
* ipset_type_get - get a set type from the kernel
* @session: session structure
* @cmd: the command which needs the set type
*
* Build up and send a private message to the kernel in order to
* get the set type. When creating the set, we send the typename
* and family and get the supported revisions of the given set type.
* When adding/deleting/testing an entry, we send the setname and
* receive the typename, family and revision.
*
* Returns the set type for success and NULL for failure.
*/
const struct ipset_type *
ipset_type_get(struct ipset_session *session, enum ipset_cmd cmd)
{
assert(session);
switch (cmd) {
case IPSET_CMD_CREATE:
return create_type_get(session);
case IPSET_CMD_ADD:
case IPSET_CMD_DEL:
case IPSET_CMD_TEST:
return adt_type_get(session);
default:
break;
}
assert(cmd == IPSET_CMD_NONE);
return NULL;
}
/**
* ipset_type_check - check the set type received from kernel
* @session: session structure
*
* Check the set type received from the kernel (typename, revision,
* family) against the userspace types looking for a matching type.
*
* Returns the set type for success and NULL for failure.
*/
const struct ipset_type *
ipset_type_check(struct ipset_session *session)
{
const struct ipset_type *t, *match = NULL;
struct ipset_data *data;
const char *typename;
uint8_t family = AF_UNSPEC, revision;
assert(session);
data = ipset_session_data(session);
assert(data);
typename = ipset_data_get(data, IPSET_OPT_TYPENAME);
family = ipset_data_family(data);
revision = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_REVISION);
/* Check registered types */
for (t = typelist; t != NULL && match == NULL; t = t->next) {
if (t->kernel_check == IPSET_KERNEL_MISMATCH)
continue;
if (ipset_match_typename(typename, t)
&& MATCH_FAMILY(t, family)
&& t->revision == revision)
match = t;
}
if (!match)
return ipset_errptr(session,
"Kernel and userspace incompatible: "
"settype %s with revision %u not supported ",
"by userspace.", typename, revision);
set_family_and_type(data, match, family);
return match;
}
/**
* ipset_type_add - add (register) a userspace set type
* @type: pointer to the set type structure
*
* Add the given set type to the type list. The types
* are added sorted, in descending revision number.
*
* Returns 0 on success or a negative error code.
*/
int
ipset_type_add(struct ipset_type *type)
{
struct ipset_type *t, *prev;
assert(type);
/* Add to the list: higher revision numbers first */
for (t = typelist, prev = NULL; t != NULL; t = t->next) {
if (STREQ(t->name, type->name)) {
if (t->revision == type->revision) {
errno = EEXIST;
return -1;
} else if (t->revision < type->revision) {
type->next = t;
if (prev)
prev->next = type;
else
typelist = type;
return 0;
}
}
if (t->next != NULL && STREQ(t->next->name, type->name)) {
if (t->next->revision == type->revision) {
errno = EEXIST;
return -1;
} else if (t->next->revision < type->revision) {
type->next = t->next;
t->next = type;
return 0;
}
}
prev = t;
}
type->next = typelist;
typelist = type;
return 0;
}
/**
* ipset_typename_resolve - resolve typename alias
* @str: typename or alias
*
* Check the typenames (and aliases) and return the
* preferred name of the set type.
*
* Returns the name of the matching set type or NULL.
*/
const char *
ipset_typename_resolve(const char *str)
{
const struct ipset_type *t;
for (t = typelist; t != NULL; t = t->next)
if (ipset_match_typename(str, t))
return t->name;
return NULL;
}
/**
* ipset_types - return the list of the set types
*
* The types can be unchecked with respect of the running kernel.
* Only useful for type specific help.
*
* Returns the list of the set types.
*/
const struct ipset_type *
ipset_types(void)
{
return typelist;
}
/**
* ipset_cache_init - initialize set cache
*
* Initialize the set cache in userspace.
*
* Returns 0 on success or a negative error code.
*/
int
ipset_cache_init(void)
{
return 0;
}
/**
* ipset_cache_fini - release the set cache
*
* Release the set cache.
*/
void
ipset_cache_fini(void)
{
struct ipset *set;
while (setlist) {
set = setlist;
setlist = setlist->next;
free(set);
}
}