From 5a0d0ecf30fb1686cfb10aaa852fee9c8ed4360a Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 24 Apr 2012 11:56:00 +0200 Subject: conntrackd: move ctnetlink code to ctnl.c (removed from run.c) This patch moves the specific ctnetlink code to ctnl.c to prepare the introduction of the cthelper infrastructure. Signed-off-by: Pablo Neira Ayuso --- src/ctnl.c | 522 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 522 insertions(+) create mode 100644 src/ctnl.c (limited to 'src/ctnl.c') diff --git a/src/ctnl.c b/src/ctnl.c new file mode 100644 index 0000000..107cd5d --- /dev/null +++ b/src/ctnl.c @@ -0,0 +1,522 @@ +/* + * (C) 2006-2012 by Pablo Neira Ayuso + * + * 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. + * + * Part of this code has been sponsored by Vyatta Inc. + */ + +#include "conntrackd.h" +#include "netlink.h" +#include "filter.h" +#include "log.h" +#include "alarm.h" +#include "fds.h" +#include "traffic_stats.h" +#include "process.h" +#include "origin.h" +#include "date.h" +#include "internal.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +void ctnl_kill(void) +{ + if (!(CONFIG(flags) & CTD_POLL)) + nfct_close(STATE(event)); + + nfct_close(STATE(resync)); + nfct_close(STATE(get)); + origin_unregister(STATE(flush)); + nfct_close(STATE(flush)); + + if (STATE(us_filter)) + ct_filter_destroy(STATE(us_filter)); + STATE(mode)->kill(); + + if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { + nfct_close(STATE(dump)); + } +} + +static void local_flush_master(void) +{ + STATE(stats).nl_kernel_table_flush++; + dlog(LOG_NOTICE, "flushing kernel conntrack table"); + + /* fork a child process that performs the flush operation, + * meanwhile the parent process handles events. */ + if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL, + NULL, NULL) == 0) { + nl_flush_conntrack_table_selective(); + exit(EXIT_SUCCESS); + } +} + +static void local_resync_master(void) +{ + if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { + STATE(stats).nl_kernel_table_resync++; + dlog(LOG_NOTICE, "resync with master conntrack table"); + nl_dump_conntrack_table(STATE(dump)); + } else { + dlog(LOG_NOTICE, "resync is unsupported in this mode"); + } +} + +static void local_exp_flush_master(void) +{ + if (!(CONFIG(flags) & CTD_EXPECT)) + return; + + STATE(stats).nl_kernel_table_flush++; + dlog(LOG_NOTICE, "flushing kernel expect table"); + + /* fork a child process that performs the flush operation, + * meanwhile the parent process handles events. */ + if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL, + NULL, NULL) == 0) { + nl_flush_expect_table(STATE(flush)); + exit(EXIT_SUCCESS); + } +} + +static void local_exp_resync_master(void) +{ + if (!(CONFIG(flags) & CTD_EXPECT)) + return; + + if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { + STATE(stats).nl_kernel_table_resync++; + dlog(LOG_NOTICE, "resync with master expect table"); + nl_dump_expect_table(STATE(dump)); + } else { + dlog(LOG_NOTICE, "resync is unsupported in this mode"); + } +} + +int ctnl_local(int fd, int type, void *data) +{ + int ret = LOCAL_RET_OK; + + switch(type) { + case CT_FLUSH_MASTER: + local_flush_master(); + break; + case CT_RESYNC_MASTER: + local_resync_master(); + break; + case EXP_FLUSH_MASTER: + local_exp_flush_master(); + break; + case EXP_RESYNC_MASTER: + local_exp_resync_master(); + break; + case ALL_FLUSH_MASTER: + local_flush_master(); + local_exp_flush_master(); + break; + case ALL_RESYNC_MASTER: + local_resync_master(); + local_exp_resync_master(); + break; + } + + ret = STATE(mode)->local(fd, type, data); + if (ret == LOCAL_RET_ERROR) { + STATE(stats).local_unknown_request++; + return LOCAL_RET_ERROR; + } + return ret; +} + +static void do_overrun_resync_alarm(struct alarm_block *a, void *data) +{ + nl_send_resync(STATE(resync)); + STATE(stats).nl_kernel_table_resync++; +} + +static void do_polling_alarm(struct alarm_block *a, void *data) +{ + if (STATE(mode)->internal->ct.purge) + STATE(mode)->internal->ct.purge(); + + if (STATE(mode)->internal->exp.purge) + STATE(mode)->internal->exp.purge(); + + nl_send_resync(STATE(resync)); + nl_send_expect_resync(STATE(resync)); + add_alarm(&STATE(polling_alarm), CONFIG(poll_kernel_secs), 0); +} + +static int event_handler(const struct nlmsghdr *nlh, + enum nf_conntrack_msg_type type, + struct nf_conntrack *ct, + void *data) +{ + int origin_type; + + STATE(stats).nl_events_received++; + + /* skip user-space filtering if already do it in the kernel */ + if (ct_filter_conntrack(ct, !CONFIG(filter_from_kernelspace))) { + STATE(stats).nl_events_filtered++; + goto out; + } + + origin_type = origin_find(nlh); + + switch(type) { + case NFCT_T_NEW: + STATE(mode)->internal->ct.new(ct, origin_type); + break; + case NFCT_T_UPDATE: + STATE(mode)->internal->ct.upd(ct, origin_type); + break; + case NFCT_T_DESTROY: + if (STATE(mode)->internal->ct.del(ct, origin_type)) + update_traffic_stats(ct); + break; + default: + STATE(stats).nl_events_unknown_type++; + break; + } + +out: + /* we reset the iteration limiter in the main select loop. */ + if (STATE(event_iterations_limit)-- <= 0) + return NFCT_CB_STOP; + else + return NFCT_CB_CONTINUE; +} + +static int exp_event_handler(const struct nlmsghdr *nlh, + enum nf_conntrack_msg_type type, + struct nf_expect *exp, + void *data) +{ + int origin_type; + const struct nf_conntrack *master = + nfexp_get_attr(exp, ATTR_EXP_MASTER); + + STATE(stats).nl_events_received++; + + if (!exp_filter_find(STATE(exp_filter), exp)) { + STATE(stats).nl_events_filtered++; + goto out; + } + if (ct_filter_conntrack(master, 1)) + return NFCT_CB_CONTINUE; + + origin_type = origin_find(nlh); + + switch(type) { + case NFCT_T_NEW: + STATE(mode)->internal->exp.new(exp, origin_type); + break; + case NFCT_T_UPDATE: + STATE(mode)->internal->exp.upd(exp, origin_type); + break; + case NFCT_T_DESTROY: + STATE(mode)->internal->exp.del(exp, origin_type); + break; + default: + STATE(stats).nl_events_unknown_type++; + break; + } + +out: + /* we reset the iteration limiter in the main select loop. */ + if (STATE(event_iterations_limit)-- <= 0) + return NFCT_CB_STOP; + else + return NFCT_CB_CONTINUE; +} + +static int dump_handler(enum nf_conntrack_msg_type type, + struct nf_conntrack *ct, + void *data) +{ + if (ct_filter_conntrack(ct, 1)) + return NFCT_CB_CONTINUE; + + switch(type) { + case NFCT_T_UPDATE: + STATE(mode)->internal->ct.populate(ct); + break; + default: + STATE(stats).nl_dump_unknown_type++; + break; + } + return NFCT_CB_CONTINUE; +} + +static int exp_dump_handler(enum nf_conntrack_msg_type type, + struct nf_expect *exp, void *data) +{ + const struct nf_conntrack *master = + nfexp_get_attr(exp, ATTR_EXP_MASTER); + + if (!exp_filter_find(STATE(exp_filter), exp)) + return NFCT_CB_CONTINUE; + + if (ct_filter_conntrack(master, 1)) + return NFCT_CB_CONTINUE; + + switch(type) { + case NFCT_T_UPDATE: + STATE(mode)->internal->exp.populate(exp); + break; + default: + STATE(stats).nl_dump_unknown_type++; + break; + } + return NFCT_CB_CONTINUE; +} + +static int get_handler(enum nf_conntrack_msg_type type, + struct nf_conntrack *ct, + void *data) +{ + if (ct_filter_conntrack(ct, 1)) + return NFCT_CB_CONTINUE; + + STATE(get_retval) = 1; + return NFCT_CB_CONTINUE; +} + +static int exp_get_handler(enum nf_conntrack_msg_type type, + struct nf_expect *exp, void *data) +{ + const struct nf_conntrack *master = + nfexp_get_attr(exp, ATTR_EXP_MASTER); + + if (!exp_filter_find(STATE(exp_filter), exp)) + return NFCT_CB_CONTINUE; + + if (ct_filter_conntrack(master, 1)) + return NFCT_CB_CONTINUE; + + STATE(get_retval) = 1; + return NFCT_CB_CONTINUE; +} + +/* we have received an event from ctnetlink */ +static void event_cb(void *data) +{ + int ret; + + ret = nfct_catch(STATE(event)); + /* reset event iteration limit counter */ + STATE(event_iterations_limit) = CONFIG(event_iterations_limit); + if (ret == -1) { + switch(errno) { + case ENOBUFS: + /* We have hit ENOBUFS, it's likely that we are + * losing events. Two possible situations may + * trigger this error: + * + * 1) The netlink receiver buffer is too small: + * increasing the netlink buffer size should + * be enough. However, some event messages + * got lost. We have to resync ourselves + * with the kernel table conntrack table to + * resolve the inconsistency. + * + * 2) The receiver is too slow to process the + * netlink messages so that the queue gets + * full quickly. This generally happens + * if the system is under heavy workload + * (busy CPU). In this case, increasing the + * size of the netlink receiver buffer + * would not help anymore since we would + * be delaying the overrun. Moreover, we + * should avoid resynchronizations. We + * should do our best here and keep + * replicating as much states as possible. + * If workload lowers at some point, + * we resync ourselves. + */ + nl_resize_socket_buffer(STATE(event)); + if (CONFIG(nl_overrun_resync) > 0 && + STATE(mode)->internal->flags & INTERNAL_F_RESYNC) { + add_alarm(&STATE(resync_alarm), + CONFIG(nl_overrun_resync),0); + } + STATE(stats).nl_catch_event_failed++; + STATE(stats).nl_overrun++; + break; + case ENOENT: + /* + * We received a message from another + * netfilter subsystem that we are not + * interested in. Just ignore it. + */ + break; + case EAGAIN: + /* No more events to receive, try later. */ + break; + default: + STATE(stats).nl_catch_event_failed++; + break; + } + } +} + +/* we previously requested a resync due to buffer overrun. */ +static void resync_cb(void *data) +{ + nfct_catch(STATE(resync)); + if (STATE(mode)->internal->ct.purge) + STATE(mode)->internal->ct.purge(); +} + +static void poll_cb(void *data) +{ + nfct_catch(STATE(resync)); +} + +int ctnl_init(void) +{ + if (CONFIG(flags) & CTD_STATS_MODE) + STATE(mode) = &stats_mode; + else if (CONFIG(flags) & CTD_SYNC_MODE) + STATE(mode) = &sync_mode; + else { + fprintf(stderr, "WARNING: No running mode specified. " + "Defaulting to statistics mode.\n"); + CONFIG(flags) |= CTD_STATS_MODE; + STATE(mode) = &stats_mode; + } + + /* Initialization */ + if (STATE(mode)->init() == -1) { + dlog(LOG_ERR, "initialization failed"); + return -1; + } + + /* resynchronize (like 'dump' socket) but it also purges old entries */ + STATE(resync) = nfct_open(CONFIG(netlink).subsys_id, 0); + if (STATE(resync)== NULL) { + dlog(LOG_ERR, "can't open netlink handler: %s", + strerror(errno)); + dlog(LOG_ERR, "no ctnetlink kernel support?"); + return -1; + } + nfct_callback_register(STATE(resync), + NFCT_T_ALL, + STATE(mode)->internal->ct.resync, + NULL); + if (CONFIG(flags) & CTD_POLL) { + register_fd(nfct_fd(STATE(resync)), poll_cb, + NULL, STATE(fds)); + } else { + register_fd(nfct_fd(STATE(resync)), resync_cb, + NULL, STATE(fds)); + } + fcntl(nfct_fd(STATE(resync)), F_SETFL, O_NONBLOCK); + + if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { + STATE(dump) = nfct_open(CONFIG(netlink).subsys_id, 0); + if (STATE(dump) == NULL) { + dlog(LOG_ERR, "can't open netlink handler: %s", + strerror(errno)); + dlog(LOG_ERR, "no ctnetlink kernel support?"); + return -1; + } + nfct_callback_register(STATE(dump), NFCT_T_ALL, + dump_handler, NULL); + + if (CONFIG(flags) & CTD_EXPECT) { + nfexp_callback_register(STATE(dump), NFCT_T_ALL, + exp_dump_handler, NULL); + } + + if (nl_dump_conntrack_table(STATE(dump)) == -1) { + dlog(LOG_ERR, "can't get kernel conntrack table"); + return -1; + } + + if (CONFIG(flags) & CTD_EXPECT) { + if (nl_dump_expect_table(STATE(dump)) == -1) { + dlog(LOG_ERR, "can't get kernel " + "expect table"); + return -1; + } + } + } + + STATE(get) = nfct_open(CONFIG(netlink).subsys_id, 0); + if (STATE(get) == NULL) { + dlog(LOG_ERR, "can't open netlink handler: %s", + strerror(errno)); + dlog(LOG_ERR, "no ctnetlink kernel support?"); + return -1; + } + nfct_callback_register(STATE(get), NFCT_T_ALL, get_handler, NULL); + + if (CONFIG(flags) & CTD_EXPECT) { + nfexp_callback_register(STATE(get), NFCT_T_ALL, + exp_get_handler, NULL); + } + + STATE(flush) = nfct_open(CONFIG(netlink).subsys_id, 0); + if (STATE(flush) == NULL) { + dlog(LOG_ERR, "cannot open flusher handler"); + return -1; + } + /* register this handler as the origin of a flush operation */ + origin_register(STATE(flush), CTD_ORIGIN_FLUSH); + + if (CONFIG(flags) & CTD_POLL) { + init_alarm(&STATE(polling_alarm), NULL, do_polling_alarm); + add_alarm(&STATE(polling_alarm), CONFIG(poll_kernel_secs), 0); + dlog(LOG_NOTICE, "running in polling mode"); + } else { + init_alarm(&STATE(resync_alarm), NULL, do_overrun_resync_alarm); + /* + * The last nfct handler that we register is the event handler. + * The reason to do this is that we may receive events while + * populating the internal cache. Thus, we hit ENOBUFS + * prematurely. However, if we open the event handler before + * populating the internal cache, we may still lose events + * that have occured during the population. + */ + STATE(event) = nl_init_event_handler(); + if (STATE(event) == NULL) { + dlog(LOG_ERR, "can't open netlink handler: %s", + strerror(errno)); + dlog(LOG_ERR, "no ctnetlink kernel support?"); + return -1; + } + nfct_callback_register2(STATE(event), NFCT_T_ALL, + event_handler, NULL); + + if (CONFIG(flags) & CTD_EXPECT) { + nfexp_callback_register2(STATE(event), NFCT_T_ALL, + exp_event_handler, NULL); + } + register_fd(nfct_fd(STATE(event)), event_cb, NULL, STATE(fds)); + } + + return 0; +} -- cgit v1.2.3 From 18bfa4bfb3bb875c36756272bb653c33e338c776 Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Thu, 2 Aug 2012 05:33:56 +0000 Subject: conntrackd: don't resync expectations if such sync has been disabled conntrackd was segfaulting with `ExpectationSync` set to `Off` and PollSecs (polling mode) in use. Signed-off-by: Vincent Bernat Signed-off-by: Pablo Neira Ayuso --- src/ctnl.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/ctnl.c') diff --git a/src/ctnl.c b/src/ctnl.c index 107cd5d..bb54727 100644 --- a/src/ctnl.c +++ b/src/ctnl.c @@ -164,7 +164,9 @@ static void do_polling_alarm(struct alarm_block *a, void *data) STATE(mode)->internal->exp.purge(); nl_send_resync(STATE(resync)); - nl_send_expect_resync(STATE(resync)); + if (CONFIG(flags) & CTD_EXPECT) + nl_send_expect_resync(STATE(resync)); + add_alarm(&STATE(polling_alarm), CONFIG(poll_kernel_secs), 0); } -- cgit v1.2.3 From 479a37a549abf197ce59a4ae1666d8cba80fe977 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 6 Jul 2013 14:04:24 +0200 Subject: conntrackd: fix crash with IPv6 expectation in the filtering code Jul 5 00:41:06 sen-fw1 kernel: [274422.060695] conntrackd[4821]: segfault at 0 ip 000000000040c660 sp 00007fffebb098a8 error 4 in conntrackd[400000+3d000] > #0 0x000000000040f217 in jhash2 (k=0x0, length=4, initval=0) at ../include/jhash.h:99 > a = 2654435769 b = 2654435769 c = 0 len = 4 > #1 0x000000000040f564 in ct_filter_hash6 (data=0x0, table=0x16ef630) at filter.c:57 > #2 0x000000000040ad34 in hashtable_hash (table=0x16ef630, data=0x0) at hash.c:63 > #3 0x000000000040fd19 in __ct_filter_test_ipv6 (f=0x16eeba0, ct=0x1703760) at filter.c:265 > id_src = 51 id_dst = 24051376 src = 0x1703760 dst = 0x0 The master conntrack of the expectation has no reply tuple. However, the filtering routine needs it. To avoid this issue, emulate the source address in the reply tuple. While at it, fix incorrect sanity checking that should have caught this issue. Thanks to Florian Westphal for initial diagnosing of this bug. Reported-by: Bill Fink Signed-off-by: Pablo Neira Ayuso --- Make_global.am | 2 +- src/ctnl.c | 31 +++++++++++++++++++++++++------ src/filter.c | 8 ++++---- 3 files changed, 30 insertions(+), 11 deletions(-) (limited to 'src/ctnl.c') diff --git a/Make_global.am b/Make_global.am index 23c7dd0..8084249 100644 --- a/Make_global.am +++ b/Make_global.am @@ -1,7 +1,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/include AM_CFLAGS = -std=gnu99 -W -Wall \ - -Wmissing-prototypes -Wwrite-strings -Wcast-qual -Wfloat-equal -Wshadow -Wpointer-arith -Wbad-function-cast -Wsign-compare -Waggregate-return -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -Wstrict-prototypes -Wundef \ + -Wmissing-prototypes -Wwrite-strings -Wfloat-equal -Wshadow -Wpointer-arith -Wbad-function-cast -Wsign-compare -Waggregate-return -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -Wstrict-prototypes -Wundef \ -Wno-unused-parameter ${LIBNFNETLINK_CFLAGS} ${LIBMNL_CFLAGS} \ ${LIBNETFILTER_CONNTRACK_CFLAGS} \ ${LIBNETFILTER_CTTIMEOUT_CFLAGS} \ diff --git a/src/ctnl.c b/src/ctnl.c index bb54727..9e1cfa1 100644 --- a/src/ctnl.c +++ b/src/ctnl.c @@ -211,14 +211,35 @@ out: return NFCT_CB_CONTINUE; } +static const struct nf_conntrack *exp_get_master_ct(struct nf_expect *exp) +{ + struct nf_conntrack *master = + (struct nf_conntrack *)nfexp_get_attr(exp, ATTR_EXP_MASTER); + + /* The function ct_filter_conntrack needs the source address of the + * reply tuple, emulate it. + */ + switch (nfct_get_attr_u8(master, ATTR_L3PROTO)) { + case AF_INET: + nfct_set_attr_u32(master, ATTR_REPL_IPV4_SRC, + nfct_get_attr_u32(master, ATTR_IPV4_DST)); + break; + case AF_INET6: + nfct_set_attr(master, ATTR_REPL_IPV6_SRC, + nfct_get_attr(master, ATTR_IPV6_DST)); + break; + } + + return master; +} + static int exp_event_handler(const struct nlmsghdr *nlh, enum nf_conntrack_msg_type type, struct nf_expect *exp, void *data) { int origin_type; - const struct nf_conntrack *master = - nfexp_get_attr(exp, ATTR_EXP_MASTER); + const struct nf_conntrack *master = exp_get_master_ct(exp); STATE(stats).nl_events_received++; @@ -275,8 +296,7 @@ static int dump_handler(enum nf_conntrack_msg_type type, static int exp_dump_handler(enum nf_conntrack_msg_type type, struct nf_expect *exp, void *data) { - const struct nf_conntrack *master = - nfexp_get_attr(exp, ATTR_EXP_MASTER); + const struct nf_conntrack *master = exp_get_master_ct(exp); if (!exp_filter_find(STATE(exp_filter), exp)) return NFCT_CB_CONTINUE; @@ -309,8 +329,7 @@ static int get_handler(enum nf_conntrack_msg_type type, static int exp_get_handler(enum nf_conntrack_msg_type type, struct nf_expect *exp, void *data) { - const struct nf_conntrack *master = - nfexp_get_attr(exp, ATTR_EXP_MASTER); + const struct nf_conntrack *master = exp_get_master_ct(exp); if (!exp_filter_find(STATE(exp_filter), exp)) return NFCT_CB_CONTINUE; diff --git a/src/filter.c b/src/filter.c index 02a8078..e21cfde 100644 --- a/src/filter.c +++ b/src/filter.c @@ -373,8 +373,8 @@ static inline int ct_filter_sanity_check(const struct nf_conntrack *ct) switch(nfct_get_attr_u8(ct, ATTR_L3PROTO)) { case AF_INET: - if (!nfct_attr_is_set(ct, ATTR_IPV4_SRC) || - !nfct_attr_is_set(ct, ATTR_IPV4_DST)) { + if (!nfct_attr_is_set(ct, ATTR_ORIG_IPV4_SRC) || + !nfct_attr_is_set(ct, ATTR_REPL_IPV4_SRC)) { dlog(LOG_ERR, "missing IPv4 address. " "You forgot to load " "nf_conntrack_ipv4?"); @@ -382,8 +382,8 @@ static inline int ct_filter_sanity_check(const struct nf_conntrack *ct) } break; case AF_INET6: - if (!nfct_attr_is_set(ct, ATTR_IPV6_SRC) || - !nfct_attr_is_set(ct, ATTR_IPV6_DST)) { + if (!nfct_attr_is_set(ct, ATTR_ORIG_IPV6_SRC) || + !nfct_attr_is_set(ct, ATTR_REPL_IPV6_SRC)) { dlog(LOG_ERR, "missing IPv6 address. " "You forgot to load " "nf_conntrack_ipv6?"); -- cgit v1.2.3 From 586382d9a8389ee553db019fd9be14a8a7c0b8ec Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 11 Jul 2013 00:43:20 +0200 Subject: conntrackd: simplify expectation filtering This patch simplifies the expectation filtering by looking up for the master conntrack. If it does not exists, then we assume that we don't want this expectation either. This simplification also fixes the current broken expectation filtering, since the master conntrack from expectations has neither reply tuple nor state, however, the filtering code assumes the opposite. This partially reverts (479a37a conntrackd: fix crash with IPv6 expectation in the filtering code) since it was incorrectly setting the reply tuple of the master conntrack. Thanks to Bill Fink for providing feedback to resolve this issue. Signed-off-by: Pablo Neira Ayuso --- include/filter.h | 1 + include/internal.h | 1 + src/cache-ct.c | 11 +++++++++-- src/ctnl.c | 37 +++++++++---------------------------- src/filter.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/internal_bypass.c | 6 ++++++ src/internal_cache.c | 11 +++++++++++ 7 files changed, 82 insertions(+), 30 deletions(-) (limited to 'src/ctnl.c') diff --git a/include/filter.h b/include/filter.h index 3c7c8cc..d0acd96 100644 --- a/include/filter.h +++ b/include/filter.h @@ -51,6 +51,7 @@ void ct_filter_set_logic(struct ct_filter *f, enum ct_filter_type type, enum ct_filter_logic logic); int ct_filter_conntrack(const struct nf_conntrack *ct, int userspace); +int ct_filter_master(const struct nf_conntrack *master); struct exp_filter; struct nf_expect; diff --git a/include/internal.h b/include/internal.h index 2ba9714..1a796a7 100644 --- a/include/internal.h +++ b/include/internal.h @@ -40,6 +40,7 @@ struct internal_handler { void (*new)(struct nf_expect *exp, int origin_type); void (*upd)(struct nf_expect *exp, int origin_type); int (*del)(struct nf_expect *exp, int origin_type); + int (*find)(const struct nf_conntrack *master); void (*dump)(int fd, int type); void (*populate)(struct nf_expect *exp); diff --git a/src/cache-ct.c b/src/cache-ct.c index a538215..f86d143 100644 --- a/src/cache-ct.c +++ b/src/cache-ct.c @@ -88,14 +88,21 @@ cache_ct_hash(const void *data, const struct hashtable *table) return ret; } +/* master conntrack of expectations have no ID */ +static inline int +cache_ct_cmp_id(const struct nf_conntrack *ct1, const struct nf_conntrack *ct2) +{ + return nfct_attr_is_set(ct2, ATTR_ID) ? + nfct_get_attr_u32(ct1, ATTR_ID) == nfct_get_attr_u32(ct2, ATTR_ID) : 1; +} + static int cache_ct_cmp(const void *data1, const void *data2) { const struct cache_object *obj = data1; const struct nf_conntrack *ct = data2; return nfct_cmp(obj->ptr, ct, NFCT_CMP_ORIG) && - nfct_get_attr_u32(obj->ptr, ATTR_ID) == - nfct_get_attr_u32(ct, ATTR_ID); + cache_ct_cmp_id(obj->ptr, ct); } static void *cache_ct_alloc(void) diff --git a/src/ctnl.c b/src/ctnl.c index 9e1cfa1..10b5f4c 100644 --- a/src/ctnl.c +++ b/src/ctnl.c @@ -211,35 +211,14 @@ out: return NFCT_CB_CONTINUE; } -static const struct nf_conntrack *exp_get_master_ct(struct nf_expect *exp) -{ - struct nf_conntrack *master = - (struct nf_conntrack *)nfexp_get_attr(exp, ATTR_EXP_MASTER); - - /* The function ct_filter_conntrack needs the source address of the - * reply tuple, emulate it. - */ - switch (nfct_get_attr_u8(master, ATTR_L3PROTO)) { - case AF_INET: - nfct_set_attr_u32(master, ATTR_REPL_IPV4_SRC, - nfct_get_attr_u32(master, ATTR_IPV4_DST)); - break; - case AF_INET6: - nfct_set_attr(master, ATTR_REPL_IPV6_SRC, - nfct_get_attr(master, ATTR_IPV6_DST)); - break; - } - - return master; -} - static int exp_event_handler(const struct nlmsghdr *nlh, enum nf_conntrack_msg_type type, struct nf_expect *exp, void *data) { int origin_type; - const struct nf_conntrack *master = exp_get_master_ct(exp); + const struct nf_conntrack *master = + nfexp_get_attr(exp, ATTR_EXP_MASTER); STATE(stats).nl_events_received++; @@ -247,7 +226,7 @@ static int exp_event_handler(const struct nlmsghdr *nlh, STATE(stats).nl_events_filtered++; goto out; } - if (ct_filter_conntrack(master, 1)) + if (ct_filter_master(master)) return NFCT_CB_CONTINUE; origin_type = origin_find(nlh); @@ -296,12 +275,13 @@ static int dump_handler(enum nf_conntrack_msg_type type, static int exp_dump_handler(enum nf_conntrack_msg_type type, struct nf_expect *exp, void *data) { - const struct nf_conntrack *master = exp_get_master_ct(exp); + const struct nf_conntrack *master = + nfexp_get_attr(exp, ATTR_EXP_MASTER); if (!exp_filter_find(STATE(exp_filter), exp)) return NFCT_CB_CONTINUE; - if (ct_filter_conntrack(master, 1)) + if (ct_filter_master(master)) return NFCT_CB_CONTINUE; switch(type) { @@ -329,12 +309,13 @@ static int get_handler(enum nf_conntrack_msg_type type, static int exp_get_handler(enum nf_conntrack_msg_type type, struct nf_expect *exp, void *data) { - const struct nf_conntrack *master = exp_get_master_ct(exp); + const struct nf_conntrack *master = + nfexp_get_attr(exp, ATTR_EXP_MASTER); if (!exp_filter_find(STATE(exp_filter), exp)) return NFCT_CB_CONTINUE; - if (ct_filter_conntrack(master, 1)) + if (ct_filter_master(master)) return NFCT_CB_CONTINUE; STATE(get_retval) = 1; diff --git a/src/filter.c b/src/filter.c index e21cfde..8fac71b 100644 --- a/src/filter.c +++ b/src/filter.c @@ -407,6 +407,51 @@ int ct_filter_conntrack(const struct nf_conntrack *ct, int userspace) return 0; } +static inline int +ct_filter_master_sanity_check(const struct nf_conntrack *master) +{ + if (master == NULL) { + dlog(LOG_ERR, "no master tuple in expectation"); + return 0; + } + + if (!nfct_attr_is_set(master, ATTR_L3PROTO)) { + dlog(LOG_ERR, "missing layer 3 protocol"); + return 0; + } + + switch (nfct_get_attr_u8(master, ATTR_L3PROTO)) { + case AF_INET: + if (!nfct_attr_is_set(master, ATTR_IPV4_SRC) || + !nfct_attr_is_set(master, ATTR_IPV4_DST)) { + dlog(LOG_ERR, "missing IPv4 address. " + "You forgot to load nf_conntrack_ipv4?"); + return 0; + } + break; + case AF_INET6: + if (!nfct_attr_is_set(master, ATTR_IPV6_SRC) || + !nfct_attr_is_set(master, ATTR_IPV6_DST)) { + dlog(LOG_ERR, "missing IPv6 address. " + "You forgot to load nf_conntrack_ipv6?"); + return 0; + } + break; + } + return 1; +} + +int ct_filter_master(const struct nf_conntrack *master) +{ + if (!ct_filter_master_sanity_check(master)) + return 1; + + /* Check if we've got a master conntrack for this expectation in our + * caches. If there is not, we don't want this expectation either. + */ + return STATE(mode)->internal->exp.find(master) ? 0 : 1; +} + struct exp_filter { struct list_head list; }; diff --git a/src/internal_bypass.c b/src/internal_bypass.c index 1194339..ce2ae46 100644 --- a/src/internal_bypass.c +++ b/src/internal_bypass.c @@ -283,6 +283,11 @@ static int internal_bypass_exp_event_del(struct nf_expect *exp, int origin) return 1; } +static int internal_bypass_exp_master_find(const struct nf_conntrack *master) +{ + return nl_get_conntrack(STATE(get), master) == 0; +} + struct internal_handler internal_bypass = { .init = internal_bypass_init, .close = internal_bypass_close, @@ -309,5 +314,6 @@ struct internal_handler internal_bypass = { .new = internal_bypass_exp_event_new, .upd = internal_bypass_exp_event_upd, .del = internal_bypass_exp_event_del, + .find = internal_bypass_exp_master_find, }, }; diff --git a/src/internal_cache.c b/src/internal_cache.c index ba2d74b..bad31f3 100644 --- a/src/internal_cache.c +++ b/src/internal_cache.c @@ -364,6 +364,16 @@ static int internal_cache_exp_event_del(struct nf_expect *exp, int origin) return 1; } +static int internal_cache_exp_master_find(const struct nf_conntrack *master) +{ + struct cache_object *obj; + int id; + + obj = cache_find(STATE(mode)->internal->ct.data, + (struct nf_conntrack *)master, &id); + return obj ? 1 : 0; +} + struct internal_handler internal_cache = { .flags = INTERNAL_F_POPULATE | INTERNAL_F_RESYNC, .init = internal_cache_init, @@ -391,5 +401,6 @@ struct internal_handler internal_cache = { .new = internal_cache_exp_event_new, .upd = internal_cache_exp_event_upd, .del = internal_cache_exp_event_del, + .find = internal_cache_exp_master_find, }, }; -- cgit v1.2.3