blob: 8372a2ffb8c4e6639e5561e8e28f191c661bca1a [file] [log] [blame]
/* 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 <net/if.h> /* IFNAMSIZ */
#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;
uint32_t mark;
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 markmask;
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;
union nf_inet_addr ip2_to;
uint8_t cidr2;
uint8_t proto;
char ether[ETH_ALEN];
char name[IPSET_MAXNAMELEN];
char nameref[IPSET_MAXNAMELEN];
char iface[IFNAMSIZ];
uint64_t packets;
uint64_t bytes;
char comment[IPSET_MAX_COMMENT_SIZE+1];
uint64_t skbmark;
uint32_t skbprio;
uint16_t skbqueue;
} adt;
};
};
static void
copy_addr(uint8_t family, union nf_inet_addr *ip, const void *value)
{
if (family == NFPROTO_IPV4)
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_strlcat - concatenate the string from src to the end of dst
* @dst: the target string buffer
* @src: the source string buffer
* @len: the length of bytes to concat, including the terminating null byte.
*
* Cooncatenate the string in src to destination, but at most len bytes are
* copied. The target is unconditionally terminated by the null byte.
*/
void
ipset_strlcat(char *dst, const char *src, size_t len)
{
assert(dst);
assert(src);
strncat(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 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_test_ignored - test ignored bits in the data blob
* @data: data blob
* @flags: the option flag to be tested
*
* Returns true if the option is ignored.
*/
bool
ipset_data_test_ignored(struct ipset_data *data, enum ipset_opt opt)
{
assert(data);
return data->ignored & IPSET_FLAG(opt);
}
/**
* 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;
data->ignored &= ~IPSET_FLAG(IPSET_OPT_FAMILY);
D("family set to %u", data->family);
break;
/* CADT options */
case IPSET_OPT_IP:
if (!(data->family == NFPROTO_IPV4 ||
data->family == NFPROTO_IPV6))
return -1;
copy_addr(data->family, &data->ip, value);
break;
case IPSET_OPT_IP_TO:
if (!(data->family == NFPROTO_IPV4 ||
data->family == NFPROTO_IPV6))
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_MARK:
data->mark = *(const uint32_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_MARKMASK:
data->create.markmask = *(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;
case IPSET_OPT_COUNTERS:
cadt_flag_type_attr(data, opt, IPSET_FLAG_WITH_COUNTERS);
break;
case IPSET_OPT_CREATE_COMMENT:
cadt_flag_type_attr(data, opt, IPSET_FLAG_WITH_COMMENT);
break;
case IPSET_OPT_FORCEADD:
cadt_flag_type_attr(data, opt, IPSET_FLAG_WITH_FORCEADD);
break;
case IPSET_OPT_SKBINFO:
cadt_flag_type_attr(data, opt, IPSET_FLAG_WITH_SKBINFO);
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 == NFPROTO_IPV4 ||
data->family == NFPROTO_IPV6))
return -1;
copy_addr(data->family, &data->adt.ip2, value);
break;
case IPSET_OPT_IP2_TO:
if (!(data->family == NFPROTO_IPV4 ||
data->family == NFPROTO_IPV6))
return -1;
copy_addr(data->family, &data->adt.ip2_to, 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;
case IPSET_OPT_IFACE:
ipset_strlcpy(data->adt.iface, value, IFNAMSIZ);
break;
case IPSET_OPT_PACKETS:
data->adt.packets = *(const uint64_t *) value;
break;
case IPSET_OPT_BYTES:
data->adt.bytes = *(const uint64_t *) value;
break;
case IPSET_OPT_ADT_COMMENT:
ipset_strlcpy(data->adt.comment, value,
IPSET_MAX_COMMENT_SIZE + 1);
break;
case IPSET_OPT_SKBMARK:
data->adt.skbmark = *(const uint64_t *) value;
break;
case IPSET_OPT_SKBPRIO:
data->adt.skbprio = *(const uint32_t *) value;
break;
case IPSET_OPT_SKBQUEUE:
data->adt.skbqueue = *(const uint16_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_PHYSDEV:
cadt_flag_type_attr(data, opt, IPSET_FLAG_PHYSDEV);
break;
case IPSET_OPT_NOMATCH:
cadt_flag_type_attr(data, opt, IPSET_FLAG_NOMATCH);
break;
case IPSET_OPT_FLAGS:
data->flags = *(const uint32_t *)value;
break;
case IPSET_OPT_CADT_FLAGS:
data->cadt_flags = *(const uint32_t *)value;
if (data->cadt_flags & IPSET_FLAG_BEFORE)
ipset_data_flags_set(data,
IPSET_FLAG(IPSET_OPT_BEFORE));
if (data->cadt_flags & IPSET_FLAG_PHYSDEV)
ipset_data_flags_set(data,
IPSET_FLAG(IPSET_OPT_PHYSDEV));
if (data->cadt_flags & IPSET_FLAG_NOMATCH)
ipset_data_flags_set(data,
IPSET_FLAG(IPSET_OPT_NOMATCH));
if (data->cadt_flags & IPSET_FLAG_WITH_COUNTERS)
ipset_data_flags_set(data,
IPSET_FLAG(IPSET_OPT_COUNTERS));
if (data->cadt_flags & IPSET_FLAG_WITH_COMMENT)
ipset_data_flags_set(data,
IPSET_FLAG(IPSET_OPT_CREATE_COMMENT));
if (data->cadt_flags & IPSET_FLAG_WITH_SKBINFO)
ipset_data_flags_set(data,
IPSET_FLAG(IPSET_OPT_SKBINFO));
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 unknown 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_MARK:
return &data->mark;
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_MARKMASK:
return &data->create.markmask;
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_IP2_TO:
return &data->adt.ip2_to;
case IPSET_OPT_CIDR2:
return &data->adt.cidr2;
case IPSET_OPT_PROTO:
return &data->adt.proto;
case IPSET_OPT_IFACE:
return &data->adt.iface;
case IPSET_OPT_PACKETS:
return &data->adt.packets;
case IPSET_OPT_BYTES:
return &data->adt.bytes;
case IPSET_OPT_ADT_COMMENT:
return &data->adt.comment;
case IPSET_OPT_SKBMARK:
return &data->adt.skbmark;
case IPSET_OPT_SKBPRIO:
return &data->adt.skbprio;
case IPSET_OPT_SKBQUEUE:
return &data->adt.skbqueue;
/* 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:
case IPSET_OPT_PHYSDEV:
case IPSET_OPT_NOMATCH:
case IPSET_OPT_COUNTERS:
case IPSET_OPT_CREATE_COMMENT:
case IPSET_OPT_FORCEADD:
case IPSET_OPT_SKBINFO:
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:
case IPSET_OPT_IP2_TO:
return family == NFPROTO_IPV4 ? sizeof(uint32_t)
: sizeof(struct in6_addr);
case IPSET_OPT_MARK:
return sizeof(uint32_t);
case IPSET_OPT_PORT:
case IPSET_OPT_PORT_TO:
case IPSET_OPT_SKBQUEUE:
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_MARKMASK:
case IPSET_OPT_SIZE:
case IPSET_OPT_ELEMENTS:
case IPSET_OPT_REFERENCES:
case IPSET_OPT_MEMSIZE:
case IPSET_OPT_SKBPRIO:
return sizeof(uint32_t);
case IPSET_OPT_PACKETS:
case IPSET_OPT_BYTES:
case IPSET_OPT_SKBMARK:
return sizeof(uint64_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 doesn't counted once :-( */
case IPSET_OPT_BEFORE:
case IPSET_OPT_PHYSDEV:
case IPSET_OPT_NOMATCH:
case IPSET_OPT_COUNTERS:
case IPSET_OPT_FORCEADD:
return sizeof(uint32_t);
case IPSET_OPT_ADT_COMMENT:
return IPSET_MAX_COMMENT_SIZE + 1;
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, NFPROTO_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 : NFPROTO_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 == NFPROTO_IPV4 ? 32 :
data->family == NFPROTO_IPV6 ? 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);
}