diff options
author | Alexander Wirt <formorer@debian.org> | 2012-06-03 08:49:55 +0200 |
---|---|---|
committer | Alexander Wirt <formorer@debian.org> | 2012-06-03 08:49:55 +0200 |
commit | ea27bb406e3d8fe9466ba274af38e6f540ff5bfc (patch) | |
tree | 9f0c78416f8b617d6af715800ce22815645ee8ec /src/filter.c | |
parent | ed902b39d4f4aa2fc8130441d25b849a69b75c15 (diff) | |
download | conntrack-tools-ea27bb406e3d8fe9466ba274af38e6f540ff5bfc.tar.gz conntrack-tools-ea27bb406e3d8fe9466ba274af38e6f540ff5bfc.zip |
Imported Upstream version 1.2.1
Diffstat (limited to 'src/filter.c')
-rw-r--r-- | src/filter.c | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/src/filter.c b/src/filter.c new file mode 100644 index 0000000..39dd4ca --- /dev/null +++ b/src/filter.c @@ -0,0 +1,485 @@ +/* + * (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]; + u_int32_t l4protomap[IPPROTO_MAX/32]; + u_int16_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_IPV4_SRC) || + !nfct_attr_is_set(ct, 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(ct, ATTR_IPV6_SRC) || + !nfct_attr_is_set(ct, ATTR_IPV6_DST)) { + 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; +} + +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 = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME); + + /* 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; +} |