diff options
author | /C=EU/ST=EU/CN=Pablo Neira Ayuso/emailAddress=pablo@netfilter.org </C=EU/ST=EU/CN=Pablo Neira Ayuso/emailAddress=pablo@netfilter.org> | 2007-04-16 19:08:42 +0000 |
---|---|---|
committer | /C=EU/ST=EU/CN=Pablo Neira Ayuso/emailAddress=pablo@netfilter.org </C=EU/ST=EU/CN=Pablo Neira Ayuso/emailAddress=pablo@netfilter.org> | 2007-04-16 19:08:42 +0000 |
commit | 5eb3bc6d5594fccfff26329a26225f999e971652 (patch) | |
tree | 242b74bb06e32ef6d9621a73a0010b1c2ab7da4a /src | |
parent | ad31f852c3454136bdbfeb7f222cb9c175f13c1c (diff) | |
download | conntrack-tools-5eb3bc6d5594fccfff26329a26225f999e971652.tar.gz conntrack-tools-5eb3bc6d5594fccfff26329a26225f999e971652.zip |
first step forward to merge conntrackd and conntrack into the same building chain
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 26 | ||||
-rw-r--r-- | src/alarm.c | 141 | ||||
-rw-r--r-- | src/buffer.c | 136 | ||||
-rw-r--r-- | src/cache.c | 446 | ||||
-rw-r--r-- | src/cache_iterators.c | 229 | ||||
-rw-r--r-- | src/cache_lifetime.c | 65 | ||||
-rw-r--r-- | src/cache_timer.c | 72 | ||||
-rw-r--r-- | src/checksum.c | 32 | ||||
-rw-r--r-- | src/conntrack.c | 1131 | ||||
-rw-r--r-- | src/hash.c | 199 | ||||
-rw-r--r-- | src/ignore_pool.c | 136 | ||||
-rw-r--r-- | src/local.c | 159 | ||||
-rw-r--r-- | src/lock.c | 32 | ||||
-rw-r--r-- | src/log.c | 57 | ||||
-rw-r--r-- | src/main.c | 302 | ||||
-rw-r--r-- | src/mcast.c | 287 | ||||
-rw-r--r-- | src/netlink.c | 326 | ||||
-rw-r--r-- | src/network.c | 282 | ||||
-rw-r--r-- | src/proxy.c | 124 | ||||
-rw-r--r-- | src/read_config_lex.l | 125 | ||||
-rw-r--r-- | src/read_config_yy.y | 550 | ||||
-rw-r--r-- | src/run.c | 227 | ||||
-rw-r--r-- | src/state_helper.c | 44 | ||||
-rw-r--r-- | src/state_helper_tcp.c | 35 | ||||
-rw-r--r-- | src/stats-mode.c | 151 | ||||
-rw-r--r-- | src/sync-mode.c | 416 | ||||
-rw-r--r-- | src/sync-nack.c | 309 | ||||
-rw-r--r-- | src/sync-notrack.c | 127 | ||||
-rw-r--r-- | src/traffic_stats.c | 54 |
29 files changed, 6220 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..381f8ac --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,26 @@ +include $(top_srcdir)/Make_global.am + +YACC=@YACC@ -d + +CLEANFILES = read_config_yy.c read_config_lex.c + +sbin_PROGRAMS = conntrack conntrackd + +conntrack_SOURCES = conntrack.c +conntrack_LDFLAGS = -rdynamic + +conntrackd_SOURCES = alarm.c main.c run.c hash.c buffer.c \ + local.c log.c mcast.c netlink.c proxy.c lock.c \ + ignore_pool.c \ + cache.c cache_iterators.c \ + cache_lifetime.c cache_timer.c \ + sync-mode.c sync-notrack.c sync-nack.c \ + traffic_stats.c stats-mode.c \ + network.c checksum.c \ + state_helper.c state_helper_tcp.c \ + read_config_yy.y read_config_lex.l + +conntrackd_LDFLAGS = $(all_libraries) -lnfnetlink -lnetfilter_conntrack \ + -lpthread + +EXTRA_DIST = read_config_yy.h diff --git a/src/alarm.c b/src/alarm.c new file mode 100644 index 0000000..1a465c2 --- /dev/null +++ b/src/alarm.c @@ -0,0 +1,141 @@ +/* + * (C) 2006 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 <unistd.h> +#include <stdlib.h> +#include "linux_list.h" +#include "conntrackd.h" +#include "alarm.h" +#include "jhash.h" +#include <pthread.h> +#include <time.h> +#include <errno.h> + +/* alarm cascade */ +#define ALARM_CASCADE_SIZE 10 +static struct list_head *alarm_cascade; + +/* thread stuff */ +static pthread_t alarm_thread; + +struct alarm_list *create_alarm() +{ + return (struct alarm_list *) malloc(sizeof(struct alarm_list)); +} + +void destroy_alarm(struct alarm_list *t) +{ + free(t); +} + +void set_alarm_expiration(struct alarm_list *t, unsigned long expires) +{ + t->expires = expires; +} + +void set_alarm_function(struct alarm_list *t, + void (*fcn)(struct alarm_list *a, void *data)) +{ + t->function = fcn; +} + +void set_alarm_data(struct alarm_list *t, void *data) +{ + t->data = data; +} + +void init_alarm(struct alarm_list *t) +{ + INIT_LIST_HEAD(&t->head); + + t->expires = 0; + t->data = 0; + t->function = NULL; +} + +void add_alarm(struct alarm_list *alarm) +{ + unsigned int pos = jhash(alarm, sizeof(alarm), 0) % ALARM_CASCADE_SIZE; + + list_add(&alarm->head, &alarm_cascade[pos]); +} + +void del_alarm(struct alarm_list *alarm) +{ + list_del(&alarm->head); +} + +int mod_alarm(struct alarm_list *alarm, unsigned long expires) +{ + alarm->expires = expires; + return 0; +} + +void __run_alarms() +{ + struct list_head *i, *tmp; + struct alarm_list *t; + struct timespec req = {0, 1000000000 / ALARM_CASCADE_SIZE}; + struct timespec rem; + static int step = 0; + +retry: + if (nanosleep(&req, &rem) == -1) { + /* interrupted syscall: retry with remaining time */ + if (errno == EINTR) { + memcpy(&req, &rem, sizeof(struct timespec)); + goto retry; + } + } + + lock(); + list_for_each_safe(i, tmp, &alarm_cascade[step]) { + t = (struct alarm_list *) i; + + t->expires--; + if (t->expires == 0) + t->function(t, t->data); + } + step = (step + 1) < ALARM_CASCADE_SIZE ? step + 1 : 0; + unlock(); +} + +void *run_alarms(void *foo) +{ + while(1) + __run_alarms(); +} + +int create_alarm_thread() +{ + int i; + + alarm_cascade = malloc(sizeof(struct list_head) * ALARM_CASCADE_SIZE); + if (alarm_cascade == NULL) + return -1; + + for (i=0; i<ALARM_CASCADE_SIZE; i++) + INIT_LIST_HEAD(&alarm_cascade[i]); + + return pthread_create(&alarm_thread, NULL, run_alarms, NULL); +} + +int destroy_alarm_thread() +{ + return pthread_cancel(alarm_thread); +} diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000..fa0b859 --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,136 @@ +/* + * (C) 2006-2007 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 "buffer.h" + +struct buffer *buffer_create(size_t max_size) +{ + struct buffer *b; + + b = malloc(sizeof(struct buffer)); + if (b == NULL) + return NULL; + memset(b, 0, sizeof(struct buffer)); + + b->max_size = max_size; + INIT_LIST_HEAD(&b->head); + pthread_mutex_init(&b->lock, NULL); + + return b; +} + +void buffer_destroy(struct buffer *b) +{ + struct list_head *i, *tmp; + struct buffer_node *node; + + pthread_mutex_lock(&b->lock); + list_for_each_safe(i, tmp, &b->head) { + node = (struct buffer_node *) i; + list_del(i); + free(node); + } + pthread_mutex_unlock(&b->lock); + pthread_mutex_destroy(&b->lock); + free(b); +} + +static struct buffer_node *buffer_node_create(const void *data, size_t size) +{ + struct buffer_node *n; + + n = malloc(sizeof(struct buffer_node) + size); + if (n == NULL) + return NULL; + + INIT_LIST_HEAD(&n->head); + n->size = size; + memcpy(n->data, data, size); + + return n; +} + +int buffer_add(struct buffer *b, const void *data, size_t size) +{ + int ret = 0; + struct buffer_node *n; + + pthread_mutex_lock(&b->lock); + + /* does it fit this buffer? */ + if (size > b->max_size) { + errno = ENOSPC; + ret = -1; + goto err; + } + +retry: + /* buffer is full: kill the oldest entry */ + if (b->cur_size + size > b->max_size) { + n = (struct buffer_node *) b->head.prev; + list_del(b->head.prev); + b->cur_size -= n->size; + free(n); + goto retry; + } + + n = buffer_node_create(data, size); + if (n == NULL) { + ret = -1; + goto err; + } + + list_add(&n->head, &b->head); + b->cur_size += size; + +err: + pthread_mutex_unlock(&b->lock); + return ret; +} + +void __buffer_del(struct buffer *b, void *data) +{ + struct buffer_node *n = container_of(data, struct buffer_node, data); + + list_del(&n->head); + b->cur_size -= n->size; + free(n); +} + +void buffer_del(struct buffer *b, void *data) +{ + pthread_mutex_lock(&b->lock); + buffer_del(b, data); + pthread_mutex_unlock(&b->lock); +} + +void buffer_iterate(struct buffer *b, + void *data, + int (*iterate)(void *data1, void *data2)) +{ + struct list_head *i, *tmp; + struct buffer_node *n; + + pthread_mutex_lock(&b->lock); + list_for_each_safe(i, tmp, &b->head) { + n = (struct buffer_node *) i; + if (iterate(n->data, data)) + break; + } + pthread_mutex_unlock(&b->lock); +} diff --git a/src/cache.c b/src/cache.c new file mode 100644 index 0000000..6f7442b --- /dev/null +++ b/src/cache.c @@ -0,0 +1,446 @@ +/* + * (C) 2006-2007 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 "jhash.h" +#include "hash.h" +#include "conntrackd.h" +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <errno.h> +#include "us-conntrack.h" +#include "cache.h" +#include "debug.h" + +static u_int32_t hash(const void *data, struct hashtable *table) +{ + unsigned int a, b; + const struct us_conntrack *u = data; + struct nf_conntrack *ct = u->ct; + + a = jhash(nfct_get_attr(ct, ATTR_ORIG_IPV4_SRC), sizeof(u_int32_t), + ((nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO) << 16) | + (nfct_get_attr_u8(ct, ATTR_ORIG_L4PROTO)))); + + b = jhash(nfct_get_attr(ct, ATTR_ORIG_IPV4_DST), sizeof(u_int32_t), + ((nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) << 16) | + (nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)))); + + return jhash_2words(a, b, 0) % table->hashsize; +} + +static u_int32_t hash6(const void *data, struct hashtable *table) +{ + unsigned int a, b; + const struct us_conntrack *u = data; + struct nf_conntrack *ct = u->ct; + + a = jhash(nfct_get_attr(ct, ATTR_ORIG_IPV6_SRC), sizeof(u_int32_t), + ((nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO) << 16) | + (nfct_get_attr_u8(ct, ATTR_ORIG_L4PROTO)))); + + b = jhash(nfct_get_attr(ct, ATTR_ORIG_IPV6_DST), sizeof(u_int32_t), + ((nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) << 16) | + (nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)))); + + return jhash_2words(a, b, 0) % table->hashsize; +} + +static int __compare(const struct nf_conntrack *ct1, + const struct nf_conntrack *ct2) +{ + return ((nfct_get_attr_u8(ct1, ATTR_ORIG_L3PROTO) == + nfct_get_attr_u8(ct2, ATTR_ORIG_L3PROTO)) && + (nfct_get_attr_u8(ct1, ATTR_ORIG_L4PROTO) == + nfct_get_attr_u8(ct2, ATTR_ORIG_L4PROTO)) && + (nfct_get_attr_u16(ct1, ATTR_ORIG_PORT_SRC) == + nfct_get_attr_u16(ct2, ATTR_ORIG_PORT_SRC)) && + (nfct_get_attr_u16(ct1, ATTR_ORIG_PORT_DST) == + nfct_get_attr_u16(ct2, ATTR_ORIG_PORT_DST)) && + (nfct_get_attr_u16(ct1, ATTR_REPL_PORT_SRC) == + nfct_get_attr_u16(ct2, ATTR_REPL_PORT_SRC)) && + (nfct_get_attr_u16(ct1, ATTR_REPL_PORT_DST) == + nfct_get_attr_u16(ct2, ATTR_REPL_PORT_DST))); +} + +static int compare(const void *data1, const void *data2) +{ + const struct us_conntrack *u1 = data1; + const struct us_conntrack *u2 = data2; + + return ((nfct_get_attr_u32(u1->ct, ATTR_ORIG_IPV4_SRC) == + nfct_get_attr_u32(u2->ct, ATTR_ORIG_IPV4_SRC)) && + (nfct_get_attr_u32(u1->ct, ATTR_ORIG_IPV4_DST) == + nfct_get_attr_u32(u2->ct, ATTR_ORIG_IPV4_DST)) && + (nfct_get_attr_u32(u1->ct, ATTR_REPL_IPV4_SRC) == + nfct_get_attr_u32(u2->ct, ATTR_REPL_IPV4_SRC)) && + (nfct_get_attr_u32(u1->ct, ATTR_REPL_IPV4_DST) == + nfct_get_attr_u32(u2->ct, ATTR_REPL_IPV4_DST)) && + __compare(u1->ct, u2->ct)); +} + +static int compare6(const void *data1, const void *data2) +{ + const struct us_conntrack *u1 = data1; + const struct us_conntrack *u2 = data2; + + return ((nfct_get_attr_u32(u1->ct, ATTR_ORIG_IPV6_SRC) == + nfct_get_attr_u32(u2->ct, ATTR_ORIG_IPV6_SRC)) && + (nfct_get_attr_u32(u1->ct, ATTR_ORIG_IPV6_DST) == + nfct_get_attr_u32(u2->ct, ATTR_ORIG_IPV6_DST)) && + (nfct_get_attr_u32(u1->ct, ATTR_REPL_IPV6_SRC) == + nfct_get_attr_u32(u2->ct, ATTR_REPL_IPV6_SRC)) && + (nfct_get_attr_u32(u1->ct, ATTR_REPL_IPV6_DST) == + nfct_get_attr_u32(u2->ct, ATTR_REPL_IPV6_DST)) && + __compare(u1->ct, u2->ct)); +} + +struct cache_feature *cache_feature[CACHE_MAX_FEATURE] = { + [TIMER_FEATURE] = &timer_feature, + [LIFETIME_FEATURE] = &lifetime_feature, +}; + +struct cache *cache_create(char *name, + unsigned int features, + u_int8_t proto, + struct cache_extra *extra) +{ + size_t size = sizeof(struct us_conntrack); + int i, j = 0; + struct cache *c; + struct cache_feature *feature_array[CACHE_MAX_FEATURE] = {}; + unsigned int feature_offset[CACHE_MAX_FEATURE] = {}; + unsigned int feature_type[CACHE_MAX_FEATURE] = {}; + + c = malloc(sizeof(struct cache)); + if (!c) + return NULL; + memset(c, 0, sizeof(struct cache)); + + strcpy(c->name, name); + + for (i = 0; i < CACHE_MAX_FEATURE; i++) { + if ((1 << i) & features) { + feature_array[j] = cache_feature[i]; + feature_offset[j] = size; + feature_type[i] = j; + size += cache_feature[i]->size; + j++; + } + } + + memcpy(c->feature_type, feature_type, sizeof(feature_type)); + + c->features = malloc(sizeof(struct cache_feature) * j); + if (!c->features) { + free(c); + return NULL; + } + memcpy(c->features, feature_array, sizeof(struct cache_feature) * j); + c->num_features = j; + + c->extra_offset = size; + c->extra = extra; + if (extra) + size += extra->size; + + c->feature_offset = malloc(sizeof(unsigned int) * j); + if (!c->feature_offset) { + free(c->features); + free(c); + return NULL; + } + memcpy(c->feature_offset, feature_offset, sizeof(unsigned int) * j); + + switch(proto) { + case AF_INET: + c->h = hashtable_create(CONFIG(hashsize), + CONFIG(limit), + size, + hash, + compare); + break; + case AF_INET6: + c->h = hashtable_create(CONFIG(hashsize), + CONFIG(limit), + size, + hash6, + compare6); + break; + } + + if (!c->h) { + free(c->features); + free(c->feature_offset); + free(c); + return NULL; + } + + return c; +} + +void cache_destroy(struct cache *c) +{ + lock(); + hashtable_destroy(c->h); + unlock(); + free(c->features); + free(c->feature_offset); + free(c); +} + +static struct us_conntrack *__add(struct cache *c, struct nf_conntrack *ct) +{ + int i; + size_t size = c->h->datasize; + char buf[size]; + struct us_conntrack *u = (struct us_conntrack *) buf; + struct nf_conntrack *newct; + + memset(u, 0, size); + + u->cache = c; + if ((u->ct = newct = nfct_new()) == NULL) { + errno = ENOMEM; + return 0; + } + memcpy(u->ct, ct, nfct_sizeof(ct)); + + u = hashtable_add(c->h, u); + if (u) { + void *data = u->data; + + for (i = 0; i < c->num_features; i++) { + c->features[i]->add(u, data); + data += c->features[i]->size; + } + + if (c->extra) + c->extra->add(u, ((void *) u) + c->extra_offset); + + return u; + } + free(newct); + + return NULL; +} + +struct us_conntrack *__cache_add(struct cache *c, struct nf_conntrack *ct) +{ + struct us_conntrack *u; + + u = __add(c, ct); + if (u) { + c->add_ok++; + return u; + } + c->add_fail++; + + return NULL; +} + +struct us_conntrack *cache_add(struct cache *c, struct nf_conntrack *ct) +{ + struct us_conntrack *u; + + lock(); + u = __cache_add(c, ct); + unlock(); + + return u; +} + +static struct us_conntrack *__update(struct cache *c, struct nf_conntrack *ct) +{ + size_t size = c->h->datasize; + char buf[size]; + struct us_conntrack *u = (struct us_conntrack *) buf; + + u->ct = ct; + + u = (struct us_conntrack *) hashtable_test(c->h, u); + if (u) { + int i; + void *data = u->data; + + for (i = 0; i < c->num_features; i++) { + c->features[i]->update(u, data); + data += c->features[i]->size; + } + + if (c->extra) + c->extra->update(u, ((void *) u) + c->extra_offset); + + if (nfct_attr_is_set(ct, ATTR_STATUS)) + nfct_set_attr_u32(u->ct, ATTR_STATUS, + nfct_get_attr_u32(ct, ATTR_STATUS)); + if (nfct_attr_is_set(ct, ATTR_TCP_STATE)) + nfct_set_attr_u8(u->ct, ATTR_TCP_STATE, + nfct_get_attr_u8(ct, ATTR_TCP_STATE)); + if (nfct_attr_is_set(ct, ATTR_TIMEOUT)) + nfct_set_attr_u32(u->ct, ATTR_TIMEOUT, + nfct_get_attr_u32(ct, ATTR_TIMEOUT)); + + return u; + } + return NULL; +} + +struct us_conntrack *__cache_update(struct cache *c, struct nf_conntrack *ct) +{ + struct us_conntrack *u; + + u = __update(c, ct); + if (u) { + c->upd_ok++; + return u; + } + c->upd_fail++; + + return NULL; +} + +struct us_conntrack *cache_update(struct cache *c, struct nf_conntrack *ct) +{ + struct us_conntrack *u; + + lock(); + u = __cache_update(c, ct); + unlock(); + + return u; +} + +struct us_conntrack *cache_update_force(struct cache *c, + struct nf_conntrack *ct) +{ + struct us_conntrack *u; + + lock(); + if ((u = __update(c, ct)) != NULL) { + c->upd_ok++; + unlock(); + return u; + } + if ((u = __add(c, ct)) != NULL) { + c->add_ok++; + unlock(); + return u; + } + c->add_fail++; + unlock(); + return NULL; +} + +int cache_test(struct cache *c, struct nf_conntrack *ct) +{ + size_t size = c->h->datasize; + char buf[size]; + struct us_conntrack *u = (struct us_conntrack *) buf; + void *ret; + + u->ct = ct; + + lock(); + ret = hashtable_test(c->h, u); + unlock(); + + return ret != NULL; +} + +static int __del(struct cache *c, struct nf_conntrack *ct) +{ + size_t size = c->h->datasize; + char buf[size]; + struct us_conntrack *u = (struct us_conntrack *) buf; + + u->ct = ct; + + u = (struct us_conntrack *) hashtable_test(c->h, u); + if (u) { + int i; + void *data = u->data; + struct nf_conntrack *p = u->ct; + + for (i = 0; i < c->num_features; i++) { + c->features[i]->destroy(u, data); + data += c->features[i]->size; + } + + if (c->extra) + c->extra->destroy(u, ((void *) u) + c->extra_offset); + + hashtable_del(c->h, u); + free(p); + return 1; + } + return 0; +} + +int __cache_del(struct cache *c, struct nf_conntrack *ct) +{ + if (__del(c, ct)) { + c->del_ok++; + return 1; + } + c->del_fail++; + + return 0; +} + +int cache_del(struct cache *c, struct nf_conntrack *ct) +{ + int ret; + + lock(); + ret = __cache_del(c, ct); + unlock(); + + return ret; +} + +struct us_conntrack *cache_get_conntrack(struct cache *c, void *data) +{ + return data - c->extra_offset; +} + +void *cache_get_extra(struct cache *c, void *data) +{ + return data + c->extra_offset; +} + +void cache_stats(struct cache *c, int fd) +{ + char buf[512]; + int size; + + lock(); + size = sprintf(buf, "cache %s:\n" + "current active connections:\t%12u\n" + "connections created:\t\t%12u\tfailed:\t%12u\n" + "connections updated:\t\t%12u\tfailed:\t%12u\n" + "connections destroyed:\t\t%12u\tfailed:\t%12u\n\n", + c->name, + hashtable_counter(c->h), + c->add_ok, + c->add_fail, + c->upd_ok, + c->upd_fail, + c->del_ok, + c->del_fail); + unlock(); + send(fd, buf, size, 0); +} diff --git a/src/cache_iterators.c b/src/cache_iterators.c new file mode 100644 index 0000000..5d5d22b --- /dev/null +++ b/src/cache_iterators.c @@ -0,0 +1,229 @@ +/* + * (C) 2006-2007 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 "cache.h" +#include "jhash.h" +#include "hash.h" +#include "conntrackd.h" +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <errno.h> +#include "us-conntrack.h" +#include "debug.h" + +struct __dump_container { + int fd; + int type; +}; + +static int do_dump(void *data1, void *data2) +{ + char buf[1024]; + int size; + struct __dump_container *container = data1; + struct us_conntrack *u = data2; + void *data = u->data; + int i; + + memset(buf, 0, sizeof(buf)); + size = nfct_snprintf(buf, + sizeof(buf), + u->ct, + NFCT_T_UNKNOWN, + container->type, + 0); + + for (i = 0; i < u->cache->num_features; i++) { + if (u->cache->features[i]->dump) { + size += u->cache->features[i]->dump(u, + data, + buf+size, + container->type); + data += u->cache->features[i]->size; + } + } + size += sprintf(buf+size, "\n"); + if (send(container->fd, buf, size, 0) == -1) { + if (errno != EPIPE) + return -1; + } + + return 0; +} + +void cache_dump(struct cache *c, int fd, int type) +{ + struct __dump_container tmp = { + .fd = fd, + .type = type + }; + + lock(); + hashtable_iterate(c->h, (void *) &tmp, do_dump); + unlock(); +} + +static int do_commit(void *data1, void *data2) +{ + int ret; + struct cache *c = data1; + struct us_conntrack *u = data2; + struct nf_conntrack *ct; + char buf[4096]; + struct nlmsghdr *nlh = (struct nlmsghdr *)buf; + + ct = nfct_clone(u->ct); + if (ct == NULL) + return 0; + + if (nfct_attr_is_set(ct, ATTR_STATUS)) { + u_int32_t status = nfct_get_attr_u32(ct, ATTR_STATUS); + status &= ~IPS_EXPECTED; + nfct_set_attr_u32(ct, ATTR_STATUS, status); + } + + if (nfct_getobjopt(ct, NFCT_GOPT_IS_SNAT)) + nfct_setobjopt(ct, NFCT_SOPT_UNDO_SNAT); + if (nfct_getobjopt(ct, NFCT_GOPT_IS_DNAT)) + nfct_setobjopt(ct, NFCT_SOPT_UNDO_DNAT); + if (nfct_getobjopt(ct, NFCT_GOPT_IS_SPAT)) + nfct_setobjopt(ct, NFCT_SOPT_UNDO_SPAT); + if (nfct_getobjopt(ct, NFCT_GOPT_IS_DPAT)) + nfct_setobjopt(ct, NFCT_SOPT_UNDO_DPAT); + + /* + * Set a reduced timeout for candidate-to-be-committed + * conntracks that live in the external cache + */ + nfct_set_attr_u32(ct, ATTR_TIMEOUT, CONFIG(commit_timeout)); + + ret = nfct_build_query(STATE(subsys_sync), + NFCT_Q_CREATE, + ct, + nlh, + sizeof(buf)); + + free(ct); + + if (ret == -1) { + /* XXX: Please cleanup this debug crap, default in logfile */ + debug("--- failed to build: %s --- \n", strerror(errno)); + return 0; + } + + ret = nfnl_query(STATE(sync), nlh); + if (ret == -1) { + switch(errno) { + case EEXIST: + c->commit_exist++; + break; + default: + c->commit_fail++; + break; + } + debug("--- failed to commit: %s --- \n", strerror(errno)); + } else { + c->commit_ok++; + debug("----- commit -----\n"); + } + + /* keep iterating even if we have found errors */ + return 0; +} + +void cache_commit(struct cache *c) +{ + unsigned int commit_ok = c->commit_ok; + unsigned int commit_exist = c->commit_exist; + unsigned int commit_fail = c->commit_fail; + + lock(); + hashtable_iterate(c->h, c, do_commit); + unlock(); + + /* calculate new entries committed */ + commit_ok = c->commit_ok - commit_ok; + commit_fail = c->commit_fail - commit_fail; + commit_exist = c->commit_exist - commit_exist; + + /* log results */ + dlog(STATE(log), "Committed %u new entries", commit_ok); + + if (commit_exist) + dlog(STATE(log), "%u entries ignored, " + "already exist", commit_exist); + if (commit_fail) + dlog(STATE(log), "%u entries can't be " + "committed", commit_fail); +} + +static int do_flush(void *data1, void *data2) +{ + struct cache *c = data1; + struct us_conntrack *u = data2; + void *data = u->data; + int i; + + for (i = 0; i < c->num_features; i++) { + c->features[i]->destroy(u, data); + data += c->features[i]->size; + } + free(u->ct); + + return 0; +} + +void cache_flush(struct cache *c) +{ + lock(); + hashtable_iterate(c->h, c, do_flush); + hashtable_flush(c->h); + c->flush++; + unlock(); +} + +#include "sync.h" +#include "network.h" + +static int do_bulk(void *data1, void *data2) +{ + int ret; + struct us_conntrack *u = data2; + char buf[4096]; + struct nlnetwork *net = (struct nlnetwork *) buf; + + ret = build_network_msg(NFCT_Q_UPDATE, + STATE(subsys_sync), + u->ct, + buf, + sizeof(buf)); + if (ret == -1) + debug_ct(u->ct, "failed to build"); + + mcast_send_netmsg(STATE_SYNC(mcast_client), net); + STATE_SYNC(mcast_sync)->post_send(net, u); + + /* keep iterating even if we have found errors */ + return 0; +} + +void cache_bulk(struct cache *c) +{ + lock(); + hashtable_iterate(c->h, NULL, do_bulk); + unlock(); +} diff --git a/src/cache_lifetime.c b/src/cache_lifetime.c new file mode 100644 index 0000000..ae54df2 --- /dev/null +++ b/src/cache_lifetime.c @@ -0,0 +1,65 @@ +/* + * (C) 2006 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 <stdio.h> +#include "conntrackd.h" +#include "us-conntrack.h" +#include "cache.h" +#include "alarm.h" + +static void lifetime_add(struct us_conntrack *u, void *data) +{ + long *lifetime = data; + struct timeval tv; + + gettimeofday(&tv, NULL); + + *lifetime = tv.tv_sec; +} + +static void lifetime_update(struct us_conntrack *u, void *data) +{ +} + +static void lifetime_destroy(struct us_conntrack *u, void *data) +{ +} + +static int lifetime_dump(struct us_conntrack *u, + void *data, + char *buf, + int type) +{ + long *lifetime = data; + struct timeval tv; + + if (type == NFCT_O_XML) + return 0; + + gettimeofday(&tv, NULL); + + return sprintf(buf, " [active since %lds]", tv.tv_sec - *lifetime); +} + +struct cache_feature lifetime_feature = { + .size = sizeof(long), + .add = lifetime_add, + .update = lifetime_update, + .destroy = lifetime_destroy, + .dump = lifetime_dump +}; diff --git a/src/cache_timer.c b/src/cache_timer.c new file mode 100644 index 0000000..213b59a --- /dev/null +++ b/src/cache_timer.c @@ -0,0 +1,72 @@ +/* + * (C) 2006 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 <stdio.h> +#include "conntrackd.h" +#include "us-conntrack.h" +#include "cache.h" +#include "alarm.h" + +static void timeout(struct alarm_list *a, void *data) +{ + struct us_conntrack *u = data; + + debug_ct(u->ct, "expired timeout"); + __cache_del(u->cache, u->ct); +} + +static void timer_add(struct us_conntrack *u, void *data) +{ + struct alarm_list *alarm = data; + + init_alarm(alarm); + set_alarm_expiration(alarm, CONFIG(cache_timeout)); + set_alarm_data(alarm, u); + set_alarm_function(alarm, timeout); + add_alarm(alarm); +} + +static void timer_update(struct us_conntrack *u, void *data) +{ + struct alarm_list *alarm = data; + mod_alarm(alarm, CONFIG(cache_timeout)); +} + +static void timer_destroy(struct us_conntrack *u, void *data) +{ + struct alarm_list *alarm = data; + del_alarm(alarm); +} + +static int timer_dump(struct us_conntrack *u, void *data, char *buf, int type) +{ + struct alarm_list *alarm = data; + + if (type == NFCT_O_XML) + return 0; + + return sprintf(buf, " [expires in %ds]", alarm->expires); +} + +struct cache_feature timer_feature = { + .size = sizeof(struct alarm_list), + .add = timer_add, + .update = timer_update, + .destroy = timer_destroy, + .dump = timer_dump +}; diff --git a/src/checksum.c b/src/checksum.c new file mode 100644 index 0000000..41866ff --- /dev/null +++ b/src/checksum.c @@ -0,0 +1,32 @@ +/* + * Extracted from RFC 1071 with some minor changes to fix compilation on GCC, + * this can probably be improved + * --pablo 11/feb/07 + */ + +#include <conntrackd.h> + +unsigned short do_csum(const void *addr, unsigned int count) +{ + unsigned int sum = 0; + + /* checksumming disabled, just skip */ + if (CONFIG(flags) & DONT_CHECKSUM) + return 0; + + while(count > 1) { + /* This is the inner loop */ + sum += *((unsigned short *) addr++); + count -= 2; + } + + /* Add left-over byte, if any */ + if(count > 0) + sum += *((unsigned char *) addr); + + /* Fold 32-bit sum to 16 bits */ + while (sum>>16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} diff --git a/src/conntrack.c b/src/conntrack.c new file mode 100644 index 0000000..30fbf69 --- /dev/null +++ b/src/conntrack.c @@ -0,0 +1,1131 @@ +/* + * (C) 2005 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. + * + * Note: + * Yes, portions of this code has been stolen from iptables ;) + * Special thanks to the the Netfilter Core Team. + * Thanks to Javier de Miguel Rodriguez <jmiguel at talika.eii.us.es> + * for introducing me to advanced firewalling stuff. + * + * --pablo 13/04/2005 + * + * 2005-04-16 Harald Welte <laforge@netfilter.org>: + * Add support for conntrack accounting and conntrack mark + * 2005-06-23 Harald Welte <laforge@netfilter.org>: + * Add support for expect creation + * 2005-09-24 Harald Welte <laforge@netfilter.org>: + * Remove remaints of "-A" + * + */ +#include <stdio.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <getopt.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <netinet/in.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#include <fcntl.h> +#include <dlfcn.h> +#include <signal.h> +#include <string.h> +#include "linux_list.h" +#include "conntrack.h" +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack_ipv4.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack_ipv6.h> + +static const char cmdflags[NUMBER_OF_CMD] += {'L','I','U','D','G','F','E','V','h','L','I','D','G','F','E'}; + +static const char cmd_need_param[NUMBER_OF_CMD] += { 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2 }; + +static const char optflags[NUMBER_OF_OPT] += {'s','d','r','q','p','t','u','z','e','[',']','{','}','a','m','i','f'}; + +static struct option original_opts[] = { + {"dump", 2, 0, 'L'}, + {"create", 1, 0, 'I'}, + {"delete", 1, 0, 'D'}, + {"update", 1, 0, 'U'}, + {"get", 1, 0, 'G'}, + {"flush", 1, 0, 'F'}, + {"event", 1, 0, 'E'}, + {"version", 0, 0, 'V'}, + {"help", 0, 0, 'h'}, + {"orig-src", 1, 0, 's'}, + {"orig-dst", 1, 0, 'd'}, + {"reply-src", 1, 0, 'r'}, + {"reply-dst", 1, 0, 'q'}, + {"protonum", 1, 0, 'p'}, + {"timeout", 1, 0, 't'}, + {"status", 1, 0, 'u'}, + {"zero", 0, 0, 'z'}, + {"event-mask", 1, 0, 'e'}, + {"tuple-src", 1, 0, '['}, + {"tuple-dst", 1, 0, ']'}, + {"mask-src", 1, 0, '{'}, + {"mask-dst", 1, 0, '}'}, + {"nat-range", 1, 0, 'a'}, + {"mark", 1, 0, 'm'}, + {"id", 2, 0, 'i'}, + {"family", 1, 0, 'f'}, + {0, 0, 0, 0} +}; + +#define OPTION_OFFSET 256 + +static struct nfct_handle *cth; +static struct option *opts = original_opts; +static unsigned int global_option_offset = 0; + +/* Table of legal combinations of commands and options. If any of the + * given commands make an option legal, that option is legal (applies to + * CMD_LIST and CMD_ZERO only). + * Key: + * 0 illegal + * 1 compulsory + * 2 optional + */ + +static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = +/* Well, it's better than "Re: Linux vs FreeBSD" */ +{ + /* s d r q p t u z e x y k l a m i f*/ +/*CT_LIST*/ {2,2,2,2,2,0,0,2,0,0,0,0,0,0,2,2,2}, +/*CT_CREATE*/ {2,2,2,2,1,1,1,0,0,0,0,0,0,2,2,0,0}, +/*CT_UPDATE*/ {2,2,2,2,1,2,2,0,0,0,0,0,0,0,2,2,0}, +/*CT_DELETE*/ {2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,2,0}, +/*CT_GET*/ {2,2,2,2,1,0,0,0,0,0,0,0,0,0,0,2,0}, +/*CT_FLUSH*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +/*CT_EVENT*/ {2,2,2,2,2,0,0,0,2,0,0,0,0,0,2,0,0}, +/*VERSION*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +/*HELP*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +/*EXP_LIST*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2}, +/*EXP_CREATE*/{1,1,2,2,1,1,2,0,0,1,1,1,1,0,0,0,0}, +/*EXP_DELETE*/{1,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0}, +/*EXP_GET*/ {1,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0}, +/*EXP_FLUSH*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +/*EXP_EVENT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, +}; + +static char *lib_dir = CONNTRACK_LIB_DIR; + +static LIST_HEAD(proto_list); + +void register_proto(struct ctproto_handler *h) +{ + if (strcmp(h->version, VERSION) != 0) { + fprintf(stderr, "plugin `%s': version %s (I'm %s)\n", + h->name, h->version, VERSION); + exit(1); + } + list_add(&h->head, &proto_list); +} + +static struct ctproto_handler *findproto(char *name) +{ + struct list_head *i; + struct ctproto_handler *cur = NULL, *handler = NULL; + + if (!name) + return handler; + + lib_dir = getenv("CONNTRACK_LIB_DIR"); + if (!lib_dir) + lib_dir = CONNTRACK_LIB_DIR; + + list_for_each(i, &proto_list) { + cur = (struct ctproto_handler *) i; + if (strcmp(cur->name, name) == 0) { + handler = cur; + break; + } + } + + if (!handler) { + char path[sizeof("ct_proto_.so") + + strlen(name) + strlen(lib_dir)]; + sprintf(path, "%s/ct_proto_%s.so", lib_dir, name); + if (dlopen(path, RTLD_NOW)) + handler = findproto(name); + else + fprintf(stderr, "%s\n", dlerror()); + } + + return handler; +} + +enum exittype { + OTHER_PROBLEM = 1, + PARAMETER_PROBLEM, + VERSION_PROBLEM +}; + +void extension_help(struct ctproto_handler *h) +{ + fprintf(stdout, "\n"); + fprintf(stdout, "Proto `%s' help:\n", h->name); + h->help(); +} + +void +exit_tryhelp(int status) +{ + fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", + PROGNAME, PROGNAME); + exit(status); +} + +static void +exit_error(enum exittype status, char *msg, ...) +{ + va_list args; + + /* On error paths, make sure that we don't leak the memory + * reserved during options merging */ + if (opts != original_opts) { + free(opts); + opts = original_opts; + global_option_offset = 0; + } + va_start(args, msg); + fprintf(stderr,"%s v%s: ", PROGNAME, VERSION); + vfprintf(stderr, msg, args); + va_end(args); + fprintf(stderr, "\n"); + if (status == PARAMETER_PROBLEM) + exit_tryhelp(status); + exit(status); +} + +static void +generic_cmd_check(int command, int options) +{ + int i; + + for (i = 0; i < NUMBER_OF_CMD; i++) { + if (!(command & (1<<i))) + continue; + + if (cmd_need_param[i] == 0 && !options) + exit_error(PARAMETER_PROBLEM, + "You need to supply parameters to `-%c'\n", + cmdflags[i]); + } +} + +static void +generic_opt_check(int command, int options) +{ + int i, j, legal = 0; + + /* Check that commands are valid with options. Complicated by the + * fact that if an option is legal with *any* command given, it is + * legal overall (ie. -z and -l). + */ + for (i = 0; i < NUMBER_OF_OPT; i++) { + legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */ + + for (j = 0; j < NUMBER_OF_CMD; j++) { + if (!(command & (1<<j))) + continue; + + if (!(options & (1<<i))) { + if (commands_v_options[j][i] == 1) + exit_error(PARAMETER_PROBLEM, + "You need to supply the " + "`-%c' option for this " + "command\n", optflags[i]); + } else { + if (commands_v_options[j][i] != 0) + legal = 1; + else if (legal == 0) + legal = -1; + } + } + if (legal == -1) + exit_error(PARAMETER_PROBLEM, "Illegal option `-%c' " + "with this command\n", optflags[i]); + } +} + +static struct option * +merge_options(struct option *oldopts, const struct option *newopts, + unsigned int *option_offset) +{ + unsigned int num_old, num_new, i; + struct option *merge; + + for (num_old = 0; oldopts[num_old].name; num_old++); + for (num_new = 0; newopts[num_new].name; num_new++); + + global_option_offset += OPTION_OFFSET; + *option_offset = global_option_offset; + + merge = malloc(sizeof(struct option) * (num_new + num_old + 1)); + memcpy(merge, oldopts, num_old * sizeof(struct option)); + for (i = 0; i < num_new; i++) { + merge[num_old + i] = newopts[i]; + merge[num_old + i].val += *option_offset; + } + memset(merge + num_old + num_new, 0, sizeof(struct option)); + + return merge; +} + +/* From linux/errno.h */ +#define ENOTSUPP 524 /* Operation is not supported */ + +/* Translates errno numbers into more human-readable form than strerror. */ +const char * +err2str(int err, enum action command) +{ + unsigned int i; + struct table_struct { + enum action act; + int err; + const char *message; + } table [] = + { { CT_LIST, -ENOTSUPP, "function not implemented" }, + { 0xFFFF, -EINVAL, "invalid parameters" }, + { CT_CREATE, -EEXIST, "Such conntrack exists, try -U to update" }, + { CT_CREATE|CT_GET|CT_DELETE, -ENOENT, + "such conntrack doesn't exist" }, + { CT_CREATE|CT_GET, -ENOMEM, "not enough memory" }, + { CT_GET, -EAFNOSUPPORT, "protocol not supported" }, + { CT_CREATE, -ETIME, "conntrack has expired" }, + { EXP_CREATE, -ENOENT, "master conntrack not found" }, + { EXP_CREATE, -EINVAL, "invalid parameters" }, + { ~0UL, -EPERM, "sorry, you must be root or get " + "CAP_NET_ADMIN capability to do this"} + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((table[i].act & command) && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} + +#define PARSE_STATUS 0 +#define PARSE_EVENT 1 +#define PARSE_MAX 2 + +static struct parse_parameter { + char *parameter[6]; + size_t size; + unsigned int value[6]; +} parse_array[PARSE_MAX] = { + { {"ASSURED", "SEEN_REPLY", "UNSET", "SRC_NAT", "DST_NAT","FIXED_TIMEOUT"}, 6, + { IPS_ASSURED, IPS_SEEN_REPLY, 0, + IPS_SRC_NAT_DONE, IPS_DST_NAT_DONE, IPS_FIXED_TIMEOUT} }, + { {"ALL", "NEW", "UPDATES", "DESTROY"}, 4, + {~0U, NF_NETLINK_CONNTRACK_NEW, NF_NETLINK_CONNTRACK_UPDATE, + NF_NETLINK_CONNTRACK_DESTROY} }, +}; + +static int +do_parse_parameter(const char *str, size_t strlen, unsigned int *value, + int parse_type) +{ + int i, ret = 0; + struct parse_parameter *p = &parse_array[parse_type]; + + for (i = 0; i < p->size; i++) + if (strncasecmp(str, p->parameter[i], strlen) == 0) { + *value |= p->value[i]; + ret = 1; + break; + } + + return ret; +} + +static void +parse_parameter(const char *arg, unsigned int *status, int parse_type) +{ + const char *comma; + + while ((comma = strchr(arg, ',')) != NULL) { + if (comma == arg + || !do_parse_parameter(arg, comma-arg, status, parse_type)) + exit_error(PARAMETER_PROBLEM,"Bad parameter `%s'", arg); + arg = comma+1; + } + + if (strlen(arg) == 0 + || !do_parse_parameter(arg, strlen(arg), status, parse_type)) + exit_error(PARAMETER_PROBLEM, "Bad parameter `%s'", arg); +} + +static void +add_command(unsigned int *cmd, const int newcmd, const int othercmds) +{ + if (*cmd & (~othercmds)) + exit_error(PARAMETER_PROBLEM, "Invalid commands combination\n"); + *cmd |= newcmd; +} + +unsigned int check_type(int argc, char *argv[]) +{ + char *table = NULL; + + /* Nasty bug or feature in getopt_long ? + * It seems that it behaves badly with optional arguments. + * Fortunately, I just stole the fix from iptables ;) */ + if (optarg) + return 0; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + table = argv[optind++]; + + if (!table) + return 0; + + if (strncmp("expect", table, 6) == 0) + return 1; + else if (strncmp("conntrack", table, 9) == 0) + return 0; + else + exit_error(PARAMETER_PROBLEM, "unknown type `%s'\n", table); + + return 0; +} + +static void set_family(int *family, int new) +{ + if (*family == AF_UNSPEC) + *family = new; + else if (*family != new) + exit_error(PARAMETER_PROBLEM, "mismatched address family\n"); +} + +struct addr_parse { + struct in_addr addr; + struct in6_addr addr6; + unsigned int family; +}; + +int __parse_inetaddr(const char *cp, struct addr_parse *parse) +{ + if (inet_aton(cp, &parse->addr)) + return AF_INET; +#ifdef HAVE_INET_PTON_IPV6 + else if (inet_pton(AF_INET6, cp, &parse->addr6) > 0) + return AF_INET6; +#endif + + exit_error(PARAMETER_PROBLEM, "Invalid IP address `%s'.", cp); +} + +int parse_inetaddr(const char *cp, union nfct_address *address) +{ + struct addr_parse parse; + int ret; + + if ((ret = __parse_inetaddr(cp, &parse)) == AF_INET) + address->v4 = parse.addr.s_addr; + else if (ret == AF_INET6) + memcpy(address->v6, &parse.addr6, sizeof(parse.addr6)); + + return ret; +} + +/* Shamelessly stolen from libipt_DNAT ;). Ranges expected in network order. */ +static void +nat_parse(char *arg, int portok, struct nfct_nat *range) +{ + char *colon, *dash, *error; + struct addr_parse parse; + + memset(range, 0, sizeof(range)); + colon = strchr(arg, ':'); + + if (colon) { + int port; + + if (!portok) + exit_error(PARAMETER_PROBLEM, + "Need TCP or UDP with port specification"); + + port = atoi(colon+1); + if (port == 0 || port > 65535) + exit_error(PARAMETER_PROBLEM, + "Port `%s' not valid\n", colon+1); + + error = strchr(colon+1, ':'); + if (error) + exit_error(PARAMETER_PROBLEM, + "Invalid port:port syntax - use dash\n"); + + dash = strchr(colon, '-'); + if (!dash) { + range->l4min.tcp.port + = range->l4max.tcp.port + = htons(port); + } else { + int maxport; + + maxport = atoi(dash + 1); + if (maxport == 0 || maxport > 65535) + exit_error(PARAMETER_PROBLEM, + "Port `%s' not valid\n", dash+1); + if (maxport < port) + /* People are stupid. */ + exit_error(PARAMETER_PROBLEM, + "Port range `%s' funky\n", colon+1); + range->l4min.tcp.port = htons(port); + range->l4max.tcp.port = htons(maxport); + } + /* Starts with a colon? No IP info... */ + if (colon == arg) + return; + *colon = '\0'; + } + + dash = strchr(arg, '-'); + if (colon && dash && dash > colon) + dash = NULL; + + if (dash) + *dash = '\0'; + + if (__parse_inetaddr(arg, &parse) != AF_INET) + return; + + range->min_ip = parse.addr.s_addr; + if (dash) { + if (__parse_inetaddr(dash+1, &parse) != AF_INET) + return; + range->max_ip = parse.addr.s_addr; + } else + range->max_ip = parse.addr.s_addr; +} + +static void event_sighandler(int s) +{ + fprintf(stdout, "Now closing conntrack event dumping...\n"); + nfct_close(cth); + exit(0); +} + +static const char usage_commands[] = + "Commands:\n" + " -L [table] [options]\t\tList conntrack or expectation table\n" + " -G [table] parameters\t\tGet conntrack or expectation\n" + " -D [table] parameters\t\tDelete conntrack or expectation\n" + " -I [table] parameters\t\tCreate a conntrack or expectation\n" + " -U [table] parameters\t\tUpdate a conntrack\n" + " -E [table] [options]\t\tShow events\n" + " -F [table]\t\t\tFlush table\n"; + +static const char usage_tables[] = + "Tables: conntrack, expect\n"; + +static const char usage_conntrack_parameters[] = + "Conntrack parameters and options:\n" + " -a, --nat-range min_ip[-max_ip]\tNAT ip range\n" + " -m, --mark mark\t\t\tSet mark\n" + " -e, --event-mask eventmask\t\tEvent mask, eg. NEW,DESTROY\n" + " -z, --zero \t\t\t\tZero counters while listing\n" + ; + +static const char usage_expectation_parameters[] = + "Expectation parameters and options:\n" + " --tuple-src ip\tSource address in expect tuple\n" + " --tuple-dst ip\tDestination address in expect tuple\n" + " --mask-src ip\t\tSource mask address\n" + " --mask-dst ip\t\tDestination mask address\n"; + +static const char usage_parameters[] = + "Common parameters and options:\n" + " -s, --orig-src ip\t\tSource address from original direction\n" + " -d, --orig-dst ip\t\tDestination address from original direction\n" + " -r, --reply-src ip\t\tSource addres from reply direction\n" + " -q, --reply-dst ip\t\tDestination address from reply direction\n" + " -p, --protonum proto\t\tLayer 4 Protocol, eg. 'tcp'\n" + " -f, --family proto\t\tLayer 3 Protocol, eg. 'ipv6'\n" + " -t, --timeout timeout\t\tSet timeout\n" + " -u, --status status\t\tSet status, eg. ASSURED\n" + " -i, --id [id]\t\t\tShow or set conntrack ID\n" + ; + + +void usage(char *prog) { + fprintf(stdout, "Tool to manipulate conntrack and expectations. Version %s\n", VERSION); + fprintf(stdout, "Usage: %s [commands] [options]\n", prog); + + fprintf(stdout, "\n%s", usage_commands); + fprintf(stdout, "\n%s", usage_tables); + fprintf(stdout, "\n%s", usage_conntrack_parameters); + fprintf(stdout, "\n%s", usage_expectation_parameters); + fprintf(stdout, "\n%s", usage_parameters); +} + +#define CT_COMPARISON (CT_OPT_PROTO | CT_OPT_ORIG | CT_OPT_REPL | CT_OPT_MARK) + +static struct nfct_tuple orig, reply, mask; +static struct nfct_tuple exptuple; +static struct ctproto_handler *h; +static union nfct_protoinfo proto; +static struct nfct_nat range; +static struct nfct_conntrack *ct; +static struct nfct_expect *exp; +static unsigned long timeout; +static unsigned int status; +static unsigned int mark; +static unsigned int id = NFCT_ANY_ID; +static struct nfct_conntrack_compare cmp; + +int main(int argc, char *argv[]) +{ + int c; + unsigned int command = 0, options = 0; + unsigned int type = 0, event_mask = 0; + unsigned int l3flags = 0, l4flags = 0, metaflags = 0; + int res = 0; + int family = AF_UNSPEC; + struct nfct_conntrack_compare *pcmp; + + while ((c = getopt_long(argc, argv, + "L::I::U::D::G::E::F::hVs:d:r:q:p:t:u:e:a:z[:]:{:}:m:i::f:", + opts, NULL)) != -1) { + switch(c) { + case 'L': + type = check_type(argc, argv); + if (type == 0) + add_command(&command, CT_LIST, CT_NONE); + else if (type == 1) + add_command(&command, EXP_LIST, CT_NONE); + break; + case 'I': + type = check_type(argc, argv); + if (type == 0) + add_command(&command, CT_CREATE, CT_NONE); + else if (type == 1) + add_command(&command, EXP_CREATE, CT_NONE); + break; + case 'U': + type = check_type(argc, argv); + if (type == 0) + add_command(&command, CT_UPDATE, CT_NONE); + else + exit_error(PARAMETER_PROBLEM, "Can't update " + "expectations"); + break; + case 'D': + type = check_type(argc, argv); + if (type == 0) + add_command(&command, CT_DELETE, CT_NONE); + else if (type == 1) + add_command(&command, EXP_DELETE, CT_NONE); + break; + case 'G': + type = check_type(argc, argv); + if (type == 0) + add_command(&command, CT_GET, CT_NONE); + else if (type == 1) + add_command(&command, EXP_GET, CT_NONE); + break; + case 'F': + type = check_type(argc, argv); + if (type == 0) + add_command(&command, CT_FLUSH, CT_NONE); + else if (type == 1) + add_command(&command, EXP_FLUSH, CT_NONE); + break; + case 'E': + type = check_type(argc, argv); + if (type == 0) + add_command(&command, CT_EVENT, CT_NONE); + else if (type == 1) + add_command(&command, EXP_EVENT, CT_NONE); + break; + case 'V': + add_command(&command, CT_VERSION, CT_NONE); + break; + case 'h': + add_command(&command, CT_HELP, CT_NONE); + break; + case 's': + options |= CT_OPT_ORIG_SRC; + if (optarg) { + orig.l3protonum = + parse_inetaddr(optarg, &orig.src); + set_family(&family, orig.l3protonum); + if (orig.l3protonum == AF_INET) + l3flags |= IPV4_ORIG_SRC; + else if (orig.l3protonum == AF_INET6) + l3flags |= IPV6_ORIG_SRC; + } + break; + case 'd': + options |= CT_OPT_ORIG_DST; + if (optarg) { + orig.l3protonum = + parse_inetaddr(optarg, &orig.dst); + set_family(&family, orig.l3protonum); + if (orig.l3protonum == AF_INET) + l3flags |= IPV4_ORIG_DST; + else if (orig.l3protonum == AF_INET6) + l3flags |= IPV6_ORIG_DST; + } + break; + case 'r': + options |= CT_OPT_REPL_SRC; + if (optarg) { + reply.l3protonum = + parse_inetaddr(optarg, &reply.src); + set_family(&family, reply.l3protonum); + if (orig.l3protonum == AF_INET) + l3flags |= IPV4_REPL_SRC; + else if (orig.l3protonum == AF_INET6) + l3flags |= IPV6_REPL_SRC; + } + break; + case 'q': + options |= CT_OPT_REPL_DST; + if (optarg) { + reply.l3protonum = + parse_inetaddr(optarg, &reply.dst); + set_family(&family, reply.l3protonum); + if (orig.l3protonum == AF_INET) + l3flags |= IPV4_REPL_DST; + else if (orig.l3protonum == AF_INET6) + l3flags |= IPV6_REPL_DST; + } + break; + case 'p': + options |= CT_OPT_PROTO; + h = findproto(optarg); + if (!h) + exit_error(PARAMETER_PROBLEM, "proto needed\n"); + orig.protonum = h->protonum; + reply.protonum = h->protonum; + exptuple.protonum = h->protonum; + mask.protonum = h->protonum; + opts = merge_options(opts, h->opts, + &h->option_offset); + break; + case 't': + options |= CT_OPT_TIMEOUT; + if (optarg) + timeout = atol(optarg); + break; + case 'u': { + if (!optarg) + continue; + + options |= CT_OPT_STATUS; + parse_parameter(optarg, &status, PARSE_STATUS); + break; + } + case 'e': + options |= CT_OPT_EVENT_MASK; + parse_parameter(optarg, &event_mask, PARSE_EVENT); + break; + case 'z': + options |= CT_OPT_ZERO; + break; + case '{': + options |= CT_OPT_MASK_SRC; + if (optarg) { + mask.l3protonum = + parse_inetaddr(optarg, &mask.src); + set_family(&family, mask.l3protonum); + } + break; + case '}': + options |= CT_OPT_MASK_DST; + if (optarg) { + mask.l3protonum = + parse_inetaddr(optarg, &mask.dst); + set_family(&family, mask.l3protonum); + } + break; + case '[': + options |= CT_OPT_EXP_SRC; + if (optarg) { + exptuple.l3protonum = + parse_inetaddr(optarg, &exptuple.src); + set_family(&family, exptuple.l3protonum); + } + break; + case ']': + options |= CT_OPT_EXP_DST; + if (optarg) { + exptuple.l3protonum = + parse_inetaddr(optarg, &exptuple.dst); + set_family(&family, exptuple.l3protonum); + } + break; + case 'a': + options |= CT_OPT_NATRANGE; + set_family(&family, AF_INET); + nat_parse(optarg, 1, &range); + break; + case 'm': + options |= CT_OPT_MARK; + mark = atol(optarg); + metaflags |= NFCT_MARK; + break; + case 'i': { + char *s = NULL; + options |= CT_OPT_ID; + if (optarg) + break; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + s = argv[optind++]; + + if (s) + id = atol(s); + break; + } + case 'f': + options |= CT_OPT_FAMILY; + if (strncmp(optarg, "ipv4", strlen("ipv4")) == 0) + set_family(&family, AF_INET); + else if (strncmp(optarg, "ipv6", strlen("ipv6")) == 0) + set_family(&family, AF_INET6); + else + exit_error(PARAMETER_PROBLEM, "Unknown " + "protocol family\n"); + break; + default: + if (h && h->parse_opts + &&!h->parse_opts(c - h->option_offset, argv, &orig, + &reply, &exptuple, &mask, &proto, + &l4flags)) + exit_error(PARAMETER_PROBLEM, "parse error\n"); + + /* Unknown argument... */ + if (!h) { + usage(argv[0]); + exit_error(PARAMETER_PROBLEM, "Missing " + "arguments...\n"); + } + break; + } + } + + /* default family */ + if (family == AF_UNSPEC) + family = AF_INET; + + generic_cmd_check(command, options); + generic_opt_check(command, options); + + if (!(command & CT_HELP) + && h && h->final_check + && !h->final_check(l4flags, command, &orig, &reply)) { + usage(argv[0]); + extension_help(h); + exit_error(PARAMETER_PROBLEM, "Missing protocol arguments!\n"); + } + + switch(command) { + + case CT_LIST: + cth = nfct_open(CONNTRACK, 0); + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + + if (options & CT_COMPARISON) { + + if (options & CT_OPT_ZERO) + exit_error(PARAMETER_PROBLEM, "Can't use -z " + "with filtering parameters"); + + ct = nfct_conntrack_alloc(&orig, &reply, timeout, + &proto, status, mark, id, + NULL); + if (!ct) + exit_error(OTHER_PROBLEM, "Not enough memory"); + + cmp.ct = ct; + cmp.flags = metaflags; + cmp.l3flags = l3flags; + cmp.l4flags = l4flags; + pcmp = &cmp; + } + + if (options & CT_OPT_ID) + nfct_register_callback(cth, + nfct_default_conntrack_display_id, + (void *) pcmp); + else + nfct_register_callback(cth, + nfct_default_conntrack_display, + (void *) pcmp); + + if (options & CT_OPT_ZERO) + res = + nfct_dump_conntrack_table_reset_counters(cth, family); + else + res = nfct_dump_conntrack_table(cth, family); + nfct_close(cth); + break; + + case EXP_LIST: + cth = nfct_open(EXPECT, 0); + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + if (options & CT_OPT_ID) + nfct_register_callback(cth, + nfct_default_expect_display_id, + NULL); + else + nfct_register_callback(cth, + nfct_default_expect_display, + NULL); + res = nfct_dump_expect_list(cth, family); + nfct_close(cth); + break; + + case CT_CREATE: + if ((options & CT_OPT_ORIG) + && !(options & CT_OPT_REPL)) { + reply.l3protonum = orig.l3protonum; + memcpy(&reply.src, &orig.dst, sizeof(reply.src)); + memcpy(&reply.dst, &orig.src, sizeof(reply.dst)); + } else if (!(options & CT_OPT_ORIG) + && (options & CT_OPT_REPL)) { + orig.l3protonum = reply.l3protonum; + memcpy(&orig.src, &reply.dst, sizeof(orig.src)); + memcpy(&orig.dst, &reply.src, sizeof(orig.dst)); + } + if (options & CT_OPT_NATRANGE) + ct = nfct_conntrack_alloc(&orig, &reply, timeout, + &proto, status, mark, id, + &range); + else + ct = nfct_conntrack_alloc(&orig, &reply, timeout, + &proto, status, mark, id, + NULL); + if (!ct) + exit_error(OTHER_PROBLEM, "Not Enough memory"); + + cth = nfct_open(CONNTRACK, 0); + if (!cth) { + nfct_conntrack_free(ct); + exit_error(OTHER_PROBLEM, "Can't open handler"); + } + res = nfct_create_conntrack(cth, ct); + nfct_close(cth); + nfct_conntrack_free(ct); + break; + + case EXP_CREATE: + if (options & CT_OPT_ORIG) + exp = nfct_expect_alloc(&orig, &exptuple, + &mask, timeout, id); + else if (options & CT_OPT_REPL) + exp = nfct_expect_alloc(&reply, &exptuple, + &mask, timeout, id); + if (!exp) + exit_error(OTHER_PROBLEM, "Not enough memory"); + + cth = nfct_open(EXPECT, 0); + if (!cth) { + nfct_expect_free(exp); + exit_error(OTHER_PROBLEM, "Can't open handler"); + } + res = nfct_create_expectation(cth, exp); + nfct_expect_free(exp); + nfct_close(cth); + break; + + case CT_UPDATE: + if ((options & CT_OPT_ORIG) + && !(options & CT_OPT_REPL)) { + reply.l3protonum = orig.l3protonum; + memcpy(&reply.src, &orig.dst, sizeof(reply.src)); + memcpy(&reply.dst, &orig.src, sizeof(reply.dst)); + } else if (!(options & CT_OPT_ORIG) + && (options & CT_OPT_REPL)) { + orig.l3protonum = reply.l3protonum; + memcpy(&orig.src, &reply.dst, sizeof(orig.src)); + memcpy(&orig.dst, &reply.src, sizeof(orig.dst)); + } + ct = nfct_conntrack_alloc(&orig, &reply, timeout, + &proto, status, mark, id, + NULL); + if (!ct) + exit_error(OTHER_PROBLEM, "Not enough memory"); + + cth = nfct_open(CONNTRACK, 0); + if (!cth) { + nfct_conntrack_free(ct); + exit_error(OTHER_PROBLEM, "Can't open handler"); + } + res = nfct_update_conntrack(cth, ct); + nfct_conntrack_free(ct); + nfct_close(cth); + break; + + case CT_DELETE: + if (!(options & CT_OPT_ORIG) && !(options & CT_OPT_REPL)) + exit_error(PARAMETER_PROBLEM, "Can't kill conntracks " + "just by its ID"); + cth = nfct_open(CONNTRACK, 0); + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + if (options & CT_OPT_ORIG) + res = nfct_delete_conntrack(cth, &orig, + NFCT_DIR_ORIGINAL, + id); + else if (options & CT_OPT_REPL) + res = nfct_delete_conntrack(cth, &reply, + NFCT_DIR_REPLY, + id); + nfct_close(cth); + break; + + case EXP_DELETE: + cth = nfct_open(EXPECT, 0); + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + if (options & CT_OPT_ORIG) + res = nfct_delete_expectation(cth, &orig, id); + else if (options & CT_OPT_REPL) + res = nfct_delete_expectation(cth, &reply, id); + nfct_close(cth); + break; + + case CT_GET: + cth = nfct_open(CONNTRACK, 0); + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + nfct_register_callback(cth, nfct_default_conntrack_display, + NULL); + if (options & CT_OPT_ORIG) + res = nfct_get_conntrack(cth, &orig, + NFCT_DIR_ORIGINAL, id); + else if (options & CT_OPT_REPL) + res = nfct_get_conntrack(cth, &reply, + NFCT_DIR_REPLY, id); + nfct_close(cth); + break; + + case EXP_GET: + cth = nfct_open(EXPECT, 0); + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + nfct_register_callback(cth, nfct_default_expect_display, + NULL); + if (options & CT_OPT_ORIG) + res = nfct_get_expectation(cth, &orig, id); + else if (options & CT_OPT_REPL) + res = nfct_get_expectation(cth, &reply, id); + nfct_close(cth); + break; + + case CT_FLUSH: + cth = nfct_open(CONNTRACK, 0); + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + res = nfct_flush_conntrack_table(cth, AF_INET); + nfct_close(cth); + break; + + case EXP_FLUSH: + cth = nfct_open(EXPECT, 0); + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + res = nfct_flush_expectation_table(cth, AF_INET); + nfct_close(cth); + break; + + case CT_EVENT: + if (options & CT_OPT_EVENT_MASK) + cth = nfct_open(CONNTRACK, event_mask); + else + cth = nfct_open(CONNTRACK, NFCT_ALL_CT_GROUPS); + + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + signal(SIGINT, event_sighandler); + + if (options & CT_COMPARISON) { + ct = nfct_conntrack_alloc(&orig, &reply, timeout, + &proto, status, mark, id, + NULL); + if (!ct) + exit_error(OTHER_PROBLEM, "Not enough memory"); + + cmp.ct = ct; + cmp.flags = metaflags; + cmp.l3flags = l3flags; + cmp.l4flags = l4flags; + pcmp = &cmp; + } + + nfct_register_callback(cth, + nfct_default_conntrack_event_display, + (void *) pcmp); + res = nfct_event_conntrack(cth); + nfct_close(cth); + break; + + case EXP_EVENT: + cth = nfct_open(EXPECT, NF_NETLINK_CONNTRACK_EXP_NEW); + if (!cth) + exit_error(OTHER_PROBLEM, "Can't open handler"); + signal(SIGINT, event_sighandler); + nfct_register_callback(cth, nfct_default_expect_display, + NULL); + res = nfct_event_expectation(cth); + nfct_close(cth); + break; + + case CT_VERSION: + fprintf(stdout, "%s v%s\n", PROGNAME, VERSION); + break; + case CT_HELP: + usage(argv[0]); + if (options & CT_OPT_PROTO) + extension_help(h); + break; + default: + usage(argv[0]); + break; + } + + if (opts != original_opts) { + free(opts); + opts = original_opts; + global_option_offset = 0; + } + + if (res < 0) { + fprintf(stderr, "Operation failed: %s\n", err2str(res, command)); + exit(OTHER_PROBLEM); + } + + return 0; +} diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 0000000..274a140 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,199 @@ +/* + * (C) 2006-2007 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. + * + * Description: generic hash table implementation + */ + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/types.h> +#include <string.h> +#include "slist.h" +#include "hash.h" + + +struct hashtable_node *hashtable_alloc_node(int datasize, void *data) +{ + struct hashtable_node *n; + int size = sizeof(struct hashtable_node) + datasize; + + n = malloc(size); + if (!n) + return NULL; + memset(n, 0, size); + memcpy(n->data, data, datasize); + + return n; +} + +void hashtable_destroy_node(struct hashtable_node *h) +{ + free(h); +} + +struct hashtable * +hashtable_create(int hashsize, int limit, int datasize, + u_int32_t (*hash)(const void *data, struct hashtable *table), + int (*compare)(const void *data1, const void *data2)) +{ + int i; + struct hashtable *h; + struct hashtype *t; + int size = sizeof(struct hashtable) + + hashsize * sizeof(struct slist_head); + + h = (struct hashtable *) malloc(size); + if (!h) { + errno = ENOMEM; + return NULL; + } + + memset(h, 0, size); + for (i=0; i<hashsize; i++) + INIT_SLIST_HEAD(h->members[i]); + + h->hashsize = hashsize; + h->limit = limit; + h->datasize = datasize; + h->hash = hash; + h->compare = compare; + + return h; +} + +void hashtable_destroy(struct hashtable *h) +{ + hashtable_flush(h); + free(h); +} + +void *hashtable_add(struct hashtable *table, void *data) +{ + struct slist_head *e; + struct hashtable_node *n; + u_int32_t id; + int i; + + /* hash table is full */ + if (table->count >= table->limit) { + errno = ENOSPC; + return NULL; + } + + id = table->hash(data, table); + + slist_for_each(e, &table->members[id]) { + n = slist_entry(e, struct hashtable_node, head); + if (table->compare(n->data, data)) { + errno = EEXIST; + return NULL; + } + } + + n = hashtable_alloc_node(table->datasize, data); + if (n == NULL) { + errno = ENOMEM; + return NULL; + } + + slist_add(&table->members[id], &n->head); + table->count++; + + return n->data; +} + +void *hashtable_test(struct hashtable *table, const void *data) +{ + struct slist_head *e; + u_int32_t id; + struct hashtable_node *n; + int i; + + id = table->hash(data, table); + + slist_for_each(e, &table->members[id]) { + n = slist_entry(e, struct hashtable_node, head); + if (table->compare(n->data, data)) + return n->data; + } + + errno = ENOENT; + return NULL; +} + +int hashtable_del(struct hashtable *table, void *data) +{ + struct slist_head *e, *next, *prev; + u_int32_t id; + struct hashtable_node *n; + int i; + + id = table->hash(data, table); + + slist_for_each_safe(e, prev, next, &table->members[id]) { + n = slist_entry(e, struct hashtable_node, head); + if (table->compare(n->data, data)) { + slist_del(e, prev); + hashtable_destroy_node(n); + table->count--; + return 0; + } + } + errno = ENOENT; + return -1; +} + +int hashtable_flush(struct hashtable *table) +{ + int i; + struct slist_head *e, *next, *prev; + struct hashtable_node *n; + + for (i=0; i < table->hashsize; i++) + slist_for_each_safe(e, prev, next, &table->members[i]) { + n = slist_entry(e, struct hashtable_node, head); + slist_del(e, prev); + hashtable_destroy_node(n); + } + + table->count = 0; + + return 0; +} + +int hashtable_iterate(struct hashtable *table, void *data, + int (*iterate)(void *data1, void *data2)) +{ + int i; + struct slist_head *e, *next, *prev; + struct hashtable_node *n; + + for (i=0; i < table->hashsize; i++) { + slist_for_each_safe(e, prev, next, &table->members[i]) { + n = slist_entry(e, struct hashtable_node, head); + if (iterate(data, n->data) == -1) + return -1; + } + } + return 0; +} + +unsigned int hashtable_counter(struct hashtable *table) +{ + return table->count; +} diff --git a/src/ignore_pool.c b/src/ignore_pool.c new file mode 100644 index 0000000..5946617 --- /dev/null +++ b/src/ignore_pool.c @@ -0,0 +1,136 @@ +/* + * (C) 2006-2007 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 "jhash.h" +#include "hash.h" +#include "conntrackd.h" +#include "ignore.h" +#include "debug.h" +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> + +#define IGNORE_POOL_SIZE 32 +#define IGNORE_POOL_LIMIT 1024 + +static u_int32_t hash(const void *data, struct hashtable *table) +{ + const u_int32_t *ip = data; + + return jhash_1word(*ip, 0) % table->hashsize; +} + +static u_int32_t hash6(const void *data, struct hashtable *table) +{ + return jhash(data, sizeof(u_int32_t)*4, 0) % table->hashsize; +} + +static int compare(const void *data1, const void *data2) +{ + const u_int32_t *ip1 = data1; + const u_int32_t *ip2 = data2; + + return *ip1 == *ip2; +} + +static int compare6(const void *data1, const void *data2) +{ + return memcmp(data1, data2, sizeof(u_int32_t)*4) == 0; +} + +struct ignore_pool *ignore_pool_create(u_int8_t proto) +{ + int i, j = 0; + struct ignore_pool *ip; + + ip = malloc(sizeof(struct ignore_pool)); + if (!ip) + return NULL; + memset(ip, 0, sizeof(struct ignore_pool)); + + switch(proto) { + case AF_INET: + ip->h = hashtable_create(IGNORE_POOL_SIZE, + IGNORE_POOL_LIMIT, + sizeof(u_int32_t), + hash, + compare); + break; + case AF_INET6: + ip->h = hashtable_create(IGNORE_POOL_SIZE, + IGNORE_POOL_LIMIT, + sizeof(u_int32_t)*4, + hash6, + compare6); + break; + } + + if (!ip->h) { + free(ip); + return NULL; + } + + return ip; +} + +void ignore_pool_destroy(struct ignore_pool *ip) +{ + hashtable_destroy(ip->h); + free(ip); +} + +int ignore_pool_add(struct ignore_pool *ip, void *data) +{ + if (!hashtable_add(ip->h, data)) + return 0; + + return 1; +} + +int __ignore_pool_test_ipv4(struct ignore_pool *ip, struct nf_conntrack *ct) +{ + return (hashtable_test(ip->h, nfct_get_attr(ct, ATTR_ORIG_IPV4_SRC)) || + hashtable_test(ip->h, nfct_get_attr(ct, ATTR_ORIG_IPV4_DST)) || + hashtable_test(ip->h, nfct_get_attr(ct, ATTR_REPL_IPV4_SRC)) || + hashtable_test(ip->h, nfct_get_attr(ct, ATTR_REPL_IPV4_DST))); +} + +int __ignore_pool_test_ipv6(struct ignore_pool *ip, struct nf_conntrack *ct) +{ + return (hashtable_test(ip->h, nfct_get_attr(ct, ATTR_ORIG_IPV6_SRC)) || + hashtable_test(ip->h, nfct_get_attr(ct, ATTR_ORIG_IPV6_DST)) || + hashtable_test(ip->h, nfct_get_attr(ct, ATTR_REPL_IPV6_SRC)) || + hashtable_test(ip->h, nfct_get_attr(ct, ATTR_REPL_IPV6_DST))); +} + +int ignore_pool_test(struct ignore_pool *ip, struct nf_conntrack *ct) +{ + int ret; + + switch(nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO)) { + case AF_INET: + ret = __ignore_pool_test_ipv4(ip, ct); + break; + case AF_INET6: + ret = __ignore_pool_test_ipv6(ip, ct); + break; + default: + dlog(STATE(log), "unknown conntrack layer 3 protocol?"); + break; + } + + return ret; +} diff --git a/src/local.c b/src/local.c new file mode 100644 index 0000000..eef70ad --- /dev/null +++ b/src/local.c @@ -0,0 +1,159 @@ +/* + * (C) 2006 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. + * + * Description: UNIX sockets library + */ + +#include <stdio.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <errno.h> +#include "debug.h" + +#include "local.h" + +int local_server_create(struct local_conf *conf) +{ + int fd; + int len; + struct sockaddr_un local; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + debug("local_server_create:socket"); + return -1; + } + + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &conf->reuseaddr, + sizeof(conf->reuseaddr)) == -1) { + debug("local_server_create:setsockopt"); + close(fd); + return -1; + } + + local.sun_family = AF_UNIX; + strcpy(local.sun_path, conf->path); + len = strlen(local.sun_path) + sizeof(local.sun_family); + unlink(conf->path); + + if (bind(fd, (struct sockaddr *) &local, len) == -1) { + debug("local_server_create:bind"); + close(fd); + return -1; + } + + if (listen(fd, conf->backlog) == -1) { + close(fd); + debug("local_server_create:listen"); + return -1; + } + + return fd; +} + +void local_server_destroy(int fd) +{ + close(fd); +} + +int do_local_server_step(int fd, void *data, + void (*process)(int fd, void *data)) +{ + int rfd; + struct sockaddr_un local; + size_t sin_size = sizeof(struct sockaddr_un); + + if ((rfd = accept(fd, (struct sockaddr *)&local, &sin_size)) == -1) { + debug("do_local_server_step:accept"); + return -1; + } + + process(rfd, data); + close(rfd); + + return 0; +} + +int local_client_create(struct local_conf *conf) +{ + int len; + struct sockaddr_un local; + int fd; + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) + return -1; + + local.sun_family = AF_UNIX; + strcpy(local.sun_path, conf->path); + len = strlen(local.sun_path) + sizeof(local.sun_family); + + if (connect(fd, (struct sockaddr *) &local, len) == -1) { + close(fd); + debug("local_client_create: connect: "); + return -1; + } + + return fd; +} + +void local_client_destroy(int fd) +{ + close(fd); +} + +int do_local_client_step(int fd, void (*process)(char *buf)) +{ + int numbytes; + char buf[1024]; + + memset(buf, 0, sizeof(buf)); + while ((numbytes = recv(fd, buf, sizeof(buf)-1, 0)) > 0) { + buf[sizeof(buf)-1] = '\0'; + if (process) + process(buf); + memset(buf, 0, sizeof(buf)); + } + + return 0; +} + +void local_step(char *buf) +{ + printf(buf); +} + +int do_local_request(int request, + struct local_conf *conf, + void (*step)(char *buf)) +{ + int fd, ret; + + fd = local_client_create(conf); + if (fd == -1) + return -1; + + ret = send(fd, &request, sizeof(int), 0); + if (ret == -1) { + debug("send:"); + return -1; + } + + do_local_client_step(fd, step); + + local_client_destroy(fd); + + return 0; +} diff --git a/src/lock.c b/src/lock.c new file mode 100644 index 0000000..cd68baf --- /dev/null +++ b/src/lock.c @@ -0,0 +1,32 @@ +/* + * (C) 2006 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 <stdio.h> +#include <pthread.h> + +static pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER; + +void lock() +{ + pthread_mutex_lock(&global_lock); +} + +void unlock() +{ + pthread_mutex_unlock(&global_lock); +} diff --git a/src/log.c b/src/log.c new file mode 100644 index 0000000..88cadea --- /dev/null +++ b/src/log.c @@ -0,0 +1,57 @@ +/* + * (C) 2006 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. + * + * Description: Logging support for the conntrack daemon + */ + +#include <stdio.h> +#include <time.h> +#include <stdarg.h> +#include <string.h> + +FILE *init_log(char *filename) +{ + FILE *fd; + + fd = fopen(filename, "a+"); + if (fd == NULL) { + fprintf(stderr, "can't open log file `%s'\n", filename); + return NULL; + } + + return fd; +} + +void dlog(FILE *fd, char *format, ...) +{ + time_t t = time(NULL); + char *buf = ctime(&t); + va_list args; + + buf[strlen(buf)-1]='\0'; + va_start(args, format); + fprintf(fd, "[%s] (pid=%d) ", buf, getpid()); + vfprintf(fd, format, args); + va_end(args); + fprintf(fd, "\n"); + fflush(fd); +} + +void close_log(FILE *fd) +{ + fclose(fd); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..1c75970 --- /dev/null +++ b/src/main.c @@ -0,0 +1,302 @@ +/* + * (C) 2006-2007 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 <stdlib.h> +#include "conntrackd.h" +#include "log.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/utsname.h> +#include <linux/capability.h> +#include <errno.h> +#include "hash.h" +#include "jhash.h" + +struct ct_general_state st; +union ct_state state; + +static const char usage_daemon_commands[] = + "Daemon mode commands:\n" + " -d [options]\t\tRun in daemon mode\n" + " -S [options]\t\tRun in statistics mode\n"; + +static const char usage_client_commands[] = + "Client mode commands:\n" + " -c, commit external cache to conntrack table\n" + " -f, flush internal and external cache\n" + " -F, flush kernel conntrack table\n" + " -i, display content of the internal cache\n" + " -e, display the content of the external cache\n" + " -k, kill conntrack daemon\n" + " -s, dump statistics\n" + " -R, resync with kernel conntrack table\n" + " -n, request resync with other node (only NACK mode)\n" + " -x, dump cache in XML format (requires -i or -e)"; + +static const char usage_options[] = + "Options:\n" + " -C [configfile], configuration file path\n"; + +void show_usage(char *progname) +{ + fprintf(stdout, "Connection tracking userspace daemon v%s\n", VERSION); + fprintf(stdout, "Usage: %s [commands] [options]\n\n", progname); + fprintf(stdout, "%s\n", usage_daemon_commands); + fprintf(stdout, "%s\n", usage_client_commands); + fprintf(stdout, "%s\n", usage_options); +} + +/* These live in run.c */ +int init(int); +void run(void); + +void set_operation_mode(int *current, int want, char *argv[]) +{ + if (*current == NOT_SET) { + *current = want; + return; + } + if (*current != want) { + show_usage(argv[0]); + fprintf(stderr, "\nError: Invalid parameters\n"); + exit(EXIT_FAILURE); + } +} + +static int check_capabilities(void) +{ + int ret; + cap_user_header_t hcap; + cap_user_data_t dcap; + + hcap = malloc(sizeof(cap_user_header_t)); + if (!hcap) + return -1; + + hcap->version = _LINUX_CAPABILITY_VERSION; + hcap->pid = getpid(); + + dcap = malloc(sizeof(cap_user_data_t)); + if (!dcap) { + free(hcap); + return -1; + } + + if (capget(hcap, dcap) == -1) { + free(hcap); + free(dcap); + return -1; + } + + ret = dcap->permitted & (1 << CAP_NET_ADMIN); + + free(hcap); + free(dcap); + + return ret; +} + +int main(int argc, char *argv[]) +{ + int ret, i, config_set = 0, action; + char config_file[PATH_MAX]; + int type = 0, mode = 0; + struct utsname u; + int version, major, minor; + + /* Check kernel version: it must be >= 2.6.18 */ + if (uname(&u) == -1) { + fprintf(stderr, "Can't retrieve kernel version via uname()\n"); + exit(EXIT_FAILURE); + } + sscanf(u.release, "%d.%d.%d", &version, &major, &minor); + if (version < 2 && major < 6) { + fprintf(stderr, "Linux kernel version must be >= 2.6.18\n"); + exit(EXIT_FAILURE); + } + + if (major == 6 && minor < 18) { + fprintf(stderr, "Linux kernel version must be >= 2.6.18\n"); + exit(EXIT_FAILURE); + } + + ret = check_capabilities(); + switch (ret) { + case -1: + fprintf(stderr, "Can't get capabilities\n"); + exit(EXIT_FAILURE); + break; + case 0: + fprintf(stderr, "You require CAP_NET_ADMIN in order " + "to run conntrackd\n"); + exit(EXIT_FAILURE); + break; + default: + break; + } + + for (i=1; i<argc; i++) { + switch(argv[i][1]) { + case 'd': + set_operation_mode(&type, DAEMON, argv); + break; + case 'c': + set_operation_mode(&type, REQUEST, argv); + action = COMMIT; + break; + case 'i': + set_operation_mode(&type, REQUEST, argv); + action = DUMP_INTERNAL; + break; + case 'e': + set_operation_mode(&type, REQUEST, argv); + action = DUMP_EXTERNAL; + break; + case 'C': + if (++i < argc) { + strncpy(config_file, argv[i], PATH_MAX); + if (strlen(argv[i]) >= PATH_MAX){ + config_file[PATH_MAX-1]='\0'; + fprintf(stderr, "Path to config file " + "to long. Cutting it " + "down to %d characters", + PATH_MAX); + } + config_set = 1; + break; + } + show_usage(argv[0]); + fprintf(stderr, "Missing config filename\n"); + break; + case 'F': + set_operation_mode(&type, REQUEST, argv); + action = FLUSH_MASTER; + case 'f': + set_operation_mode(&type, REQUEST, argv); + action = FLUSH_CACHE; + break; + case 'R': + set_operation_mode(&type, REQUEST, argv); + action = RESYNC_MASTER; + break; + case 'B': + set_operation_mode(&type, REQUEST, argv); + action = SEND_BULK; + break; + case 'k': + set_operation_mode(&type, REQUEST, argv); + action = KILL; + break; + case 's': + set_operation_mode(&type, REQUEST, argv); + action = STATS; + break; + case 'S': + set_operation_mode(&mode, STATS_MODE, argv); + break; + case 'n': + set_operation_mode(&type, REQUEST, argv); + action = REQUEST_DUMP; + break; + case 'x': + if (action == DUMP_INTERNAL) + action = DUMP_INT_XML; + else if (action == DUMP_EXTERNAL) + action = DUMP_EXT_XML; + else { + show_usage(argv[0]); + fprintf(stderr, "Error: Invalid parameters\n"); + exit(EXIT_FAILURE); + + } + break; + default: + show_usage(argv[0]); + fprintf(stderr, "Unknown option: %s\n", argv[i]); + return 0; + break; + } + } + + if (config_set == 0) + strcpy(config_file, DEFAULT_CONFIGFILE); + + if ((ret = init_config(config_file)) == -1) { + fprintf(stderr, "can't open config file `%s'\n", config_file); + exit(EXIT_FAILURE); + } + + /* + * Setting up logfile + */ + STATE(log) = init_log(CONFIG(logfile)); + if (!STATE(log)) { + fprintf(stdout, "can't open logfile `%s\n'", CONFIG(logfile)); + exit(EXIT_FAILURE); + } + + if (type == REQUEST) { + if (do_local_request(action, &conf.local, local_step) == -1) + fprintf(stderr, "can't connect: is conntrackd " + "running? appropiate permissions?\n"); + exit(EXIT_SUCCESS); + } + + /* + * lock file + */ + if ((ret = open(CONFIG(lockfile), O_CREAT | O_EXCL | O_TRUNC)) == -1) { + fprintf(stderr, "lockfile `%s' exists, perhaps conntrackd " + "already running?\n", CONFIG(lockfile)); + exit(EXIT_FAILURE); + } + close(ret); + + /* Daemonize conntrackd */ + if (type == DAEMON) { + pid_t pid; + + if ((pid = fork()) == -1) { + dlog(STATE(log), "fork() failed: " + "%s", strerror(errno)); + exit(EXIT_FAILURE); + } else if (pid) + exit(EXIT_SUCCESS); + + dlog(STATE(log), "--- starting in daemon mode ---"); + } else + dlog(STATE(log), "--- starting in console mode ---"); + + /* + * initialization process + */ + + if (init(mode) == -1) { + close_log(STATE(log)); + fprintf(stderr, "ERROR: conntrackd cannot start, please " + "check the logfile for more info\n"); + unlink(CONFIG(lockfile)); + exit(EXIT_FAILURE); + } + + /* + * run main process + */ + run(); +} diff --git a/src/mcast.c b/src/mcast.c new file mode 100644 index 0000000..9904544 --- /dev/null +++ b/src/mcast.c @@ -0,0 +1,287 @@ +/* + * (C) 2006 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. + * + * Description: multicast socket library + */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <arpa/inet.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/time.h> +#include <unistd.h> +#include <string.h> +#include "mcast.h" +#include "debug.h" + +struct mcast_sock *mcast_server_create(struct mcast_conf *conf) +{ + int yes = 1; + union { + struct ip_mreq ipv4; + struct ipv6_mreq ipv6; + } mreq; + struct mcast_sock *m; + + m = (struct mcast_sock *) malloc(sizeof(struct mcast_sock)); + if (!m) + return NULL; + memset(m, 0, sizeof(struct mcast_sock)); + + switch(conf->ipproto) { + case AF_INET: + mreq.ipv4.imr_multiaddr.s_addr = conf->in.inet_addr.s_addr; + mreq.ipv4.imr_interface.s_addr =conf->ifa.interface_addr.s_addr; + + m->addr.ipv4.sin_family = AF_INET; + m->addr.ipv4.sin_port = htons(conf->port); + m->addr.ipv4.sin_addr.s_addr = htonl(INADDR_ANY); + break; + + case AF_INET6: + memcpy(&mreq.ipv6.ipv6mr_multiaddr, &conf->in.inet_addr6, + sizeof(u_int32_t) * 4); + memcpy(&mreq.ipv6.ipv6mr_interface, &conf->ifa.interface_addr6, + sizeof(u_int32_t) * 4); + + m->addr.ipv6.sin6_family = AF_INET6; + m->addr.ipv6.sin6_port = htons(conf->port); + m->addr.ipv6.sin6_addr = in6addr_any; + break; + } + + if ((m->fd = socket(conf->ipproto, SOCK_DGRAM, 0)) == -1) { + debug("mcast_sock_server_create:socket"); + free(m); + return NULL; + } + + if (setsockopt(m->fd, SOL_SOCKET, SO_REUSEADDR, &yes, + sizeof(int)) == -1) { + debug("mcast_sock_server_create:setsockopt1"); + close(m->fd); + free(m); + return NULL; + } + + if (bind(m->fd, (struct sockaddr *) &m->addr, + sizeof(struct sockaddr)) == -1) { + debug("mcast_sock_server_create:bind"); + close(m->fd); + free(m); + return NULL; + } + + + switch(conf->ipproto) { + case AF_INET: + if (setsockopt(m->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreq.ipv4, sizeof(mreq.ipv4)) < 0) { + debug("mcast_sock_server_create:setsockopt2"); + close(m->fd); + free(m); + return NULL; + } + break; + case AF_INET6: + if (setsockopt(m->fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, + &mreq.ipv6, sizeof(mreq.ipv6)) < 0) { + debug("mcast_sock_server_create:setsockopt2"); + close(m->fd); + free(m); + return NULL; + } + break; + } + + return m; +} + +void mcast_server_destroy(struct mcast_sock *m) +{ + close(m->fd); + free(m); +} + +static int +__mcast_client_create_ipv4(struct mcast_sock *m, struct mcast_conf *conf) +{ + int no = 0; + + m->addr.ipv4.sin_family = AF_INET; + m->addr.ipv4.sin_port = htons(conf->port); + m->addr.ipv4.sin_addr = conf->in.inet_addr; + + if (setsockopt(m->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &no, + sizeof(int)) < 0) { + debug("mcast_sock_client_create:setsockopt2"); + close(m->fd); + return -1; + } + + if (setsockopt(m->fd, IPPROTO_IP, IP_MULTICAST_IF, + &conf->ifa.interface_addr, + sizeof(struct in_addr)) == -1) { + debug("mcast_sock_client_create:setsockopt3"); + close(m->fd); + free(m); + return -1; + } + + return 0; +} + +static int +__mcast_client_create_ipv6(struct mcast_sock *m, struct mcast_conf *conf) +{ + int no = 0; + + m->addr.ipv6.sin6_family = AF_INET6; + m->addr.ipv6.sin6_port = htons(conf->port); + memcpy(&m->addr.ipv6.sin6_addr, + &conf->in.inet_addr6, + sizeof(struct in6_addr)); + + if (setsockopt(m->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &no, + sizeof(int)) < 0) { + debug("mcast_sock_client_create:setsockopt2"); + close(m->fd); + return -1; + } + + if (setsockopt(m->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, + &conf->ifa.interface_addr, + sizeof(struct in_addr)) == -1) { + debug("mcast_sock_client_create:setsockopt3"); + close(m->fd); + free(m); + return -1; + } + + return 0; +} + +struct mcast_sock *mcast_client_create(struct mcast_conf *conf) +{ + int ret = 0; + struct sockaddr_in addr; + struct mcast_sock *m; + + m = (struct mcast_sock *) malloc(sizeof(struct mcast_sock)); + if (!m) + return NULL; + memset(m, 0, sizeof(struct mcast_sock)); + + if ((m->fd = socket(conf->ipproto, SOCK_DGRAM, 0)) == -1) { + debug("mcast_sock_client_create:socket"); + return NULL; + } + + switch(conf->ipproto) { + case AF_INET: + ret = __mcast_client_create_ipv4(m, conf); + break; + case AF_INET6: + ret = __mcast_client_create_ipv6(m, conf); + break; + default: + break; + } + + if (ret == -1) { + free(m); + m = NULL; + } + + return m; +} + +void mcast_client_destroy(struct mcast_sock *m) +{ + close(m->fd); + free(m); +} + +int mcast_send(struct mcast_sock *m, void *data, int size) +{ + int ret; + + ret = sendto(m->fd, + data, + size, + 0, + (struct sockaddr *) &m->addr, + sizeof(struct sockaddr)); + if (ret == -1) { + debug("mcast_sock_send"); + m->stats.error++; + return ret; + } + + m->stats.bytes += ret; + m->stats.messages++; + + return ret; +} + +int mcast_recv(struct mcast_sock *m, void *data, int size) +{ + int ret; + socklen_t sin_size = sizeof(struct sockaddr_in); + + ret = recvfrom(m->fd, + data, + size, + 0, + (struct sockaddr *)&m->addr, + &sin_size); + if (ret == -1) { + debug("mcast_sock_recv"); + m->stats.error++; + return ret; + } + + m->stats.bytes += ret; + m->stats.messages++; + + return ret; +} + +struct mcast_stats *mcast_get_stats(struct mcast_sock *m) +{ + return &m->stats; +} + +void mcast_dump_stats(int fd, struct mcast_sock *s, struct mcast_sock *r) +{ + char buf[512]; + int size; + + size = sprintf(buf, "multicast traffic:\n" + "%20llu Bytes sent " + "%20llu Bytes recv\n" + "%20llu Pckts sent " + "%20llu Pckts recv\n" + "%20llu Error send " + "%20llu Error recv\n\n", + s->stats.bytes, r->stats.bytes, + s->stats.messages, r->stats.messages, + s->stats.error, r->stats.error); + + send(fd, buf, size, 0); +} diff --git a/src/netlink.c b/src/netlink.c new file mode 100644 index 0000000..0bde632 --- /dev/null +++ b/src/netlink.c @@ -0,0 +1,326 @@ +/* + * (C) 2006 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 "conntrackd.h" +#include <libnfnetlink/libnfnetlink.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <errno.h> +#include "us-conntrack.h" +#include <signal.h> +#include <stdlib.h> +#include "network.h" + +static int ignore_conntrack(struct nf_conntrack *ct) +{ + /* ignore a certain protocol */ + if (CONFIG(ignore_protocol)[nfct_get_attr_u8(ct, ATTR_ORIG_L4PROTO)]) + return 1; + + /* Accept DNAT'ed traffic: not really coming to the local machine */ + if ((CONFIG(flags) & STRIP_NAT) && + nfct_getobjopt(ct, NFCT_GOPT_IS_DNAT)) { + debug_ct(ct, "DNAT"); + return 0; + } + + /* Accept SNAT'ed traffic: not really coming to the local machine */ + if ((CONFIG(flags) & STRIP_NAT) && + nfct_getobjopt(ct, NFCT_GOPT_IS_SNAT)) { + debug_ct(ct, "SNAT"); + return 0; + } + + /* Ignore traffic */ + if (ignore_pool_test(STATE(ignore_pool), ct)) { + debug_ct(ct, "ignore traffic"); + return 1; + } + + return 0; +} + +static int nl_event_handler(struct nlmsghdr *nlh, + struct nfattr *nfa[], + void *data) +{ + char tmp[1024]; + struct nf_conntrack *ct = (struct nf_conntrack *) tmp; + int type; + + memset(tmp, 0, sizeof(tmp)); + + if ((type = nfct_parse_conntrack(NFCT_T_ALL, nlh, ct)) == NFCT_T_ERROR) + return NFCT_CB_STOP; + + /* + * Ignore this conntrack: it talks about a + * connection that is not interesting for us. + */ + if (ignore_conntrack(ct)) + return NFCT_CB_STOP; + + switch(type) { + case NFCT_T_NEW: + STATE(mode)->event_new(ct, nlh); + break; + case NFCT_T_UPDATE: + STATE(mode)->event_upd(ct, nlh); + break; + case NFCT_T_DESTROY: + if (STATE(mode)->event_dst(ct, nlh)) + update_traffic_stats(ct); + break; + default: + dlog(STATE(log), "received unknown msg from ctnetlink\n"); + break; + } + + return NFCT_CB_STOP; +} + +int nl_init_event_handler(void) +{ + struct nfnl_callback cb_events = { + .call = nl_event_handler, + .attr_count = CTA_MAX + }; + + /* open event netlink socket */ + STATE(event) = nfnl_open(); + if (!STATE(event)) + return -1; + + /* set up socket buffer size */ + if (CONFIG(netlink_buffer_size)) + nfnl_rcvbufsiz(STATE(event), CONFIG(netlink_buffer_size)); + else { + socklen_t socklen = sizeof(unsigned int); + unsigned int read_size; + + /* get current buffer size */ + getsockopt(nfnl_fd(STATE(event)), SOL_SOCKET, + SO_RCVBUF, &read_size, &socklen); + + CONFIG(netlink_buffer_size) = read_size; + } + + /* ensure that maximum grown size is >= than maximum size */ + if (CONFIG(netlink_buffer_size_max_grown) < CONFIG(netlink_buffer_size)) + CONFIG(netlink_buffer_size_max_grown) = + CONFIG(netlink_buffer_size); + + /* open event subsystem */ + STATE(subsys_event) = nfnl_subsys_open(STATE(event), + NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_MAX, + NFCT_ALL_CT_GROUPS); + if (STATE(subsys_event) == NULL) + return -1; + + /* register callback for new and update events */ + nfnl_callback_register(STATE(subsys_event), + IPCTNL_MSG_CT_NEW, + &cb_events); + + /* register callback for delete events */ + nfnl_callback_register(STATE(subsys_event), + IPCTNL_MSG_CT_DELETE, + &cb_events); + + return 0; +} + +static int nl_dump_handler(struct nlmsghdr *nlh, + struct nfattr *nfa[], + void *data) +{ + char buf[1024]; + struct nf_conntrack *ct = (struct nf_conntrack *) buf; + int type; + + memset(buf, 0, sizeof(buf)); + + if ((type = nfct_parse_conntrack(NFCT_T_ALL, nlh, ct)) == NFCT_T_ERROR) + return NFCT_CB_CONTINUE; + + /* + * Ignore this conntrack: it talks about a + * connection that is not interesting for us. + */ + if (ignore_conntrack(ct)) + return NFCT_CB_CONTINUE; + + switch(type) { + case NFCT_T_UPDATE: + STATE(mode)->dump(ct, nlh); + break; + default: + dlog(STATE(log), "received unknown msg from ctnetlink"); + break; + } + return NFCT_CB_CONTINUE; +} + +int nl_init_dump_handler(void) +{ + struct nfnl_callback cb_dump = { + .call = nl_dump_handler, + .attr_count = CTA_MAX + }; + + /* open dump netlink socket */ + STATE(dump) = nfnl_open(); + if (!STATE(dump)) + return -1; + + /* open dump subsystem */ + STATE(subsys_dump) = nfnl_subsys_open(STATE(dump), + NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_MAX, + 0); + if (STATE(subsys_dump) == NULL) + return -1; + + /* register callback for dumped entries */ + nfnl_callback_register(STATE(subsys_dump), + IPCTNL_MSG_CT_NEW, + &cb_dump); + + if (nl_dump_conntrack_table(STATE(dump), STATE(subsys_dump)) == -1) + return -1; + + return 0; +} + +static int nl_overrun_handler(struct nlmsghdr *nlh, + struct nfattr *nfa[], + void *data) +{ + char buf[1024]; + struct nf_conntrack *ct = (struct nf_conntrack *) buf; + int type; + + memset(buf, 0, sizeof(buf)); + + if ((type = nfct_parse_conntrack(NFCT_T_ALL, nlh, ct)) == NFCT_T_ERROR) + return NFCT_CB_CONTINUE; + + /* + * Ignore this conntrack: it talks about a + * connection that is not interesting for us. + */ + if (ignore_conntrack(ct)) + return NFCT_CB_CONTINUE; + + switch(type) { + case NFCT_T_UPDATE: + if (STATE(mode)->overrun) + STATE(mode)->overrun(ct, nlh); + break; + default: + dlog(STATE(log), "received unknown msg from ctnetlink"); + break; + } + return NFCT_CB_CONTINUE; +} + +int nl_init_overrun_handler(void) +{ + struct nfnl_callback cb_sync = { + .call = nl_overrun_handler, + .attr_count = CTA_MAX + }; + + /* open sync netlink socket */ + STATE(sync) = nfnl_open(); + if (!STATE(sync)) + return -1; + + /* open synchronizer subsystem */ + STATE(subsys_sync) = nfnl_subsys_open(STATE(sync), + NFNL_SUBSYS_CTNETLINK, + IPCTNL_MSG_MAX, + 0); + if (STATE(subsys_sync) == NULL) + return -1; + + /* register callback for dumped entries */ + nfnl_callback_register(STATE(subsys_sync), + IPCTNL_MSG_CT_NEW, + &cb_sync); + + return 0; +} + +static int warned = 0; + +void nl_resize_socket_buffer(struct nfnl_handle *h) +{ + unsigned int s = CONFIG(netlink_buffer_size) * 2; + + /* already warned that we have reached the maximum buffer size */ + if (warned) + return; + + if (s > CONFIG(netlink_buffer_size_max_grown)) { + dlog(STATE(log), "maximum netlink socket buffer size reached"); + s = CONFIG(netlink_buffer_size_max_grown); + warned = 1; + } + + CONFIG(netlink_buffer_size) = nfnl_rcvbufsiz(h, s); + + /* notify the sysadmin */ + dlog(STATE(log), "netlink socket buffer size has been set to %u bytes", + CONFIG(netlink_buffer_size)); +} + +int nl_dump_conntrack_table(struct nfnl_handle *h, + struct nfnl_subsys_handle *subsys) +{ + struct nfnlhdr req; + + memset(&req, 0, sizeof(req)); + nfct_build_query(subsys, + NFCT_Q_DUMP, + &CONFIG(family), + &req, + sizeof(req)); + + if (nfnl_query(h, &req.nlh) == -1) + return -1; + + return 0; +} + +int nl_flush_master_conntrack_table(void) +{ + struct nfnlhdr req; + + memset(&req, 0, sizeof(req)); + nfct_build_query(STATE(subsys_sync), + NFCT_Q_FLUSH, + &CONFIG(family), + &req, + sizeof(req)); + + if (nfnl_query(STATE(sync), &req.nlh) == -1) + return -1; + + return 0; +} diff --git a/src/network.c b/src/network.c new file mode 100644 index 0000000..b9be318 --- /dev/null +++ b/src/network.c @@ -0,0 +1,282 @@ +/* + * (C) 2006-2007 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 "conntrackd.h" +#include "network.h" + +#if 0 +#define _TEST_DROP +#else +#undef _TEST_DROP +#endif + +static int drop = 0; /* debugging purposes */ +static unsigned int seq_set, cur_seq; + +static int send_netmsg(struct mcast_sock *m, void *data, unsigned int len) +{ + struct nlnetwork *net = data; + +#ifdef _TEST_DROP + if (++drop > 10) { + drop = 0; + printf("dropping resend (seq=%u)\n", ntohl(net->seq)); + return 0; + } +#endif + return mcast_send(m, net, len); +} + +int mcast_send_netmsg(struct mcast_sock *m, void *data) +{ + struct nlmsghdr *nlh = data + sizeof(struct nlnetwork); + unsigned int len = nlh->nlmsg_len + sizeof(struct nlnetwork); + struct nlnetwork *net = data; + + if (!seq_set) { + seq_set = 1; + cur_seq = time(NULL); + net->flags |= NET_HELLO; + } + + net->flags = htons(net->flags); + net->seq = htonl(cur_seq++); + + if (nlh_host2network(nlh) == -1) + return -1; + + net->checksum = 0; + net->checksum = ntohs(do_csum(data, len)); + + return send_netmsg(m, data, len); +} + +int mcast_resend_netmsg(struct mcast_sock *m, void *data) +{ + struct nlnetwork *net = data; + struct nlmsghdr *nlh = data + sizeof(struct nlnetwork); + unsigned int len = htonl(nlh->nlmsg_len) + sizeof(struct nlnetwork); + + net->flags = ntohs(net->flags); + + if (!seq_set) { + seq_set = 1; + cur_seq = time(NULL); + net->flags |= NET_HELLO; + } + + if (net->flags & NET_NACK || net->flags & NET_ACK) { + struct nlnetwork_ack *nack = (struct nlnetwork_ack *) net; + len = sizeof(struct nlnetwork_ack); + } + + net->flags = htons(net->flags); + net->seq = htonl(cur_seq++); + net->checksum = 0; + net->checksum = ntohs(do_csum(data, len)); + + return send_netmsg(m, data, len); +} + +int mcast_send_error(struct mcast_sock *m, void *data) +{ + struct nlnetwork *net = data; + unsigned int len = sizeof(struct nlnetwork); + + if (!seq_set) { + seq_set = 1; + cur_seq = time(NULL); + net->flags |= NET_HELLO; + } + + if (net->flags & NET_NACK || net->flags & NET_ACK) { + struct nlnetwork_ack *nack = (struct nlnetwork_ack *) net; + nack->from = htonl(nack->from); + nack->to = htonl(nack->to); + len = sizeof(struct nlnetwork_ack); + } + + net->flags = htons(net->flags); + net->seq = htonl(cur_seq++); + net->checksum = 0; + net->checksum = ntohs(do_csum(data, len)); + + return send_netmsg(m, data, len); +} + +static int valid_checksum(void *data, unsigned int len) +{ + struct nlnetwork *net = data; + unsigned short checksum, tmp; + + checksum = ntohs(net->checksum); + + /* no checksum, skip */ + if (!checksum) + return 1; + + net->checksum = 0; + tmp = do_csum(data, len); + + return tmp == checksum; +} + +int mcast_recv_netmsg(struct mcast_sock *m, void *data, int len) +{ + int ret; + struct nlnetwork *net = data; + struct nlmsghdr *nlh = data + sizeof(struct nlnetwork); + struct nfgenmsg *nfhdr; + + ret = mcast_recv(m, net, len); + if (ret <= 0) + return ret; + + if (ret < sizeof(struct nlnetwork)) + return -1; + + if (!valid_checksum(data, ret)) + return -1; + + net->flags = ntohs(net->flags); + net->seq = ntohl(net->seq); + + if (net->flags & NET_HELLO) + STATE_SYNC(last_seq_recv) = net->seq-1; + + if (net->flags & NET_NACK || net->flags & NET_ACK) { + struct nlnetwork_ack *nack = (struct nlnetwork_ack *) net; + + if (ret < sizeof(struct nlnetwork_ack)) + return -1; + + nack->from = ntohl(nack->from); + nack->to = ntohl(nack->to); + + return ret; + } + + if (net->flags & NET_RESYNC) + return ret; + + /* information received is too small */ + if (ret < NLMSG_SPACE(sizeof(struct nfgenmsg))) + return -1; + + /* information received and message length does not match */ + if (ret != ntohl(nlh->nlmsg_len) + sizeof(struct nlnetwork)) + return -1; + + /* this message does not come from ctnetlink */ + if (NFNL_SUBSYS_ID(ntohs(nlh->nlmsg_type)) != NFNL_SUBSYS_CTNETLINK) + return -1; + + nfhdr = NLMSG_DATA(nlh); + + /* only AF_INET and AF_INET6 are supported */ + if (nfhdr->nfgen_family != AF_INET && + nfhdr->nfgen_family != AF_INET6) + return -1; + + /* only process message coming from nfnetlink v0 */ + if (nfhdr->version != NFNETLINK_V0) + return -1; + + if (nlh_network2host(nlh) == -1) + return -1; + + return ret; +} + +int mcast_track_seq(u_int32_t seq, u_int32_t *exp_seq) +{ + static int seq_set = 0; + int ret = 1; + + /* netlink sequence tracking initialization */ + if (!seq_set) { + seq_set = 1; + goto out; + } + + /* fast path: we received the correct sequence */ + if (seq == STATE_SYNC(last_seq_recv)+1) + goto out; + + /* out of sequence: some messages got lost */ + if (seq > STATE_SYNC(last_seq_recv)+1) { + STATE_SYNC(packets_lost) += seq-STATE_SYNC(last_seq_recv)+1; + ret = 0; + goto out; + } + + /* out of sequence: replayed or sequence wrapped around issues */ + if (seq < STATE_SYNC(last_seq_recv)+1) { + /* + * Check if the sequence has wrapped around. + * Perhaps it can be a replayed packet. + */ + if (STATE_SYNC(last_seq_recv)+1-seq > ~0U/2) { + /* + * Indeed, it is a wrapped around + */ + STATE_SYNC(packets_lost) += + ~0U-STATE_SYNC(last_seq_recv)+1+seq; + } else { + /* + * It is a delayed packet + */ + dlog(STATE(log), "delayed packet? exp=%u rcv=%u", + STATE_SYNC(last_seq_recv)+1, seq); + } + ret = 0; + } + +out: + *exp_seq = STATE_SYNC(last_seq_recv)+1; + /* update expected sequence */ + STATE_SYNC(last_seq_recv) = seq; + + return ret; +} + +int build_network_msg(const int msg_type, + struct nfnl_subsys_handle *ssh, + struct nf_conntrack *ct, + void *buffer, + unsigned int size) +{ + memset(buffer, 0, size); + buffer += sizeof(struct nlnetwork); + return nfct_build_query(ssh, msg_type, ct, buffer, size); +} + +unsigned int parse_network_msg(struct nf_conntrack *ct, + const struct nlmsghdr *nlh) +{ + /* + * The parsing of netlink messages going through network is + * similar to the one that is done for messages coming from + * kernel, therefore do not replicate more code and use the + * function provided in the libraries. + * + * Yup, this is a hack 8) + */ + return nfct_parse_conntrack(NFCT_T_ALL, nlh, ct); +} + diff --git a/src/proxy.c b/src/proxy.c new file mode 100644 index 0000000..b9bb04e --- /dev/null +++ b/src/proxy.c @@ -0,0 +1,124 @@ +/* + * (C) 2006 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 <libnfnetlink/libnfnetlink.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> + +#if 0 +#define dprintf printf +#else +#define dprintf +#endif + +int nlh_payload_host2network(struct nfattr *nfa, int len) +{ + struct nfattr *__nfa; + + while (NFA_OK(nfa, len)) { + + dprintf("type=%d nfalen=%d len=%d [%s]\n", + nfa->nfa_type & 0x7fff, + nfa->nfa_len, len, + nfa->nfa_type & NFNL_NFA_NEST ? "NEST":""); + + if (nfa->nfa_type & NFNL_NFA_NEST) { + if (NFA_PAYLOAD(nfa) > len) + return -1; + + if (nlh_payload_host2network(NFA_DATA(nfa), + NFA_PAYLOAD(nfa)) == -1) + return -1; + } + + __nfa = NFA_NEXT(nfa, len); + + nfa->nfa_type = htons(nfa->nfa_type); + nfa->nfa_len = htons(nfa->nfa_len); + + nfa = __nfa; + } + return 0; +} + +int nlh_host2network(struct nlmsghdr *nlh) +{ + struct nfgenmsg *nfhdr = NLMSG_DATA(nlh); + struct nfattr *cda[CTA_MAX]; + unsigned int min_len = NLMSG_SPACE(sizeof(struct nfgenmsg)); + unsigned int len = nlh->nlmsg_len - NLMSG_ALIGN(min_len); + + nlh->nlmsg_len = htonl(nlh->nlmsg_len); + nlh->nlmsg_type = htons(nlh->nlmsg_type); + nlh->nlmsg_flags = htons(nlh->nlmsg_flags); + nlh->nlmsg_seq = htonl(nlh->nlmsg_seq); + nlh->nlmsg_pid = htonl(nlh->nlmsg_pid); + + nfhdr->res_id = htons(nfhdr->res_id); + + return nlh_payload_host2network(NFM_NFA(NLMSG_DATA(nlh)), len); +} + +int nlh_payload_network2host(struct nfattr *nfa, int len) +{ + nfa->nfa_type = ntohs(nfa->nfa_type); + nfa->nfa_len = ntohs(nfa->nfa_len); + + while(NFA_OK(nfa, len)) { + + dprintf("type=%d nfalen=%d len=%d [%s]\n", + nfa->nfa_type & 0x7fff, + nfa->nfa_len, len, + nfa->nfa_type & NFNL_NFA_NEST ? "NEST":""); + + if (nfa->nfa_type & NFNL_NFA_NEST) { + if (NFA_PAYLOAD(nfa) > len) + return -1; + + if (nlh_payload_network2host(NFA_DATA(nfa), + NFA_PAYLOAD(nfa)) == -1) + return -1; + } + + nfa = NFA_NEXT(nfa,len); + + if (len < NFA_LENGTH(0)) + break; + + nfa->nfa_type = ntohs(nfa->nfa_type); + nfa->nfa_len = ntohs(nfa->nfa_len); + } + return 0; +} + +int nlh_network2host(struct nlmsghdr *nlh) +{ + struct nfgenmsg *nfhdr = NLMSG_DATA(nlh); + struct nfattr *cda[CTA_MAX]; + unsigned int min_len = NLMSG_SPACE(sizeof(struct nfgenmsg)); + unsigned int len = ntohl(nlh->nlmsg_len) - NLMSG_ALIGN(min_len); + + nlh->nlmsg_len = ntohl(nlh->nlmsg_len); + nlh->nlmsg_type = ntohs(nlh->nlmsg_type); + nlh->nlmsg_flags = ntohs(nlh->nlmsg_flags); + nlh->nlmsg_seq = ntohl(nlh->nlmsg_seq); + nlh->nlmsg_pid = ntohl(nlh->nlmsg_pid); + + nfhdr->res_id = ntohs(nfhdr->res_id); + + return nlh_payload_network2host(NFM_NFA(NLMSG_DATA(nlh)), len); +} diff --git a/src/read_config_lex.l b/src/read_config_lex.l new file mode 100644 index 0000000..dee90c9 --- /dev/null +++ b/src/read_config_lex.l @@ -0,0 +1,125 @@ +%{ +/* + * (C) 2006 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. + * + * Description: configuration file syntax + */ + +#include "read_config_yy.h" +#include "conntrackd.h" +%} + +%option yylineno +%option nounput + +ws [ \t]+ +comment #.*$ +nl [\n\r] + +is_on [o|O][n|N] +is_off [o|O][f|F][f|F] +integer [0-9]+ +path \/[^\"\n ]* +ip4_end [0-9]*[0-9]+ +ip4_part [0-2]*{ip4_end} +ip4 {ip4_part}\.{ip4_part}\.{ip4_part}\.{ip4_part} +hex_255 [0-9a-fA-F]{1,4} +ip6_part {hex_255}":"? +ip6_form1 {ip6_part}{0,16}"::"{ip6_part}{0,16} +ip6_form2 ({hex_255}":"){16}{hex_255} +ip6 {ip6_form1}|{ip6_form2} +string [a-zA-Z]* +persistent [P|p][E|e][R|r][S|s][I|i][S|s][T|t][E|e][N|n][T|T] +nack [N|n][A|a][C|c][K|k] + +%% +"UNIX" { return T_UNIX; } +"IPv4_address" { return T_IPV4_ADDR; } +"IPv6_address" { return T_IPV6_ADDR; } +"IPv4_interface" { return T_IPV4_IFACE; } +"IPv6_interface" { return T_IPV6_IFACE; } +"Port" { return T_PORT; } +"Multicast" { return T_MULTICAST; } +"HashSize" { return T_HASHSIZE; } +"RefreshTime" { return T_REFRESH; } +"CacheTimeout" { return T_EXPIRE; } +"CommitTimeout" { return T_TIMEOUT; } +"DelayDestroyMessages" { return T_DELAY; } +"HashLimit" { return T_HASHLIMIT; } +"Path" { return T_PATH; } +"IgnoreProtocol" { return T_IGNORE_PROTOCOL; } +"UDP" { return T_UDP; } +"ICMP" { return T_ICMP; } +"VRRP" { return T_VRRP; } +"IGMP" { return T_IGMP; } +"TCP" { return T_TCP; } +"IgnoreTrafficFor" { return T_IGNORE_TRAFFIC; } +"StripNAT" { return T_STRIP_NAT; } +"Backlog" { return T_BACKLOG; } +"Group" { return T_GROUP; } +"LogFile" { return T_LOG; } +"LockFile" { return T_LOCK; } +"General" { return T_GENERAL; } +"Sync" { return T_SYNC; } +"Stats" { return T_STATS; } +"RelaxTransitions" { return T_RELAX_TRANSITIONS; } +"SocketBufferSize" { return T_BUFFER_SIZE; } +"SocketBufferSizeMaxGrown" { return T_BUFFER_SIZE_MAX_GROWN; } +"SocketBufferSizeMaxGrowth" { return T_BUFFER_SIZE_MAX_GROWN; } +"Mode" { return T_SYNC_MODE; } +"ListenTo" { return T_LISTEN_TO; } +"Family" { return T_FAMILY; } +"ResendBufferSize" { return T_RESEND_BUFFER_SIZE; } +"Checksum" { return T_CHECKSUM; } +"ACKWindowSize" { return T_WINDOWSIZE; } +"Replicate" { return T_REPLICATE; } +"for" { return T_FOR; } +"SYN_SENT" { return T_SYN_SENT; } +"SYN_RECV" { return T_SYN_RECV; } +"ESTABLISHED" { return T_ESTABLISHED; } +"FIN_WAIT" { return T_FIN_WAIT; } +"CLOSE_WAIT" { return T_CLOSE_WAIT; } +"LAST_ACK" { return T_LAST_ACK; } +"TIME_WAIT" { return T_TIME_WAIT; } +"CLOSE" { return T_CLOSE; } +"LISTEN" { return T_LISTEN; } + +{is_on} { return T_ON; } +{is_off} { return T_OFF; } +{integer} { yylval.val = atoi(yytext); return T_NUMBER; } +{ip4} { yylval.string = strdup(yytext); return T_IP; } +{ip6} { yylval.string = strdup(yytext); return T_IP; } +{path} { yylval.string = strdup(yytext); return T_PATH_VAL; } +{persistent} { return T_PERSISTENT; } +{nack} { return T_NACK; } +{string} { yylval.string = strdup(yytext); return T_STRING; } + +{comment} ; +{ws} ; +{nl} ; + +<<EOF>> { yyterminate(); } + +. { return yytext[0]; } + +%% + +int +yywrap() +{ + return 1; +} diff --git a/src/read_config_yy.y b/src/read_config_yy.y new file mode 100644 index 0000000..1668919 --- /dev/null +++ b/src/read_config_yy.y @@ -0,0 +1,550 @@ +%{ +/* + * (C) 2006 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. + * + * Description: configuration file abstract grammar + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "conntrackd.h" +#include "ignore.h" + +extern char *yytext; +extern int yylineno; + +struct ct_conf conf; +%} + +%union { + int val; + char *string; +} + +%token T_IPV4_ADDR T_IPV4_IFACE T_PORT T_HASHSIZE T_HASHLIMIT T_MULTICAST +%token T_PATH T_UNIX T_REFRESH T_IPV6_ADDR T_IPV6_IFACE +%token T_IGNORE_UDP T_IGNORE_ICMP T_IGNORE_TRAFFIC T_BACKLOG T_GROUP +%token T_LOG T_UDP T_ICMP T_IGMP T_VRRP T_TCP T_IGNORE_PROTOCOL +%token T_LOCK T_STRIP_NAT T_BUFFER_SIZE_MAX_GROWN T_EXPIRE T_TIMEOUT +%token T_GENERAL T_SYNC T_STATS T_RELAX_TRANSITIONS T_BUFFER_SIZE T_DELAY +%token T_SYNC_MODE T_LISTEN_TO T_FAMILY T_RESEND_BUFFER_SIZE +%token T_PERSISTENT T_NACK T_CHECKSUM T_WINDOWSIZE T_ON T_OFF +%token T_REPLICATE T_FOR +%token T_ESTABLISHED T_SYN_SENT T_SYN_RECV T_FIN_WAIT +%token T_CLOSE_WAIT T_LAST_ACK T_TIME_WAIT T_CLOSE T_LISTEN + + +%token <string> T_IP T_PATH_VAL +%token <val> T_NUMBER +%token <string> T_STRING + +%% + +configfile : + | lines + ; + +lines : line + | lines line + ; + +line : ignore_protocol + | ignore_traffic + | strip_nat + | general + | sync + | stats + ; + +log : T_LOG T_PATH_VAL +{ + strncpy(conf.logfile, $2, FILENAME_MAXLEN); +}; + +lock : T_LOCK T_PATH_VAL +{ + strncpy(conf.lockfile, $2, FILENAME_MAXLEN); +}; + +strip_nat: T_STRIP_NAT +{ + conf.flags |= STRIP_NAT; +}; + +refreshtime : T_REFRESH T_NUMBER +{ + conf.refresh = $2; +}; + +expiretime: T_EXPIRE T_NUMBER +{ + conf.cache_timeout = $2; +}; + +timeout: T_TIMEOUT T_NUMBER +{ + conf.commit_timeout = $2; +}; + +checksum: T_CHECKSUM T_ON +{ +}; + +checksum: T_CHECKSUM T_OFF +{ + conf.flags |= DONT_CHECKSUM; +}; + +ignore_traffic : T_IGNORE_TRAFFIC '{' ignore_traffic_options '}'; + +ignore_traffic_options : + | ignore_traffic_options ignore_traffic_option; + +ignore_traffic_option : T_IPV4_ADDR T_IP +{ + union inet_address ip; + int family = 0; + + memset(&ip, 0, sizeof(union inet_address)); + + if (inet_aton($2, &ip.ipv4)) + family = AF_INET; +#ifdef HAVE_INET_PTON_IPV6 + else if (inet_pton(AF_INET6, $2, &ip.ipv6) > 0) + family = AF_INET6; +#endif + + if (!family) { + fprintf(stdout, "%s is not a valid IP, ignoring", $2); + return; + } + + if (!STATE(ignore_pool)) { + STATE(ignore_pool) = ignore_pool_create(family); + if (!STATE(ignore_pool)) { + fprintf(stdout, "Can't create ignore pool!\n"); + exit(EXIT_FAILURE); + } + } + + if (!ignore_pool_add(STATE(ignore_pool), &ip)) { + if (errno == EEXIST) + fprintf(stdout, "IP %s is repeated " + "in the ignore pool\n", $2); + if (errno == ENOSPC) + fprintf(stdout, "Too many IP in the ignore pool!\n"); + } +}; + +multicast_line : T_MULTICAST '{' multicast_options '}'; + +multicast_options : + | multicast_options multicast_option; + +multicast_option : T_IPV4_ADDR T_IP +{ + if (!inet_aton($2, &conf.mcast.in)) { + fprintf(stderr, "%s is not a valid IPv4 address\n"); + return; + } + + if (conf.mcast.ipproto == AF_INET6) { + fprintf(stderr, "Your multicast address is IPv4 but " + "is binded to an IPv6 interface? Surely " + "this is not what you want\n"); + return; + } + + conf.mcast.ipproto = AF_INET; +}; + +multicast_option : T_IPV6_ADDR T_IP +{ +#ifdef HAVE_INET_PTON_IPV6 + if (inet_pton(AF_INET6, $2, &conf.mcast.in) <= 0) + fprintf(stderr, "%s is not a valid IPv6 address\n", $2); +#endif + + if (conf.mcast.ipproto == AF_INET) { + fprintf(stderr, "Your multicast address is IPv6 but " + "is binded to an IPv4 interface? Surely " + "this is not what you want\n"); + return; + } + + conf.mcast.ipproto = AF_INET6; +}; + +multicast_option : T_IPV4_IFACE T_IP +{ + if (!inet_aton($2, &conf.mcast.ifa)) { + fprintf(stderr, "%s is not a valid IPv4 address\n"); + return; + } + + if (conf.mcast.ipproto == AF_INET6) { + fprintf(stderr, "Your multicast interface is IPv4 but " + "is binded to an IPv6 interface? Surely " + "this is not what you want\n"); + return; + } + + conf.mcast.ipproto = AF_INET; +}; + +multicast_option : T_IPV6_IFACE T_IP +{ +#ifdef HAVE_INET_PTON_IPV6 + if (inet_pton(AF_INET6, $2, &conf.mcast.ifa) <= 0) + fprintf(stderr, "%s is not a valid IPv6 address\n", $2); +#endif + + if (conf.mcast.ipproto == AF_INET) { + fprintf(stderr, "Your multicast interface is IPv6 but " + "is binded to an IPv4 interface? Surely " + "this is not what you want\n"); + return; + } + + conf.mcast.ipproto = AF_INET6; +}; + +multicast_option : T_BACKLOG T_NUMBER +{ + conf.mcast.backlog = $2; +}; + +multicast_option : T_GROUP T_NUMBER +{ + conf.mcast.port = $2; +}; + +hashsize : T_HASHSIZE T_NUMBER +{ + conf.hashsize = $2; +}; + +hashlimit: T_HASHLIMIT T_NUMBER +{ + conf.limit = $2; +}; + +unix_line: T_UNIX '{' unix_options '}'; + +unix_options: + | unix_options unix_option + ; + +unix_option : T_PATH T_PATH_VAL +{ + strcpy(conf.local.path, $2); +}; + +unix_option : T_BACKLOG T_NUMBER +{ + conf.local.backlog = $2; +}; + +ignore_protocol: T_IGNORE_PROTOCOL '{' ignore_proto_list '}'; + +ignore_proto_list: + | ignore_proto_list ignore_proto + ; + +ignore_proto: T_NUMBER +{ + if ($1 < IPPROTO_MAX) + conf.ignore_protocol[$1] = 1; + else + fprintf(stdout, "Protocol number `%d' is freak\n", $1); +}; + +ignore_proto: T_UDP +{ + conf.ignore_protocol[IPPROTO_UDP] = 1; +}; + +ignore_proto: T_ICMP +{ + conf.ignore_protocol[IPPROTO_ICMP] = 1; +}; + +ignore_proto: T_VRRP +{ + conf.ignore_protocol[IPPROTO_VRRP] = 1; +}; + +ignore_proto: T_IGMP +{ + conf.ignore_protocol[IPPROTO_IGMP] = 1; +}; + +sync: T_SYNC '{' sync_list '}'; + +sync_list: + | sync_list sync_line; + +sync_line: refreshtime + | expiretime + | timeout + | checksum + | multicast_line + | relax_transitions + | delay_destroy_msgs + | sync_mode_persistent + | sync_mode_nack + | listen_to + | state_replication + ; + +sync_mode_persistent: T_SYNC_MODE T_PERSISTENT '{' sync_mode_persistent_list '}' +{ + conf.flags |= SYNC_MODE_PERSISTENT; +}; + +sync_mode_nack: T_SYNC_MODE T_NACK '{' sync_mode_nack_list '}' +{ + conf.flags |= SYNC_MODE_NACK; +}; + +sync_mode_persistent_list: + | sync_mode_persistent_list sync_mode_persistent_line; + +sync_mode_persistent_line: refreshtime + | expiretime + | timeout + | relax_transitions + | delay_destroy_msgs + ; + +sync_mode_nack_list: + | sync_mode_nack_list sync_mode_nack_line; + +sync_mode_nack_line: resend_buffer_size + | timeout + | window_size + ; + +resend_buffer_size: T_RESEND_BUFFER_SIZE T_NUMBER +{ + conf.resend_buffer_size = $2; +}; + +window_size: T_WINDOWSIZE T_NUMBER +{ + conf.window_size = $2; +}; + +relax_transitions: T_RELAX_TRANSITIONS +{ + conf.flags |= RELAX_TRANSITIONS; +}; + +delay_destroy_msgs: T_DELAY +{ + conf.flags |= DELAY_DESTROY_MSG; +}; + +listen_to: T_LISTEN_TO T_IP +{ + union inet_address addr; + +#ifdef HAVE_INET_PTON_IPV6 + if (inet_pton(AF_INET6, $2, &addr.ipv6) <= 0) +#endif + if (inet_aton($2, &addr.ipv4) <= 0) { + fprintf(stderr, "%s is not a valid IP address\n", $2); + exit(EXIT_FAILURE); + } + + if (CONFIG(listen_to_len) == 0 || CONFIG(listen_to_len) % 16) { + CONFIG(listen_to) = realloc(CONFIG(listen_to), + sizeof(union inet_address) * + (CONFIG(listen_to_len) + 16)); + if (CONFIG(listen_to) == NULL) { + fprintf(stderr, "cannot init listen_to array\n"); + exit(EXIT_FAILURE); + } + + memset(CONFIG(listen_to) + + (CONFIG(listen_to_len) * sizeof(union inet_address)), + 0, sizeof(union inet_address) * 16); + + } +}; + +state_replication: T_REPLICATE states T_FOR state_proto; + +states: + | states state; + +state_proto: T_TCP; +state: tcp_state; + +tcp_state: T_SYN_SENT +{ + extern struct state_replication_helper tcp_state_helper; + state_helper_register(&tcp_state_helper, TCP_CONNTRACK_SYN_SENT); +}; +tcp_state: T_SYN_RECV +{ + extern struct state_replication_helper tcp_state_helper; + state_helper_register(&tcp_state_helper, TCP_CONNTRACK_SYN_RECV); +}; +tcp_state: T_ESTABLISHED +{ + extern struct state_replication_helper tcp_state_helper; + state_helper_register(&tcp_state_helper, TCP_CONNTRACK_ESTABLISHED); +}; +tcp_state: T_FIN_WAIT +{ + extern struct state_replication_helper tcp_state_helper; + state_helper_register(&tcp_state_helper, TCP_CONNTRACK_FIN_WAIT); +}; +tcp_state: T_CLOSE_WAIT +{ + extern struct state_replication_helper tcp_state_helper; + state_helper_register(&tcp_state_helper, TCP_CONNTRACK_CLOSE_WAIT); +}; +tcp_state: T_LAST_ACK +{ + extern struct state_replication_helper tcp_state_helper; + state_helper_register(&tcp_state_helper, TCP_CONNTRACK_LAST_ACK); +}; +tcp_state: T_TIME_WAIT +{ + extern struct state_replication_helper tcp_state_helper; + state_helper_register(&tcp_state_helper, TCP_CONNTRACK_TIME_WAIT); +}; +tcp_state: T_CLOSE +{ + extern struct state_replication_helper tcp_state_helper; + state_helper_register(&tcp_state_helper, TCP_CONNTRACK_CLOSE); +}; +tcp_state: T_LISTEN +{ + extern struct state_replication_helper tcp_state_helper; + state_helper_register(&tcp_state_helper, TCP_CONNTRACK_LISTEN); +}; + +general: T_GENERAL '{' general_list '}'; + +general_list: + | general_list general_line + ; + +general_line: hashsize + | hashlimit + | log + | lock + | unix_line + | netlink_buffer_size + | netlink_buffer_size_max_grown + | family + ; + +netlink_buffer_size: T_BUFFER_SIZE T_NUMBER +{ + conf.netlink_buffer_size = $2; +}; + +netlink_buffer_size_max_grown : T_BUFFER_SIZE_MAX_GROWN T_NUMBER +{ + conf.netlink_buffer_size_max_grown = $2; +}; + +family : T_FAMILY T_STRING +{ + if (strncmp($2, "IPv6", strlen("IPv6")) == 0) + conf.family = AF_INET6; + else + conf.family = AF_INET; +}; + +stats: T_SYNC '{' stats_list '}'; + +stats_list: + | stats_list stat_line + ; + +stat_line: + | + ; + +%% + +int +yyerror(char *msg) +{ + printf("Error parsing config file: "); + printf("line (%d), symbol '%s': %s\n", yylineno, yytext, msg); + exit(EXIT_FAILURE); +} + +int +init_config(char *filename) +{ + FILE *fp; + + fp = fopen(filename, "r"); + if (!fp) + return -1; + + yyrestart(fp); + yyparse(); + fclose(fp); + + /* default to IPv4 */ + if (CONFIG(family) == 0) + CONFIG(family) = AF_INET; + + /* set to default is not specified */ + if (strcmp(CONFIG(lockfile), "") == 0) + strncpy(CONFIG(lockfile), DEFAULT_LOCKFILE, FILENAME_MAXLEN); + + /* default to 180 seconds of expiration time: cache entries */ + if (CONFIG(cache_timeout) == 0) + CONFIG(cache_timeout) = 180; + + /* default to 180 seconds: committed entries */ + if (CONFIG(commit_timeout) == 0) + CONFIG(commit_timeout) = 180; + + /* default to 60 seconds of refresh time */ + if (CONFIG(refresh) == 0) + CONFIG(refresh) = 60; + + if (CONFIG(resend_buffer_size) == 0) + CONFIG(resend_buffer_size) = 262144; + + /* create empty pool */ + if (!STATE(ignore_pool)) { + STATE(ignore_pool) = ignore_pool_create(CONFIG(family)); + if (!STATE(ignore_pool)) { + fprintf(stdout, "Can't create ignore pool!\n"); + exit(EXIT_FAILURE); + } + } + + /* default to a window size of 20 packets */ + if (CONFIG(window_size) == 0) + CONFIG(window_size) = 20; + + return 0; +} diff --git a/src/run.c b/src/run.c new file mode 100644 index 0000000..67437d8 --- /dev/null +++ b/src/run.c @@ -0,0 +1,227 @@ +/* + * (C) 2006-2007 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. + * + * Description: run and init functions + */ + +#include "conntrackd.h" +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <errno.h> +#include "us-conntrack.h" +#include <signal.h> +#include <stdlib.h> + +void killer(int foo) +{ + /* no signals while handling signals */ + sigprocmask(SIG_BLOCK, &STATE(block), NULL); + + nfnl_subsys_close(STATE(subsys_event)); + nfnl_subsys_close(STATE(subsys_dump)); + nfnl_subsys_close(STATE(subsys_sync)); + nfnl_close(STATE(event)); + nfnl_close(STATE(dump)); + nfnl_close(STATE(sync)); + + ignore_pool_destroy(STATE(ignore_pool)); + local_server_destroy(STATE(local)); + STATE(mode)->kill(); + unlink(CONFIG(lockfile)); + dlog(STATE(log), "------- shutdown received ----"); + close_log(STATE(log)); + + sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); + + exit(0); +} + +void local_handler(int fd, void *data) +{ + int ret; + int type; + + ret = read(fd, &type, sizeof(type)); + if (ret == -1) { + dlog(STATE(log), "can't read from unix socket\n"); + return; + } + if (ret == 0) { + debug("nothing to process\n"); + return; + } + + switch(type) { + case FLUSH_MASTER: + dlog(STATE(log), "[REQ] flushing master table"); + nl_flush_master_conntrack_table(); + return; + case RESYNC_MASTER: + dlog(STATE(log), "[REQ] resync with master table"); + nl_dump_conntrack_table(STATE(dump), STATE(subsys_dump)); + return; + } + + if (!STATE(mode)->local(fd, type, data)) + dlog(STATE(log), "[FAIL] unknown local request %d", type); +} + +int init(int mode) +{ + switch(mode) { + case STATS_MODE: + STATE(mode) = &stats_mode; + break; + case SYNC_MODE: + STATE(mode) = &sync_mode; + break; + default: + fprintf(stderr, "Unknown running mode! default " + "to synchronization mode\n"); + STATE(mode) = &sync_mode; + break; + } + + /* Initialization */ + if (STATE(mode)->init() == -1) { + dlog(STATE(log), "[FAIL] initialization failed"); + return -1; + } + + /* local UNIX socket */ + STATE(local) = local_server_create(&CONFIG(local)); + if (!STATE(local)) { + dlog(STATE(log), "[FAIL] can't open unix socket!"); + return -1; + } + + if (nl_init_event_handler() == -1) { + dlog(STATE(log), "[FAIL] can't open netlink handler! " + "no ctnetlink kernel support?"); + return -1; + } + + if (nl_init_dump_handler() == -1) { + dlog(STATE(log), "[FAIL] can't open netlink handler! " + "no ctnetlink kernel support?"); + return -1; + } + + if (nl_init_overrun_handler() == -1) { + dlog(STATE(log), "[FAIL] can't open netlink handler! " + "no ctnetlink kernel support?"); + return -1; + } + + /* Signals handling */ + sigemptyset(&STATE(block)); + sigaddset(&STATE(block), SIGTERM); + sigaddset(&STATE(block), SIGINT); + + if (signal(SIGINT, killer) == SIG_ERR) + return -1; + + if (signal(SIGTERM, killer) == SIG_ERR) + return -1; + + /* ignore connection reset by peer */ + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + return -1; + + dlog(STATE(log), "[OK] initialization completed"); + + return 0; +} + +#define POLL_NSECS 1 + +static void __run(void) +{ + int max, ret; + fd_set readfds; + struct timeval tv = { + .tv_sec = POLL_NSECS, + .tv_usec = 0 + }; + + FD_ZERO(&readfds); + FD_SET(STATE(local), &readfds); + FD_SET(nfnl_fd(STATE(event)), &readfds); + + max = MAX(STATE(local), nfnl_fd(STATE(event))); + + if (STATE(mode)->add_fds_to_set) + max = MAX(max, STATE(mode)->add_fds_to_set(&readfds)); + + ret = select(max+1, &readfds, NULL, NULL, &tv); + if (ret == -1) { + /* interrupted syscall, retry */ + if (errno == EINTR) + return; + + dlog(STATE(log), "select() failed: %s", strerror(errno)); + return; + } + + /* signals are racy */ + sigprocmask(SIG_BLOCK, &STATE(block), NULL); + + /* order received via UNIX socket */ + if (FD_ISSET(STATE(local), &readfds)) + do_local_server_step(STATE(local), NULL, local_handler); + + /* conntrack event has happened */ + if (FD_ISSET(nfnl_fd(STATE(event)), &readfds)) { + ret = nfnl_catch(STATE(event)); + if (ret == -1) { + switch(errno) { + case ENOBUFS: + /* + * It seems that ctnetlink can't back off, + * it's likely that we're losing events. + * Solution: duplicate the socket buffer + * size and resync with master conntrack table. + */ + nl_resize_socket_buffer(STATE(event)); + nl_dump_conntrack_table(STATE(sync), + STATE(subsys_sync)); + break; + case ENOENT: + /* + * We received a message from another + * netfilter subsystem that we are not + * interested in. Just ignore it. + */ + break; + default: + dlog(STATE(log), "event catch says: %s", + strerror(errno)); + break; + } + } + } + + if (STATE(mode)->step) + STATE(mode)->step(&readfds); + + sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); +} + +void run(void) +{ + while(1) + __run(); +} diff --git a/src/state_helper.c b/src/state_helper.c new file mode 100644 index 0000000..81b0d09 --- /dev/null +++ b/src/state_helper.c @@ -0,0 +1,44 @@ +/* + * (C) 2006-2007 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 "conntrackd.h" +#include "state_helper.h" + +static struct state_replication_helper *helper[IPPROTO_MAX]; + +int state_helper_verdict(int type, struct nf_conntrack *ct) +{ + u_int8_t l4proto; + + if (type == NFCT_T_DESTROY) + return ST_H_REPLICATE; + + l4proto = nfct_get_attr_u8(ct, ATTR_ORIG_L4PROTO); + if (helper[l4proto]) + return helper[l4proto]->verdict(helper[l4proto], ct); + + return ST_H_REPLICATE; +} + +void state_helper_register(struct state_replication_helper *h, int state) +{ + if (helper[h->proto] == NULL) + helper[h->proto] = h; + + helper[h->proto]->state |= (1 << state); +} diff --git a/src/state_helper_tcp.c b/src/state_helper_tcp.c new file mode 100644 index 0000000..af714dc --- /dev/null +++ b/src/state_helper_tcp.c @@ -0,0 +1,35 @@ +/* + * (C) 2006-2007 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 "conntrackd.h" +#include "state_helper.h" + +static int tcp_verdict(const struct state_replication_helper *h, + const struct nf_conntrack *ct) +{ + u_int8_t state = nfct_get_attr_u8(ct, ATTR_TCP_STATE); + if (h->state & (1 << state)) + return ST_H_REPLICATE; + + return ST_H_SKIP; +} + +struct state_replication_helper tcp_state_helper = { + .proto = IPPROTO_TCP, + .verdict = tcp_verdict, +}; diff --git a/src/stats-mode.c b/src/stats-mode.c new file mode 100644 index 0000000..9647bbf --- /dev/null +++ b/src/stats-mode.c @@ -0,0 +1,151 @@ +/* + * (C) 2006 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 <stdlib.h> +#include "cache.h" +#include "conntrackd.h" +#include <libnfnetlink/libnfnetlink.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <errno.h> +#include "us-conntrack.h" +#include <signal.h> +#include <sys/select.h> + +static int init_stats(void) +{ + int ret; + + state.stats = malloc(sizeof(struct ct_stats_state)); + if (!state.stats) { + dlog(STATE(log), "[FAIL] can't allocate memory for stats sync"); + return -1; + } + memset(state.stats, 0, sizeof(struct ct_stats_state)); + + STATE_STATS(cache) = cache_create("stats", + LIFETIME, + CONFIG(family), + NULL); + if (!STATE_STATS(cache)) { + dlog(STATE(log), "[FAIL] can't allocate memory for the " + "external cache"); + return -1; + } + + return 0; +} + +static void kill_stats() +{ + cache_destroy(STATE_STATS(cache)); +} + +/* handler for requests coming via UNIX socket */ +static int local_handler_stats(int fd, int type, void *data) +{ + int ret = 1; + + switch(type) { + case DUMP_INTERNAL: + cache_dump(STATE_STATS(cache), fd, NFCT_O_PLAIN); + break; + case DUMP_INT_XML: + cache_dump(STATE_SYNC(internal), fd, NFCT_O_XML); + break; + case FLUSH_CACHE: + dlog(STATE(log), "[REQ] flushing caches"); + cache_flush(STATE_STATS(cache)); + break; + case KILL: + killer(); + break; + case STATS: + cache_stats(STATE_STATS(cache), fd); + dump_traffic_stats(fd); + break; + default: + ret = 0; + break; + } + + return ret; +} + +static void dump_stats(struct nf_conntrack *ct, struct nlmsghdr *nlh) +{ + if (cache_update_force(STATE_STATS(cache), ct)) + debug_ct(ct, "resync entry"); +} + +static void event_new_stats(struct nf_conntrack *ct, struct nlmsghdr *nlh) +{ + debug_ct(ct, "debug event"); + if (cache_add(STATE_STATS(cache), ct)) { + debug_ct(ct, "cache new"); + } else { + dlog(STATE(log), "can't add to cache cache: " + "%s\n", strerror(errno)); + debug_ct(ct, "can't add"); + } +} + +static void event_update_stats(struct nf_conntrack *ct, struct nlmsghdr *nlh) +{ + debug_ct(ct, "update"); + + if (!cache_update(STATE_STATS(cache), ct)) { + /* + * Perhaps we are losing events. If we are working + * in relax mode then add a new entry to the cache. + * + * FIXME: relax transitions not implemented yet + */ + if ((CONFIG(flags) & RELAX_TRANSITIONS) + && cache_add(STATE_STATS(cache), ct)) { + debug_ct(ct, "forcing cache update"); + } else { + debug_ct(ct, "can't update"); + return; + } + } + debug_ct(ct, "update"); +} + +static int event_destroy_stats(struct nf_conntrack *ct, struct nlmsghdr *nlh) +{ + if (cache_del(STATE_STATS(cache), ct)) { + debug_ct(ct, "cache destroy"); + return 1; + } else { + debug_ct(ct, "can't destroy!"); + return 0; + } +} + +struct ct_mode stats_mode = { + .init = init_stats, + .add_fds_to_set = NULL, + .step = NULL, + .local = local_handler_stats, + .kill = kill_stats, + .dump = dump_stats, + .overrun = dump_stats, + .event_new = event_new_stats, + .event_upd = event_update_stats, + .event_dst = event_destroy_stats +}; diff --git a/src/sync-mode.c b/src/sync-mode.c new file mode 100644 index 0000000..b32bef7 --- /dev/null +++ b/src/sync-mode.c @@ -0,0 +1,416 @@ +/* + * (C) 2006 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 <stdlib.h> +#include "cache.h" +#include "conntrackd.h" +#include <libnfnetlink/libnfnetlink.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <errno.h> +#include "us-conntrack.h" +#include <signal.h> +#include <sys/select.h> +#include "sync.h" +#include "network.h" + +/* handler for multicast messages received */ +static void mcast_handler() +{ + int ret; + char buf[4096], tmp[256]; + struct mcast_sock *m = STATE_SYNC(mcast_server); + unsigned int type; + struct nlnetwork *net = (struct nlnetwork *) buf; + unsigned int size = sizeof(struct nlnetwork); + struct nlmsghdr *nlh = (struct nlmsghdr *) (buf + size); + struct nf_conntrack *ct = (struct nf_conntrack *) tmp; + struct us_conntrack *u = NULL; + + memset(tmp, 0, sizeof(tmp)); + + ret = mcast_recv_netmsg(m, buf, sizeof(buf)); + if (ret <= 0) { + STATE(malformed)++; + return; + } + + if (STATE_SYNC(mcast_sync)->pre_recv(net)) + return; + + if ((type = parse_network_msg(ct, nlh)) == NFCT_T_ERROR) { + STATE(malformed)++; + return; + } + + nfct_attr_unset(ct, ATTR_TIMEOUT); + nfct_attr_unset(ct, ATTR_ORIG_COUNTER_BYTES); + nfct_attr_unset(ct, ATTR_ORIG_COUNTER_PACKETS); + nfct_attr_unset(ct, ATTR_REPL_COUNTER_BYTES); + nfct_attr_unset(ct, ATTR_REPL_COUNTER_PACKETS); + + switch(type) { + case NFCT_T_NEW: +retry: + if ((u = cache_add(STATE_SYNC(external), ct))) { + debug_ct(u->ct, "external new"); + } else { + /* + * One certain connection A arrives to the cache but + * another existing connection B in the cache has + * the same configuration, therefore B clashes with A. + */ + if (errno == EEXIST) { + cache_del(STATE_SYNC(external), ct); + goto retry; + } + debug_ct(ct, "can't add"); + } + break; + case NFCT_T_UPDATE: + if ((u = cache_update_force(STATE_SYNC(external), ct))) { + debug_ct(u->ct, "external update"); + } else + debug_ct(ct, "can't update"); + break; + case NFCT_T_DESTROY: + if (cache_del(STATE_SYNC(external), ct)) + debug_ct(ct, "external destroy"); + else + debug_ct(ct, "can't destroy"); + break; + default: + debug("unknown type %d\n", type); + break; + } +} + +static int init_sync(void) +{ + int ret; + + state.sync = malloc(sizeof(struct ct_sync_state)); + if (!state.sync) { + dlog(STATE(log), "[FAIL] can't allocate memory for state sync"); + return -1; + } + memset(state.sync, 0, sizeof(struct ct_sync_state)); + + if (CONFIG(flags) & SYNC_MODE_NACK) + STATE_SYNC(mcast_sync) = &nack; + else + /* default to persistent mode */ + STATE_SYNC(mcast_sync) = ¬rack; + + if (STATE_SYNC(mcast_sync)->init) + STATE_SYNC(mcast_sync)->init(); + + STATE_SYNC(internal) = + cache_create("internal", + STATE_SYNC(mcast_sync)->internal_cache_flags, + CONFIG(family), + STATE_SYNC(mcast_sync)->internal_cache_extra); + + if (!STATE_SYNC(internal)) { + dlog(STATE(log), "[FAIL] can't allocate memory for " + "the internal cache"); + return -1; + } + + STATE_SYNC(external) = + cache_create("external", + STATE_SYNC(mcast_sync)->external_cache_flags, + CONFIG(family), + NULL); + + if (!STATE_SYNC(external)) { + dlog(STATE(log), "[FAIL] can't allocate memory for the " + "external cache"); + return -1; + } + + /* multicast server to receive events from the wire */ + STATE_SYNC(mcast_server) = mcast_server_create(&CONFIG(mcast)); + if (STATE_SYNC(mcast_server) == NULL) { + dlog(STATE(log), "[FAIL] can't open multicast server!"); + return -1; + } + + /* multicast client to send events on the wire */ + STATE_SYNC(mcast_client) = mcast_client_create(&CONFIG(mcast)); + if (STATE_SYNC(mcast_client) == NULL) { + dlog(STATE(log), "[FAIL] can't open client multicast socket!"); + return -1; + } + + /* initialization of multicast sequence generation */ + STATE_SYNC(last_seq_sent) = time(NULL); + + if (create_alarm_thread() == -1) { + dlog(STATE(log), "[FAIL] can't initialize alarm thread"); + return -1; + } + + return 0; +} + +static int add_fds_to_set_sync(fd_set *readfds) +{ + FD_SET(STATE_SYNC(mcast_server->fd), readfds); + + return STATE_SYNC(mcast_server->fd); +} + +static void step_sync(fd_set *readfds) +{ + /* multicast packet has been received */ + if (FD_ISSET(STATE_SYNC(mcast_server->fd), readfds)) + mcast_handler(); +} + +static void kill_sync() +{ + cache_destroy(STATE_SYNC(internal)); + cache_destroy(STATE_SYNC(external)); + + mcast_server_destroy(STATE_SYNC(mcast_server)); + mcast_client_destroy(STATE_SYNC(mcast_client)); + + destroy_alarm_thread(); + + if (STATE_SYNC(mcast_sync)->kill) + STATE_SYNC(mcast_sync)->kill(); +} + +static dump_stats_sync(int fd) +{ + char buf[512]; + int size; + + size = sprintf(buf, "multicast sequence tracking:\n" + "%20llu Pckts mfrm " + "%20llu Pckts lost\n\n", + STATE(malformed), + STATE_SYNC(packets_lost)); + + send(fd, buf, size, 0); +} + +/* handler for requests coming via UNIX socket */ +static int local_handler_sync(int fd, int type, void *data) +{ + int ret = 1; + + switch(type) { + case DUMP_INTERNAL: + cache_dump(STATE_SYNC(internal), fd, NFCT_O_PLAIN); + break; + case DUMP_EXTERNAL: + cache_dump(STATE_SYNC(external), fd, NFCT_O_PLAIN); + break; + case DUMP_INT_XML: + cache_dump(STATE_SYNC(internal), fd, NFCT_O_XML); + break; + case DUMP_EXT_XML: + cache_dump(STATE_SYNC(external), fd, NFCT_O_XML); + break; + case COMMIT: + dlog(STATE(log), "[REQ] commit external cache to master table"); + cache_commit(STATE_SYNC(external)); + break; + case FLUSH_CACHE: + dlog(STATE(log), "[REQ] flushing caches"); + cache_flush(STATE_SYNC(internal)); + cache_flush(STATE_SYNC(external)); + break; + case KILL: + killer(); + break; + case STATS: + cache_stats(STATE_SYNC(internal), fd); + cache_stats(STATE_SYNC(external), fd); + dump_traffic_stats(fd); + mcast_dump_stats(fd, STATE_SYNC(mcast_client), + STATE_SYNC(mcast_server)); + dump_stats_sync(fd); + break; + case SEND_BULK: + dlog(STATE(log), "[REQ] sending bulk update"); + cache_bulk(STATE_SYNC(internal)); + break; + default: + if (STATE_SYNC(mcast_sync)->local) + ret = STATE_SYNC(mcast_sync)->local(fd, type, data); + break; + } + + return ret; +} + +static void dump_sync(struct nf_conntrack *ct, struct nlmsghdr *nlh) +{ + /* This is required by kernels < 2.6.20 */ + nfct_attr_unset(ct, ATTR_TIMEOUT); + nfct_attr_unset(ct, ATTR_ORIG_COUNTER_BYTES); + nfct_attr_unset(ct, ATTR_ORIG_COUNTER_PACKETS); + nfct_attr_unset(ct, ATTR_REPL_COUNTER_BYTES); + nfct_attr_unset(ct, ATTR_REPL_COUNTER_PACKETS); + nfct_attr_unset(ct, ATTR_USE); + + if (cache_update_force(STATE_SYNC(internal), ct)) + debug_ct(ct, "resync"); +} + +static void mcast_send_sync(struct nlmsghdr *nlh, + struct us_conntrack *u, + struct nf_conntrack *ct, + int type) +{ + char buf[4096]; + struct nlnetwork *net = (struct nlnetwork *) buf; + int mangled = 0; + + memset(buf, 0, sizeof(buf)); + + if (!state_helper_verdict(type, ct)) + return; + + if (!mangled) + memcpy(buf + sizeof(struct nlnetwork), nlh, nlh->nlmsg_len); + + mcast_send_netmsg(STATE_SYNC(mcast_client), net); + STATE_SYNC(mcast_sync)->post_send(net, u); +} + +static void overrun_sync(struct nf_conntrack *ct, struct nlmsghdr *nlh) +{ + struct us_conntrack *u; + + /* This is required by kernels < 2.6.20 */ + nfct_attr_unset(ct, ATTR_TIMEOUT); + nfct_attr_unset(ct, ATTR_ORIG_COUNTER_BYTES); + nfct_attr_unset(ct, ATTR_ORIG_COUNTER_PACKETS); + nfct_attr_unset(ct, ATTR_REPL_COUNTER_BYTES); + nfct_attr_unset(ct, ATTR_REPL_COUNTER_PACKETS); + nfct_attr_unset(ct, ATTR_USE); + + if (!cache_test(STATE_SYNC(internal), ct)) { + if ((u = cache_update_force(STATE_SYNC(internal), ct))) { + debug_ct(ct, "overrun resync"); + mcast_send_sync(nlh, u, ct, NFCT_T_UPDATE); + } + } +} + +static void event_new_sync(struct nf_conntrack *ct, struct nlmsghdr *nlh) +{ + struct us_conntrack *u; + + /* required by linux kernel <= 2.6.20 */ + nfct_attr_unset(ct, ATTR_ORIG_COUNTER_BYTES); + nfct_attr_unset(ct, ATTR_ORIG_COUNTER_PACKETS); + nfct_attr_unset(ct, ATTR_REPL_COUNTER_BYTES); + nfct_attr_unset(ct, ATTR_REPL_COUNTER_PACKETS); + nfct_attr_unset(ct, ATTR_TIMEOUT); +retry: + if ((u = cache_add(STATE_SYNC(internal), ct))) { + mcast_send_sync(nlh, u, ct, NFCT_T_NEW); + debug_ct(u->ct, "internal new"); + } else { + if (errno == EEXIST) { + char buf[4096]; + struct nlmsghdr *nlh = (struct nlmsghdr *) buf; + + int ret = build_network_msg(NFCT_Q_DESTROY, + STATE(subsys_event), + ct, + buf, + sizeof(buf)); + if (ret == -1) + return; + + cache_del(STATE_SYNC(internal), ct); + mcast_send_sync(nlh, NULL, ct, NFCT_T_NEW); + goto retry; + } + dlog(STATE(log), "can't add to internal cache: " + "%s\n", strerror(errno)); + debug_ct(ct, "can't add"); + } +} + +static void event_update_sync(struct nf_conntrack *ct, struct nlmsghdr *nlh) +{ + struct us_conntrack *u; + + nfct_attr_unset(ct, ATTR_TIMEOUT); + + if ((u = cache_update(STATE_SYNC(internal), ct)) == NULL) { + /* + * Perhaps we are losing events. If we are working + * in relax mode then add a new entry to the cache. + * + * FIXME: relax transitions not implemented yet + */ + if ((CONFIG(flags) & RELAX_TRANSITIONS) + && (u = cache_add(STATE_SYNC(internal), ct))) { + debug_ct(u->ct, "forcing internal update"); + } else { + debug_ct(ct, "can't update"); + return; + } + } + debug_ct(u->ct, "internal update"); + mcast_send_sync(nlh, u, ct, NFCT_T_UPDATE); +} + +static int event_destroy_sync(struct nf_conntrack *ct, struct nlmsghdr *nlh) +{ + nfct_attr_unset(ct, ATTR_TIMEOUT); + + if (CONFIG(flags) & DELAY_DESTROY_MSG) { + + nfct_set_attr_u32(ct, ATTR_STATUS, IPS_DYING); + + if (cache_update(STATE_SYNC(internal), ct)) { + debug_ct(ct, "delay internal destroy"); + return 1; + } else { + debug_ct(ct, "can't delay destroy!"); + return 0; + } + } else { + if (cache_del(STATE_SYNC(internal), ct)) { + mcast_send_sync(nlh, NULL, ct, NFCT_T_DESTROY); + debug_ct(ct, "internal destroy"); + } else + debug_ct(ct, "can't destroy"); + } +} + +struct ct_mode sync_mode = { + .init = init_sync, + .add_fds_to_set = add_fds_to_set_sync, + .step = step_sync, + .local = local_handler_sync, + .kill = kill_sync, + .dump = dump_sync, + .overrun = overrun_sync, + .event_new = event_new_sync, + .event_upd = event_update_sync, + .event_dst = event_destroy_sync +}; diff --git a/src/sync-nack.c b/src/sync-nack.c new file mode 100644 index 0000000..288dba4 --- /dev/null +++ b/src/sync-nack.c @@ -0,0 +1,309 @@ +/* + * (C) 2006-2007 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 <errno.h> +#include "conntrackd.h" +#include "sync.h" +#include "linux_list.h" +#include "us-conntrack.h" +#include "buffer.h" +#include "debug.h" +#include "network.h" +#include <libnfnetlink/libnfnetlink.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> + +#if 0 +#define dp printf +#else +#define dp +#endif + +static LIST_HEAD(queue); + +struct cache_nack { + struct list_head head; + u_int32_t seq; +}; + +static void cache_nack_add(struct us_conntrack *u, void *data) +{ + struct cache_nack *cn = data; + + INIT_LIST_HEAD(&cn->head); + list_add(&cn->head, &queue); +} + +static void cache_nack_update(struct us_conntrack *u, void *data) +{ + struct cache_nack *cn = data; + + if (cn->head.next != LIST_POISON1 && + cn->head.prev != LIST_POISON2) + list_del(&cn->head); + + INIT_LIST_HEAD(&cn->head); + list_add(&cn->head, &queue); +} + +static void cache_nack_destroy(struct us_conntrack *u, void *data) +{ + struct cache_nack *cn = data; + + if (cn->head.next != LIST_POISON1 && + cn->head.prev != LIST_POISON2) + list_del(&cn->head); +} + +static struct cache_extra cache_nack_extra = { + .size = sizeof(struct cache_nack), + .add = cache_nack_add, + .update = cache_nack_update, + .destroy = cache_nack_destroy +}; + +static int nack_init() +{ + STATE_SYNC(buffer) = buffer_create(CONFIG(resend_buffer_size)); + if (STATE_SYNC(buffer) == NULL) + return -1; + + return 0; +} + +static void nack_kill() +{ + buffer_destroy(STATE_SYNC(buffer)); +} + +static void mcast_send_nack(u_int32_t expt_seq, u_int32_t recv_seq) +{ + struct nlnetwork_ack nack = { + .flags = NET_NACK, + .from = expt_seq, + .to = recv_seq, + }; + + mcast_send_error(STATE_SYNC(mcast_client), &nack); + buffer_add(STATE_SYNC(buffer), &nack, sizeof(struct nlnetwork_ack)); +} + +static void mcast_send_ack(u_int32_t from, u_int32_t to) +{ + struct nlnetwork_ack ack = { + .flags = NET_ACK, + .from = from, + .to = to, + }; + + mcast_send_error(STATE_SYNC(mcast_client), &ack); + buffer_add(STATE_SYNC(buffer), &ack, sizeof(struct nlnetwork_ack)); +} + +static void mcast_send_resync() +{ + struct nlnetwork net = { + .flags = NET_RESYNC, + }; + + mcast_send_error(STATE_SYNC(mcast_client), &net); + buffer_add(STATE_SYNC(buffer), &net, sizeof(struct nlnetwork)); +} + +int nack_local(int fd, int type, void *data) +{ + int ret = 1; + + switch(type) { + case REQUEST_DUMP: + mcast_send_resync(); + dlog(STATE(log), "[REQ] request resync"); + break; + default: + ret = 0; + break; + } + + return ret; +} + +static int buffer_compare(void *data1, void *data2) +{ + struct nlnetwork *net = data1; + struct nlnetwork_ack *nack = data2; + struct nlmsghdr *nlh = data1 + sizeof(struct nlnetwork); + + unsigned old_seq = ntohl(net->seq); + + if (ntohl(net->seq) >= nack->from && ntohl(net->seq) <= nack->to) { + if (mcast_resend_netmsg(STATE_SYNC(mcast_client), net)) + dp("resend destroy (old seq=%u) (seq=%u)\n", + old_seq, ntohl(net->seq)); + } + return 0; +} + +static int buffer_remove(void *data1, void *data2) +{ + struct nlnetwork *net = data1; + struct nlnetwork_ack *h = data2; + + if (ntohl(net->seq) >= h->from && ntohl(net->seq) <= h->to) { + dp("remove from buffer (seq=%u)\n", ntohl(net->seq)); + __buffer_del(STATE_SYNC(buffer), data1); + } + return 0; +} + +static void queue_resend(struct cache *c, unsigned int from, unsigned int to) +{ + struct list_head *n; + struct us_conntrack *u; + unsigned int *seq; + + lock(); + list_for_each(n, &queue) { + struct cache_nack *cn = (struct cache_nack *) n; + struct us_conntrack *u; + + u = cache_get_conntrack(STATE_SYNC(internal), cn); + + if (cn->seq >= from && cn->seq <= to) { + debug_ct(u->ct, "resend nack"); + dp("resending nack'ed (oldseq=%u) ", cn->seq); + + char buf[4096]; + struct nlnetwork *net = (struct nlnetwork *) buf; + + int ret = build_network_msg(NFCT_Q_UPDATE, + STATE(subsys_event), + u->ct, + buf, + sizeof(buf)); + if (ret == -1) { + unlock(); + break; + } + + mcast_send_netmsg(STATE_SYNC(mcast_client), buf); + STATE_SYNC(mcast_sync)->post_send(net, u); + dp("(newseq=%u)\n", *seq); + } + } + unlock(); +} + +static void queue_empty(struct cache *c, unsigned int from, unsigned int to) +{ + struct list_head *n, *tmp; + struct us_conntrack *u; + unsigned int *seq; + + lock(); + dp("ACK from %u to %u\n", from, to); + list_for_each_safe(n, tmp, &queue) { + struct cache_nack *cn = (struct cache_nack *) n; + + u = cache_get_conntrack(STATE_SYNC(internal), cn); + if (cn->seq >= from && cn->seq <= to) { + dp("remove %u\n", cn->seq); + debug_ct(u->ct, "ack received: empty queue"); + dp("queue: deleting from queue (seq=%u)\n", cn->seq); + list_del(&cn->head); + } + } + unlock(); +} + +static int nack_pre_recv(const struct nlnetwork *net) +{ + static unsigned int window = 0; + unsigned int exp_seq; + + if (window == 0) + window = CONFIG(window_size); + + if (!mcast_track_seq(net->seq, &exp_seq)) { + dp("OOS: sending nack (seq=%u)\n", exp_seq); + mcast_send_nack(exp_seq, net->seq - 1); + window = CONFIG(window_size); + } else { + /* received a window, send an acknowledgement */ + if (--window == 0) { + dp("sending ack (seq=%u)\n", net->seq); + mcast_send_ack(net->seq-CONFIG(window_size), net->seq); + } + } + + if (net->flags & NET_NACK) { + struct nlnetwork_ack *nack = (struct nlnetwork_ack *) net; + + dp("NACK: from seq=%u to seq=%u\n", nack->from, nack->to); + queue_resend(STATE_SYNC(internal), nack->from, nack->to); + buffer_iterate(STATE_SYNC(buffer), nack, buffer_compare); + return 1; + } else if (net->flags & NET_RESYNC) { + dp("RESYNC ALL\n"); + cache_bulk(STATE_SYNC(internal)); + return 1; + } else if (net->flags & NET_ACK) { + struct nlnetwork_ack *h = (struct nlnetwork_ack *) net; + + dp("ACK: from seq=%u to seq=%u\n", h->from, h->to); + queue_empty(STATE_SYNC(internal), h->from, h->to); + buffer_iterate(STATE_SYNC(buffer), h, buffer_remove); + return 1; + } + + return 0; +} + +static void nack_post_send(const struct nlnetwork *net, struct us_conntrack *u) +{ + unsigned int size = sizeof(struct nlnetwork); + struct nlmsghdr *nlh = (struct nlmsghdr *) ((void *) net + size); + + if (NFNL_MSG_TYPE(ntohs(nlh->nlmsg_type)) == IPCTNL_MSG_CT_DELETE) { + buffer_add(STATE_SYNC(buffer), net, + ntohl(nlh->nlmsg_len) + size); + } else if (u != NULL) { + unsigned int *seq; + struct list_head *n; + struct cache_nack *cn; + + cn = (struct cache_nack *) + cache_get_extra(STATE_SYNC(internal), u); + cn->seq = ntohl(net->seq); + if (cn->head.next != LIST_POISON1 && + cn->head.prev != LIST_POISON2) + list_del(&cn->head); + + INIT_LIST_HEAD(&cn->head); + list_add(&cn->head, &queue); + } +} + +struct sync_mode nack = { + .internal_cache_flags = LIFETIME, + .external_cache_flags = LIFETIME, + .internal_cache_extra = &cache_nack_extra, + .init = nack_init, + .kill = nack_kill, + .local = nack_local, + .pre_recv = nack_pre_recv, + .post_send = nack_post_send, +}; diff --git a/src/sync-notrack.c b/src/sync-notrack.c new file mode 100644 index 0000000..2b5ae38 --- /dev/null +++ b/src/sync-notrack.c @@ -0,0 +1,127 @@ +/* + * (C) 2006-2007 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 "conntrackd.h" +#include "sync.h" +#include "network.h" +#include "us-conntrack.h" +#include "alarm.h" + +static void refresher(struct alarm_list *a, void *data) +{ + struct us_conntrack *u = data; + char buf[8192]; + int size; + + if (nfct_get_attr_u32(u->ct, ATTR_STATUS) & IPS_DYING) { + + debug_ct(u->ct, "persistence destroy"); + + size = build_network_msg(NFCT_Q_DESTROY, + STATE(subsys_event), + u->ct, + buf, + sizeof(buf)); + + __cache_del(u->cache, u->ct); + mcast_send_netmsg(STATE_SYNC(mcast_client), buf); + } else { + + debug_ct(u->ct, "persistence update"); + + a->expires = random() % CONFIG(refresh) + 1; + size = build_network_msg(NFCT_Q_UPDATE, + STATE(subsys_event), + u->ct, + buf, + sizeof(buf)); + mcast_send_netmsg(STATE_SYNC(mcast_client), buf); + } +} + +static void cache_notrack_add(struct us_conntrack *u, void *data) +{ + struct alarm_list *alarm = data; + + init_alarm(alarm); + set_alarm_expiration(alarm, (random() % conf.refresh) + 1); + set_alarm_data(alarm, u); + set_alarm_function(alarm, refresher); + add_alarm(alarm); +} + +static void cache_notrack_update(struct us_conntrack *u, void *data) +{ + struct alarm_list *alarm = data; + mod_alarm(alarm, (random() % conf.refresh) + 1); +} + +static void cache_notrack_destroy(struct us_conntrack *u, void *data) +{ + struct alarm_list *alarm = data; + del_alarm(alarm); +} + +static struct cache_extra cache_notrack_extra = { + .size = sizeof(struct alarm_list), + .add = cache_notrack_add, + .update = cache_notrack_update, + .destroy = cache_notrack_destroy +}; + +static int notrack_pre_recv(const struct nlnetwork *net) +{ + unsigned int exp_seq; + + /* + * Ignore error messages: Although this message type is not ever + * generated in notrack mode, we don't want to crash the daemon + * if someone nuts mixes nack and notrack. + */ + if (net->flags & (NET_RESYNC | NET_NACK)) + return 1; + + /* + * Multicast sequence tracking: we keep track of multicast messages + * although we don't do any explicit message recovery. So, why do + * we do sequence tracking? Just to let know the sysadmin. + * + * Let t be 1 < t < RefreshTime. To ensure consistency, conntrackd + * retransmit every t seconds a message with the state of a certain + * entry even if such entry did not change. This mechanism also + * provides passive resynchronization, in other words, there is + * no facility to request a full synchronization from new nodes that + * just joined the cluster, instead they just get resynchronized in + * RefreshTime seconds at worst case. + */ + mcast_track_seq(net->seq, &exp_seq); + + return 0; +} + +static void notrack_post_send(const struct nlnetwork *n, struct us_conntrack *u) +{ +} + +struct sync_mode notrack = { + .internal_cache_flags = LIFETIME, + .external_cache_flags = TIMER | LIFETIME, + .internal_cache_extra = &cache_notrack_extra, + .pre_recv = notrack_pre_recv, + .post_send = notrack_post_send, +}; diff --git a/src/traffic_stats.c b/src/traffic_stats.c new file mode 100644 index 0000000..b510b77 --- /dev/null +++ b/src/traffic_stats.c @@ -0,0 +1,54 @@ +/* + * (C) 2006 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 "cache.h" +#include "hash.h" +#include "conntrackd.h" +#include <libnfnetlink/libnfnetlink.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <errno.h> +#include "us-conntrack.h" +#include <signal.h> + +void update_traffic_stats(struct nf_conntrack *ct) +{ + STATE(bytes)[NFCT_DIR_ORIGINAL] += + nfct_get_attr_u32(ct, ATTR_ORIG_COUNTER_BYTES); + STATE(bytes)[NFCT_DIR_REPLY] += + nfct_get_attr_u32(ct, ATTR_REPL_COUNTER_BYTES); + STATE(packets)[NFCT_DIR_ORIGINAL] += + nfct_get_attr_u32(ct, ATTR_ORIG_COUNTER_PACKETS); + STATE(packets)[NFCT_DIR_REPLY] += + nfct_get_attr_u32(ct, ATTR_REPL_COUNTER_PACKETS); +} + +void dump_traffic_stats(int fd) +{ + char buf[512]; + int size; + u_int64_t bytes = STATE(bytes)[NFCT_DIR_ORIGINAL] + + STATE(bytes)[NFCT_DIR_REPLY]; + u_int64_t packets = STATE(packets)[NFCT_DIR_ORIGINAL] + + STATE(packets)[NFCT_DIR_REPLY]; + + size = sprintf(buf, "traffic processed:\n"); + size += sprintf(buf+size, "%20llu Bytes ", bytes); + size += sprintf(buf+size, "%20llu Pckts\n\n", packets); + + send(fd, buf, size, 0); +} |