summaryrefslogtreecommitdiff
path: root/src/filter.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/filter.c')
-rw-r--r--src/filter.c409
1 files changed, 409 insertions, 0 deletions
diff --git a/src/filter.c b/src/filter.c
new file mode 100644
index 0000000..6a09c77
--- /dev/null
+++ b/src/filter.c
@@ -0,0 +1,409 @@
+/*
+ * (C) 2006-2008 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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, 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, 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, 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, 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(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) ||
+ !nfct_attr_is_set(ct, ATTR_REPL_IPV4_SRC) ||
+ !nfct_attr_is_set(ct, ATTR_REPL_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) ||
+ !nfct_attr_is_set(ct, ATTR_REPL_IPV6_SRC) ||
+ !nfct_attr_is_set(ct, ATTR_REPL_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(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;
+}