summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am26
-rw-r--r--src/alarm.c141
-rw-r--r--src/buffer.c136
-rw-r--r--src/cache.c446
-rw-r--r--src/cache_iterators.c229
-rw-r--r--src/cache_lifetime.c65
-rw-r--r--src/cache_timer.c72
-rw-r--r--src/checksum.c32
-rw-r--r--src/conntrack.c1131
-rw-r--r--src/hash.c199
-rw-r--r--src/ignore_pool.c136
-rw-r--r--src/local.c159
-rw-r--r--src/lock.c32
-rw-r--r--src/log.c57
-rw-r--r--src/main.c302
-rw-r--r--src/mcast.c287
-rw-r--r--src/netlink.c326
-rw-r--r--src/network.c282
-rw-r--r--src/proxy.c124
-rw-r--r--src/read_config_lex.l125
-rw-r--r--src/read_config_yy.y550
-rw-r--r--src/run.c227
-rw-r--r--src/state_helper.c44
-rw-r--r--src/state_helper_tcp.c35
-rw-r--r--src/stats-mode.c151
-rw-r--r--src/sync-mode.c416
-rw-r--r--src/sync-nack.c309
-rw-r--r--src/sync-notrack.c127
-rw-r--r--src/traffic_stats.c54
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) = &notrack;
+
+ 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);
+}