blob: 1ae2cc58d1b38d32108b141ed17c02b29c87ff3a [file] [log] [blame]
/*
* (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
* (C) 2011-2012 by Vyatta Inc <http://www.vyatta.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "filter.h"
#include "bitops.h"
#include "jhash.h"
#include "hash.h"
#include "vector.h"
#include "conntrackd.h"
#include "log.h"
#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
struct ct_filter {
int logic[CT_FILTER_MAX];
uint32_t l4protomap[IPPROTO_MAX/32];
uint16_t statemap[IPPROTO_MAX];
struct hashtable *h;
struct hashtable *h6;
struct vector *v;
struct vector *v6;
};
/* XXX: These should be configurable, better use a rb-tree */
#define FILTER_POOL_SIZE 128
#define FILTER_POOL_LIMIT INT_MAX
static uint32_t ct_filter_hash(const void *data, const struct hashtable *table)
{
const uint32_t *f = data;
return jhash_1word(*f, 0) % table->hashsize;
}
static uint32_t ct_filter_hash6(const void *data, const struct hashtable *table)
{
return jhash2(data, 4, 0) % table->hashsize;
}
static int ct_filter_compare(const void *data1, const void *data2)
{
const struct ct_filter_ipv4_hnode *f1 = data1;
const uint32_t *f2 = data2;
return f1->ip == *f2;
}
static int ct_filter_compare6(const void *data1, const void *data2)
{
const struct ct_filter_ipv6_hnode *f = data1;
return memcmp(f->ipv6, data2, sizeof(uint32_t)*4) == 0;
}
struct ct_filter *ct_filter_create(void)
{
int i;
struct ct_filter *filter;
filter = calloc(sizeof(struct ct_filter), 1);
if (!filter)
return NULL;
filter->h = hashtable_create(FILTER_POOL_SIZE,
FILTER_POOL_LIMIT,
ct_filter_hash,
ct_filter_compare);
if (!filter->h) {
free(filter);
return NULL;
}
filter->h6 = hashtable_create(FILTER_POOL_SIZE,
FILTER_POOL_LIMIT,
ct_filter_hash6,
ct_filter_compare6);
if (!filter->h6) {
free(filter->h);
free(filter);
return NULL;
}
filter->v = vector_create(sizeof(struct ct_filter_netmask_ipv4));
if (!filter->v) {
free(filter->h6);
free(filter->h);
free(filter);
return NULL;
}
filter->v6 = vector_create(sizeof(struct ct_filter_netmask_ipv6));
if (!filter->v6) {
free(filter->v);
free(filter->h6);
free(filter->h);
free(filter);
return NULL;
}
for (i=0; i<CT_FILTER_MAX; i++)
filter->logic[i] = -1;
return filter;
}
void ct_filter_destroy(struct ct_filter *filter)
{
hashtable_destroy(filter->h);
hashtable_destroy(filter->h6);
vector_destroy(filter->v);
vector_destroy(filter->v6);
free(filter);
}
/* this is ugly, but it simplifies read_config_yy.y */
static struct ct_filter *__filter_alloc(struct ct_filter *filter)
{
if (!STATE(us_filter)) {
STATE(us_filter) = ct_filter_create();
if (!STATE(us_filter)) {
fprintf(stderr, "Can't create ignore pool!\n");
exit(EXIT_FAILURE);
}
}
return STATE(us_filter);
}
void ct_filter_set_logic(struct ct_filter *filter,
enum ct_filter_type type,
enum ct_filter_logic logic)
{
filter = __filter_alloc(filter);
filter->logic[type] = logic;
}
int ct_filter_add_ip(struct ct_filter *filter, void *data, uint8_t family)
{
int id;
filter = __filter_alloc(filter);
switch(family) {
case AF_INET:
id = hashtable_hash(filter->h, data);
if (!hashtable_find(filter->h, data, id)) {
struct ct_filter_ipv4_hnode *n;
n = malloc(sizeof(struct ct_filter_ipv4_hnode));
if (n == NULL)
return 0;
memcpy(&n->ip, data, sizeof(uint32_t));
hashtable_add(filter->h, &n->node, id);
return 0;
}
break;
case AF_INET6:
id = hashtable_hash(filter->h6, data);
if (!hashtable_find(filter->h6, data, id)) {
struct ct_filter_ipv6_hnode *n;
n = malloc(sizeof(struct ct_filter_ipv6_hnode));
if (n == NULL)
return 0;
memcpy(n->ipv6, data, sizeof(uint32_t)*4);
hashtable_add(filter->h6, &n->node, id);
return 0;
}
break;
}
return 1;
}
static int cmp_ipv4_addr(const void *a, const void *b)
{
return memcmp(a, b, sizeof(struct ct_filter_netmask_ipv4)) == 0;
}
static int cmp_ipv6_addr(const void *a, const void *b)
{
return memcmp(a, b, sizeof(struct ct_filter_netmask_ipv6)) == 0;
}
int ct_filter_add_netmask(struct ct_filter *filter, void *data, uint8_t family)
{
filter = __filter_alloc(filter);
switch(family) {
case AF_INET:
if (vector_iterate(filter->v, data, cmp_ipv4_addr)) {
errno = EEXIST;
return 0;
}
vector_add(filter->v, data);
break;
case AF_INET6:
if (vector_iterate(filter->v, data, cmp_ipv6_addr)) {
errno = EEXIST;
return 0;
}
vector_add(filter->v6, data);
break;
}
return 1;
}
void ct_filter_add_proto(struct ct_filter *f, int protonum)
{
f = __filter_alloc(f);
set_bit_u32(protonum, f->l4protomap);
}
void ct_filter_add_state(struct ct_filter *f, int protonum, int val)
{
f = __filter_alloc(f);
set_bit_u16(val, &f->statemap[protonum]);
}
static inline int
__ct_filter_test_ipv4(struct ct_filter *f, const struct nf_conntrack *ct)
{
int id_src, id_dst;
uint32_t src, dst;
/* we only use the real source and destination address */
src = nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_SRC);
dst = nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC);
id_src = hashtable_hash(f->h, &src);
id_dst = hashtable_hash(f->h, &dst);
return hashtable_find(f->h, &src, id_src) ||
hashtable_find(f->h, &dst, id_dst);
}
static inline int
__ct_filter_test_ipv6(struct ct_filter *f, const struct nf_conntrack *ct)
{
int id_src, id_dst;
const uint32_t *src, *dst;
src = nfct_get_attr(ct, ATTR_ORIG_IPV6_SRC);
dst = nfct_get_attr(ct, ATTR_REPL_IPV6_SRC);
id_src = hashtable_hash(f->h6, src);
id_dst = hashtable_hash(f->h6, dst);
return hashtable_find(f->h6, src, id_src) ||
hashtable_find(f->h6, dst, id_dst);
}
static int
__ct_filter_test_mask4(const void *ptr, const void *ct)
{
const struct ct_filter_netmask_ipv4 *elem = ptr;
const uint32_t src = nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_SRC);
const uint32_t dst = nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC);
return ((elem->ip & elem->mask) == (src & elem->mask) ||
(elem->ip & elem->mask) == (dst & elem->mask));
}
static int
__ct_filter_test_mask6(const void *ptr, const void *ct)
{
const struct ct_filter_netmask_ipv6 *elem = ptr;
const uint32_t *src = nfct_get_attr(ct, ATTR_ORIG_IPV6_SRC);
const uint32_t *dst = nfct_get_attr(ct, ATTR_REPL_IPV6_SRC);
return (((elem->ip[0] & elem->mask[0]) == (src[0] & elem->mask[0]) &&
(elem->ip[1] & elem->mask[1]) == (src[1] & elem->mask[1]) &&
(elem->ip[2] & elem->mask[2]) == (src[2] & elem->mask[2]) &&
(elem->ip[3] & elem->mask[3]) == (src[3] & elem->mask[3])) ||
((elem->ip[0] & elem->mask[0]) == (dst[0] & elem->mask[0]) &&
(elem->ip[1] & elem->mask[1]) == (dst[1] & elem->mask[1]) &&
(elem->ip[2] & elem->mask[2]) == (dst[2] & elem->mask[2]) &&
(elem->ip[3] & elem->mask[3]) == (dst[3] & elem->mask[3])));
}
static int
__ct_filter_test_state(struct ct_filter *f, const struct nf_conntrack *ct)
{
uint16_t val = 0;
uint8_t protonum = nfct_get_attr_u8(ct, ATTR_L4PROTO);
switch(protonum) {
case IPPROTO_TCP:
if (!nfct_attr_is_set(ct, ATTR_TCP_STATE))
return -1;
val = nfct_get_attr_u8(ct, ATTR_TCP_STATE);
break;
default:
return -1;
}
return test_bit_u16(val, &f->statemap[protonum]);
}
static int
ct_filter_check(struct ct_filter *f, const struct nf_conntrack *ct)
{
int ret, protonum = nfct_get_attr_u8(ct, ATTR_L4PROTO);
/* no event filtering at all */
if (f == NULL)
return 1;
if (f->logic[CT_FILTER_L4PROTO] != -1) {
ret = test_bit_u32(protonum, f->l4protomap);
if (ret ^ f->logic[CT_FILTER_L4PROTO])
return 0;
}
if (f->logic[CT_FILTER_ADDRESS] != -1) {
switch(nfct_get_attr_u8(ct, ATTR_L3PROTO)) {
case AF_INET:
ret = vector_iterate(f->v, ct, __ct_filter_test_mask4);
if (ret ^ f->logic[CT_FILTER_ADDRESS])
return 0;
ret = __ct_filter_test_ipv4(f, ct);
if (ret ^ f->logic[CT_FILTER_ADDRESS])
return 0;
break;
case AF_INET6:
ret = vector_iterate(f->v6, ct, __ct_filter_test_mask6);
if (ret ^ f->logic[CT_FILTER_ADDRESS])
return 0;
ret = __ct_filter_test_ipv6(f, ct);
if (ret ^ f->logic[CT_FILTER_ADDRESS])
return 0;
break;
default:
break;
}
}
if (f->logic[CT_FILTER_STATE] != -1) {
ret = __ct_filter_test_state(f, ct);
/* ret is -1 if we don't know what to do */
if (ret != -1 && ret ^ f->logic[CT_FILTER_STATE])
return 0;
}
return 1;
}
static inline int ct_filter_sanity_check(const struct nf_conntrack *ct)
{
if (!nfct_attr_is_set(ct, ATTR_L3PROTO)) {
dlog(LOG_ERR, "missing layer 3 protocol");
return 0;
}
switch(nfct_get_attr_u8(ct, ATTR_L3PROTO)) {
case AF_INET:
if (!nfct_attr_is_set(ct, ATTR_ORIG_IPV4_SRC) ||
!nfct_attr_is_set(ct, ATTR_REPL_IPV4_SRC)) {
dlog(LOG_ERR, "missing IPv4 address. "
"You forgot to load "
"nf_conntrack_ipv4?");
return 0;
}
break;
case AF_INET6:
if (!nfct_attr_is_set(ct, ATTR_ORIG_IPV6_SRC) ||
!nfct_attr_is_set(ct, ATTR_REPL_IPV6_SRC)) {
dlog(LOG_ERR, "missing IPv6 address. "
"You forgot to load "
"nf_conntrack_ipv6?");
return 0;
}
break;
}
return 1;
}
/* we do user-space filtering for dump and resyncs */
int ct_filter_conntrack(const struct nf_conntrack *ct, int userspace)
{
/* missing mandatory attributes in object */
if (!ct_filter_sanity_check(ct))
return 1;
if (userspace && !ct_filter_check(STATE(us_filter), ct))
return 1;
return 0;
}
static inline int
ct_filter_master_sanity_check(const struct nf_conntrack *master)
{
if (master == NULL) {
dlog(LOG_ERR, "no master tuple in expectation");
return 0;
}
if (!nfct_attr_is_set(master, ATTR_L3PROTO)) {
dlog(LOG_ERR, "missing layer 3 protocol");
return 0;
}
switch (nfct_get_attr_u8(master, ATTR_L3PROTO)) {
case AF_INET:
if (!nfct_attr_is_set(master, ATTR_IPV4_SRC) ||
!nfct_attr_is_set(master, ATTR_IPV4_DST)) {
dlog(LOG_ERR, "missing IPv4 address. "
"You forgot to load nf_conntrack_ipv4?");
return 0;
}
break;
case AF_INET6:
if (!nfct_attr_is_set(master, ATTR_IPV6_SRC) ||
!nfct_attr_is_set(master, ATTR_IPV6_DST)) {
dlog(LOG_ERR, "missing IPv6 address. "
"You forgot to load nf_conntrack_ipv6?");
return 0;
}
break;
}
return 1;
}
int ct_filter_master(const struct nf_conntrack *master)
{
if (!ct_filter_master_sanity_check(master))
return 1;
/* Check if we've got a master conntrack for this expectation in our
* caches. If there is not, we don't want this expectation either.
*/
return STATE(mode)->internal->exp.find(master) ? 0 : 1;
}
struct exp_filter {
struct list_head list;
};
struct exp_filter *exp_filter_create(void)
{
struct exp_filter *f;
f = calloc(1, sizeof(struct exp_filter));
if (f == NULL)
return NULL;
INIT_LIST_HEAD(&f->list);
return f;
}
struct exp_filter_item {
struct list_head head;
char helper_name[NFCT_HELPER_NAME_MAX];
};
/* this is ugly, but it simplifies read_config_yy.y */
static struct exp_filter *exp_filter_alloc(void)
{
if (STATE(exp_filter) == NULL) {
STATE(exp_filter) = exp_filter_create();
if (STATE(exp_filter) == NULL) {
fprintf(stderr, "Can't init expectation filtering!\n");
return NULL;
}
}
return STATE(exp_filter);;
}
int exp_filter_add(struct exp_filter *f, const char *helper_name)
{
struct exp_filter_item *item;
f = exp_filter_alloc();
if (f == NULL)
return -1;
list_for_each_entry(item, &f->list, head) {
if (strncasecmp(item->helper_name, helper_name,
NFCT_HELPER_NAME_MAX) == 0) {
return -1;
}
}
item = calloc(1, sizeof(struct exp_filter_item));
if (item == NULL)
return -1;
strncpy(item->helper_name, helper_name, NFCT_HELPER_NAME_MAX);
list_add(&item->head, &f->list);
return 0;
}
int exp_filter_find(struct exp_filter *f, const struct nf_expect *exp)
{
struct exp_filter_item *item;
/* if filtering is not active, accept everything. */
if (f == NULL)
return 1;
list_for_each_entry(item, &f->list, head) {
const char *name;
if (nfexp_attr_is_set(exp, ATTR_EXP_HELPER_NAME))
name = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME);
else {
/* No helper name, this is likely to be a kernel older
* which does not include the helper name, just skip
* this so we don't crash.
*/
return 0;
}
/* we allow partial matching to support things like sip-PORT. */
if (strncasecmp(item->helper_name, name,
strlen(item->helper_name)) == 0) {
return 1;
}
}
return 0;
}