From aa925010951e79a860d0c1e4365f72d68eedf02d Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 14 Feb 2012 03:17:56 +0100 Subject: conntrack: allow to filter by mark from kernel-space This patch uses the new infrastructure that allows us to filter by mark from kernel-space. This change ensures backward compatibility with kernels with no support for filtering by mark (Linux kernel <= 3.4.x). This requires lastest libnetfilter_conntrack library. Signed-off-by: Pablo Neira Ayuso --- src/conntrack.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/conntrack.c b/src/conntrack.c index 31beba5..b065211 100644 --- a/src/conntrack.c +++ b/src/conntrack.c @@ -1,5 +1,6 @@ /* - * (C) 2005-2008 by Pablo Neira Ayuso + * (C) 2005-2012 by Pablo Neira Ayuso + * (C) 2012 by Intra2net AG * * 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 @@ -72,6 +73,9 @@ static struct { /* Allows filtering/setting specific bits in the ctmark */ struct u32_mask mark; + + /* Allow to filter by mark from kernel-space. */ + struct nfct_filter_dump_mark filter_mark_kernel; } tmpl; static int alloc_tmpl_objects(void) @@ -1632,6 +1636,8 @@ int main(int argc, char *argv[]) case 'm': options |= opt2type[c]; parse_u32_mask(optarg, &tmpl.mark); + tmpl.filter_mark_kernel.val = tmpl.mark.value; + tmpl.filter_mark_kernel.mask = tmpl.mark.mask; break; case 'a': fprintf(stderr, "WARNING: ignoring -%c, " @@ -1705,6 +1711,7 @@ int main(int argc, char *argv[]) h->final_check(l4flags, cmd, tmpl.ct); switch(command) { + struct nfct_filter_dump *filter_dump; case CT_LIST: cth = nfct_open(CONNTRACK, 0); @@ -1718,10 +1725,23 @@ int main(int argc, char *argv[]) nfct_callback_register(cth, NFCT_T_ALL, dump_cb, tmpl.ct); + filter_dump = nfct_filter_dump_create(); + if (filter_dump == NULL) + exit_error(OTHER_PROBLEM, "OOM"); + + nfct_filter_dump_set_attr(filter_dump, NFCT_FILTER_DUMP_MARK, + &tmpl.filter_mark_kernel); + nfct_filter_dump_set_attr_u8(filter_dump, + NFCT_FILTER_DUMP_L3NUM, + family); + if (options & CT_OPT_ZERO) - res = nfct_query(cth, NFCT_Q_DUMP_RESET, &family); + res = nfct_query(cth, NFCT_Q_DUMP_FILTER_RESET, + filter_dump); else - res = nfct_query(cth, NFCT_Q_DUMP, &family); + res = nfct_query(cth, NFCT_Q_DUMP_FILTER, filter_dump); + + nfct_filter_dump_destroy(filter_dump); if (dump_xml_header_done == 0) { printf("\n"); @@ -1800,7 +1820,20 @@ int main(int argc, char *argv[]) nfct_callback_register(cth, NFCT_T_ALL, delete_cb, tmpl.ct); - res = nfct_query(cth, NFCT_Q_DUMP, &family); + filter_dump = nfct_filter_dump_create(); + if (filter_dump == NULL) + exit_error(OTHER_PROBLEM, "OOM"); + + nfct_filter_dump_set_attr(filter_dump, NFCT_FILTER_DUMP_MARK, + &tmpl.filter_mark_kernel); + nfct_filter_dump_set_attr_u8(filter_dump, + NFCT_FILTER_DUMP_L3NUM, + family); + + res = nfct_query(cth, NFCT_Q_DUMP_FILTER, filter_dump); + + nfct_filter_dump_destroy(filter_dump); + nfct_close(ith); nfct_close(cth); break; -- cgit v1.2.3 From 2a022fb7174939a80d51936325f6af562ad5a85f Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 21 Feb 2012 15:59:00 +0100 Subject: conntrackd: allow using lower/upper case in ExpectationSync You can use: ExpectationSync { ftp ras q.931 sip } or: ExpectationSync { FTP RAS Q.931 SIP } no matter lower/upper case. Signed-off-by: Pablo Neira Ayuso --- src/filter.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/filter.c b/src/filter.c index afefbfa..39dd4ca 100644 --- a/src/filter.c +++ b/src/filter.c @@ -1,6 +1,7 @@ /* - * (C) 2006-2008 by Pablo Neira Ayuso - * + * (C) 2006-2012 by Pablo Neira Ayuso + * (C) 2011-2012 by Vyatta Inc + * * 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 @@ -449,7 +450,7 @@ int exp_filter_add(struct exp_filter *f, const char *helper_name) return -1; list_for_each_entry(item, &f->list, head) { - if (strncmp(item->helper_name, helper_name, + if (strncasecmp(item->helper_name, helper_name, NFCT_HELPER_NAME_MAX) == 0) { return -1; } @@ -475,7 +476,7 @@ int exp_filter_find(struct exp_filter *f, const struct nf_expect *exp) const char *name = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME); /* we allow partial matching to support things like sip-PORT. */ - if (strncmp(item->helper_name, name, + if (strncasecmp(item->helper_name, name, strlen(item->helper_name)) == 0) { return 1; } -- cgit v1.2.3 From bb2534c7aaf0bdb6521371b8a31af6333d3a6a2d Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 21 Feb 2012 16:03:21 +0100 Subject: doc: add ras, q.931 and h.245 to examples configuration file Now it includes: ExpectationSync { ... ras q.931 h.245 } Which are the set of helpers for h.323. Signed-off-by: Pablo Neira Ayuso --- doc/sync/alarm/conntrackd.conf | 4 +++- doc/sync/ftfw/conntrackd.conf | 4 +++- doc/sync/notrack/conntrackd.conf | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/sync/alarm/conntrackd.conf b/doc/sync/alarm/conntrackd.conf index deed291..b9520fb 100644 --- a/doc/sync/alarm/conntrackd.conf +++ b/doc/sync/alarm/conntrackd.conf @@ -198,7 +198,9 @@ Sync { # # ExpectationSync { # ftp - # h323 + # ras + # q.931 + # h.245 # sip # } # diff --git a/doc/sync/ftfw/conntrackd.conf b/doc/sync/ftfw/conntrackd.conf index 0304f0f..53a7d0f 100644 --- a/doc/sync/ftfw/conntrackd.conf +++ b/doc/sync/ftfw/conntrackd.conf @@ -221,7 +221,9 @@ Sync { # # ExpectationSync { # ftp - # h323 + # ras + # q.931 + # h.245 # sip # } # diff --git a/doc/sync/notrack/conntrackd.conf b/doc/sync/notrack/conntrackd.conf index 34e7b32..11f022e 100644 --- a/doc/sync/notrack/conntrackd.conf +++ b/doc/sync/notrack/conntrackd.conf @@ -260,7 +260,9 @@ Sync { # # ExpectationSync { # ftp - # h323 + # ras + # q.931 + # h.245 # sip # } # -- cgit v1.2.3 From 1de3034f8c4f597cbe4be35b2f84e2848e46e64e Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 5 Mar 2012 23:13:12 +0100 Subject: doc: fix example on how to filter events via iptables CT target You have to use this: iptables -I PREROUTING -t raw -j CT --ctevents assured,destroy instead of: iptables -I PREROUTING -t raw -j CT --ctevents assured Otherwise, conntrackd cache gets full since no destroy events are delivered. Reported-by: Kerin Millar Signed-off-by: Pablo Neira Ayuso --- doc/manual/conntrack-tools.tmpl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/manual/conntrack-tools.tmpl b/doc/manual/conntrack-tools.tmpl index 4936a76..dbf836d 100644 --- a/doc/manual/conntrack-tools.tmpl +++ b/doc/manual/conntrack-tools.tmpl @@ -641,10 +641,11 @@ Sync { broken. The following example shows how to only generate the - assured event: + assured and destroy + events: - # iptables -I PREROUTING -t raw -j CT --ctevents assured + # iptables -I PREROUTING -t raw -j CT --ctevents assured,destroy Assured flows -- cgit v1.2.3 From 36da3b40b821452d2d4607928ce54ced707541df Mon Sep 17 00:00:00 2001 From: Adrian Bridgett Date: Tue, 20 Mar 2012 13:02:54 +0100 Subject: src: manpage and help display improvements This patch adds missing information regarding several conntrackd options to the manpage and the help info that is displayed in the command line. Signed-off-by: Pablo Neira Ayuso --- conntrack.8 | 6 +++--- conntrackd.8 | 10 ++++++++++ src/main.c | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/conntrack.8 b/conntrack.8 index 6525123..a411fd4 100644 --- a/conntrack.8 +++ b/conntrack.8 @@ -6,17 +6,17 @@ .SH NAME conntrack \- command line interface for netfilter connection tracking .SH SYNOPSIS -.BR "conntrack -L [table] [-z]" +.BR "conntrack -L [table] [options] [-z]" .br .BR "conntrack -G [table] parameters" .br -.BR "conntrack -D [table] paramaters" +.BR "conntrack -D [table] parameters" .br .BR "conntrack -I [table] parameters" .br .BR "conntrack -U [table] parameters" .br -.BR "conntrack -E [table] parameters" +.BR "conntrack -E [table] [options]" .br .BR "conntrack -F [table]" .br diff --git a/conntrackd.8 b/conntrackd.8 index f07ad7a..131a7ac 100644 --- a/conntrackd.8 +++ b/conntrackd.8 @@ -41,10 +41,16 @@ Flush the internal and/or external cache Flush the kernel conntrack table (if you use a Linux kernel >= 2.6.29, this option will not flush your internal and external cache). .TP +.BI "-c " +Commit external cache to conntrack table. +.TP .BI "-B " Force a bulk send to other replica firewalls. With this command, you will ask conntrackd to send the state-entries that it owns to others. .TP +.BI "-n " +Request resync with other node (only FT-FW and NOTRACK modes). +.TP .BI "-k " Kill the daemon .TP @@ -69,6 +75,10 @@ Display version information. .TP .BI "-h " Display help information. +.TP +.BI "-C config file" +Configuration file path. +.TP .SH DIAGNOSTICS The exit code is 0 for correct function. Errors cause an exit code of 1. .SH EXAMPLES diff --git a/src/main.c b/src/main.c index 0850a29..f7803fd 100644 --- a/src/main.c +++ b/src/main.c @@ -49,6 +49,7 @@ static const char usage_client_commands[] = "dump statistics\n" " -R [ct|expect], resync with kernel conntrack table\n" " -n, request resync with other node (only FT-FW and NOTRACK modes)\n" + " -B, force a bulk send to other replica firewalls\n" " -x, dump cache in XML format (requires -i or -e)\n" " -t, reset the kernel timeout (see PurgeTimeout clause)\n" " -v, display conntrackd version\n" -- cgit v1.2.3 From 405c4f7dbfb16e69d75df5b631f195f5b9da29e4 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 27 Mar 2012 11:18:32 +0200 Subject: icmp[v6]: --icmp[v6]-[type|code] are optional for updates and deletes Signed-off-by: Pablo Neira Ayuso --- extensions/libct_proto_icmp.c | 4 ++-- extensions/libct_proto_icmpv6.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extensions/libct_proto_icmp.c b/extensions/libct_proto_icmp.c index 3a346ed..d04397f 100644 --- a/extensions/libct_proto_icmp.c +++ b/extensions/libct_proto_icmp.c @@ -43,8 +43,8 @@ static char icmp_commands_v_options[NUMBER_OF_CMD][ICMP_NUMBER_OF_OPT] = /* 1 2 3 */ /*CT_LIST*/ {2,2,2}, /*CT_CREATE*/ {1,1,2}, -/*CT_UPDATE*/ {1,1,2}, -/*CT_DELETE*/ {1,1,2}, +/*CT_UPDATE*/ {2,2,2}, +/*CT_DELETE*/ {2,2,2}, /*CT_GET*/ {1,1,2}, /*CT_FLUSH*/ {0,0,0}, /*CT_EVENT*/ {2,2,2}, diff --git a/extensions/libct_proto_icmpv6.c b/extensions/libct_proto_icmpv6.c index 070eb7f..f8c2c68 100644 --- a/extensions/libct_proto_icmpv6.c +++ b/extensions/libct_proto_icmpv6.c @@ -46,8 +46,8 @@ static char icmpv6_commands_v_options[NUMBER_OF_CMD][ICMPV6_NUMBER_OF_OPT] = /* 1 2 3 */ /*CT_LIST*/ {2,2,2}, /*CT_CREATE*/ {1,1,2}, -/*CT_UPDATE*/ {1,1,2}, -/*CT_DELETE*/ {1,1,2}, +/*CT_UPDATE*/ {2,2,2}, +/*CT_DELETE*/ {2,2,2}, /*CT_GET*/ {1,1,2}, /*CT_FLUSH*/ {0,0,0}, /*CT_EVENT*/ {2,2,2}, -- cgit v1.2.3 From 867b5b6496a3296078146ba3d06616eda3b0717e Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Fri, 18 May 2012 01:36:49 +0000 Subject: conntrack: flush stdout for each expectation event, too else, piping "conntrack -E expect" output will be buffered/delayed, which is not what users expect. Normal conntrack events are already flushed. Signed-off-by: Florian Westphal Signed-off-by: Pablo Neira Ayuso --- src/conntrack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/conntrack.c b/src/conntrack.c index b065211..0920bc5 100644 --- a/src/conntrack.c +++ b/src/conntrack.c @@ -1380,6 +1380,7 @@ static int event_exp_cb(enum nf_conntrack_msg_type type, nfexp_snprintf(buf,sizeof(buf), exp, type, op_type, op_flags); printf("%s\n", buf); + fflush(stdout); counter++; return NFCT_CB_CONTINUE; -- cgit v1.2.3 From d2e942c76f87ea061d5e8643007f1d4c3ed39694 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 10 May 2012 10:15:09 +0200 Subject: src: integrate nfct into the conntrack-tools tree I'll need for the upcoming cthelper infrastructure. Moreover, we avoid more fragmentation in the netfilter user-space utilities. And the plan is that `nfct' will replace `conntrack' at some point. Signed-off-by: Pablo Neira Ayuso --- Make_global.am | 5 +- configure.ac | 4 +- include/Makefile.am | 3 +- include/linux/Makefile.am | 1 + include/linux/netfilter/Makefile.am | 1 + include/linux/netfilter/nfnetlink.h | 94 +++++ include/linux/netfilter/nfnetlink_cttimeout.h | 114 ++++++ include/nfct.h | 29 ++ src/Makefile.am | 8 +- src/nfct-extensions/timeout.c | 486 ++++++++++++++++++++++++++ src/nfct.c | 116 ++++++ 11 files changed, 856 insertions(+), 5 deletions(-) create mode 100644 include/linux/Makefile.am create mode 100644 include/linux/netfilter/Makefile.am create mode 100644 include/linux/netfilter/nfnetlink.h create mode 100644 include/linux/netfilter/nfnetlink_cttimeout.h create mode 100644 include/nfct.h create mode 100644 src/nfct-extensions/timeout.c create mode 100644 src/nfct.c diff --git a/Make_global.am b/Make_global.am index e8f603a..7b5342d 100644 --- a/Make_global.am +++ b/Make_global.am @@ -2,5 +2,6 @@ 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 \ - -Wno-unused-parameter ${LIBNFNETLINK_CFLAGS} \ - ${LIBNETFILTER_CONNTRACK_CFLAGS} + -Wno-unused-parameter ${LIBNFNETLINK_CFLAGS} ${LIBMNL_CFLAGS} \ + ${LIBNETFILTER_CONNTRACK_CFLAGS} \ + ${LIBNETFILTER_CTTIMEOUT_CFLAGS} diff --git a/configure.ac b/configure.ac index 26a7e02..5c4ac58 100644 --- a/configure.ac +++ b/configure.ac @@ -52,7 +52,9 @@ else fi PKG_CHECK_MODULES([LIBNFNETLINK], [libnfnetlink >= 1.0.0]) +PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.0]) PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_CTTIMEOUT], [libnetfilter_cttimeout >= 1.0.0]) AC_CHECK_HEADERS([linux/capability.h],, [AC_MSG_ERROR([Cannot find linux/capabibility.h])]) @@ -112,5 +114,5 @@ dnl debug/src/Makefile dnl extensions/Makefile dnl src/Makefile]) -AC_CONFIG_FILES([Makefile src/Makefile include/Makefile extensions/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/linux/Makefile include/linux/netfilter/Makefile extensions/Makefile]) AC_OUTPUT diff --git a/include/Makefile.am b/include/Makefile.am index cbbca6b..138005d 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,8 +1,9 @@ +SUBDIRS = linux noinst_HEADERS = alarm.h jhash.h cache.h linux_list.h linux_rbtree.h \ sync.h conntrackd.h local.h udp.h tcp.h \ debug.h log.h hash.h mcast.h conntrack.h \ network.h filter.h queue.h vector.h cidr.h \ traffic_stats.h netlink.h fds.h event.h bitops.h channel.h \ - process.h origin.h internal.h external.h date.h + process.h origin.h internal.h external.h date.h nfct.h diff --git a/include/linux/Makefile.am b/include/linux/Makefile.am new file mode 100644 index 0000000..38eb109 --- /dev/null +++ b/include/linux/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = netfilter diff --git a/include/linux/netfilter/Makefile.am b/include/linux/netfilter/Makefile.am new file mode 100644 index 0000000..84315e3 --- /dev/null +++ b/include/linux/netfilter/Makefile.am @@ -0,0 +1 @@ +noinst_HEADERS = nfnetlink.h nfnetlink_cttimeout.h diff --git a/include/linux/netfilter/nfnetlink.h b/include/linux/netfilter/nfnetlink.h new file mode 100644 index 0000000..b64454c --- /dev/null +++ b/include/linux/netfilter/nfnetlink.h @@ -0,0 +1,94 @@ +#ifndef _NFNETLINK_H +#define _NFNETLINK_H +#include +#include + +enum nfnetlink_groups { + NFNLGRP_NONE, +#define NFNLGRP_NONE NFNLGRP_NONE + NFNLGRP_CONNTRACK_NEW, +#define NFNLGRP_CONNTRACK_NEW NFNLGRP_CONNTRACK_NEW + NFNLGRP_CONNTRACK_UPDATE, +#define NFNLGRP_CONNTRACK_UPDATE NFNLGRP_CONNTRACK_UPDATE + NFNLGRP_CONNTRACK_DESTROY, +#define NFNLGRP_CONNTRACK_DESTROY NFNLGRP_CONNTRACK_DESTROY + NFNLGRP_CONNTRACK_EXP_NEW, +#define NFNLGRP_CONNTRACK_EXP_NEW NFNLGRP_CONNTRACK_EXP_NEW + NFNLGRP_CONNTRACK_EXP_UPDATE, +#define NFNLGRP_CONNTRACK_EXP_UPDATE NFNLGRP_CONNTRACK_EXP_UPDATE + NFNLGRP_CONNTRACK_EXP_DESTROY, +#define NFNLGRP_CONNTRACK_EXP_DESTROY NFNLGRP_CONNTRACK_EXP_DESTROY + __NFNLGRP_MAX, +}; +#define NFNLGRP_MAX (__NFNLGRP_MAX - 1) + +/* General form of address family dependent message. + */ +struct nfgenmsg { + __u8 nfgen_family; /* AF_xxx */ + __u8 version; /* nfnetlink version */ + __be16 res_id; /* resource id */ +}; + +#define NFNETLINK_V0 0 + +/* netfilter netlink message types are split in two pieces: + * 8 bit subsystem, 8bit operation. + */ + +#define NFNL_SUBSYS_ID(x) ((x & 0xff00) >> 8) +#define NFNL_MSG_TYPE(x) (x & 0x00ff) + +/* No enum here, otherwise __stringify() trick of MODULE_ALIAS_NFNL_SUBSYS() + * won't work anymore */ +#define NFNL_SUBSYS_NONE 0 +#define NFNL_SUBSYS_CTNETLINK 1 +#define NFNL_SUBSYS_CTNETLINK_EXP 2 +#define NFNL_SUBSYS_QUEUE 3 +#define NFNL_SUBSYS_ULOG 4 +#define NFNL_SUBSYS_OSF 5 +#define NFNL_SUBSYS_IPSET 6 +#define NFNL_SUBSYS_ACCT 7 +#define NFNL_SUBSYS_COUNT 8 + +#ifdef __KERNEL__ + +#include +#include +#include + +struct nfnl_callback { + int (*call)(struct sock *nl, struct sk_buff *skb, + const struct nlmsghdr *nlh, + const struct nlattr * const cda[]); + int (*call_rcu)(struct sock *nl, struct sk_buff *skb, + const struct nlmsghdr *nlh, + const struct nlattr * const cda[]); + const struct nla_policy *policy; /* netlink attribute policy */ + const u_int16_t attr_count; /* number of nlattr's */ +}; + +struct nfnetlink_subsystem { + const char *name; + __u8 subsys_id; /* nfnetlink subsystem ID */ + __u8 cb_count; /* number of callbacks */ + const struct nfnl_callback *cb; /* callback for individual types */ +}; + +extern int nfnetlink_subsys_register(const struct nfnetlink_subsystem *n); +extern int nfnetlink_subsys_unregister(const struct nfnetlink_subsystem *n); + +extern int nfnetlink_has_listeners(struct net *net, unsigned int group); +extern int nfnetlink_send(struct sk_buff *skb, struct net *net, u32 pid, unsigned group, + int echo, gfp_t flags); +extern int nfnetlink_set_err(struct net *net, u32 pid, u32 group, int error); +extern int nfnetlink_unicast(struct sk_buff *skb, struct net *net, u_int32_t pid, int flags); + +extern void nfnl_lock(void); +extern void nfnl_unlock(void); + +#define MODULE_ALIAS_NFNL_SUBSYS(subsys) \ + MODULE_ALIAS("nfnetlink-subsys-" __stringify(subsys)) + +#endif /* __KERNEL__ */ +#endif /* _NFNETLINK_H */ diff --git a/include/linux/netfilter/nfnetlink_cttimeout.h b/include/linux/netfilter/nfnetlink_cttimeout.h new file mode 100644 index 0000000..a2810a7 --- /dev/null +++ b/include/linux/netfilter/nfnetlink_cttimeout.h @@ -0,0 +1,114 @@ +#ifndef _CTTIMEOUT_NETLINK_H +#define _CTTIMEOUT_NETLINK_H +#include + +enum ctnl_timeout_msg_types { + IPCTNL_MSG_TIMEOUT_NEW, + IPCTNL_MSG_TIMEOUT_GET, + IPCTNL_MSG_TIMEOUT_DELETE, + + IPCTNL_MSG_TIMEOUT_MAX +}; + +enum ctattr_timeout { + CTA_TIMEOUT_UNSPEC, + CTA_TIMEOUT_NAME, + CTA_TIMEOUT_L3PROTO, + CTA_TIMEOUT_L4PROTO, + CTA_TIMEOUT_DATA, + CTA_TIMEOUT_USE, + __CTA_TIMEOUT_MAX +}; +#define CTA_TIMEOUT_MAX (__CTA_TIMEOUT_MAX - 1) + +enum ctattr_timeout_generic { + CTA_TIMEOUT_GENERIC_UNSPEC, + CTA_TIMEOUT_GENERIC_TIMEOUT, + __CTA_TIMEOUT_GENERIC_MAX +}; +#define CTA_TIMEOUT_GENERIC_MAX (__CTA_TIMEOUT_GENERIC_MAX - 1) + +enum ctattr_timeout_tcp { + CTA_TIMEOUT_TCP_UNSPEC, + CTA_TIMEOUT_TCP_SYN_SENT, + CTA_TIMEOUT_TCP_SYN_RECV, + CTA_TIMEOUT_TCP_ESTABLISHED, + CTA_TIMEOUT_TCP_FIN_WAIT, + CTA_TIMEOUT_TCP_CLOSE_WAIT, + CTA_TIMEOUT_TCP_LAST_ACK, + CTA_TIMEOUT_TCP_TIME_WAIT, + CTA_TIMEOUT_TCP_CLOSE, + CTA_TIMEOUT_TCP_SYN_SENT2, + CTA_TIMEOUT_TCP_RETRANS, + CTA_TIMEOUT_TCP_UNACK, + __CTA_TIMEOUT_TCP_MAX +}; +#define CTA_TIMEOUT_TCP_MAX (__CTA_TIMEOUT_TCP_MAX - 1) + +enum ctattr_timeout_udp { + CTA_TIMEOUT_UDP_UNSPEC, + CTA_TIMEOUT_UDP_UNREPLIED, + CTA_TIMEOUT_UDP_REPLIED, + __CTA_TIMEOUT_UDP_MAX +}; +#define CTA_TIMEOUT_UDP_MAX (__CTA_TIMEOUT_UDP_MAX - 1) + +enum ctattr_timeout_udplite { + CTA_TIMEOUT_UDPLITE_UNSPEC, + CTA_TIMEOUT_UDPLITE_UNREPLIED, + CTA_TIMEOUT_UDPLITE_REPLIED, + __CTA_TIMEOUT_UDPLITE_MAX +}; +#define CTA_TIMEOUT_UDPLITE_MAX (__CTA_TIMEOUT_UDPLITE_MAX - 1) + +enum ctattr_timeout_icmp { + CTA_TIMEOUT_ICMP_UNSPEC, + CTA_TIMEOUT_ICMP_TIMEOUT, + __CTA_TIMEOUT_ICMP_MAX +}; +#define CTA_TIMEOUT_ICMP_MAX (__CTA_TIMEOUT_ICMP_MAX - 1) + +enum ctattr_timeout_dccp { + CTA_TIMEOUT_DCCP_UNSPEC, + CTA_TIMEOUT_DCCP_REQUEST, + CTA_TIMEOUT_DCCP_RESPOND, + CTA_TIMEOUT_DCCP_PARTOPEN, + CTA_TIMEOUT_DCCP_OPEN, + CTA_TIMEOUT_DCCP_CLOSEREQ, + CTA_TIMEOUT_DCCP_CLOSING, + CTA_TIMEOUT_DCCP_TIMEWAIT, + __CTA_TIMEOUT_DCCP_MAX +}; +#define CTA_TIMEOUT_DCCP_MAX (__CTA_TIMEOUT_DCCP_MAX - 1) + +enum ctattr_timeout_sctp { + CTA_TIMEOUT_SCTP_UNSPEC, + CTA_TIMEOUT_SCTP_CLOSED, + CTA_TIMEOUT_SCTP_COOKIE_WAIT, + CTA_TIMEOUT_SCTP_COOKIE_ECHOED, + CTA_TIMEOUT_SCTP_ESTABLISHED, + CTA_TIMEOUT_SCTP_SHUTDOWN_SENT, + CTA_TIMEOUT_SCTP_SHUTDOWN_RECD, + CTA_TIMEOUT_SCTP_SHUTDOWN_ACK_SENT, + __CTA_TIMEOUT_SCTP_MAX +}; +#define CTA_TIMEOUT_SCTP_MAX (__CTA_TIMEOUT_SCTP_MAX - 1) + +enum ctattr_timeout_icmpv6 { + CTA_TIMEOUT_ICMPV6_UNSPEC, + CTA_TIMEOUT_ICMPV6_TIMEOUT, + __CTA_TIMEOUT_ICMPV6_MAX +}; +#define CTA_TIMEOUT_ICMPV6_MAX (__CTA_TIMEOUT_ICMPV6_MAX - 1) + +enum ctattr_timeout_gre { + CTA_TIMEOUT_GRE_UNSPEC, + CTA_TIMEOUT_GRE_UNREPLIED, + CTA_TIMEOUT_GRE_REPLIED, + __CTA_TIMEOUT_GRE_MAX +}; +#define CTA_TIMEOUT_GRE_MAX (__CTA_TIMEOUT_GRE_MAX - 1) + +#define CTNL_TIMEOUT_NAME_MAX 32 + +#endif diff --git a/include/nfct.h b/include/nfct.h new file mode 100644 index 0000000..d6271cf --- /dev/null +++ b/include/nfct.h @@ -0,0 +1,29 @@ +#ifndef _NFCT_H_ +#define _NFCT_H_ + +enum { + NFCT_SUBSYS_NONE = 0, + NFCT_SUBSYS_TIMEOUT, + NFCT_SUBSYS_VERSION, + NFCT_SUBSYS_HELP, +}; + +enum { + NFCT_CMD_NONE = 0, + NFCT_CMD_LIST, + NFCT_CMD_ADD, + NFCT_CMD_DELETE, + NFCT_CMD_GET, + NFCT_CMD_FLUSH, +}; + +void nfct_perror(const char *msg); + +int nfct_cmd_timeout_parse_params(int argc, char *argv[]); +int nfct_cmd_timeout_list(int argc, char *argv[]); +int nfct_cmd_timeout_add(int argc, char *argv[]); +int nfct_cmd_timeout_delete(int argc, char *argv[]); +int nfct_cmd_timeout_get(int argc, char *argv[]); +int nfct_cmd_timeout_flush(int argc, char *argv[]); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 7d7b2ac..5dbdef3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,11 +4,17 @@ AM_YFLAGS = -d CLEANFILES = read_config_yy.c read_config_lex.c -sbin_PROGRAMS = conntrack conntrackd +sbin_PROGRAMS = conntrack conntrackd nfct conntrack_SOURCES = conntrack.c conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS} +nfct_SOURCES = nfct.c \ + nfct-extensions/timeout.c +nfct_LDADD = ${LIBMNL_LIBS} \ + ${LIBNETFILTER_CONNTRACK_LIBS} \ + ${LIBNETFILTER_CTTIMEOUT_LIBS} + conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ local.c log.c mcast.c udp.c netlink.c vector.c \ filter.c fds.c event.c process.c origin.c date.c \ diff --git a/src/nfct-extensions/timeout.c b/src/nfct-extensions/timeout.c new file mode 100644 index 0000000..a69537d --- /dev/null +++ b/src/nfct-extensions/timeout.c @@ -0,0 +1,486 @@ +/* + * (C) 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 code has been sponsored by Vyatta Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "nfct.h" + +static void +nfct_cmd_timeout_usage(char *argv[]) +{ + fprintf(stderr, "nfct v%s: Missing command\n" + "%s timeout list|add|delete|get|flush " + "[parameters...]\n", VERSION, argv[0]); +} + +int nfct_cmd_timeout_parse_params(int argc, char *argv[]) +{ + int cmd = NFCT_CMD_NONE, ret = 0; + + if (argc < 3) { + nfct_cmd_timeout_usage(argv); + return -1; + } + if (strncmp(argv[2], "list", strlen(argv[2])) == 0) + cmd = NFCT_CMD_LIST; + else if (strncmp(argv[2], "add", strlen(argv[2])) == 0) + cmd = NFCT_CMD_ADD; + else if (strncmp(argv[2], "delete", strlen(argv[2])) == 0) + cmd = NFCT_CMD_DELETE; + else if (strncmp(argv[2], "get", strlen(argv[2])) == 0) + cmd = NFCT_CMD_GET; + else if (strncmp(argv[2], "flush", strlen(argv[2])) == 0) + cmd = NFCT_CMD_FLUSH; + else { + fprintf(stderr, "nfct v%s: Unknown command: %s\n", + VERSION, argv[2]); + nfct_cmd_timeout_usage(argv); + return -1; + } + switch(cmd) { + case NFCT_CMD_LIST: + ret = nfct_cmd_timeout_list(argc, argv); + break; + case NFCT_CMD_ADD: + ret = nfct_cmd_timeout_add(argc, argv); + break; + case NFCT_CMD_DELETE: + ret = nfct_cmd_timeout_delete(argc, argv); + break; + case NFCT_CMD_GET: + ret = nfct_cmd_timeout_get(argc, argv); + break; + case NFCT_CMD_FLUSH: + ret = nfct_cmd_timeout_flush(argc, argv); + break; + } + + return 0; +} + +static int nfct_timeout_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nfct_timeout *t; + char buf[4096]; + + t = nfct_timeout_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + goto err; + } + + if (nfct_timeout_nlmsg_parse_payload(nlh, t) < 0) { + nfct_perror("nfct_timeout_nlmsg_parse_payload"); + goto err_free; + } + + nfct_timeout_snprintf(buf, sizeof(buf), t, 0); + printf("%s\n", buf); + +err_free: + nfct_timeout_free(t); +err: + return MNL_CB_OK; +} + +int nfct_cmd_timeout_list(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + unsigned int seq, portid; + int ret; + + if (argc > 3) { + nfct_perror("too many arguments"); + return -1; + } + + seq = time(NULL); + nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_GET, + NLM_F_DUMP, seq); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, nfct_timeout_cb, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +static uint32_t nfct_timeout_attr_max[IPPROTO_MAX] = { + [IPPROTO_ICMP] = NFCT_TIMEOUT_ATTR_ICMP_MAX, + [IPPROTO_TCP] = NFCT_TIMEOUT_ATTR_TCP_MAX, + [IPPROTO_UDP] = NFCT_TIMEOUT_ATTR_UDP_MAX, + [IPPROTO_UDPLITE] = NFCT_TIMEOUT_ATTR_UDPLITE_MAX, + [IPPROTO_SCTP] = NFCT_TIMEOUT_ATTR_SCTP_MAX, + [IPPROTO_DCCP] = NFCT_TIMEOUT_ATTR_DCCP_MAX, + [IPPROTO_ICMPV6] = NFCT_TIMEOUT_ATTR_ICMPV6_MAX, + [IPPROTO_GRE] = NFCT_TIMEOUT_ATTR_GRE_MAX, + [IPPROTO_RAW] = NFCT_TIMEOUT_ATTR_GENERIC_MAX, +}; + +int nfct_cmd_timeout_add(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_timeout *t; + uint16_t l3proto; + uint8_t l4proto; + int ret, i; + unsigned int j; + + if (argc < 6) { + nfct_perror("missing parameters\n" + "syntax: nfct timeout add name " + "family protocol state1 " + "timeout1 state2 timeout2..."); + return -1; + } + + t = nfct_timeout_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + + nfct_timeout_attr_set(t, NFCT_TIMEOUT_ATTR_NAME, argv[3]); + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + nfct_timeout_attr_set_u16(t, NFCT_TIMEOUT_ATTR_L3PROTO, l3proto); + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else if (strcmp(argv[5], "udplite") == 0) + l4proto = IPPROTO_UDPLITE; + else if (strcmp(argv[5], "sctp") == 0) + l4proto = IPPROTO_SCTP; + else if (strcmp(argv[5], "dccp") == 0) + l4proto = IPPROTO_DCCP; + else if (strcmp(argv[5], "icmp") == 0) + l4proto = IPPROTO_ICMP; + else if (strcmp(argv[5], "icmpv6") == 0) + l4proto = IPPROTO_ICMPV6; + else if (strcmp(argv[5], "gre") == 0) + l4proto = IPPROTO_GRE; + else if (strcmp(argv[5], "generic") == 0) + l4proto = IPPROTO_RAW; + else { + nfct_perror("unknown layer 4 protocol"); + return -1; + } + nfct_timeout_attr_set_u8(t, NFCT_TIMEOUT_ATTR_L4PROTO, l4proto); + + for (i=6; i= argc) { + nfct_perror("missing value for this timeout"); + return -1; + } + nfct_timeout_policy_attr_set_u32(t, matching, + atoi(argv[i+1])); + matching = -1; + } else { + fprintf(stderr, "nfct v%s: Wrong state name: `%s' " + "for protocol `%s'\n", + VERSION, argv[i], argv[5]); + return -1; + } + } + + seq = time(NULL); + nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_timeout_nlmsg_build_payload(nlh, t); + + nfct_timeout_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_timeout_delete(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_timeout *t; + int ret; + + if (argc < 4) { + nfct_perror("missing timeout policy name"); + return -1; + } else if (argc > 4) { + nfct_perror("too many arguments"); + return -1; + } + + t = nfct_timeout_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + + nfct_timeout_attr_set(t, NFCT_TIMEOUT_ATTR_NAME, argv[3]); + + seq = time(NULL); + nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_DELETE, + NLM_F_ACK, seq); + nfct_timeout_nlmsg_build_payload(nlh, t); + + nfct_timeout_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_timeout_get(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_timeout *t; + int ret; + + if (argc < 4) { + nfct_perror("missing timeout policy name"); + return -1; + } else if (argc > 4) { + nfct_perror("too many arguments"); + return -1; + } + + t = nfct_timeout_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_timeout_attr_set(t, NFCT_TIMEOUT_ATTR_NAME, argv[3]); + + seq = time(NULL); + nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_GET, + NLM_F_ACK, seq); + + nfct_timeout_nlmsg_build_payload(nlh, t); + + nfct_timeout_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, nfct_timeout_cb, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_timeout_flush(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + int ret; + + if (argc > 3) { + nfct_perror("too many arguments"); + return -1; + } + + seq = time(NULL); + nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_DELETE, + NLM_F_ACK, seq); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} diff --git a/src/nfct.c b/src/nfct.c new file mode 100644 index 0000000..db629e7 --- /dev/null +++ b/src/nfct.c @@ -0,0 +1,116 @@ +/* + * (C) 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 code has been sponsored by Vyatta Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "nfct.h" + +static int nfct_cmd_version(int argc, char *argv[]); +static int nfct_cmd_help(int argc, char *argv[]); + +static void usage(char *argv[]) +{ + fprintf(stderr, "Usage: %s subsystem command [parameters]...\n", + argv[0]); +} + +void nfct_perror(const char *msg) +{ + if (errno == 0) { + fprintf(stderr, "nfct v%s: %s\n", VERSION, msg); + } else { + fprintf(stderr, "nfct v%s: %s: %s\n", + VERSION, msg, strerror(errno)); + } +} + +int main(int argc, char *argv[]) +{ + int subsys = NFCT_SUBSYS_NONE, ret = 0; + + if (argc < 2) { + usage(argv); + exit(EXIT_FAILURE); + } + if (strncmp(argv[1], "timeout", strlen(argv[1])) == 0) { + subsys = NFCT_SUBSYS_TIMEOUT; + } else if (strncmp(argv[1], "version", strlen(argv[1])) == 0) + subsys = NFCT_SUBSYS_VERSION; + else if (strncmp(argv[1], "help", strlen(argv[1])) == 0) + subsys = NFCT_SUBSYS_HELP; + else { + fprintf(stderr, "nfct v%s: Unknown subsystem: %s\n", + VERSION, argv[1]); + usage(argv); + exit(EXIT_FAILURE); + } + + switch(subsys) { + case NFCT_SUBSYS_TIMEOUT: + ret = nfct_cmd_timeout_parse_params(argc, argv); + break; + case NFCT_SUBSYS_VERSION: + ret = nfct_cmd_version(argc, argv); + break; + case NFCT_SUBSYS_HELP: + ret = nfct_cmd_help(argc, argv); + break; + } + return ret < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static const char version_msg[] = + "nfct v%s: utility for the Netfilter's Connection Tracking System\n" + "Copyright (C) 2012 Pablo Neira Ayuso \n" + "This program comes with ABSOLUTELY NO WARRANTY.\n" + "This is free software, and you are welcome to redistribute it under " + "certain \nconditions; see LICENSE file distributed in this package " + "for details.\n"; + +static int nfct_cmd_version(int argc, char *argv[]) +{ + printf(version_msg, VERSION); + return 0; +} + +static const char help_msg[] = + "nfct v%s: utility for the Netfilter's Connection Tracking System\n" + "Usage: %s command [parameters]...\n\n" + "Subsystem:\n" + " timeout\t\tAllows definition of fine-grain timeout policies\n" + " version\t\tDisplay version and disclaimer\n" + " help\t\t\tDisplay this help message\n" + "Commands:\n" + " list [reset]\t\tList the accounting object table (and reset)\n" + " add object-name\tAdd new accounting object to table\n" + " delete object-name\tDelete existing accounting object\n" + " get object-name\tGet existing accounting object\n" + " flush\t\t\tFlush accounting object table\n"; + +static int nfct_cmd_help(int argc, char *argv[]) +{ + printf(help_msg, VERSION, argv[0]); + return 0; +} -- cgit v1.2.3 From 5b6f524eea1ea8d2f0ecb2e17abfba7df708732f Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 15:46:52 +0200 Subject: tests: add nfct tests for cttimeout This patch adds the automated tests for the cttimeout infrastructure. Signed-off-by: Pablo Neira Ayuso --- tests/nfct/run-test.sh | 20 +++++++++ tests/nfct/test-live.sh | 73 +++++++++++++++++++++++++++++++ tests/nfct/test.c | 100 +++++++++++++++++++++++++++++++++++++++++++ tests/nfct/timeout/00tcp | 16 +++++++ tests/nfct/timeout/01udp | 16 +++++++ tests/nfct/timeout/02generic | 16 +++++++ tests/nfct/timeout/03udplite | 16 +++++++ tests/nfct/timeout/04icmp | 16 +++++++ tests/nfct/timeout/05icmpv6 | 16 +++++++ tests/nfct/timeout/06sctp | 16 +++++++ tests/nfct/timeout/07dccp | 16 +++++++ tests/nfct/timeout/08gre | 16 +++++++ 12 files changed, 337 insertions(+) create mode 100644 tests/nfct/run-test.sh create mode 100644 tests/nfct/test-live.sh create mode 100644 tests/nfct/test.c create mode 100644 tests/nfct/timeout/00tcp create mode 100644 tests/nfct/timeout/01udp create mode 100644 tests/nfct/timeout/02generic create mode 100644 tests/nfct/timeout/03udplite create mode 100644 tests/nfct/timeout/04icmp create mode 100644 tests/nfct/timeout/05icmpv6 create mode 100644 tests/nfct/timeout/06sctp create mode 100644 tests/nfct/timeout/07dccp create mode 100644 tests/nfct/timeout/08gre diff --git a/tests/nfct/run-test.sh b/tests/nfct/run-test.sh new file mode 100644 index 0000000..9bcad0d --- /dev/null +++ b/tests/nfct/run-test.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +UID=`id -u` +if [ $UID -ne 0 ] +then + echo "Run this test as root" + exit 1 +fi + +gcc test.c -o test +# +# XXX: module auto-load not support by nfnetlink_cttimeout yet :-( +# +modprobe nf_conntrack_ipv4 +modprobe nf_conntrack_ipv6 +modprobe nf_conntrack_proto_udplite +modprobe nf_conntrack_proto_sctp +modprobe nf_conntrack_proto_dccp +modprobe nf_conntrack_proto_gre +./test timeout diff --git a/tests/nfct/test-live.sh b/tests/nfct/test-live.sh new file mode 100644 index 0000000..c338e63 --- /dev/null +++ b/tests/nfct/test-live.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# +# simple testing for cttimeout infrastructure using one single computer +# + +WAIT_BETWEEN_TESTS=10 + +# flush cttimeout table +nfct timeout flush + +# flush the conntrack table +conntrack -F + +# +# No.1: test generic timeout policy +# + +echo "---- test no. 1 ----" + +conntrack -E -p 13 & + +nfct timeout add test-generic inet generic timeout 100 +iptables -I OUTPUT -t raw -p all -j CT --timeout test-generic +hping3 -c 1 -V -I eth0 -0 8.8.8.8 -H 13 + +killall -15 conntrack + +echo "---- end test no. 1 ----" + +sleep $WAIT_BETWEEN_TESTS + +iptables -D OUTPUT -t raw -p all -j CT --timeout test-generic +nfct timeout del test-generic + +# +# No.2: test TCP timeout policy +# + +echo "---- test no. 2 ----" + +conntrack -E -p tcp & + +nfct timeout add test-tcp inet tcp syn_sent 100 +iptables -I OUTPUT -t raw -p tcp -j CT --timeout test-tcp +hping3 -V -S -p 80 -s 5050 8.8.8.8 -c 1 + +sleep $WAIT_BETWEEN_TESTS + +iptables -D OUTPUT -t raw -p tcp -j CT --timeout test-tcp +nfct timeout del test-tcp + +killall -15 conntrack + +echo "---- end test no. 2 ----" + +# +# No. 3: test ICMP timeout policy +# + +echo "---- test no. 3 ----" + +conntrack -E -p icmp & + +nfct timeout add test-icmp inet icmp timeout 50 +iptables -I OUTPUT -t raw -p icmp -j CT --timeout test-icmp +hping3 -1 8.8.8.8 -c 2 + +iptables -D OUTPUT -t raw -p icmp -j CT --timeout test-icmp +nfct timeout del test-icmp + +killall -15 conntrack + +echo "---- end test no. 3 ----" diff --git a/tests/nfct/test.c b/tests/nfct/test.c new file mode 100644 index 0000000..a833dcc --- /dev/null +++ b/tests/nfct/test.c @@ -0,0 +1,100 @@ +/* + * (c) 2012 by Pablo Neira Ayuso + * + * Extremely simple test utility for the command line tools. + * + * Based on test-conntrack.c + */ + +#include +#include +#include +#include +#include +#include +#include + +#define PATH "/usr/sbin" + +int main(int argc, char *argv[]) +{ + int ret, ok = 0, bad = 0, line; + FILE *fp; + DIR *d; + char buf[1024]; + struct dirent *dent; + char file[1024]; + + if (argc < 2) { + fprintf(stderr, "Usage: %s directory\n", argv[0]); + exit(EXIT_FAILURE); + } + + d = opendir(argv[1]); + if (d == NULL) { + perror("opendir"); + exit(EXIT_FAILURE); + } + + setenv("PATH", PATH, 1); + + while ((dent = readdir(d)) != NULL) { + + sprintf(file, "%s/%s", argv[1], dent->d_name); + + line = 0; + + fp = fopen(file, "r"); + if (fp == NULL) { + perror("cannot find testsuite file"); + exit(EXIT_FAILURE); + } + + while (fgets(buf, sizeof(buf), fp)) { + char *res; + + line++; + + if (buf[0] == '#' || buf[0] == ' ') + continue; + + res = strchr(buf, ';'); + if (!res) { + printf("malformed file %s at line %d\n", + dent->d_name, line); + exit(EXIT_FAILURE); + } + *res = '\0'; + res+=2; + + printf("(%d) Executing: %s\n", line, buf); + + ret = system(buf); + + if (WIFEXITED(ret) && + WEXITSTATUS(ret) == EXIT_SUCCESS) { + if (res[0] == 'O' && + res[1] == 'K') + ok++; + else { + bad++; + printf("^----- BAD\n"); + } + } else { + if (res[0] == 'B' && + res[1] == 'A' && + res[2] == 'D') + ok++; + else { + bad++; + printf("^----- BAD\n"); + } + } + printf("=====\n"); + } + fclose(fp); + } + closedir(d); + + fprintf(stdout, "OK: %d BAD: %d\n", ok, bad); +} diff --git a/tests/nfct/timeout/00tcp b/tests/nfct/timeout/00tcp new file mode 100644 index 0000000..c9d7d24 --- /dev/null +++ b/tests/nfct/timeout/00tcp @@ -0,0 +1,16 @@ +# add policy object `test' +nfct timeout add test inet tcp established 100 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK +# get unexistent policy object `dummy' +nfct timeout get test ; BAD +# delete policy object `test', however, it does not exists anymore +nfct timeout delete test ; BAD +# add policy object `test' +nfct timeout add test inet tcp syn_sent 1 syn_recv 2 established 3 fin_wait 4 close_wait 5 last_ack 6 time_wait 7 close 8 syn_sent2 9 retrans 10 unacknowledged 11 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK diff --git a/tests/nfct/timeout/01udp b/tests/nfct/timeout/01udp new file mode 100644 index 0000000..952526c --- /dev/null +++ b/tests/nfct/timeout/01udp @@ -0,0 +1,16 @@ +# add policy object `test' +nfct timeout add test inet udp unreplied 10 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK +# get unexistent policy object `dummy' +nfct timeout get test ; BAD +# delete policy object `test', however, it does not exists anymore +nfct timeout delete test ; BAD +# add policy object `test' +nfct timeout add test inet udp unreplied 1 replied 2 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK diff --git a/tests/nfct/timeout/02generic b/tests/nfct/timeout/02generic new file mode 100644 index 0000000..b6ca699 --- /dev/null +++ b/tests/nfct/timeout/02generic @@ -0,0 +1,16 @@ +# add policy object `test' +nfct timeout add test inet generic timeout 10 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK +# get unexistent policy object `dummy' +nfct timeout get test ; BAD +# delete policy object `test', however, it does not exists anymore +nfct timeout delete test ; BAD +# add policy object `test' +nfct timeout add test inet generic timeout 1 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK diff --git a/tests/nfct/timeout/03udplite b/tests/nfct/timeout/03udplite new file mode 100644 index 0000000..69dda15 --- /dev/null +++ b/tests/nfct/timeout/03udplite @@ -0,0 +1,16 @@ +# add policy object `test' +nfct timeout add test inet udplite unreplied 10 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK +# get unexistent policy object `dummy' +nfct timeout get test ; BAD +# delete policy object `test', however, it does not exists anymore +nfct timeout delete test ; BAD +# add policy object `test' +nfct timeout add test inet udplite unreplied 1 replied 2 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK diff --git a/tests/nfct/timeout/04icmp b/tests/nfct/timeout/04icmp new file mode 100644 index 0000000..606e8b9 --- /dev/null +++ b/tests/nfct/timeout/04icmp @@ -0,0 +1,16 @@ +# add policy object `test' +nfct timeout add test inet icmp timeout 10 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK +# get unexistent policy object `dummy' +nfct timeout get test ; BAD +# delete policy object `test', however, it does not exists anymore +nfct timeout delete test ; BAD +# add policy object `test' +nfct timeout add test inet icmp timeout 1 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK diff --git a/tests/nfct/timeout/05icmpv6 b/tests/nfct/timeout/05icmpv6 new file mode 100644 index 0000000..16541f5 --- /dev/null +++ b/tests/nfct/timeout/05icmpv6 @@ -0,0 +1,16 @@ +# add policy object `test' +nfct timeout add test inet6 icmpv6 timeout 10 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK +# get unexistent policy object `dummy' +nfct timeout get test ; BAD +# delete policy object `test', however, it does not exists anymore +nfct timeout delete test ; BAD +# add policy object `test' +nfct timeout add test inet6 icmpv6 timeout 1 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK diff --git a/tests/nfct/timeout/06sctp b/tests/nfct/timeout/06sctp new file mode 100644 index 0000000..f475215 --- /dev/null +++ b/tests/nfct/timeout/06sctp @@ -0,0 +1,16 @@ +# add policy object `test' +nfct timeout add test inet sctp established 100 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK +# get unexistent policy object `dummy' +nfct timeout get test ; BAD +# delete policy object `test', however, it does not exists anymore +nfct timeout delete test ; BAD +# add policy object `test' +nfct timeout add test inet sctp closed 1 cookie_wait 2 cookie_echoed 3 established 4 shutdown_sent 5 shutdown_recd 6 shutdown_ack_sent 7 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK diff --git a/tests/nfct/timeout/07dccp b/tests/nfct/timeout/07dccp new file mode 100644 index 0000000..1bd4fa5 --- /dev/null +++ b/tests/nfct/timeout/07dccp @@ -0,0 +1,16 @@ +# add policy object `test' +nfct timeout add test inet dccp request 100 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK +# get unexistent policy object `dummy' +nfct timeout get test ; BAD +# delete policy object `test', however, it does not exists anymore +nfct timeout delete test ; BAD +# add policy object `test' +nfct timeout add test inet dccp request 1 respond 2 partopen 3 open 4 closereq 5 closing 6 timewait 7 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK diff --git a/tests/nfct/timeout/08gre b/tests/nfct/timeout/08gre new file mode 100644 index 0000000..7ef4bdb --- /dev/null +++ b/tests/nfct/timeout/08gre @@ -0,0 +1,16 @@ +# add policy object `test' +nfct timeout add test inet gre unreplied 10 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK +# get unexistent policy object `dummy' +nfct timeout get test ; BAD +# delete policy object `test', however, it does not exists anymore +nfct timeout delete test ; BAD +# add policy object `test' +nfct timeout add test inet gre unreplied 1 replied 2 ; OK +# get policy object `test' +nfct timeout get test ; OK +# delete policy object `test' +nfct timeout delete test ; OK -- cgit v1.2.3 From 790d922771acf3a1dc7a2bd8cdbf27ac5ed83401 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 15:51:09 +0200 Subject: build: bump version to 1.2.0 Major milestone including the new `nfct' utility and the expectation support for conntrackd. Signed-off-by: Pablo Neira Ayuso --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 5c4ac58..18d73c9 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT(conntrack-tools, 1.0.1, pablo@netfilter.org) +AC_INIT(conntrack-tools, 1.2.0, pablo@netfilter.org) AC_CONFIG_AUX_DIR([build-aux]) AC_CANONICAL_HOST -- cgit v1.2.3 From 658ef8980f09e12ee8c4d9a52afdf94fd097b47b Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 15:53:28 +0200 Subject: nfct: fix compilation warning in cttimeout support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CC nfct-extensions/timeout.o ../../src/nfct-extensions/timeout.c: In function ‘nfct_cmd_timeout_parse_params’: ../../src/nfct-extensions/timeout.c:40:27: warning: variable ‘ret’ set but not used [-Wunused-but-set-variable] Signed-off-by: Pablo Neira Ayuso --- src/nfct-extensions/timeout.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nfct-extensions/timeout.c b/src/nfct-extensions/timeout.c index a69537d..a1a5c52 100644 --- a/src/nfct-extensions/timeout.c +++ b/src/nfct-extensions/timeout.c @@ -37,7 +37,7 @@ nfct_cmd_timeout_usage(char *argv[]) int nfct_cmd_timeout_parse_params(int argc, char *argv[]) { - int cmd = NFCT_CMD_NONE, ret = 0; + int cmd = NFCT_CMD_NONE, ret; if (argc < 3) { nfct_cmd_timeout_usage(argv); @@ -77,7 +77,7 @@ int nfct_cmd_timeout_parse_params(int argc, char *argv[]) break; } - return 0; + return ret; } static int nfct_timeout_cb(const struct nlmsghdr *nlh, void *data) -- cgit v1.2.3 From 1975dc432a57a78880e28aadceb3d7bcf923fe8b Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 17:39:30 +0200 Subject: build: update dependencies with libnetfilter_conntrack (>= 1.0.1) libnetfilter_conntrack 1.0.1 includes important updates for the expectation side, which is used in this major milestone release. Signed-off-by: Pablo Neira Ayuso --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 18d73c9..580e078 100644 --- a/configure.ac +++ b/configure.ac @@ -53,7 +53,7 @@ fi PKG_CHECK_MODULES([LIBNFNETLINK], [libnfnetlink >= 1.0.0]) PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.0]) -PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.1]) PKG_CHECK_MODULES([LIBNETFILTER_CTTIMEOUT], [libnetfilter_cttimeout >= 1.0.0]) AC_CHECK_HEADERS([linux/capability.h],, [AC_MSG_ERROR([Cannot find linux/capabibility.h])]) -- cgit v1.2.3 From 0e1ce4f491e2134d6207f55c4a5f52e157a54707 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 17:43:49 +0200 Subject: move qa directory to tests/conntrack/ All automated testing for the conntrack-tools will now reside under the test directory. Signed-off-by: Pablo Neira Ayuso --- qa/test-conntrack.c | 94 -------------------------------------- qa/testsuite/00create | 20 -------- qa/testsuite/01delete | 6 --- qa/testsuite/02filter | 23 ---------- qa/testsuite/03nat | 40 ---------------- qa/testsuite/04zone | 8 ---- qa/testsuite/05mark | 27 ----------- qa/testsuite/06update | 8 ---- tests/conntrack/test-conntrack.c | 94 ++++++++++++++++++++++++++++++++++++++ tests/conntrack/testsuite/00create | 20 ++++++++ tests/conntrack/testsuite/01delete | 6 +++ tests/conntrack/testsuite/02filter | 23 ++++++++++ tests/conntrack/testsuite/03nat | 40 ++++++++++++++++ tests/conntrack/testsuite/04zone | 8 ++++ tests/conntrack/testsuite/05mark | 27 +++++++++++ tests/conntrack/testsuite/06update | 8 ++++ 16 files changed, 226 insertions(+), 226 deletions(-) delete mode 100644 qa/test-conntrack.c delete mode 100644 qa/testsuite/00create delete mode 100644 qa/testsuite/01delete delete mode 100644 qa/testsuite/02filter delete mode 100644 qa/testsuite/03nat delete mode 100644 qa/testsuite/04zone delete mode 100644 qa/testsuite/05mark delete mode 100644 qa/testsuite/06update create mode 100644 tests/conntrack/test-conntrack.c create mode 100644 tests/conntrack/testsuite/00create create mode 100644 tests/conntrack/testsuite/01delete create mode 100644 tests/conntrack/testsuite/02filter create mode 100644 tests/conntrack/testsuite/03nat create mode 100644 tests/conntrack/testsuite/04zone create mode 100644 tests/conntrack/testsuite/05mark create mode 100644 tests/conntrack/testsuite/06update diff --git a/qa/test-conntrack.c b/qa/test-conntrack.c deleted file mode 100644 index c9097b6..0000000 --- a/qa/test-conntrack.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Very simple test-tool for the command line tool `conntrack'. - * This code is released under GPLv2 or any later at your option. - * - * gcc test-conntrack.c -o test - * - * Do not forget that you need *root* or CAP_NET_ADMIN capabilities ;-) - * - * (c) 2008 Pablo Neira Ayuso - */ - -#include -#include -#include -#include -#include -#include -#include - -#define CT_PROG "/usr/sbin/conntrack" - -int main() -{ - int ret, ok = 0, bad = 0, line; - FILE *fp; - DIR *d; - char buf[1024]; - struct dirent *dent; - char file[1024]; - - d = opendir("testsuite"); - - while ((dent = readdir(d)) != NULL) { - - sprintf(file, "testsuite/%s", dent->d_name); - - line = 0; - - fp = fopen(file, "r"); - if (fp == NULL) { - perror("cannot find testsuite file"); - exit(EXIT_FAILURE); - } - - while (fgets(buf, sizeof(buf), fp)) { - char tmp[1024] = CT_PROG, *res; - tmp[strlen(CT_PROG)] = ' '; - - line++; - - if (buf[0] == '#' || buf[0] == ' ') - continue; - - res = strchr(buf, ';'); - if (!res) { - printf("malformed file %s at line %d\n", - dent->d_name, line); - exit(EXIT_FAILURE); - } - *res = '\0'; - res+=2; - - strcpy(tmp + strlen(CT_PROG) + 1, buf); - printf("(%d) Executing: %s\n", line, tmp); - - ret = system(tmp); - - if (WIFEXITED(ret) && - WEXITSTATUS(ret) == EXIT_SUCCESS) { - if (res[0] == 'O' && - res[1] == 'K') - ok++; - else { - bad++; - printf("^----- BAD\n"); - } - } else { - if (res[0] == 'B' && - res[1] == 'A' && - res[2] == 'D') - ok++; - else { - bad++; - printf("^----- BAD\n"); - } - } - printf("=====\n"); - } - fclose(fp); - } - closedir(d); - - fprintf(stdout, "OK: %d BAD: %d\n", ok, bad); -} diff --git a/qa/testsuite/00create b/qa/testsuite/00create deleted file mode 100644 index 40e2c19..0000000 --- a/qa/testsuite/00create +++ /dev/null @@ -1,20 +0,0 @@ -#missing destination --I -s 1.1.1.1 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD -#missing source --I -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD -#missing protocol --I -s 1.1.1.1 -d 2.2.2.2 --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD -#missing source port --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD -#missing timeout --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY ; BAD -# create a conntrack --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK -# create again --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD -# delete --D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ; OK -# create from reply --I -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ; OK -# delete reverse --D -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 ; OK diff --git a/qa/testsuite/01delete b/qa/testsuite/01delete deleted file mode 100644 index 3c38ac5..0000000 --- a/qa/testsuite/01delete +++ /dev/null @@ -1,6 +0,0 @@ -# create dummy --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK -# delete bad source --D -s 2.2.2.2 -p tcp --sport 10 --dport 20 ; BAD -# delete by source --D -s 1.1.1.1 ; OK diff --git a/qa/testsuite/02filter b/qa/testsuite/02filter deleted file mode 100644 index 204c4e8..0000000 --- a/qa/testsuite/02filter +++ /dev/null @@ -1,23 +0,0 @@ -# create dummy -conntrack -I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK -# filter by source -conntrack -L -s 1.1.1.1 ; OK -# filter by destination -conntrack -L -d 2.2.2.2 ; OK -# filter by protocol -conntrack -L -p tcp ; OK -# filter by status -conntrack -L -u SEEN_REPLY ; OK -# filter by TCP protocol state -conntrack -L -p tcp --state LISTEN ; OK -# update mark of dummy conntrack -conntrack -U -s 1.1.1.1 -m 1 ; OK -# filter by mark -conntrack -L -m 1 ; OK -# filter by layer 3 protocol -conntrack -L -f ipv4 ; OK -# filter by mark -conntrack -L --mark 0 ; OK -conntrack -L --mark 0/0xffffffff; OK -# delete dummy -conntrack -D -d 2.2.2.2 ; OK diff --git a/qa/testsuite/03nat b/qa/testsuite/03nat deleted file mode 100644 index f94e8ff..0000000 --- a/qa/testsuite/03nat +++ /dev/null @@ -1,40 +0,0 @@ -# create dummy --I -s 1.1.1.1 -d 2.2.2.2 --dst-nat 3.3.3.3 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK -# show --L --dst-nat ; OK -# show --L --dst-nat 3.3.3.3 ; OK -# show --L --src-nat ; OK -# delete --D -s 1.1.1.1 ; OK -# create dummy again --I -s 1.1.1.1 -d 2.2.2.2 --src-nat 3.3.3.3 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK -# show --L --src-nat ; OK -# show --L --src-nat 3.3.3.3 ; OK -# show --L --dst-nat ; OK -# show any-nat --L --any-nat ; OK -# delete --D -s 1.1.1.1 ; OK -# bad combination --L --dst-nat --any-nat ; BAD -# bad combination --L --src-nat --any-nat ; BAD -# bad combination --L --src-nat --dst-nat --any-nat ; BAD -# create --I -s 1.1.1.1 -d 2.2.2.2 --dst-nat 3.3.3.3:80 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK -# show --L --dst-nat 3.3.3.3:80 ; OK -# show --L --any-nat 3.3.3.3:80 ; OK -# show --L --dst-nat 3.3.3.3:81 ; OK -# show --L --dst-nat 1.1.1.1:80 ; OK -# delete --D -s 1.1.1.1 ; OK diff --git a/qa/testsuite/04zone b/qa/testsuite/04zone deleted file mode 100644 index 4ff3d34..0000000 --- a/qa/testsuite/04zone +++ /dev/null @@ -1,8 +0,0 @@ -# create dummy --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --zone 1; OK -# display dummy --L --zone 1; OK -# display dummy --L --zone 0; OK -# delete dummy --D --zone 1; OK diff --git a/qa/testsuite/05mark b/qa/testsuite/05mark deleted file mode 100644 index 4d99dea..0000000 --- a/qa/testsuite/05mark +++ /dev/null @@ -1,27 +0,0 @@ -# create with a mark --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --mark 42 ; OK -# find it again using mark --L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42 ; OK --L --mark 42; OK -# ct already exists --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --mark 42/0xffffffff ; BAD -# delete by mark --D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42/0xffffffff ; OK -# try again after del --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --mark 417889/0xffffffff ; OK -# delete by mark --D --mark 417889 ; OK --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --mark 0xffffffff ; OK -# zap top 16. --U -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 0/0xffff0000 ; OK --L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 0x0000ffff ; OK --U -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42/0xffff ; OK --L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42/0x0000ffff ; OK --L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42/42 ; OK --L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 2/2 ; OK --L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 2/3 ; OK -# OK, but no flow entries should be shown here: --L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 2/0xf ; OK -# BAD, because no updates done (mark is already 42). --U -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42 ; BAD --D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42 ; OK diff --git a/qa/testsuite/06update b/qa/testsuite/06update deleted file mode 100644 index 0408303..0000000 --- a/qa/testsuite/06update +++ /dev/null @@ -1,8 +0,0 @@ -# create dummy flow --I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state SYN_RECV -u SEEN_REPLY,ASSURED -t 50 ; OK -# find it again using mark --L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ; OK -# set fixed timeout --U -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 -u FIXED_TIMEOUT; OK -# delete it --D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20; OK diff --git a/tests/conntrack/test-conntrack.c b/tests/conntrack/test-conntrack.c new file mode 100644 index 0000000..c9097b6 --- /dev/null +++ b/tests/conntrack/test-conntrack.c @@ -0,0 +1,94 @@ +/* + * Very simple test-tool for the command line tool `conntrack'. + * This code is released under GPLv2 or any later at your option. + * + * gcc test-conntrack.c -o test + * + * Do not forget that you need *root* or CAP_NET_ADMIN capabilities ;-) + * + * (c) 2008 Pablo Neira Ayuso + */ + +#include +#include +#include +#include +#include +#include +#include + +#define CT_PROG "/usr/sbin/conntrack" + +int main() +{ + int ret, ok = 0, bad = 0, line; + FILE *fp; + DIR *d; + char buf[1024]; + struct dirent *dent; + char file[1024]; + + d = opendir("testsuite"); + + while ((dent = readdir(d)) != NULL) { + + sprintf(file, "testsuite/%s", dent->d_name); + + line = 0; + + fp = fopen(file, "r"); + if (fp == NULL) { + perror("cannot find testsuite file"); + exit(EXIT_FAILURE); + } + + while (fgets(buf, sizeof(buf), fp)) { + char tmp[1024] = CT_PROG, *res; + tmp[strlen(CT_PROG)] = ' '; + + line++; + + if (buf[0] == '#' || buf[0] == ' ') + continue; + + res = strchr(buf, ';'); + if (!res) { + printf("malformed file %s at line %d\n", + dent->d_name, line); + exit(EXIT_FAILURE); + } + *res = '\0'; + res+=2; + + strcpy(tmp + strlen(CT_PROG) + 1, buf); + printf("(%d) Executing: %s\n", line, tmp); + + ret = system(tmp); + + if (WIFEXITED(ret) && + WEXITSTATUS(ret) == EXIT_SUCCESS) { + if (res[0] == 'O' && + res[1] == 'K') + ok++; + else { + bad++; + printf("^----- BAD\n"); + } + } else { + if (res[0] == 'B' && + res[1] == 'A' && + res[2] == 'D') + ok++; + else { + bad++; + printf("^----- BAD\n"); + } + } + printf("=====\n"); + } + fclose(fp); + } + closedir(d); + + fprintf(stdout, "OK: %d BAD: %d\n", ok, bad); +} diff --git a/tests/conntrack/testsuite/00create b/tests/conntrack/testsuite/00create new file mode 100644 index 0000000..40e2c19 --- /dev/null +++ b/tests/conntrack/testsuite/00create @@ -0,0 +1,20 @@ +#missing destination +-I -s 1.1.1.1 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD +#missing source +-I -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD +#missing protocol +-I -s 1.1.1.1 -d 2.2.2.2 --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD +#missing source port +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD +#missing timeout +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY ; BAD +# create a conntrack +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK +# create again +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; BAD +# delete +-D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ; OK +# create from reply +-I -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 --state LISTEN -u SEEN_REPLY -t 50 ; OK +# delete reverse +-D -r 2.2.2.2 -q 1.1.1.1 -p tcp --reply-port-src 11 --reply-port-dst 21 ; OK diff --git a/tests/conntrack/testsuite/01delete b/tests/conntrack/testsuite/01delete new file mode 100644 index 0000000..3c38ac5 --- /dev/null +++ b/tests/conntrack/testsuite/01delete @@ -0,0 +1,6 @@ +# create dummy +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK +# delete bad source +-D -s 2.2.2.2 -p tcp --sport 10 --dport 20 ; BAD +# delete by source +-D -s 1.1.1.1 ; OK diff --git a/tests/conntrack/testsuite/02filter b/tests/conntrack/testsuite/02filter new file mode 100644 index 0000000..204c4e8 --- /dev/null +++ b/tests/conntrack/testsuite/02filter @@ -0,0 +1,23 @@ +# create dummy +conntrack -I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK +# filter by source +conntrack -L -s 1.1.1.1 ; OK +# filter by destination +conntrack -L -d 2.2.2.2 ; OK +# filter by protocol +conntrack -L -p tcp ; OK +# filter by status +conntrack -L -u SEEN_REPLY ; OK +# filter by TCP protocol state +conntrack -L -p tcp --state LISTEN ; OK +# update mark of dummy conntrack +conntrack -U -s 1.1.1.1 -m 1 ; OK +# filter by mark +conntrack -L -m 1 ; OK +# filter by layer 3 protocol +conntrack -L -f ipv4 ; OK +# filter by mark +conntrack -L --mark 0 ; OK +conntrack -L --mark 0/0xffffffff; OK +# delete dummy +conntrack -D -d 2.2.2.2 ; OK diff --git a/tests/conntrack/testsuite/03nat b/tests/conntrack/testsuite/03nat new file mode 100644 index 0000000..f94e8ff --- /dev/null +++ b/tests/conntrack/testsuite/03nat @@ -0,0 +1,40 @@ +# create dummy +-I -s 1.1.1.1 -d 2.2.2.2 --dst-nat 3.3.3.3 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK +# show +-L --dst-nat ; OK +# show +-L --dst-nat 3.3.3.3 ; OK +# show +-L --src-nat ; OK +# delete +-D -s 1.1.1.1 ; OK +# create dummy again +-I -s 1.1.1.1 -d 2.2.2.2 --src-nat 3.3.3.3 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK +# show +-L --src-nat ; OK +# show +-L --src-nat 3.3.3.3 ; OK +# show +-L --dst-nat ; OK +# show any-nat +-L --any-nat ; OK +# delete +-D -s 1.1.1.1 ; OK +# bad combination +-L --dst-nat --any-nat ; BAD +# bad combination +-L --src-nat --any-nat ; BAD +# bad combination +-L --src-nat --dst-nat --any-nat ; BAD +# create +-I -s 1.1.1.1 -d 2.2.2.2 --dst-nat 3.3.3.3:80 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 ; OK +# show +-L --dst-nat 3.3.3.3:80 ; OK +# show +-L --any-nat 3.3.3.3:80 ; OK +# show +-L --dst-nat 3.3.3.3:81 ; OK +# show +-L --dst-nat 1.1.1.1:80 ; OK +# delete +-D -s 1.1.1.1 ; OK diff --git a/tests/conntrack/testsuite/04zone b/tests/conntrack/testsuite/04zone new file mode 100644 index 0000000..4ff3d34 --- /dev/null +++ b/tests/conntrack/testsuite/04zone @@ -0,0 +1,8 @@ +# create dummy +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --zone 1; OK +# display dummy +-L --zone 1; OK +# display dummy +-L --zone 0; OK +# delete dummy +-D --zone 1; OK diff --git a/tests/conntrack/testsuite/05mark b/tests/conntrack/testsuite/05mark new file mode 100644 index 0000000..4d99dea --- /dev/null +++ b/tests/conntrack/testsuite/05mark @@ -0,0 +1,27 @@ +# create with a mark +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --mark 42 ; OK +# find it again using mark +-L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42 ; OK +-L --mark 42; OK +# ct already exists +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --mark 42/0xffffffff ; BAD +# delete by mark +-D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42/0xffffffff ; OK +# try again after del +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --mark 417889/0xffffffff ; OK +# delete by mark +-D --mark 417889 ; OK +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state LISTEN -u SEEN_REPLY -t 50 --mark 0xffffffff ; OK +# zap top 16. +-U -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 0/0xffff0000 ; OK +-L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 0x0000ffff ; OK +-U -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42/0xffff ; OK +-L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42/0x0000ffff ; OK +-L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42/42 ; OK +-L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 2/2 ; OK +-L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 2/3 ; OK +# OK, but no flow entries should be shown here: +-L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 2/0xf ; OK +# BAD, because no updates done (mark is already 42). +-U -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42 ; BAD +-D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --mark 42 ; OK diff --git a/tests/conntrack/testsuite/06update b/tests/conntrack/testsuite/06update new file mode 100644 index 0000000..0408303 --- /dev/null +++ b/tests/conntrack/testsuite/06update @@ -0,0 +1,8 @@ +# create dummy flow +-I -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 --state SYN_RECV -u SEEN_REPLY,ASSURED -t 50 ; OK +# find it again using mark +-L -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 ; OK +# set fixed timeout +-U -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20 -u FIXED_TIMEOUT; OK +# delete it +-D -s 1.1.1.1 -d 2.2.2.2 -p tcp --sport 10 --dport 20; OK -- cgit v1.2.3 From 5e4ce59027bf7170c865388d3d703086f187ce59 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 17:47:15 +0200 Subject: tests: conntrack: add run-test.sh script For automated testing of the conntrack utility. Signed-off-by: Pablo Neira Ayuso --- tests/conntrack/run-test.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/conntrack/run-test.sh diff --git a/tests/conntrack/run-test.sh b/tests/conntrack/run-test.sh new file mode 100644 index 0000000..2b7b6f2 --- /dev/null +++ b/tests/conntrack/run-test.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +UID=`id -u` +if [ $UID -ne 0 ] +then + echo "Run this test as root" + exit 1 +fi + +gcc test-conntrack.c -o test +# +# XXX: module auto-load not support by nfnetlink_cttimeout yet :-( +# +modprobe nf_conntrack_ipv4 +modprobe nf_conntrack_ipv6 +modprobe nf_conntrack_proto_udplite +modprobe nf_conntrack_proto_sctp +modprobe nf_conntrack_proto_dccp +modprobe nf_conntrack_proto_gre +./test testcases -- cgit v1.2.3 From 7276986d8a2d539fde3835e00e217f52f6e595ac Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 18:02:12 +0200 Subject: add nfct(8) manpage Signed-off-by: Pablo Neira Ayuso --- Makefile.am | 2 +- nfct.8 | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 nfct.8 diff --git a/Makefile.am b/Makefile.am index afb4595..bd366bf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,7 @@ include Make_global.am ACLOCAL_AMFLAGS = -I m4 -man_MANS = conntrack.8 conntrackd.8 +man_MANS = conntrack.8 conntrackd.8 nfct.8 EXTRA_DIST = $(man_MANS) Make_global.am doc m4 SUBDIRS = extensions src diff --git a/nfct.8 b/nfct.8 new file mode 100644 index 0000000..6f5190a --- /dev/null +++ b/nfct.8 @@ -0,0 +1,64 @@ +.TH NFCT 8 "Feb 29, 2012" "" "" + +.\" Man page written by Pablo Neira Ayuso (Feb 2012) + +.SH NAME +nfct \- command line tool to interact with the connection tracking system +.SH SYNOPSIS +.BR "nfct subsystem command [parameters]" +.SH DESCRIPTION +.B nfct +is the command line tool that allows you Netfilter's manipulate Connection Tracking System. +.SH SUBSYS +By the time this manpage has been written, the supported subsystem are +.B timeout +.TP +.BI "timeout " +The timeout subsystem allows you to define fine-grain timeout policies. +.TP +.BI "version " +Displays the version information. +.TP +.BI "help " +Displays the help message. +.SH TIMEOUT SUBSYSTEM +.TP +.BI "list " +List the existing timeout policies. +.TP +.BI "add " +Add new timeout policy. +.TP +.BI "delete " +Delete timeout policy. +.TP +.BI "get " +Get existing timeout policy. +.SH EXAMPLE +.TP +.B nfct timeout add test-tcp inet tcp established 100 close 10 close_wait 10 +.TP +This creates a timeout policy for tcp using 100 seconds for the ESTABLISHED state, 10 seconds for CLOSE state and 10 seconds for the CLOSE_WAIT state. +.TP +Then, you can attach the timeout policy with the iptables CT target: +.TP +.B iptables -I PREROUTING -t raw -p tcp -j CT --timeout test-tcp +.TP +.B iptables -I OUTPUT -t raw -p tcp -j CT --timeout test-tcp +.TP +You can test that the timeout policy with: +.TP +.B conntrack -E -p tcp +.TP +It should display: +.TP +.B [UPDATE] tcp 6 100 ESTABLISHED src=192.168.39.100 dst=57.126.1.20 sport=56463 dport=80 src=57.126.1.20 dst=192.168.39.100 sport=80 dport=56463 [ASSURED] +.SH SEE ALSO +.BR iptables (8), conntrack (8) +.SH BUGS +Please, report them to netfilter-devel@vger.kernel.org or file a bug in +Netfilter's bugzilla (https://bugzilla.netfilter.org). +.SH AUTHORS +Pablo Neira Ayuso wrote and maintains the nfct tool. +.PP +Man page written by Pablo Neira Ayuso . -- cgit v1.2.3 From 318756cd4df1cb7760bf32d3e1d3756c41d1858b Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 18:03:32 +0200 Subject: add README.nfct This files includes some short description on `nfct'. Signed-off-by: Pablo Neira Ayuso --- README.nfct | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 README.nfct diff --git a/README.nfct b/README.nfct new file mode 100644 index 0000000..4d8e6cc --- /dev/null +++ b/README.nfct @@ -0,0 +1,62 @@ += nfct: command line tool to interact with the Connection Tracking System = + +This tool only supports the cttimeout infrastructure by now. However, +the plan is that it will replace `conntrack' with a syntax that looks +more similar to `ip' and `nftables' tools (in the long run!). + +== cttimeout: fine-grain timeout tuning for the Connection Tracking System == + +The `nfct' command line tool allows you to define custom timeout +policies: + +# nfct timeout add custom-tcp-policy1 inet tcp established 100 + +You can also retrieve the existing timeout policies with: + +# nfct timeout list +.tcp-policy = { + .l3proto = 2, + .l4proto = 6, + .policy = { + .SYN_SENT = 120, + .SYN_RECV = 60, + .ESTABLISHED = 100, + .FIN_WAIT = 120, + .CLOSE_WAIT = 60, + .LAST_ACK = 30, + .TIME_WAIT = 120, + .CLOSE = 10, + .SYN_SENT2 = 120, + .RETRANS = 300, + .UNACKNOWLEDGED = 300, + }, +}; + +Then, you can use the timeout policy with iptables: + +# iptables -I PREROUTING -t raw -s 1.1.1.1 -d 2.2.2.2 -p tcp \ + -j CT --timeout custom-tcp-policy1 + +You can define policies for other protocols as well, eg: + +# nfct timeout add custom-udp-policy1 inet udp unreplied 10 replied 20 + +And attach them via iptables: + +# iptables -I PREROUTING -t raw -s 1.1.1.1 -d 2.2.2.2 -p udp \ + -j CT --timeout custom-udp-policy1 + +== Compilation & Installation == + +This tool requires libmnl and libnetfilter_cttimeout. You also require +nfnetlink_cttimeout support in the Linux kernel. + +If you obtain a working copy from git, you have to run: + +$ autoreconf -fi # this is the lingo that replaces old autogen.sh scripts +$ ./configure --prefix=/usr +$ make +$ sudo make install + +-o- +(c) 2012 by Pablo Neira Ayuso -- cgit v1.2.3 From 18efbc802479ce042378dc0cccc7577e1c6afa41 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 20:58:16 +0200 Subject: nfct: fix compilation of timeout extension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nfct-extensions/timeout.c: In function ‘nfct_timeout_cb’: nfct-extensions/timeout.c:99:2: warning: passing argument 4 of ‘nfct_timeout_snprintf’ makes integer from pointer without a cast [enabled by default] /usr/include/libnetfilter_cttimeout/libnetfilter_cttimeout.h:114:5: note: expected ‘unsigned int’ but argument is of type ‘struct nfct_timeout *’ Signed-off-by: Pablo Neira Ayuso --- src/nfct-extensions/timeout.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nfct-extensions/timeout.c b/src/nfct-extensions/timeout.c index a1a5c52..5b32023 100644 --- a/src/nfct-extensions/timeout.c +++ b/src/nfct-extensions/timeout.c @@ -96,7 +96,7 @@ static int nfct_timeout_cb(const struct nlmsghdr *nlh, void *data) goto err_free; } - nfct_timeout_snprintf(buf, sizeof(buf), t, 0); + nfct_timeout_snprintf(buf, sizeof(buf), t, NFCT_TIMEOUT_O_DEFAULT, 0); printf("%s\n", buf); err_free: -- cgit v1.2.3 From 202a92db668aa4e9847d99ec5e0d5b3b0224c340 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 26 May 2012 21:05:18 +0200 Subject: bump version to 1.2.1 this release fixes a compilation issue in 1.2.0, sorry. Signed-off-by: Pablo Neira Ayuso --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 580e078..07a1fa1 100644 --- a/configure.ac +++ b/configure.ac @@ -1,4 +1,4 @@ -AC_INIT(conntrack-tools, 1.2.0, pablo@netfilter.org) +AC_INIT(conntrack-tools, 1.2.1, pablo@netfilter.org) AC_CONFIG_AUX_DIR([build-aux]) AC_CANONICAL_HOST -- cgit v1.2.3 From c5e0c17653274e9cc21f677e6fe3bd822a569a00 Mon Sep 17 00:00:00 2001 From: Jan Engelhardt Date: Sat, 26 May 2012 22:18:35 +0200 Subject: update .gitignore --- .gitignore | 1 + src/.gitignore | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 928e44b..f7a5fc7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .deps/ .libs/ +.dirstamp Makefile Makefile.in *.o diff --git a/src/.gitignore b/src/.gitignore index 6e6763d..55a0d27 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,5 +1,6 @@ /conntrack /conntrackd +/nfct /read_config_lex.c /read_config_yy.c -- cgit v1.2.3 From fcd6f78d277113628205789c8aba9ab1f5152fc4 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 28 May 2012 12:28:40 +0200 Subject: conntrackd: simplify TCP connection handling logic Before this patch, we called accept() to likely return EAGAIN. This is not required as select() will tell us that we're ready to accept. Therefore, that early accept() invocation complicates the whole handling just to get the connection accepted a bit before. Signed-off-by: Pablo Neira Ayuso --- src/tcp.c | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/src/tcp.c b/src/tcp.c index c551c54..f6b05ef 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -27,7 +27,7 @@ struct tcp_sock *tcp_server_create(struct tcp_conf *c) { - int yes = 1, ret; + int yes = 1; struct tcp_sock *m; socklen_t socklen = sizeof(int); @@ -109,30 +109,7 @@ struct tcp_sock *tcp_server_create(struct tcp_conf *c) return NULL; } - /* now we accept new connections ... */ - ret = accept(m->fd, NULL, NULL); - if (ret == -1) { - if (errno != EAGAIN) { - /* unexpected error, give up. */ - close(m->fd); - free(m); - m = NULL; - } else { - /* still in progress ... we'll do it in tcp_recv() */ - m->state = TCP_SERVER_ACCEPTING; - } - } else { - /* very unlikely at this stage. */ - if (fcntl(ret, F_SETFL, O_NONBLOCK) == -1) { - /* unexpected error, give up. */ - close(m->fd); - free(m); - return NULL; - } - m->client_fd = ret; - m->state = TCP_SERVER_CONNECTED; - register_fd(m->client_fd, STATE(fds)); - } + m->state = TCP_SERVER_ACCEPTING; return m; } @@ -367,7 +344,6 @@ ssize_t tcp_recv(struct tcp_sock *m, void *data, int size) close(m->client_fd); m->client_fd = -1; m->state = TCP_SERVER_ACCEPTING; - tcp_accept(m); } else if (errno != EAGAIN) { m->stats.error++; } @@ -377,7 +353,6 @@ ssize_t tcp_recv(struct tcp_sock *m, void *data, int size) close(m->client_fd); m->client_fd = -1; m->state = TCP_SERVER_ACCEPTING; - tcp_accept(m); } if (ret >= 0) { -- cgit v1.2.3 From 1250135046b96f2778bda51517c8a722171a6c16 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 24 Apr 2012 10:55:33 +0200 Subject: conntrackd: generalize file descriptor infrastructure This patch generalizes the select-based file descriptor infrastructure by allowing you to register file descriptors and its callbacks. Instead of hardcoding the descriptors that needs to be checked. Now, struct fds_item contains a callback and pointer to data that is passed to it: struct fds_item { struct list_head head; int fd; + void (*cb)(void *data); + void *data; }; Then, we check which ones are active in the select_main_step() function: list_for_each_entry(cur, &STATE(fds)->list, head) { if (FD_ISSET(cur->fd, &readfds)) cur->cb(cur->data); } And it invoked the corresponding callback. I had to slightly modify the channel infrastructure to fit it into the changes. This modularity is required for the upcoming cthelper support. Signed-off-by: Pablo Neira Ayuso --- include/channel.h | 11 ++- include/conntrackd.h | 3 +- include/fds.h | 4 +- src/channel.c | 5 + src/channel_tcp.c | 1 + src/fds.c | 61 ++++++++++++- src/main.c | 2 +- src/multichannel.c | 7 +- src/run.c | 251 +++++++++++++++++++-------------------------------- src/stats-mode.c | 1 - src/sync-mode.c | 146 +++++++++++++++--------------- src/tcp.c | 1 - 12 files changed, 249 insertions(+), 244 deletions(-) diff --git a/include/channel.h b/include/channel.h index 9b5fad8..46a354f 100644 --- a/include/channel.h +++ b/include/channel.h @@ -35,7 +35,8 @@ struct tcp_channel { #define CHANNEL_F_BUFFERED (1 << 1) #define CHANNEL_F_STREAM (1 << 2) #define CHANNEL_F_ERRORS (1 << 3) -#define CHANNEL_F_MAX (1 << 4) +#define CHANNEL_F_ACCEPT (1 << 4) +#define CHANNEL_F_MAX (1 << 5) union channel_type_conf { struct mcast_conf mcast; @@ -52,8 +53,12 @@ struct channel_conf { struct nlif_handle; +#define CHANNEL_T_DATAGRAM 0 +#define CHANNEL_T_STREAM 1 + struct channel_ops { int headersiz; + int type; void * (*open)(void *conf); void (*close)(void *channel); int (*send)(void *channel, const void *data, int len); @@ -97,6 +102,8 @@ void channel_stats(struct channel *c, int fd); void channel_stats_extended(struct channel *c, int active, struct nlif_handle *h, int fd); +int channel_type(struct channel *c); + #define MULTICHANNEL_MAX 4 struct multichannel { @@ -119,6 +126,6 @@ void multichannel_stats_extended(struct multichannel *m, int multichannel_get_ifindex(struct multichannel *m, int i); int multichannel_get_current_ifindex(struct multichannel *m); void multichannel_set_current_channel(struct multichannel *m, int i); -void multichannel_change_current_channel(struct multichannel *m, int i); +void multichannel_change_current_channel(struct multichannel *m, struct channel *c); #endif /* _CHANNEL_H_ */ diff --git a/include/conntrackd.h b/include/conntrackd.h index 9359dfa..0e203e7 100644 --- a/include/conntrackd.h +++ b/include/conntrackd.h @@ -264,7 +264,6 @@ extern struct ct_general_state st; struct ct_mode { struct internal_handler *internal; int (*init)(void); - void (*run)(fd_set *readfds); int (*local)(int fd, int type, void *data); void (*kill)(void); }; @@ -278,7 +277,7 @@ extern struct ct_mode stats_mode; /* These live in run.c */ void killer(int foo); int init(void); -void run(void); +void select_main_loop(void); /* from read_config_yy.c */ int diff --git a/include/fds.h b/include/fds.h index f3728d7..ed0c8be 100644 --- a/include/fds.h +++ b/include/fds.h @@ -12,11 +12,13 @@ struct fds { struct fds_item { struct list_head head; int fd; + void (*cb)(void *data); + void *data; }; struct fds *create_fds(void); void destroy_fds(struct fds *); -int register_fd(int fd, struct fds *fds); +int register_fd(int fd, void (*cb)(void *data), void *data, struct fds *fds); int unregister_fd(int fd, struct fds *fds); #endif diff --git a/src/channel.c b/src/channel.c index 818bb01..8b7c319 100644 --- a/src/channel.c +++ b/src/channel.c @@ -310,3 +310,8 @@ int channel_accept(struct channel *c) { return c->ops->accept(c); } + +int channel_type(struct channel *c) +{ + return c->ops->type; +} diff --git a/src/channel_tcp.c b/src/channel_tcp.c index f132840..a84603c 100644 --- a/src/channel_tcp.c +++ b/src/channel_tcp.c @@ -137,6 +137,7 @@ channel_tcp_accept(struct channel *c) struct channel_ops channel_tcp = { .headersiz = 40, /* IP header (20 bytes) + TCP header 20 (bytes) */ + .type = CHANNEL_T_STREAM, .open = channel_tcp_open, .close = channel_tcp_close, .send = channel_tcp_send, diff --git a/src/fds.c b/src/fds.c index 347eee1..0b95437 100644 --- a/src/fds.c +++ b/src/fds.c @@ -1,5 +1,5 @@ /* - * (C) 2006-2008 by Pablo Neira Ayuso + * (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 @@ -14,9 +14,16 @@ * 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 #include +#include +#include + +#include "conntrackd.h" +#include "date.h" #include "fds.h" struct fds *create_fds(void) @@ -44,7 +51,7 @@ void destroy_fds(struct fds *fds) free(fds); } -int register_fd(int fd, struct fds *fds) +int register_fd(int fd, void (*cb)(void *data), void *data, struct fds *fds) { struct fds_item *item; @@ -58,7 +65,10 @@ int register_fd(int fd, struct fds *fds) return -1; item->fd = fd; - list_add(&item->head, &fds->list); + item->cb = cb; + item->data = data; + /* Order matters: the descriptors are served in FIFO basis. */ + list_add_tail(&item->head, &fds->list); return 0; } @@ -92,3 +102,48 @@ int unregister_fd(int fd, struct fds *fds) return 0; } +static void select_main_step(struct timeval *next_alarm) +{ + int ret; + fd_set readfds = STATE(fds)->readfds; + struct fds_item *cur, *tmp; + + ret = select(STATE(fds)->maxfd + 1, &readfds, NULL, NULL, next_alarm); + if (ret == -1) { + /* interrupted syscall, retry */ + if (errno == EINTR) + return; + + STATE(stats).select_failed++; + return; + } + + /* signals are racy */ + sigprocmask(SIG_BLOCK, &STATE(block), NULL); + + list_for_each_entry_safe(cur, tmp, &STATE(fds)->list, head) { + if (FD_ISSET(cur->fd, &readfds)) + cur->cb(cur->data); + } + + sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); +} + +void __attribute__((noreturn)) select_main_loop(void) +{ + struct timeval next_alarm; + struct timeval *next = NULL; + + while(1) { + do_gettimeofday(); + + sigprocmask(SIG_BLOCK, &STATE(block), NULL); + if (next != NULL && !timerisset(next)) + next = do_alarm_run(&next_alarm); + else + next = get_next_alarm_run(&next_alarm); + sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); + + select_main_step(next); + } +} diff --git a/src/main.c b/src/main.c index f7803fd..26f6c14 100644 --- a/src/main.c +++ b/src/main.c @@ -406,6 +406,6 @@ int main(int argc, char *argv[]) /* * run main process */ - run(); + select_main_loop(); return 0; } diff --git a/src/multichannel.c b/src/multichannel.c index de69d5c..952b567 100644 --- a/src/multichannel.c +++ b/src/multichannel.c @@ -109,8 +109,9 @@ void multichannel_set_current_channel(struct multichannel *m, int i) m->current = m->channel[i]; } -void multichannel_change_current_channel(struct multichannel *m, int i) +void +multichannel_change_current_channel(struct multichannel *m, struct channel *c) { - if (m->current != m->channel[i]) - m->current = m->channel[i]; + if (m->current != c) + m->current = c; } diff --git a/src/run.c b/src/run.c index 26c1783..171dee5 100644 --- a/src/run.c +++ b/src/run.c @@ -1,5 +1,5 @@ /* - * (C) 2006-2011 by Pablo Neira Ayuso + * (C) 2006-2012 by Pablo Neira Ayuso * (C) 2011 by Vyatta Inc. * * This program is free software; you can redistribute it and/or modify @@ -16,7 +16,7 @@ * 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 + * Part of this code has been sponsored by Vyatta Inc. */ #include "conntrackd.h" @@ -460,6 +460,87 @@ static int exp_get_handler(enum nf_conntrack_msg_type type, return NFCT_CB_CONTINUE; } +/* order received via UNIX socket */ +static void local_cb(void *data) +{ + do_local_server_step(&STATE(local), NULL, local_handler); +} + +/* 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 init(void) { @@ -493,7 +574,7 @@ init(void) dlog(LOG_ERR, "can't open unix socket!"); return -1; } - register_fd(STATE(local).fd, STATE(fds)); + register_fd(STATE(local).fd, local_cb, NULL, STATE(fds)); /* resynchronize (like 'dump' socket) but it also purges old entries */ STATE(resync) = nfct_open(CONFIG(netlink).subsys_id, 0); @@ -507,7 +588,13 @@ init(void) NFCT_T_ALL, STATE(mode)->internal->ct.resync, NULL); - register_fd(nfct_fd(STATE(resync)), STATE(fds)); + 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) { @@ -590,7 +677,7 @@ init(void) nfexp_callback_register2(STATE(event), NFCT_T_ALL, exp_event_handler, NULL); } - register_fd(nfct_fd(STATE(event)), STATE(fds)); + register_fd(nfct_fd(STATE(event)), event_cb, NULL, STATE(fds)); } /* Signals handling */ @@ -618,157 +705,3 @@ init(void) return 0; } - -static void run_events(struct timeval *next_alarm) -{ - int ret; - fd_set readfds = STATE(fds)->readfds; - - ret = select(STATE(fds)->maxfd + 1, &readfds, NULL, NULL, next_alarm); - if (ret == -1) { - /* interrupted syscall, retry */ - if (errno == EINTR) - return; - - STATE(stats).select_failed++; - return; - } - - /* signals are racy */ - sigprocmask(SIG_BLOCK, &STATE(block), NULL); - - /* order received via UNIX socket */ - if (FD_ISSET(STATE(local).fd, &readfds)) - do_local_server_step(&STATE(local), NULL, local_handler); - - /* we have receive an event from ctnetlink */ - if (FD_ISSET(nfct_fd(STATE(event)), &readfds)) { - 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. */ - if (FD_ISSET(nfct_fd(STATE(resync)), &readfds)) { - nfct_catch(STATE(resync)); - if (STATE(mode)->internal->ct.purge) - STATE(mode)->internal->ct.purge(); - } - - if (STATE(mode)->run) - STATE(mode)->run(&readfds); - - sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); -} - -static void run_polling(struct timeval *next_alarm) -{ - int ret; - fd_set readfds = STATE(fds)->readfds; - - ret = select(STATE(fds)->maxfd + 1, &readfds, NULL, NULL, next_alarm); - if (ret == -1) { - /* interrupted syscall, retry */ - if (errno == EINTR) - return; - - STATE(stats).select_failed++; - return; - } - - /* signals are racy */ - sigprocmask(SIG_BLOCK, &STATE(block), NULL); - - /* order received via UNIX socket */ - if (FD_ISSET(STATE(local).fd, &readfds)) - do_local_server_step(&STATE(local), NULL, local_handler); - - /* we requested a dump from the kernel via polling_alarm */ - if (FD_ISSET(nfct_fd(STATE(resync)), &readfds)) - nfct_catch(STATE(resync)); - - if (STATE(mode)->run) - STATE(mode)->run(&readfds); - - sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); -} - -static void __attribute__((noreturn)) -do_run(void (*run_step)(struct timeval *next_alarm)) -{ - struct timeval next_alarm; - struct timeval *next = NULL; - - while(1) { - do_gettimeofday(); - - sigprocmask(SIG_BLOCK, &STATE(block), NULL); - if (next != NULL && !timerisset(next)) - next = do_alarm_run(&next_alarm); - else - next = get_next_alarm_run(&next_alarm); - sigprocmask(SIG_UNBLOCK, &STATE(block), NULL); - - run_step(next); - } -} - -void run(void) -{ - if (CONFIG(flags) & CTD_POLL) { - do_run(run_polling); - } else { - do_run(run_events); - } -} diff --git a/src/stats-mode.c b/src/stats-mode.c index b768033..6b7f08d 100644 --- a/src/stats-mode.c +++ b/src/stats-mode.c @@ -201,7 +201,6 @@ static struct internal_handler internal_cache_stats = { struct ct_mode stats_mode = { .init = init_stats, - .run = NULL, .local = local_handler_stats, .kill = kill_stats, .internal = &internal_cache_stats, diff --git a/src/sync-mode.c b/src/sync-mode.c index 10fdb9e..71c320c 100644 --- a/src/sync-mode.c +++ b/src/sync-mode.c @@ -1,5 +1,5 @@ /* - * (C) 2006-2011 by Pablo Neira Ayuso + * (C) 2006-2012 by Pablo Neira Ayuso * (C) 2011 by Vyatta Inc. * * This program is free software; you can redistribute it and/or modify @@ -78,7 +78,7 @@ static struct nf_expect *msg2exp_alloc(struct nethdr *net, size_t remain) } static void -do_channel_handler_step(int i, struct nethdr *net, size_t remain) +do_channel_handler_step(struct channel *c, struct nethdr *net, size_t remain) { struct nf_conntrack *ct = NULL; struct nf_expect *exp = NULL; @@ -91,10 +91,10 @@ do_channel_handler_step(int i, struct nethdr *net, size_t remain) switch (STATE_SYNC(sync)->recv(net)) { case MSG_DATA: - multichannel_change_current_channel(STATE_SYNC(channel), i); + multichannel_change_current_channel(STATE_SYNC(channel), c); break; case MSG_CTL: - multichannel_change_current_channel(STATE_SYNC(channel), i); + multichannel_change_current_channel(STATE_SYNC(channel), c); return; case MSG_BAD: STATE_SYNC(error).msg_rcv_malformed++; @@ -175,7 +175,7 @@ static int channel_stream(struct channel *m, const char *ptr, ssize_t remain) } /* handler for messages received */ -static int channel_handler_routine(struct channel *m, int i) +static int channel_handler_routine(struct channel *m) { ssize_t numbytes; ssize_t remain, pending = cur - __net; @@ -242,7 +242,7 @@ static int channel_handler_routine(struct channel *m, int i) HDR_NETWORK2HOST(net); - do_channel_handler_step(i, net, remain); + do_channel_handler_step(m, net, remain); ptr += net->len; remain -= net->len; } @@ -250,12 +250,13 @@ static int channel_handler_routine(struct channel *m, int i) } /* handler for messages received */ -static void channel_handler(struct channel *m, int i) +static void channel_handler(void *data) { + struct channel *c = data; int k; for (k=0; kinternal->ct.flush(); } +static void commit_cb(void *data) +{ + int ret; + + read_evfd(STATE_SYNC(commit).evfd); + + ret = STATE_SYNC(commit).rq[0].cb(STATE_SYNC(commit).h, 0); + if (ret == 0) { + /* we still have things in the callback queue. */ + if (STATE_SYNC(commit).rq[1].cb) { + int fd = STATE_SYNC(commit).clientfd; + + STATE_SYNC(commit).rq[0].cb = + STATE_SYNC(commit).rq[1].cb; + + STATE_SYNC(commit).rq[1].cb = NULL; + + STATE_SYNC(commit).clientfd = -1; + STATE_SYNC(commit).rq[0].cb(STATE_SYNC(commit).h, fd); + } else { + /* Close the client socket now, we're done. */ + close(STATE_SYNC(commit).clientfd); + STATE_SYNC(commit).clientfd = -1; + } + } +} + +static void channel_accept_cb(void *data) +{ + struct channel *c = data; + int fd; + + fd = channel_accept(data); + if (fd < 0) + return; + + register_fd(fd, channel_handler, c, STATE(fds)); +} + +static void tx_queue_cb(void *data) +{ + STATE_SYNC(sync)->xmit(); + + /* flush pending messages */ + multichannel_send_flush(STATE_SYNC(channel)); +} + static int init_sync(void) { int i; @@ -370,8 +418,19 @@ static int init_sync(void) for (i=0; ichannel_num; i++) { int fd = channel_get_fd(STATE_SYNC(channel)->channel[i]); fcntl(fd, F_SETFL, O_NONBLOCK); - if (register_fd(fd, STATE(fds)) == -1) - return -1; + + switch(channel_type(STATE_SYNC(channel)->channel[i])) { + case CHANNEL_T_STREAM: + register_fd(fd, channel_accept_cb, + STATE_SYNC(channel)->channel[i], + STATE(fds)); + break; + case CHANNEL_T_DATAGRAM: + register_fd(fd, channel_handler, + STATE_SYNC(channel)->channel[i], + STATE(fds)); + break; + } } STATE_SYNC(interface) = nl_init_interface_handler(); @@ -379,7 +438,8 @@ static int init_sync(void) dlog(LOG_ERR, "can't open interface watcher"); return -1; } - if (register_fd(nlif_fd(STATE_SYNC(interface)), STATE(fds)) == -1) + if (register_fd(nlif_fd(STATE_SYNC(interface)), + interface_handler, NULL, STATE(fds)) == -1) return -1; STATE_SYNC(tx_queue) = queue_create("txqueue", INT_MAX, QUEUE_F_EVFD); @@ -387,8 +447,8 @@ static int init_sync(void) dlog(LOG_ERR, "cannot create tx queue"); return -1; } - if (register_fd(queue_get_eventfd(STATE_SYNC(tx_queue)), - STATE(fds)) == -1) + if (register_fd(queue_get_eventfd(STATE_SYNC(tx_queue)), + tx_queue_cb, NULL, STATE(fds)) == -1) return -1; STATE_SYNC(commit).h = nfct_open(CONFIG(netlink).subsys_id, 0); @@ -404,7 +464,7 @@ static int init_sync(void) return -1; } if (register_fd(get_read_evfd(STATE_SYNC(commit).evfd), - STATE(fds)) == -1) { + commit_cb, NULL, STATE(fds)) == -1) { return -1; } STATE_SYNC(commit).clientfd = -1; @@ -417,61 +477,6 @@ static int init_sync(void) return 0; } -static void channel_check(struct channel *c, int i, fd_set *readfds) -{ - /* In case that this channel is connection-oriented. */ - if (channel_accept_isset(c, readfds)) - channel_accept(c); - - /* For data handling. */ - if (channel_isset(c, readfds)) - channel_handler(c, i); -} - -static void run_sync(fd_set *readfds) -{ - int i; - - for (i=0; ichannel_num; i++) - channel_check(STATE_SYNC(channel)->channel[i], i, readfds); - - if (FD_ISSET(queue_get_eventfd(STATE_SYNC(tx_queue)), readfds)) - STATE_SYNC(sync)->xmit(); - - if (FD_ISSET(nlif_fd(STATE_SYNC(interface)), readfds)) - interface_handler(); - - if (FD_ISSET(get_read_evfd(STATE_SYNC(commit).evfd), readfds)) { - int ret; - - read_evfd(STATE_SYNC(commit).evfd); - - ret = STATE_SYNC(commit).rq[0].cb(STATE_SYNC(commit).h, 0); - if (ret == 0) { - /* we still have things in the callback queue. */ - if (STATE_SYNC(commit).rq[1].cb) { - int fd = STATE_SYNC(commit).clientfd; - - STATE_SYNC(commit).rq[0].cb = - STATE_SYNC(commit).rq[1].cb; - - STATE_SYNC(commit).rq[1].cb = NULL; - - STATE_SYNC(commit).clientfd = -1; - STATE_SYNC(commit).rq[0].cb( - STATE_SYNC(commit).h, fd); - } else { - /* Close the client socket now, we're done. */ - close(STATE_SYNC(commit).clientfd); - STATE_SYNC(commit).clientfd = -1; - } - } - } - - /* flush pending messages */ - multichannel_send_flush(STATE_SYNC(channel)); -} - static void kill_sync(void) { STATE(mode)->internal->close(); @@ -729,7 +734,6 @@ static int local_handler_sync(int fd, int type, void *data) struct ct_mode sync_mode = { .init = init_sync, - .run = run_sync, .local = local_handler_sync, .kill = kill_sync, /* the internal handler is set in run-time. */ diff --git a/src/tcp.c b/src/tcp.c index f6b05ef..af27c46 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -264,7 +264,6 @@ int tcp_accept(struct tcp_sock *m) m->client_fd = ret; m->state = TCP_SERVER_CONNECTED; - register_fd(m->client_fd, STATE(fds)); } return m->client_fd; } -- cgit v1.2.3 From e47233151ca5098b268281329b119a398918d75f 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 --- include/conntrackd.h | 7 +- src/Makefile.am | 1 + src/ctnl.c | 522 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/run.c | 477 ++-------------------------------------------- src/sync-mode.c | 3 - 5 files changed, 543 insertions(+), 467 deletions(-) create mode 100644 src/ctnl.c diff --git a/include/conntrackd.h b/include/conntrackd.h index 0e203e7..ec720ec 100644 --- a/include/conntrackd.h +++ b/include/conntrackd.h @@ -268,7 +268,12 @@ struct ct_mode { void (*kill)(void); }; -/* conntrackd modes */ +/* basic ctnl functions */ +void ctnl_kill(void); +int ctnl_local(int fd, int type, void *data); +int ctnl_init(void); + +/* conntrackd ctnl modes */ extern struct ct_mode sync_mode; extern struct ct_mode stats_mode; diff --git a/src/Makefile.am b/src/Makefile.am index 5dbdef3..bbea176 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,6 +20,7 @@ conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ filter.c fds.c event.c process.c origin.c date.c \ cache.c cache-ct.c cache-exp.c \ cache_timer.c \ + ctnl.c \ sync-mode.c sync-alarm.c sync-ftfw.c sync-notrack.c \ traffic_stats.c stats-mode.c \ network.c cidr.c \ diff --git a/src/ctnl.c b/src/ctnl.c new file mode 100644 index 0000000..019c7e8 --- /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(STATE(flush)); + 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; +} diff --git a/src/run.c b/src/run.c index 171dee5..852bec6 100644 --- a/src/run.c +++ b/src/run.c @@ -45,24 +45,12 @@ void killer(int foo) /* no signals while handling signals */ sigprocmask(SIG_BLOCK, &STATE(block), NULL); - 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)); local_server_destroy(&STATE(local)); - STATE(mode)->kill(); - if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) { - nfct_close(STATE(dump)); - } - destroy_fds(STATE(fds)); + if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) + ctnl_kill(); + destroy_fds(STATE(fds)); unlink(CONFIG(lockfile)); dlog(LOG_NOTICE, "---- shutdown received ----"); close_log(); @@ -187,62 +175,6 @@ static void dump_stats_runtime(int fd) send(fd, buf, size, 0); } -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(STATE(flush)); - 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"); - } -} - static int local_handler(int fd, void *data) { int ret = LOCAL_RET_OK; @@ -253,26 +185,9 @@ static int local_handler(int fd, void *data) return 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; + case KILL: + killer(0); + break; case STATS_RUNTIME: dump_stats_runtime(fd); break; @@ -281,183 +196,10 @@ static int local_handler(int fd, void *data) 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; -} + if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) + return ctnl_local(fd, type, data); -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; + return ret; } /* order received via UNIX socket */ @@ -466,109 +208,17 @@ static void local_cb(void *data) do_local_server_step(&STATE(local), NULL, local_handler); } -/* 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 init(void) { do_gettimeofday(); - 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; - } - STATE(fds) = create_fds(); if (STATE(fds) == NULL) { dlog(LOG_ERR, "can't create file descriptor pool"); return -1; } - /* Initialization */ - if (STATE(mode)->init() == -1) { - dlog(LOG_ERR, "initialization failed"); - return -1; - } - /* local UNIX socket */ if (local_server_create(&STATE(local), &CONFIG(local)) == -1) { dlog(LOG_ERR, "can't open unix socket!"); @@ -576,110 +226,6 @@ init(void) } register_fd(STATE(local).fd, local_cb, NULL, STATE(fds)); - /* 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)); - } - /* Signals handling */ sigemptyset(&STATE(block)); sigaddset(&STATE(block), SIGTERM); @@ -699,6 +245,11 @@ init(void) if (signal(SIGCHLD, child) == SIG_ERR) return -1; + /* Initialization */ + if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) + if (ctnl_init() < 0) + return -1; + time(&STATE(stats).daemon_start_time); dlog(LOG_NOTICE, "initialization completed"); diff --git a/src/sync-mode.c b/src/sync-mode.c index 71c320c..b5707d5 100644 --- a/src/sync-mode.c +++ b/src/sync-mode.c @@ -629,9 +629,6 @@ static int local_handler_sync(int fd, int type, void *data) dlog(LOG_NOTICE, "flushing external cache"); STATE_SYNC(external)->ct.flush(); break; - case KILL: - killer(0); - break; case STATS: STATE(mode)->internal->ct.stats(fd); STATE_SYNC(external)->ct.stats(fd); -- cgit v1.2.3 From e96bfcd1b9f79cb3642b365a584359b1672f2ac0 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Tue, 15 May 2012 01:51:29 +0200 Subject: conntrackd: add cthelper infrastructure (+ example FTP helper) This patch adds the user-space helper infrastructure. It also contains the implementation of the FTP helper in user-space. There's one example file that you can use to configure conntrackd as user-space connection tracking helper under: doc/helper/conntrackd.conf Signed-off-by: Pablo Neira Ayuso --- configure.ac | 9 +- doc/helper/conntrackd.conf | 82 ++++ include/Makefile.am | 3 +- include/conntrackd.h | 21 +- include/helper.h | 104 +++++ include/linux/netfilter/Makefile.am | 2 +- include/linux/netfilter/nfnetlink_cthelper.h | 55 +++ include/linux/netfilter/nfnetlink_queue.h | 98 +++++ include/myct.h | 43 ++ include/nfct.h | 10 + include/stack.h | 28 ++ src/Makefile.am | 24 +- src/cthelper.c | 521 ++++++++++++++++++++++ src/expect.c | 212 +++++++++ src/helpers.c | 76 ++++ src/helpers/Makefile.am | 9 + src/helpers/ftp.c | 599 ++++++++++++++++++++++++++ src/main.c | 3 +- src/nfct-extensions/helper.c | 619 +++++++++++++++++++++++++++ src/nfct.c | 6 + src/read_config_lex.l | 5 + src/read_config_yy.y | 200 +++++++++ src/run.c | 11 + src/stack.c | 56 +++ src/utils.c | 243 +++++++++++ 25 files changed, 3027 insertions(+), 12 deletions(-) create mode 100644 doc/helper/conntrackd.conf create mode 100644 include/helper.h create mode 100644 include/linux/netfilter/nfnetlink_cthelper.h create mode 100644 include/linux/netfilter/nfnetlink_queue.h create mode 100644 include/myct.h create mode 100644 include/stack.h create mode 100644 src/cthelper.c create mode 100644 src/expect.c create mode 100644 src/helpers.c create mode 100644 src/helpers/Makefile.am create mode 100644 src/helpers/ftp.c create mode 100644 src/nfct-extensions/helper.c create mode 100644 src/stack.c create mode 100644 src/utils.c diff --git a/configure.ac b/configure.ac index 07a1fa1..f628521 100644 --- a/configure.ac +++ b/configure.ac @@ -9,6 +9,9 @@ AM_INIT_AUTOMAKE([-Wall foreign subdir-objects dnl kernel style compile messages m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) +AC_SEARCH_LIBS([dlopen], [dl], [libdl_LIBS="$LIBS"; LIBS=""]) +AC_SUBST([libdl_LIBS]) + AC_PROG_CC AC_DISABLE_STATIC AM_PROG_LIBTOOL @@ -52,9 +55,11 @@ else fi PKG_CHECK_MODULES([LIBNFNETLINK], [libnfnetlink >= 1.0.0]) -PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.0]) +PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.3]) PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.1]) PKG_CHECK_MODULES([LIBNETFILTER_CTTIMEOUT], [libnetfilter_cttimeout >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_CTHELPER], [libnetfilter_cthelper >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_QUEUE], [libnetfilter_queue >= 1.0.0]) AC_CHECK_HEADERS([linux/capability.h],, [AC_MSG_ERROR([Cannot find linux/capabibility.h])]) @@ -114,5 +119,5 @@ dnl debug/src/Makefile dnl extensions/Makefile dnl src/Makefile]) -AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/linux/Makefile include/linux/netfilter/Makefile extensions/Makefile]) +AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/linux/Makefile include/linux/netfilter/Makefile extensions/Makefile src/helpers/Makefile]) AC_OUTPUT diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf new file mode 100644 index 0000000..711b309 --- /dev/null +++ b/doc/helper/conntrackd.conf @@ -0,0 +1,82 @@ +# +# Helper settings +# + +Helper { + # Before this, you have to make sure you have registered the `ftp' + # user-space helper stub via: + # + # nfct helper add ftp inet tcp + # + Type ftp inet tcp { + # + # Set NFQUEUE number you want to use to receive traffic from + # the kernel. + # + QueueNum 0 + # + # Set the Expectation policy for this helper. + # + Policy ftp { + # + # Maximum number of simultaneous expectations + # + ExpectMax 1 + # + # Maximum living time for one expectation (in seconds). + # + ExpectTimeout 300 + } + } +} + +# +# General settings +# +General { + # + # Set the nice value of the daemon, this value goes from -20 + # (most favorable scheduling) to 19 (least favorable). Using a + # very low value reduces the chances to lose state-change events. + # Default is 0 but this example file sets it to most favourable + # scheduling as this is generally a good idea. See man nice(1) for + # more information. + # + Nice -20 + + # + # Select a different scheduler for the daemon, you can select between + # RR and FIFO and the process priority (minimum is 0, maximum is 99). + # See man sched_setscheduler(2) for more information. Using a RT + # scheduler reduces the chances to overrun the Netlink buffer. + # + # Scheduler { + # Type FIFO + # Priority 99 + # } + + # + # Logfile: on (/var/log/conntrackd.log), off, or a filename + # Default: off + # + LogFile on + + # + # Syslog: on, off or a facility name (daemon (default) or local0..7) + # Default: off + # + #Syslog on + + # + # Lockfile + # + LockFile /var/lock/conntrack.lock + + # + # Unix socket configuration + # + UNIX { + Path /var/run/conntrackd.ctl + Backlog 20 + } +} diff --git a/include/Makefile.am b/include/Makefile.am index 138005d..6bd0f7f 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -5,5 +5,6 @@ noinst_HEADERS = alarm.h jhash.h cache.h linux_list.h linux_rbtree.h \ debug.h log.h hash.h mcast.h conntrack.h \ network.h filter.h queue.h vector.h cidr.h \ traffic_stats.h netlink.h fds.h event.h bitops.h channel.h \ - process.h origin.h internal.h external.h date.h nfct.h + process.h origin.h internal.h external.h date.h nfct.h \ + helper.h myct.h stack.h diff --git a/include/conntrackd.h b/include/conntrackd.h index ec720ec..19e613c 100644 --- a/include/conntrackd.h +++ b/include/conntrackd.h @@ -69,6 +69,7 @@ #define CTD_SYNC_NOTRACK (1UL << 4) #define CTD_POLL (1UL << 5) #define CTD_EXPECT (1UL << 6) +#define CTD_HELPER (1UL << 7) /* FILENAME_MAX is 4096 on my system, perhaps too much? */ #ifndef FILENAME_MAXLEN @@ -134,6 +135,9 @@ struct ct_conf { int syslog_facility; size_t buffer_size; } stats; + struct { + struct list_head list; + } cthelper; }; #define STATE(x) st.x @@ -252,13 +256,21 @@ struct ct_stats_state { struct cache *cache; /* internal events cache (netlink) */ }; -union ct_state { +#define STATE_CTH(x) state.cthelper->x + +struct ct_helper_state { + struct mnl_socket *nl; + uint32_t portid; +}; + +struct ct_state { struct ct_sync_state *sync; struct ct_stats_state *stats; + struct ct_helper_state *cthelper; }; extern struct ct_conf conf; -extern union ct_state state; +extern struct ct_state state; extern struct ct_general_state st; struct ct_mode { @@ -273,6 +285,11 @@ void ctnl_kill(void); int ctnl_local(int fd, int type, void *data); int ctnl_init(void); +/* basic cthelper functions */ +void cthelper_kill(void); +int cthelper_local(int fd, int type, void *data); +int cthelper_init(void); + /* conntrackd ctnl modes */ extern struct ct_mode sync_mode; extern struct ct_mode stats_mode; diff --git a/include/helper.h b/include/helper.h new file mode 100644 index 0000000..ce5ae9b --- /dev/null +++ b/include/helper.h @@ -0,0 +1,104 @@ +#ifndef _CTD_HELPER_H_ +#define _CTD_HELPER_H_ + +#include +#include "linux_list.h" +#include "myct.h" + +#include + +struct pkt_buff; + +#define CTD_HELPER_NAME_LEN 16 +#define CTD_HELPER_POLICY_MAX 4 + +struct ctd_helper_policy { + char name[CTD_HELPER_NAME_LEN]; + uint32_t expect_timeout; + uint32_t expect_max; +}; + +struct ctd_helper { + struct list_head head; + char name[CTD_HELPER_NAME_LEN]; + uint8_t l4proto; + int (*cb)(struct pkt_buff *pkt, + uint32_t protoff, + struct myct *ct, + u_int32_t ctinfo); + + struct ctd_helper_policy policy[CTD_HELPER_POLICY_MAX]; + + int priv_data_len; +}; + +struct ctd_helper_instance { + struct list_head head; + uint32_t queue_num; + uint16_t l3proto; + uint8_t l4proto; + struct ctd_helper *helper; + struct ctd_helper_policy policy[CTD_HELPER_POLICY_MAX]; +}; + +extern int cthelper_expect_init(struct nf_expect *exp, struct nf_conntrack *master, uint32_t class, union nfct_attr_grp_addr *saddr, union nfct_attr_grp_addr *daddr, uint8_t l4proto, uint16_t *sport, uint16_t *dport); +extern int cthelper_add_expect(struct nf_expect *exp); +extern int cthelper_del_expect(struct nf_expect *exp); + +extern void cthelper_get_addr_src(struct nf_conntrack *ct, int dir, union nfct_attr_grp_addr *addr); +extern void cthelper_get_addr_dst(struct nf_conntrack *ct, int dir, union nfct_attr_grp_addr *addr); + +extern int in4_pton(const char *src, int srclen, uint8_t *dst, int delim, const char **end); +extern int in6_pton(const char *src, int srclen, uint8_t *dst, int delim, const char **end); + +extern void helper_register(struct ctd_helper *helper); +struct ctd_helper *helper_find(const char *libdir_path, const char *name, uint8_t l4proto, int flags); + +#define min_t(type, x, y) ({ \ + type __min1 = (x); \ + type __min2 = (y); \ + __min1 < __min2 ? __min1: __min2; }) + +#define max_t(type, x, y) ({ \ + type __max1 = (x); \ + type __max2 = (y); \ + __max1 > __max2 ? __max1: __max2; }) + +#define ARRAY_SIZE MNL_ARRAY_SIZE + +enum ip_conntrack_dir { + IP_CT_DIR_ORIGINAL, + IP_CT_DIR_REPLY, + IP_CT_DIR_MAX +}; + +/* Connection state tracking for netfilter. This is separated from, + but required by, the NAT layer; it can also be used by an iptables + extension. */ +enum ip_conntrack_info { + /* Part of an established connection (either direction). */ + IP_CT_ESTABLISHED, + + /* Like NEW, but related to an existing connection, or ICMP error + (in either direction). */ + IP_CT_RELATED, + + /* Started a new connection to track (only + IP_CT_DIR_ORIGINAL); may be a retransmission. */ + IP_CT_NEW, + + /* >= this indicates reply direction */ + IP_CT_IS_REPLY, + + IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY, + IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY, + IP_CT_NEW_REPLY = IP_CT_NEW + IP_CT_IS_REPLY, + /* Number of distinct IP_CT types (no NEW in reply dirn). */ + IP_CT_NUMBER = IP_CT_IS_REPLY * 2 - 1 +}; + +#define CTINFO2DIR(ctinfo) ((ctinfo) >= IP_CT_IS_REPLY ? IP_CT_DIR_REPLY : IP_CT_DIR_ORIGINAL) + +#define pr_debug printf + +#endif diff --git a/include/linux/netfilter/Makefile.am b/include/linux/netfilter/Makefile.am index 84315e3..6574060 100644 --- a/include/linux/netfilter/Makefile.am +++ b/include/linux/netfilter/Makefile.am @@ -1 +1 @@ -noinst_HEADERS = nfnetlink.h nfnetlink_cttimeout.h +noinst_HEADERS = nfnetlink.h nfnetlink_cttimeout.h nfnetlink_queue.h nfnetlink_cthelper.h diff --git a/include/linux/netfilter/nfnetlink_cthelper.h b/include/linux/netfilter/nfnetlink_cthelper.h new file mode 100644 index 0000000..33659f6 --- /dev/null +++ b/include/linux/netfilter/nfnetlink_cthelper.h @@ -0,0 +1,55 @@ +#ifndef _NFNL_CTHELPER_H_ +#define _NFNL_CTHELPER_H_ + +#define NFCT_HELPER_STATUS_DISABLED 0 +#define NFCT_HELPER_STATUS_ENABLED 1 + +enum nfnl_acct_msg_types { + NFNL_MSG_CTHELPER_NEW, + NFNL_MSG_CTHELPER_GET, + NFNL_MSG_CTHELPER_DEL, + NFNL_MSG_CTHELPER_MAX +}; + +enum nfnl_cthelper_type { + NFCTH_UNSPEC, + NFCTH_NAME, + NFCTH_TUPLE, + NFCTH_QUEUE_NUM, + NFCTH_POLICY, + NFCTH_PRIV_DATA_LEN, + NFCTH_STATUS, + __NFCTH_MAX +}; +#define NFCTH_MAX (__NFCTH_MAX - 1) + +enum nfnl_cthelper_policy_type { + NFCTH_POLICY_SET_UNSPEC, + NFCTH_POLICY_SET_NUM, + NFCTH_POLICY_SET, + NFCTH_POLICY_SET1 = NFCTH_POLICY_SET, + NFCTH_POLICY_SET2, + NFCTH_POLICY_SET3, + NFCTH_POLICY_SET4, + __NFCTH_POLICY_SET_MAX +}; +#define NFCTH_POLICY_SET_MAX (__NFCTH_POLICY_SET_MAX - 1) + +enum nfnl_cthelper_pol_type { + NFCTH_POLICY_UNSPEC, + NFCTH_POLICY_NAME, + NFCTH_POLICY_EXPECT_MAX, + NFCTH_POLICY_EXPECT_TIMEOUT, + __NFCTH_POLICY_MAX +}; +#define NFCTH_POLICY_MAX (__NFCTH_POLICY_MAX - 1) + +enum nfnl_cthelper_tuple_type { + NFCTH_TUPLE_UNSPEC, + NFCTH_TUPLE_L3PROTONUM, + NFCTH_TUPLE_L4PROTONUM, + __NFCTH_TUPLE_MAX, +}; +#define NFCTH_TUPLE_MAX (__NFCTH_TUPLE_MAX - 1) + +#endif /* _NFNL_CTHELPER_H */ diff --git a/include/linux/netfilter/nfnetlink_queue.h b/include/linux/netfilter/nfnetlink_queue.h new file mode 100644 index 0000000..da44b33 --- /dev/null +++ b/include/linux/netfilter/nfnetlink_queue.h @@ -0,0 +1,98 @@ +#ifndef _NFNETLINK_QUEUE_H +#define _NFNETLINK_QUEUE_H + +#include +#include + +enum nfqnl_msg_types { + NFQNL_MSG_PACKET, /* packet from kernel to userspace */ + NFQNL_MSG_VERDICT, /* verdict from userspace to kernel */ + NFQNL_MSG_CONFIG, /* connect to a particular queue */ + NFQNL_MSG_VERDICT_BATCH, /* batchv from userspace to kernel */ + + NFQNL_MSG_MAX +}; + +struct nfqnl_msg_packet_hdr { + __be32 packet_id; /* unique ID of packet in queue */ + __be16 hw_protocol; /* hw protocol (network order) */ + __u8 hook; /* netfilter hook */ +} __attribute__ ((packed)); + +struct nfqnl_msg_packet_hw { + __be16 hw_addrlen; + __u16 _pad; + __u8 hw_addr[8]; +}; + +struct nfqnl_msg_packet_timestamp { + __aligned_be64 sec; + __aligned_be64 usec; +}; + +enum nfqnl_attr_type { + NFQA_UNSPEC, + NFQA_PACKET_HDR, + NFQA_VERDICT_HDR, /* nfqnl_msg_verdict_hrd */ + NFQA_MARK, /* __u32 nfmark */ + NFQA_TIMESTAMP, /* nfqnl_msg_packet_timestamp */ + NFQA_IFINDEX_INDEV, /* __u32 ifindex */ + NFQA_IFINDEX_OUTDEV, /* __u32 ifindex */ + NFQA_IFINDEX_PHYSINDEV, /* __u32 ifindex */ + NFQA_IFINDEX_PHYSOUTDEV, /* __u32 ifindex */ + NFQA_HWADDR, /* nfqnl_msg_packet_hw */ + NFQA_PAYLOAD, /* opaque data payload */ + NFQA_CT, /* nf_conntrack_netlink.h */ + NFQA_CT_INFO, /* enum ip_conntrack_info */ + + __NFQA_MAX +}; +#define NFQA_MAX (__NFQA_MAX - 1) + +struct nfqnl_msg_verdict_hdr { + __be32 verdict; + __be32 id; +}; + + +enum nfqnl_msg_config_cmds { + NFQNL_CFG_CMD_NONE, + NFQNL_CFG_CMD_BIND, + NFQNL_CFG_CMD_UNBIND, + NFQNL_CFG_CMD_PF_BIND, + NFQNL_CFG_CMD_PF_UNBIND, +}; + +struct nfqnl_msg_config_cmd { + __u8 command; /* nfqnl_msg_config_cmds */ + __u8 _pad; + __be16 pf; /* AF_xxx for PF_[UN]BIND */ +}; + +enum nfqnl_config_mode { + NFQNL_COPY_NONE, + NFQNL_COPY_META, + NFQNL_COPY_PACKET, +}; + +struct nfqnl_msg_config_params { + __be32 copy_range; + __u8 copy_mode; /* enum nfqnl_config_mode */ +} __attribute__ ((packed)); + +enum nfqnl_flags { + NFQNL_F_NONE = 0, + NFQNL_F_CONNTRACK = (1 << 0), +}; + +enum nfqnl_attr_config { + NFQA_CFG_UNSPEC, + NFQA_CFG_CMD, /* nfqnl_msg_config_cmd */ + NFQA_CFG_PARAMS, /* nfqnl_msg_config_params */ + NFQA_CFG_QUEUE_MAXLEN, /* __u32 */ + NFQA_CFG_FLAGS, /* __u32 */ + __NFQA_CFG_MAX +}; +#define NFQA_CFG_MAX (__NFQA_CFG_MAX-1) + +#endif /* _NFNETLINK_QUEUE_H */ diff --git a/include/myct.h b/include/myct.h new file mode 100644 index 0000000..45d9f29 --- /dev/null +++ b/include/myct.h @@ -0,0 +1,43 @@ +#ifndef _MYCT_H_ +#define _MYCT_H_ + +#include "linux_list.h" + +#include + +struct nf_conntrack; + +enum { + MYCT_NONE = 0, + MYCT_ESTABLISHED = (1 << 0), +}; + +enum { + MYCT_DIR_ORIG = 0, + MYCT_DIR_REPL, + MYCT_DIR_MAX, +}; + +union myct_proto { + uint16_t port; + uint16_t all; +}; + +struct myct_man { + union nfct_attr_grp_addr u3; + union myct_proto u; + uint16_t l3num; + uint8_t protonum; +}; + +struct myct_tuple { + struct myct_man src; + struct myct_man dst; +}; + +struct myct { + struct nf_conntrack *ct; + void *priv_data; +}; + +#endif diff --git a/include/nfct.h b/include/nfct.h index d6271cf..5548b03 100644 --- a/include/nfct.h +++ b/include/nfct.h @@ -4,6 +4,7 @@ enum { NFCT_SUBSYS_NONE = 0, NFCT_SUBSYS_TIMEOUT, + NFCT_SUBSYS_HELPER, NFCT_SUBSYS_VERSION, NFCT_SUBSYS_HELP, }; @@ -15,6 +16,7 @@ enum { NFCT_CMD_DELETE, NFCT_CMD_GET, NFCT_CMD_FLUSH, + NFCT_CMD_DISABLE, }; void nfct_perror(const char *msg); @@ -26,4 +28,12 @@ int nfct_cmd_timeout_delete(int argc, char *argv[]); int nfct_cmd_timeout_get(int argc, char *argv[]); int nfct_cmd_timeout_flush(int argc, char *argv[]); +int nfct_cmd_helper_parse_params(int argc, char *argv[]); +int nfct_cmd_helper_list(int argc, char *argv[]); +int nfct_cmd_helper_add(int argc, char *argv[]); +int nfct_cmd_helper_delete(int argc, char *argv[]); +int nfct_cmd_helper_get(int argc, char *argv[]); +int nfct_cmd_helper_flush(int argc, char *argv[]); +int nfct_cmd_helper_disable(int argc, char *argv[]); + #endif diff --git a/include/stack.h b/include/stack.h new file mode 100644 index 0000000..512a30f --- /dev/null +++ b/include/stack.h @@ -0,0 +1,28 @@ +#ifndef _STACK_H_ +#define _STACK_H_ + +#include "linux_list.h" + +struct stack { + struct list_head list; + int items; +}; + +static inline void stack_init(struct stack *s) +{ + INIT_LIST_HEAD(&s->list); +} + +struct stack_item { + struct list_head head; + int type; + int data_len; + char data[0]; +}; + +struct stack_item *stack_item_alloc(int type, size_t data_len); +void stack_item_free(struct stack_item *e); +void stack_item_push(struct stack *s, struct stack_item *e); +struct stack_item *stack_item_pop(struct stack *s, int type); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index bbea176..0b047e9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,7 @@ include $(top_srcdir)/Make_global.am +SUBDIRS = helpers + AM_YFLAGS = -d CLEANFILES = read_config_yy.c read_config_lex.c @@ -10,17 +12,24 @@ conntrack_SOURCES = conntrack.c conntrack_LDADD = ../extensions/libct_proto_tcp.la ../extensions/libct_proto_udp.la ../extensions/libct_proto_udplite.la ../extensions/libct_proto_icmp.la ../extensions/libct_proto_icmpv6.la ../extensions/libct_proto_sctp.la ../extensions/libct_proto_dccp.la ../extensions/libct_proto_gre.la ../extensions/libct_proto_unknown.la ${LIBNETFILTER_CONNTRACK_LIBS} nfct_SOURCES = nfct.c \ - nfct-extensions/timeout.c + helpers.c \ + nfct-extensions/timeout.c \ + nfct-extensions/helper.c + nfct_LDADD = ${LIBMNL_LIBS} \ ${LIBNETFILTER_CONNTRACK_LIBS} \ - ${LIBNETFILTER_CTTIMEOUT_LIBS} + ${LIBNETFILTER_CTTIMEOUT_LIBS} \ + ${LIBNETFILTER_CTHELPER_LIBS} \ + ${libdl_LIBS} + +nfct_LDFLAGS = -export-dynamic conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ local.c log.c mcast.c udp.c netlink.c vector.c \ filter.c fds.c event.c process.c origin.c date.c \ cache.c cache-ct.c cache-exp.c \ cache_timer.c \ - ctnl.c \ + ctnl.c cthelper.c \ sync-mode.c sync-alarm.c sync-ftfw.c sync-notrack.c \ traffic_stats.c stats-mode.c \ network.c cidr.c \ @@ -29,11 +38,16 @@ conntrackd_SOURCES = alarm.c main.c run.c hash.c queue.c rbtree.c \ tcp.c channel_tcp.c \ external_cache.c external_inject.c \ internal_cache.c internal_bypass.c \ - read_config_yy.y read_config_lex.l + read_config_yy.y read_config_lex.l \ + stack.c helpers.c utils.c expect.c # yacc and lex generate dirty code read_config_yy.o read_config_lex.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -Wno-implicit-function-declaration -Wno-nested-externs -Wno-undef -Wno-redundant-decls -conntrackd_LDADD = ${LIBNETFILTER_CONNTRACK_LIBS} +conntrackd_LDADD = ${LIBMNL_LIBS} ${LIBNETFILTER_CONNTRACK_LIBS} \ + ${LIBNETFILTER_QUEUE_LIBS} ${LIBNETFILTER_CTHELPER_LIBS} \ + ${libdl_LIBS} + +conntrackd_LDFLAGS = -export-dynamic EXTRA_DIST = read_config_yy.h diff --git a/src/cthelper.c b/src/cthelper.c new file mode 100644 index 0000000..7624dc0 --- /dev/null +++ b/src/cthelper.c @@ -0,0 +1,521 @@ +/* + * (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. + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "conntrackd.h" +#include "log.h" +#include "fds.h" +#include "helper.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef __aligned_be64 +#define __aligned_be64 unsigned long long __attribute__((aligned(8))) +#endif + +#include + +#include +#include +#include +#include +#include + +void cthelper_kill(void) +{ + mnl_socket_close(STATE_CTH(nl)); + free(state.cthelper); +} + +int cthelper_local(int fd, int type, void *data) +{ + /* No services to obtain information on helpers yet, sorry */ + return LOCAL_RET_OK; +} + +static struct nlmsghdr * +nfq_build_header(char *buf, int type, uint32_t queue_num) +{ + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | type; + nlh->nlmsg_flags = NLM_F_REQUEST; + + struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_UNSPEC; + nfg->version = NFNETLINK_V0; + nfg->res_id = htons(queue_num); + + return nlh; +} + +static int +pkt_get(void *pkt, uint32_t pktlen, uint16_t proto, uint32_t *protoff) +{ + switch(proto) { + case ETHERTYPE_IP: { + struct iphdr *ip = (struct iphdr *) pkt; + + /* No room for IPv4 header. */ + if (pktlen < sizeof(struct iphdr)) { + dlog(LOG_ERR, "no room for IPv4 header"); + return -1; + } + + /* this is not IPv4, skip. */ + if (ip->version != 4) { + dlog(LOG_ERR, "not IPv4, skipping"); + return -1; + } + + *protoff = 4 * ip->ihl; + + switch (ip->protocol) { + case IPPROTO_TCP: { + struct tcphdr *tcph = + (struct tcphdr *) ((char *)pkt + *protoff); + + /* No room for IPv4 header plus TCP header. */ + if (pktlen < *protoff + sizeof(struct tcphdr) + || pktlen < *protoff + tcph->doff * 4) { + dlog(LOG_ERR, "no room for IPv4 + TCP header, skip"); + return -1; + } + return 0; + } + case IPPROTO_UDP: + /* No room for IPv4 header plus UDP header. */ + if (pktlen < *protoff + sizeof(struct udphdr)) { + dlog(LOG_ERR, "no room for IPv4 + UDP header, skip"); + return -1; + } + return 0; + default: + dlog(LOG_ERR, "not TCP/UDP, skipping"); + return -1; + } + break; + } + case ETHERTYPE_IPV6: + dlog(LOG_ERR, "no IPv6 support sorry"); + return 0; + default: + /* Unknown layer 3 protocol. */ + dlog(LOG_ERR, "unknown layer 3 protocol (%d), skipping", proto); + return -1; + } + return 0; +} + +static int +pkt_verdict_issue(struct ctd_helper_instance *cur, struct myct *myct, + uint16_t queue_num, uint32_t id, uint32_t verdict) +{ + struct nlmsghdr *nlh; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlattr *nest; + + nlh = nfq_build_header(buf, NFQNL_MSG_VERDICT, queue_num); + + /* save private data and send it back to kernel-space. */ + nfct_set_attr_l(myct->ct, ATTR_HELPER_INFO, myct->priv_data, + cur->helper->priv_data_len); + + nfq_nlmsg_verdict_build(nlh, id, verdict); + + nest = mnl_attr_nest_start(nlh, NFQA_CT); + if (nest == NULL) + return -1; + + nfct_nlmsg_build(nlh, myct->ct); + mnl_attr_nest_end(nlh, nest); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno)); + return -1; + } + + return 0; +} + +static int +pkt_verdict_error(uint16_t queue_num, uint32_t id) +{ + struct nlmsghdr *nlh; + char buf[MNL_SOCKET_BUFFER_SIZE]; + + nlh = nfq_build_header(buf, NFQNL_MSG_VERDICT, queue_num); + nfq_nlmsg_verdict_build(nlh, id, NF_ACCEPT); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send verdict: %s", strerror(errno)); + return -1; + } + return 0; +} + +static struct ctd_helper_instance * +helper_run(void *pkt, uint32_t pktlen, uint32_t protoff, + struct myct *myct, uint32_t ctinfo, uint32_t queue_num, + int *verdict) +{ + struct ctd_helper_instance *cur, *helper = NULL; + + list_for_each_entry(cur, &CONFIG(cthelper).list, head) { + if (cur->queue_num == queue_num) { + const void *priv_data; + struct pkt_buff *pktb; + + /* retrieve helper private data. */ + priv_data = nfct_get_attr(myct->ct, ATTR_HELPER_INFO); + if (priv_data != NULL) { + myct->priv_data = + calloc(1, cur->helper->priv_data_len); + + if (myct->priv_data == NULL) + continue; + + memcpy(myct->priv_data, priv_data, + cur->helper->priv_data_len); + } + + /* XXX: 256 bytes for extra allocation for all mangling + * we do in helpers. + */ + pktb = pktb_alloc(AF_INET, pkt, pktlen, 256); + if (pktb == NULL) + break; + + *verdict = cur->helper->cb(pktb, protoff, myct, ctinfo); + + pktb_free(pktb); + + helper = cur; + break; + } + } + return helper; +} + +static int nfq_queue_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nfqnl_msg_packet_hdr *ph = NULL; + struct nlattr *attr[NFQA_MAX+1] = {}; + struct nfgenmsg *nfg; + uint8_t *pkt; + uint16_t l3num; + uint32_t id, ctinfo, queue_num = 0, protoff = 0, pktlen; + struct nf_conntrack *ct = NULL; + struct myct *myct; + struct ctd_helper_instance *helper; + int verdict = NF_ACCEPT; + + if (nfq_nlmsg_parse(nlh, attr) < 0) { + dlog(LOG_ERR, "problems parsing message from kernel"); + return MNL_CB_ERROR; + } + + ph = (struct nfqnl_msg_packet_hdr *) + mnl_attr_get_payload(attr[NFQA_PACKET_HDR]); + if (ph == NULL) { + dlog(LOG_ERR, "problems retrieving metaheader"); + return MNL_CB_ERROR; + } + + id = ntohl(ph->packet_id); + + if (!attr[NFQA_PAYLOAD]) { + dlog(LOG_ERR, "packet with no payload"); + goto err; + } + if (!attr[NFQA_CT] || !attr[NFQA_CT_INFO]) { + dlog(LOG_ERR, "no CT attached to this packet"); + goto err; + } + + pkt = mnl_attr_get_payload(attr[NFQA_PAYLOAD]); + pktlen = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]); + + nfg = mnl_nlmsg_get_payload(nlh); + l3num = nfg->nfgen_family; + queue_num = ntohs(nfg->res_id); + + if (pkt_get(pkt, pktlen, ntohs(ph->hw_protocol), &protoff)) + goto err; + + ct = nfct_new(); + if (ct == NULL) + goto err; + + if (nfct_payload_parse(mnl_attr_get_payload(attr[NFQA_CT]), + mnl_attr_get_payload_len(attr[NFQA_CT]), + l3num, ct) < 0) { + dlog(LOG_ERR, "cannot convert message to CT"); + goto err; + } + + myct = calloc(1, sizeof(struct myct)); + if (myct == NULL) + goto err; + + myct->ct = ct; + ctinfo = ntohl(mnl_attr_get_u32(attr[NFQA_CT_INFO])); + + /* Misconfiguration: if no helper found, accept the packet. */ + helper = helper_run(pkt, pktlen, protoff, myct, ctinfo, queue_num, + &verdict); + if (!helper) + goto err; + + if (pkt_verdict_issue(helper, myct, queue_num, id, verdict) < 0) + goto err; + + if (ct != NULL) + nfct_destroy(ct); + if (myct && myct->priv_data != NULL) + free(myct->priv_data); + if (myct != NULL) + free(myct); + + return MNL_CB_OK; +err: + /* In case of error, we don't want to disrupt traffic. We accept all. + * This is connection tracking after all. The policy is not to drop + * packet unless we enter some inconsistent state. + */ + pkt_verdict_error(queue_num, id); + + if (ct != NULL) + nfct_destroy(ct); + + return MNL_CB_OK; +} + +static void nfq_cb(void *data) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + int ret; + + ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); + if (ret == -1) { + dlog(LOG_ERR, "failed to receive message: %s", strerror(errno)); + return; + } + + ret = mnl_cb_run(buf, ret, 0, STATE_CTH(portid), nfq_queue_cb, NULL); + if (ret < 0){ + dlog(LOG_ERR, "failed to process message"); + return; + } +} + +static int cthelper_setup(struct ctd_helper_instance *cur) +{ + struct nfct_helper *t; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t seq; + int j, ret; + + t = nfct_helper_alloc(); + if (t == NULL) { + dlog(LOG_ERR, "cannot allocate object for helper"); + return -1; + } + + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, cur->helper->name); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_QUEUE_NUM, cur->queue_num); + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, cur->l3proto); + nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, cur->l4proto); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, + NFCT_HELPER_STATUS_ENABLED); + + dlog(LOG_NOTICE, "configuring helper `%s' with queuenum=%d", + cur->helper->name, cur->queue_num); + + for (j=0; jhelper->policy[j].name[0]) + break; + + p = nfct_helper_policy_alloc(); + if (p == NULL) { + dlog(LOG_ERR, "cannot allocate object for helper"); + return -1; + } + /* FIXME: get existing policy values from the kernel first. */ + nfct_helper_policy_attr_set(p, NFCTH_ATTR_POLICY_NAME, + cur->helper->policy[j].name); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_TIMEOUT, + cur->helper->policy[j].expect_timeout); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_MAX, + cur->helper->policy[j].expect_max); + + dlog(LOG_NOTICE, "policy name=%s expect_timeout=%d expect_max=%d", + cur->helper->policy[j].name, + cur->helper->policy[j].expect_timeout, + cur->helper->policy[j].expect_max); + + nfct_helper_attr_set(t, NFCTH_ATTR_POLICY+j, p); + } + + if (j == 0) { + dlog(LOG_ERR, "you have to define one policy for helper"); + return -1; + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "sending cthelper configuration"); + return -1; + } + + ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, STATE_CTH(portid), NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(STATE_CTH(nl), buf, sizeof(buf)); + } + if (ret == -1) { + dlog(LOG_ERR, "trying to configure cthelper `%s': %s", + cur->helper->name, strerror(errno)); + return -1; + } + + return 0; +} + +static int cthelper_nfqueue_setup(struct ctd_helper_instance *cur) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nfq_build_header(buf, NFQNL_MSG_CONFIG, cur->queue_num); + nfq_nlmsg_cfg_build_request(nlh, AF_INET, NFQNL_CFG_CMD_BIND); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send bind command"); + return -1; + } + + nlh = nfq_build_header(buf, NFQNL_MSG_CONFIG, cur->queue_num); + nfq_nlmsg_cfg_add_copy(nlh, NFQNL_COPY_PACKET, 0xffff); + mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQNL_F_CONNTRACK)); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send configuration"); + return -1; + } + + return 0; +} + +static int cthelper_configure(struct ctd_helper_instance *cur) +{ + /* First, configure cthelper. */ + if (cthelper_setup(cur) < 0) + return -1; + + /* Now, we are ready to configure nfqueue attached to this helper. */ + if (cthelper_nfqueue_setup(cur) < 0) + return -1; + + dlog(LOG_NOTICE, "helper `%s' configured successfully", + cur->helper->name); + + return 0; +} + +static int nfq_configure(void) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nfq_build_header(buf, NFQNL_MSG_CONFIG, 0); + nfq_nlmsg_cfg_build_request(nlh, AF_INET, NFQNL_CFG_CMD_PF_UNBIND); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send pf unbind command"); + return -1; + } + + nlh = nfq_build_header(buf, NFQNL_MSG_CONFIG, 0); + nfq_nlmsg_cfg_build_request(nlh, AF_INET, NFQNL_CFG_CMD_PF_BIND); + + if (mnl_socket_sendto(STATE_CTH(nl), nlh, nlh->nlmsg_len) < 0) { + dlog(LOG_ERR, "failed to send pf bind command"); + return -1; + } + + return 0; +} + +int cthelper_init(void) +{ + struct ctd_helper_instance *cur; + int ret; + + state.cthelper = calloc(1, sizeof(struct ct_helper_state)); + if (state.cthelper == NULL) { + dlog(LOG_ERR, "can't allocate memory for cthelper struct"); + return -1; + } + + STATE_CTH(nl) = mnl_socket_open(NETLINK_NETFILTER); + if (STATE_CTH(nl) == NULL) { + dlog(LOG_ERR, "cannot open nfq socket"); + return -1; + } + fcntl(mnl_socket_get_fd(STATE_CTH(nl)), F_SETFL, O_NONBLOCK); + + if (mnl_socket_bind(STATE_CTH(nl), 0, MNL_SOCKET_AUTOPID) < 0) { + dlog(LOG_ERR, "cannot bind nfq socket"); + return -1; + } + STATE_CTH(portid) = mnl_socket_get_portid(STATE_CTH(nl)); + + if (nfq_configure()) + return -1; + + list_for_each_entry(cur, &CONFIG(cthelper).list, head) { + ret = cthelper_configure(cur); + if (ret < 0) + return ret; + } + + register_fd(mnl_socket_get_fd(STATE_CTH(nl)), nfq_cb, NULL, STATE(fds)); + + return 0; +} diff --git a/src/expect.c b/src/expect.c new file mode 100644 index 0000000..eab9094 --- /dev/null +++ b/src/expect.c @@ -0,0 +1,212 @@ +/* + * (C) 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 version 2 as + * published by the Free Software Foundation (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "helper.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +int +cthelper_expect_init(struct nf_expect *exp, struct nf_conntrack *master, + uint32_t class, + union nfct_attr_grp_addr *saddr, + union nfct_attr_grp_addr *daddr, + uint8_t l4proto, uint16_t *sport, uint16_t *dport) +{ + struct nf_conntrack *expected, *mask; + + expected = nfct_new(); + if (!expected) + return -1; + + mask = nfct_new(); + if (!mask) + return -1; + + if (saddr) { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, saddr->ip); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, saddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } else { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, 0x00000000); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0x00000000); + break; + case AF_INET6: + for (i=0; i<4; i++) + memset(addr, 0x00000000, sizeof(uint32_t)); + + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, addr); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } + + if (sport) { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, *sport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0xffff); + break; + default: + break; + } + } else { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, 0x0000); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0x0000); + break; + default: + break; + } + } + + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + uint32_t addr[4] = {}; + int i; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_DST, daddr->ip); + nfct_set_attr_u32(mask, ATTR_IPV4_DST, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_DST, daddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr(mask, ATTR_IPV6_DST, addr); + break; + default: + break; + } + + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_DST, *dport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_DST, 0xffff); + break; + default: + break; + } + + nfexp_set_attr(exp, ATTR_EXP_MASTER, master); + nfexp_set_attr(exp, ATTR_EXP_EXPECTED, expected); + nfexp_set_attr(exp, ATTR_EXP_MASK, mask); + + nfct_destroy(expected); + nfct_destroy(mask); + + return 0; +} + +static int cthelper_expect_cmd(struct nf_expect *exp, int cmd) +{ + int ret; + struct nfct_handle *h; + + h = nfct_open(EXPECT, 0); + if (!h) + return -1; + + ret = nfexp_query(h, cmd, exp); + + nfct_close(h); + return ret; +} + +int cthelper_add_expect(struct nf_expect *exp) +{ + return cthelper_expect_cmd(exp, NFCT_Q_CREATE); +} + +int cthelper_del_expect(struct nf_expect *exp) +{ + return cthelper_expect_cmd(exp, NFCT_Q_DESTROY); +} + +void +cthelper_get_addr_src(struct nf_conntrack *ct, int dir, + union nfct_attr_grp_addr *addr) +{ + switch (dir) { + case MYCT_DIR_ORIG: + nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_SRC, addr); + break; + case MYCT_DIR_REPL: + nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_SRC, addr); + break; + } +} + +void +cthelper_get_addr_dst(struct nf_conntrack *ct, int dir, + union nfct_attr_grp_addr *addr) +{ + switch (dir) { + case MYCT_DIR_ORIG: + nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_DST, addr); + break; + case MYCT_DIR_REPL: + nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_DST, addr); + break; + } +} diff --git a/src/helpers.c b/src/helpers.c new file mode 100644 index 0000000..3e4e6c8 --- /dev/null +++ b/src/helpers.c @@ -0,0 +1,76 @@ +/* + * (C) 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 version 2 as + * published by the Free Software Foundation (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "helper.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(helper_list); + +void helper_register(struct ctd_helper *helper) +{ + list_add(&helper->head, &helper_list); +} + +static struct ctd_helper * +__helper_find(const char *helper_name, uint8_t l4proto) +{ + struct ctd_helper *cur, *helper = NULL; + + list_for_each_entry(cur, &helper_list, head) { + if (strncmp(cur->name, helper_name, CTD_HELPER_NAME_LEN) != 0) + continue; + + if (cur->l4proto != l4proto) + continue; + + helper = cur; + break; + } + return helper; +} + +struct ctd_helper * +helper_find(const char *libdir_path, + const char *helper_name, uint8_t l4proto, int flag) +{ + char path[PATH_MAX]; + struct ctd_helper *helper; + struct stat sb; + + helper = __helper_find(helper_name, l4proto); + if (helper != NULL) + return helper; + + snprintf(path, sizeof(path), "%s/ct_helper_%s.so", + libdir_path, helper_name); + + if (stat(path, &sb) != 0) { + if (errno == ENOENT) + return NULL; + fprintf(stderr, "%s: %s\n", path, + strerror(errno)); + return NULL; + } + + if (dlopen(path, flag) == NULL) { + fprintf(stderr, "%s: %s\n", path, dlerror()); + return NULL; + } + + return __helper_find(helper_name, l4proto); +} diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am new file mode 100644 index 0000000..2c9d63b --- /dev/null +++ b/src/helpers/Makefile.am @@ -0,0 +1,9 @@ +include $(top_srcdir)/Make_global.am + +pkglib_LTLIBRARIES = ct_helper_ftp.la + +ct_helper_ftp_la_SOURCES = ftp.c +ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + + diff --git a/src/helpers/ftp.c b/src/helpers/ftp.c new file mode 100644 index 0000000..c6ad7da --- /dev/null +++ b/src/helpers/ftp.c @@ -0,0 +1,599 @@ +/* + * (C) 2010-2012 by Pablo Neira Ayuso + * + * Based on: kernel-space FTP extension for connection tracking. + * + * This port has been sponsored by Vyatta Inc. + * + * Original copyright notice: + * + * (C) 1999-2001 Paul `Rusty' Russell + * (C) 2002-2004 Netfilter Core Team + * (C) 2003,2004 USAGI/WIDE Project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "conntrackd.h" +#include "network.h" /* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include /* for isdigit */ +#include + +#include + +#include +#include +#include +#include +#include +#include + +static bool loose; /* XXX: export this as config option. */ + +#define NUM_SEQ_TO_REMEMBER 2 + +/* This structure exists only once per master */ +struct ftp_info { + /* Valid seq positions for cmd matching after newline */ + uint32_t seq_aft_nl[MYCT_DIR_MAX][NUM_SEQ_TO_REMEMBER]; + /* 0 means seq_match_aft_nl not set */ + int seq_aft_nl_num[MYCT_DIR_MAX]; +}; + +enum nf_ct_ftp_type { + /* PORT command from client */ + NF_CT_FTP_PORT, + /* PASV response from server */ + NF_CT_FTP_PASV, + /* EPRT command from client */ + NF_CT_FTP_EPRT, + /* EPSV response from server */ + NF_CT_FTP_EPSV, +}; + +static int +get_ipv6_addr(const char *src, size_t dlen, struct in6_addr *dst, u_int8_t term) +{ + const char *end; + int ret = in6_pton(src, min_t(size_t, dlen, 0xffff), + (uint8_t *)dst, term, &end); + if (ret > 0) + return (int)(end - src); + return 0; +} + +static int try_number(const char *data, size_t dlen, uint32_t array[], + int array_size, char sep, char term) +{ + uint32_t len; + int i; + + memset(array, 0, sizeof(array[0])*array_size); + + /* Keep data pointing at next char. */ + for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) { + if (*data >= '0' && *data <= '9') { + array[i] = array[i]*10 + *data - '0'; + } + else if (*data == sep) + i++; + else { + /* Unexpected character; true if it's the + terminator and we're finished. */ + if (*data == term && i == array_size - 1) + return len; + pr_debug("Char %u (got %u nums) `%u' unexpected\n", + len, i, *data); + return 0; + } + } + pr_debug("Failed to fill %u numbers separated by %c\n", + array_size, sep); + return 0; +} + +/* Grab port: number up to delimiter */ +static int get_port(const char *data, int start, size_t dlen, char delim, + struct myct_man *cmd) +{ + uint16_t tmp_port = 0; + uint32_t i; + + for (i = start; i < dlen; i++) { + /* Finished? */ + if (data[i] == delim) { + if (tmp_port == 0) + break; + cmd->u.port = htons(tmp_port); + pr_debug("get_port: return %d\n", tmp_port); + return i + 1; + } + else if (data[i] >= '0' && data[i] <= '9') + tmp_port = tmp_port*10 + data[i] - '0'; + else { /* Some other crap */ + pr_debug("get_port: invalid char.\n"); + break; + } + } + return 0; +} + +/* Returns 0, or length of numbers: 192,168,1,1,5,6 */ +static int try_rfc959(const char *data, size_t dlen, struct myct_man *cmd, + uint16_t l3protonum, char term) +{ + int length; + uint32_t array[6]; + + length = try_number(data, dlen, array, 6, ',', term); + if (length == 0) + return 0; + + cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | + (array[2] << 8) | array[3]); + cmd->u.port = htons((array[4] << 8) | array[5]); + return length; +} + +/* Returns 0, or length of numbers: |1|132.235.1.2|6275| or |2|3ffe::1|6275| */ +static int try_eprt(const char *data, size_t dlen, + struct myct_man *cmd, uint16_t l3protonum, char term) +{ + char delim; + int length; + + /* First character is delimiter, then "1" for IPv4 or "2" for IPv6, + then delimiter again. */ + if (dlen <= 3) { + pr_debug("EPRT: too short\n"); + return 0; + } + delim = data[0]; + if (isdigit(delim) || delim < 33 || delim > 126 || data[2] != delim) { + pr_debug("try_eprt: invalid delimitter.\n"); + return 0; + } + + if ((l3protonum == PF_INET && data[1] != '1') || + (l3protonum == PF_INET6 && data[1] != '2')) { + pr_debug("EPRT: invalid protocol number.\n"); + return 0; + } + + pr_debug("EPRT: Got %c%c%c\n", delim, data[1], delim); + if (data[1] == '1') { + uint32_t array[4]; + + /* Now we have IP address. */ + length = try_number(data + 3, dlen - 3, array, 4, '.', delim); + if (length != 0) + cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) + | (array[2] << 8) | array[3]); + } else { + /* Now we have IPv6 address. */ + length = get_ipv6_addr(data + 3, dlen - 3, + (struct in6_addr *)cmd->u3.ip6, delim); + } + + if (length == 0) + return 0; + pr_debug("EPRT: Got IP address!\n"); + /* Start offset includes initial "|1|", and trailing delimiter */ + return get_port(data, 3 + length + 1, dlen, delim, cmd); +} + +/* Returns 0, or length of numbers: |||6446| */ +static int try_epsv_response(const char *data, size_t dlen, + struct myct_man *cmd, + uint16_t l3protonum, char term) +{ + char delim; + + /* Three delimiters. */ + if (dlen <= 3) return 0; + delim = data[0]; + if (isdigit(delim) || delim < 33 || delim > 126 || + data[1] != delim || data[2] != delim) + return 0; + + return get_port(data, 3, dlen, delim, cmd); +} + +static struct ftp_search { + const char *pattern; + size_t plen; + char skip; + char term; + enum nf_ct_ftp_type ftptype; + int (*getnum)(const char *, size_t, struct myct_man *, uint16_t, char); +} search[MYCT_DIR_MAX][2] = { + [MYCT_DIR_ORIG] = { + { + .pattern = "PORT", + .plen = sizeof("PORT") - 1, + .skip = ' ', + .term = '\r', + .ftptype = NF_CT_FTP_PORT, + .getnum = try_rfc959, + }, + { + .pattern = "EPRT", + .plen = sizeof("EPRT") - 1, + .skip = ' ', + .term = '\r', + .ftptype = NF_CT_FTP_EPRT, + .getnum = try_eprt, + }, + }, + [MYCT_DIR_REPL] = { + { + .pattern = "227 ", + .plen = sizeof("227 ") - 1, + .skip = '(', + .term = ')', + .ftptype = NF_CT_FTP_PASV, + .getnum = try_rfc959, + }, + { + .pattern = "229 ", + .plen = sizeof("229 ") - 1, + .skip = '(', + .term = ')', + .ftptype = NF_CT_FTP_EPSV, + .getnum = try_epsv_response, + }, + }, +}; + +static int ftp_find_pattern(struct pkt_buff *pkt, + unsigned int dataoff, unsigned int dlen, + const char *pattern, size_t plen, + char skip, char term, + unsigned int *matchoff, unsigned int *matchlen, + struct myct_man *cmd, + int (*getnum)(const char *, size_t, + struct myct_man *cmd, + uint16_t, char), + int dir) +{ + char *data = (char *)pktb_network_header(pkt) + dataoff; + int numlen; + uint32_t i; + + if (dlen == 0) + return 0; + + /* short packet, skip partial matching. */ + if (dlen <= plen) + return 0; + + if (strncmp(data, pattern, plen) != 0) + return 0; + + pr_debug("Pattern matches!\n"); + + /* Now we've found the constant string, try to skip + to the 'skip' character */ + for (i = plen; data[i] != skip; i++) + if (i == dlen - 1) return 0; + + /* Skip over the last character */ + i++; + + pr_debug("Skipped up to `%c'!\n", skip); + + numlen = getnum(data + i, dlen - i, cmd, PF_INET, term); + if (!numlen) + return 0; + + pr_debug("Match succeded!\n"); + return 1; +} + +/* Look up to see if we're just after a \n. */ +static int find_nl_seq(uint32_t seq, struct ftp_info *info, int dir) +{ + int i; + + for (i = 0; i < info->seq_aft_nl_num[dir]; i++) + if (info->seq_aft_nl[dir][i] == seq) + return 1; + return 0; +} + +/* We don't update if it's older than what we have. */ +static void update_nl_seq(uint32_t nl_seq, struct ftp_info *info, int dir) +{ + int i, oldest; + + /* Look for oldest: if we find exact match, we're done. */ + for (i = 0; i < info->seq_aft_nl_num[dir]; i++) { + if (info->seq_aft_nl[dir][i] == nl_seq) + return; + } + + if (info->seq_aft_nl_num[dir] < NUM_SEQ_TO_REMEMBER) { + info->seq_aft_nl[dir][info->seq_aft_nl_num[dir]++] = nl_seq; + } else { + if (before(info->seq_aft_nl[dir][0], info->seq_aft_nl[dir][1])) + oldest = 0; + else + oldest = 1; + + if (after(nl_seq, info->seq_aft_nl[dir][oldest])) + info->seq_aft_nl[dir][oldest] = nl_seq; + } +} + +static int nf_nat_ftp_fmt_cmd(enum nf_ct_ftp_type type, + char *buffer, size_t buflen, + uint32_t addr, uint16_t port) +{ + switch (type) { + case NF_CT_FTP_PORT: + case NF_CT_FTP_PASV: + return snprintf(buffer, buflen, "%u,%u,%u,%u,%u,%u", + ((unsigned char *)&addr)[0], + ((unsigned char *)&addr)[1], + ((unsigned char *)&addr)[2], + ((unsigned char *)&addr)[3], + port >> 8, + port & 0xFF); + case NF_CT_FTP_EPRT: + return snprintf(buffer, buflen, "|1|%pI4|%u|", &addr, port); + case NF_CT_FTP_EPSV: + return snprintf(buffer, buflen, "|||%u|", port); + } + + return 0; +} + +/* So, this packet has hit the connection tracking matching code. + Mangle it, and change the expectation to match the new version. */ +static unsigned int nf_nat_ftp(struct pkt_buff *pkt, + int dir, + int ctinfo, + enum nf_ct_ftp_type type, + unsigned int matchoff, + unsigned int matchlen, + struct nf_conntrack *ct, + struct nf_expect *exp) +{ + union nfct_attr_grp_addr newip; + uint16_t port; + char buffer[sizeof("|1|255.255.255.255|65535|")]; + unsigned int buflen; + const struct nf_conntrack *expected; + struct nf_conntrack *nat_tuple; + uint16_t initial_port; + + pr_debug("FTP_NAT: type %i, off %u len %u\n", type, matchoff, matchlen); + + /* Connection will come from wherever this packet goes, hence !dir */ + cthelper_get_addr_dst(ct, !dir, &newip); + + expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + + nat_tuple = nfct_new(); + if (nat_tuple == NULL) + return NF_ACCEPT; + + initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + + nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + + /* libnetfilter_conntrack needs this */ + nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); + nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + + /* When you see the packet, we need to NAT it the same as the + * this one. */ + nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + + /* Try to get same port: if not, try to change it. */ + for (port = ntohs(initial_port); port != 0; port++) { + int ret; + + nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); + nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + + ret = cthelper_add_expect(exp); + if (ret == 0) + break; + else if (ret != -EBUSY) { + port = 0; + break; + } + } + + if (port == 0) + return NF_DROP; + + buflen = nf_nat_ftp_fmt_cmd(type, buffer, sizeof(buffer), + newip.ip, port); + if (!buflen) + goto out; + + if (!nfq_tcp_mangle(pkt, matchoff, matchlen, buffer, buflen)) + goto out; + + return NF_ACCEPT; + +out: + cthelper_del_expect(exp); + return NF_DROP; +} + +static int +ftp_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + struct tcphdr *th; + unsigned int dataoff; + unsigned int matchoff = 0, matchlen = 0; /* makes gcc happy. */ + unsigned int datalen; + unsigned int i; + int found = 0, ends_in_nl; + uint32_t seq; + int ret = NF_ACCEPT; + struct myct_man cmd; + union nfct_attr_grp_addr addr; + union nfct_attr_grp_addr daddr; + int dir = CTINFO2DIR(ctinfo); + struct ftp_info *ftp_info = myct->priv_data; + struct nf_expect *exp = NULL; + + memset(&cmd, 0, sizeof(struct myct_man)); + memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); + + /* Until there's been traffic both ways, don't look in packets. */ + if (ctinfo != IP_CT_ESTABLISHED && + ctinfo != IP_CT_ESTABLISHED_REPLY) { + pr_debug("ftp: Conntrackinfo = %u\n", ctinfo); + goto out; + } + + th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); + + dataoff = protoff + th->doff * 4; + datalen = pktb_len(pkt) - dataoff; + + ends_in_nl = (pktb_network_header(pkt)[pktb_len(pkt) - 1] == '\n'); + seq = ntohl(th->seq) + datalen; + + /* Look up to see if we're just after a \n. */ + if (!find_nl_seq(ntohl(th->seq), ftp_info, dir)) { + /* Now if this ends in \n, update ftp info. */ + pr_debug("nf_conntrack_ftp: wrong seq pos %s(%u) or %s(%u)\n", + ftp_info->seq_aft_nl_num[dir] > 0 ? "" : "(UNSET)", + ftp_info->seq_aft_nl[dir][0], + ftp_info->seq_aft_nl_num[dir] > 1 ? "" : "(UNSET)", + ftp_info->seq_aft_nl[dir][1]); + goto out_update_nl; + } + + /* Initialize IP/IPv6 addr to expected address (it's not mentioned + in EPSV responses) */ + cmd.l3num = nfct_get_attr_u16(myct->ct, ATTR_L3PROTO); + nfct_get_attr_grp(myct->ct, ATTR_GRP_ORIG_ADDR_SRC, &cmd.u3); + + for (i = 0; i < ARRAY_SIZE(search[dir]); i++) { + found = ftp_find_pattern(pkt, dataoff, datalen, + search[dir][i].pattern, + search[dir][i].plen, + search[dir][i].skip, + search[dir][i].term, + &matchoff, &matchlen, + &cmd, + search[dir][i].getnum, + dir); + if (found) break; + } + if (found == 0) /* No match */ + goto out_update_nl; + + pr_debug("conntrack_ftp: match `%.*s' (%u bytes at %u)\n", + matchlen, pktb_network_header(pkt) + matchoff, + matchlen, ntohl(th->seq) + matchoff); + + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + cthelper_get_addr_dst(myct->ct, !dir, &daddr); + + cthelper_get_addr_src(myct->ct, dir, &addr); + + /* Update the ftp info */ + if ((cmd.l3num == nfct_get_attr_u16(myct->ct, ATTR_L3PROTO)) && + memcmp(&cmd.u3, &addr, sizeof(addr)) != 0) { + /* Enrico Scholz's passive FTP to partially RNAT'd ftp + server: it really wants us to connect to a + different IP address. Simply don't record it for + NAT. */ + if (cmd.l3num == PF_INET) { + pr_debug("conntrack_ftp: NOT RECORDING: %pI4 != %pI4\n", + &cmd.u3.ip, &addr); + } else { + pr_debug("conntrack_ftp: NOT RECORDING: %pI6 != %pI6\n", + cmd.u3.ip6, &addr); + } + /* Thanks to Cristiano Lincoln Mattos + for reporting this potential + problem (DMZ machines opening holes to internal + networks, or the packet filter itself). */ + if (!loose) { + ret = NF_ACCEPT; + goto out; + } + memcpy(&daddr, &cmd.u3, sizeof(cmd.u3)); + } + + exp = nfexp_new(); + if (exp == NULL) + goto out_update_nl; + + cthelper_get_addr_src(myct->ct, !dir, &addr); + + if (cthelper_expect_init(exp, myct->ct, 0, &addr, &daddr, IPPROTO_TCP, + NULL, &cmd.u.port)) { + pr_debug("conntrack_ftp: failed to init expectation\n"); + goto out_update_nl; + } + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. */ + if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { + ret = nf_nat_ftp(pkt, dir, ctinfo, search[dir][i].ftptype, + matchoff, matchlen, myct->ct, exp); + goto out_update_nl; + } + + /* Can't expect this? Best to drop packet now. */ + if (cthelper_add_expect(exp) < 0) { + pr_debug("conntrack_ftp: cannot add expectation: %s\n", + strerror(errno)); + ret = NF_DROP; + goto out_update_nl; + } + +out_update_nl: + if (exp != NULL) + nfexp_destroy(exp); + + /* Now if this ends in \n, update ftp info. Seq may have been + * adjusted by NAT code. */ + if (ends_in_nl) + update_nl_seq(seq, ftp_info, dir); +out: + return ret; +} + +static struct ctd_helper ftp_helper = { + .name = "ftp", + .l4proto = IPPROTO_TCP, + .cb = ftp_helper_cb, + .priv_data_len = sizeof(struct ftp_info), + .policy = { + [0] = { + .name = "ftp", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) ftp_init(void); + +void ftp_init(void) +{ + helper_register(&ftp_helper); +} diff --git a/src/main.c b/src/main.c index 26f6c14..831a3c2 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,7 @@ #include "conntrackd.h" #include "log.h" +#include "helper.h" #include #include @@ -31,7 +32,7 @@ #include struct ct_general_state st; -union ct_state state; +struct ct_state state; static const char usage_daemon_commands[] = "Daemon mode commands:\n" diff --git a/src/nfct-extensions/helper.c b/src/nfct-extensions/helper.c new file mode 100644 index 0000000..e8f85bb --- /dev/null +++ b/src/nfct-extensions/helper.c @@ -0,0 +1,619 @@ +/* + * (C) 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 code has been sponsored by Vyatta Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "nfct.h" +#include "helper.h" + +static void +nfct_cmd_helper_usage(char *argv[]) +{ + fprintf(stderr, "nfct v%s: Missing command\n" + "%s helper list|add|delete|get|flush " + "[parameters...]\n", VERSION, argv[0]); +} + +int +nfct_cmd_helper_parse_params(int argc, char *argv[]) +{ + int cmd = NFCT_CMD_NONE, ret = 0; + + if (argc < 3) { + fprintf(stderr, "nfct v%s: Missing command\n" + "%s helper list|add|delete|get|flush " + "[parameters...]\n", VERSION, argv[0]); + exit(EXIT_FAILURE); + } + if (strncmp(argv[2], "list", strlen(argv[2])) == 0) + cmd = NFCT_CMD_LIST; + else if (strncmp(argv[2], "add", strlen(argv[2])) == 0) + cmd = NFCT_CMD_ADD; + else if (strncmp(argv[2], "delete", strlen(argv[2])) == 0) + cmd = NFCT_CMD_DELETE; + else if (strncmp(argv[2], "get", strlen(argv[2])) == 0) + cmd = NFCT_CMD_GET; + else if (strncmp(argv[2], "flush", strlen(argv[2])) == 0) + cmd = NFCT_CMD_FLUSH; + else if (strncmp(argv[2], "disable", strlen(argv[2])) == 0) + cmd = NFCT_CMD_DISABLE; + else { + fprintf(stderr, "nfct v%s: Unknown command: %s\n", + VERSION, argv[2]); + nfct_cmd_helper_usage(argv); + exit(EXIT_FAILURE); + } + switch(cmd) { + case NFCT_CMD_LIST: + ret = nfct_cmd_helper_list(argc, argv); + break; + case NFCT_CMD_ADD: + ret = nfct_cmd_helper_add(argc, argv); + break; + case NFCT_CMD_DELETE: + ret = nfct_cmd_helper_delete(argc, argv); + break; + case NFCT_CMD_GET: + ret = nfct_cmd_helper_get(argc, argv); + break; + case NFCT_CMD_FLUSH: + ret = nfct_cmd_helper_flush(argc, argv); + break; + case NFCT_CMD_DISABLE: + ret = nfct_cmd_helper_disable(argc, argv); + break; + } + + return ret; +} + +static int nfct_helper_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nfct_helper *t; + char buf[4096]; + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + goto err; + } + + if (nfct_helper_nlmsg_parse_payload(nlh, t) < 0) { + nfct_perror("nfct_helper_nlmsg_parse_payload"); + goto err_free; + } + + nfct_helper_snprintf(buf, sizeof(buf), t, 0, 0); + printf("%s\n", buf); + +err_free: + nfct_helper_free(t); +err: + return MNL_CB_OK; +} + +int nfct_cmd_helper_list(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + unsigned int seq, portid; + int ret; + + if (argc > 3) { + nfct_perror("too many arguments"); + return -1; + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_GET, + NLM_F_DUMP, seq); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, nfct_helper_cb, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_add(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + uint16_t l3proto; + uint8_t l4proto; + struct ctd_helper *helper; + int ret, j; + + if (argc < 6) { + nfct_perror("missing parameters\n" + "syntax: nfct helper add name " + "family protocol"); + return -1; + } + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + + /* XXX use prefix defined in configure.ac. */ + helper = helper_find("/usr/lib/conntrack-tools", + argv[3], l4proto, RTLD_LAZY); + if (helper == NULL) { + nfct_perror("that helper is not supported"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_PRIV_DATA_LEN, + helper->priv_data_len); + + for (j=0; jpolicy[j].name[0]) + break; + + p = nfct_helper_policy_alloc(); + if (p == NULL) { + nfct_perror("OOM"); + return -1; + } + + nfct_helper_policy_attr_set(p, NFCTH_ATTR_POLICY_NAME, + helper->policy[j].name); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_TIMEOUT, + helper->policy[j].expect_timeout); + nfct_helper_policy_attr_set_u32(p, NFCTH_ATTR_POLICY_MAX, + helper->policy[j].expect_max); + + nfct_helper_attr_set(t, NFCTH_ATTR_POLICY+j, p); + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_delete(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + int ret; + + if (argc < 4) { + nfct_perror("missing helper policy name"); + return -1; + } else if (argc > 6) { + nfct_perror("too many arguments"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + + if (argc >= 5) { + uint16_t l3proto; + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + } + + if (argc == 6) { + uint8_t l4proto; + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + nfct_helper_attr_set_u32(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_DEL, + NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_get(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + int ret; + + if (argc < 4) { + nfct_perror("missing helper policy name"); + return -1; + } else if (argc > 6) { + nfct_perror("too many arguments"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + + if (argc >= 5) { + uint16_t l3proto; + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + } + + if (argc == 6) { + uint8_t l4proto; + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + nfct_helper_attr_set_u32(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_GET, + NLM_F_ACK, seq); + + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, nfct_helper_cb, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_flush(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + int ret; + + if (argc > 3) { + nfct_perror("too many arguments"); + return -1; + } + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_DEL, + NLM_F_ACK, seq); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + + mnl_socket_close(nl); + + return 0; +} + +int nfct_cmd_helper_disable(int argc, char *argv[]) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq; + struct nfct_helper *t; + uint16_t l3proto; + uint8_t l4proto; + struct ctd_helper *helper; + int ret; + + if (argc < 6) { + nfct_perror("missing parameters\n" + "syntax: nfct helper add name " + "family protocol"); + return -1; + } + + if (strcmp(argv[4], "inet") == 0) + l3proto = AF_INET; + else if (strcmp(argv[4], "inet6") == 0) + l3proto = AF_INET6; + else { + nfct_perror("unknown layer 3 protocol"); + return -1; + } + + if (strcmp(argv[5], "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp(argv[5], "udp") == 0) + l4proto = IPPROTO_UDP; + else { + nfct_perror("unsupported layer 4 protocol"); + return -1; + } + + /* XXX use prefix defined in configure.ac. */ + helper = helper_find("/usr/lib/conntrack-tools", + argv[3], l4proto, RTLD_LAZY); + if (helper == NULL) { + nfct_perror("that helper is not supported"); + return -1; + } + + t = nfct_helper_alloc(); + if (t == NULL) { + nfct_perror("OOM"); + return -1; + } + nfct_helper_attr_set(t, NFCTH_ATTR_NAME, argv[3]); + nfct_helper_attr_set_u16(t, NFCTH_ATTR_PROTO_L3NUM, l3proto); + nfct_helper_attr_set_u8(t, NFCTH_ATTR_PROTO_L4NUM, l4proto); + nfct_helper_attr_set_u32(t, NFCTH_ATTR_STATUS, + NFCT_HELPER_STATUS_DISABLED); + + seq = time(NULL); + nlh = nfct_helper_nlmsg_build_hdr(buf, NFNL_MSG_CTHELPER_NEW, + NLM_F_CREATE | NLM_F_ACK, seq); + nfct_helper_nlmsg_build_payload(nlh, t); + + nfct_helper_free(t); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + nfct_perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + nfct_perror("mnl_socket_bind"); + return -1; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + nfct_perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + if (ret == -1) { + nfct_perror("error"); + return -1; + } + mnl_socket_close(nl); + + return 0; +} + diff --git a/src/nfct.c b/src/nfct.c index db629e7..b5c9654 100644 --- a/src/nfct.c +++ b/src/nfct.c @@ -56,6 +56,8 @@ int main(int argc, char *argv[]) } if (strncmp(argv[1], "timeout", strlen(argv[1])) == 0) { subsys = NFCT_SUBSYS_TIMEOUT; + } else if (strncmp(argv[1], "helper", strlen(argv[1])) == 0) { + subsys = NFCT_SUBSYS_HELPER; } else if (strncmp(argv[1], "version", strlen(argv[1])) == 0) subsys = NFCT_SUBSYS_VERSION; else if (strncmp(argv[1], "help", strlen(argv[1])) == 0) @@ -71,6 +73,9 @@ int main(int argc, char *argv[]) case NFCT_SUBSYS_TIMEOUT: ret = nfct_cmd_timeout_parse_params(argc, argv); break; + case NFCT_SUBSYS_HELPER: + ret = nfct_cmd_helper_parse_params(argc, argv); + break; case NFCT_SUBSYS_VERSION: ret = nfct_cmd_version(argc, argv); break; @@ -99,6 +104,7 @@ static const char help_msg[] = "nfct v%s: utility for the Netfilter's Connection Tracking System\n" "Usage: %s command [parameters]...\n\n" "Subsystem:\n" + " helper\t\tAllows to configure user-space helper\n" " timeout\t\tAllows definition of fine-grain timeout policies\n" " version\t\tDisplay version and disclaimer\n" " help\t\t\tDisplay this help message\n" diff --git a/src/read_config_lex.l b/src/read_config_lex.l index 01fe4fc..31fa32e 100644 --- a/src/read_config_lex.l +++ b/src/read_config_lex.l @@ -142,6 +142,11 @@ notrack [N|n][O|o][T|t][R|r][A|a][C|c][K|k] "TCPWindowTracking" { return T_TCP_WINDOW_TRACKING; } "ExpectationSync" { return T_EXPECT_SYNC; } "ErrorQueueLength" { return T_ERROR_QUEUE_LENGTH; } +"Helper" { return T_HELPER; } +"QueueNum" { return T_HELPER_QUEUE_NUM; } +"Policy" { return T_HELPER_POLICY; } +"ExpectMax" { return T_HELPER_EXPECT_MAX; } +"ExpectTimeout" { return T_HELPER_EXPECT_TIMEOUT; } {is_on} { return T_ON; } {is_off} { return T_OFF; } diff --git a/src/read_config_yy.y b/src/read_config_yy.y index b22784c..c9235d3 100644 --- a/src/read_config_yy.y +++ b/src/read_config_yy.y @@ -28,8 +28,11 @@ #include "conntrackd.h" #include "bitops.h" #include "cidr.h" +#include "helper.h" +#include "stack.h" #include #include +#include #include #include @@ -48,6 +51,15 @@ static void print_err(int err, const char *msg, ...); static void __kernel_filter_start(void); static void __kernel_filter_add_state(int value); static void __max_dedicated_links_reached(void); + +struct stack symbol_stack; + +enum { + SYMBOL_HELPER_QUEUE_NUM, + SYMBOL_HELPER_POLICY_EXPECT_ROOT, + SYMBOL_HELPER_EXPECT_POLICY_LEAF, +}; + %} %union { @@ -74,6 +86,8 @@ static void __max_dedicated_links_reached(void); %token T_SCHEDULER T_TYPE T_PRIO T_NETLINK_EVENTS_RELIABLE %token T_DISABLE_INTERNAL_CACHE T_DISABLE_EXTERNAL_CACHE T_ERROR_QUEUE_LENGTH %token T_OPTIONS T_TCP_WINDOW_TRACKING T_EXPECT_SYNC +%token T_HELPER T_HELPER_QUEUE_NUM T_HELPER_POLICY T_HELPER_EXPECT_MAX +%token T_HELPER_EXPECT_TIMEOUT %token T_IP T_PATH_VAL %token T_NUMBER @@ -96,6 +110,7 @@ line : ignore_protocol | general | sync | stats + | helper ; logfile_bool : T_LOG T_ON @@ -1561,6 +1576,186 @@ buffer_size: T_STAT_BUFFER_SIZE T_NUMBER print_err(CTD_CFG_WARN, "`LogFileBufferSize' is deprecated"); }; +helper: T_HELPER '{' helper_list '}' +{ + conf.flags |= CTD_HELPER; +}; + +helper_list: + | helper_list helper_line + ; + +helper_line: helper_type + ; + +helper_type: T_TYPE T_STRING T_STRING T_STRING '{' helper_type_list '}' +{ + struct ctd_helper_instance *helper_inst; + struct ctd_helper *helper; + struct stack_item *e; + uint16_t l3proto; + uint8_t l4proto; + + if (strcmp($3, "inet") == 0) + l3proto = AF_INET; + else if (strcmp($3, "inet6") == 0) + l3proto = AF_INET6; + else { + print_err(CTD_CFG_ERROR, "unknown layer 3 protocol"); + exit(EXIT_FAILURE); + } + + if (strcmp($4, "tcp") == 0) + l4proto = IPPROTO_TCP; + else if (strcmp($4, "udp") == 0) + l4proto = IPPROTO_UDP; + else { + print_err(CTD_CFG_ERROR, "unknown layer 4 protocol"); + exit(EXIT_FAILURE); + } + + /* XXX use configure.ac definitions. */ + helper = helper_find("/usr/lib/conntrack-tools", $2, l4proto, RTLD_NOW); + if (helper == NULL) { + print_err(CTD_CFG_ERROR, "Unknown `%s' helper", $2); + exit(EXIT_FAILURE); + } + + helper_inst = calloc(1, sizeof(struct ctd_helper_instance)); + if (helper_inst == NULL) + break; + + helper_inst->l3proto = l3proto; + helper_inst->l4proto = l4proto; + helper_inst->helper = helper; + + while ((e = stack_item_pop(&symbol_stack, -1)) != NULL) { + + switch(e->type) { + case SYMBOL_HELPER_QUEUE_NUM: { + int *qnum = (int *) &e->data; + + helper_inst->queue_num = *qnum; + stack_item_free(e); + break; + } + case SYMBOL_HELPER_POLICY_EXPECT_ROOT: { + struct ctd_helper_policy *pol = + (struct ctd_helper_policy *) &e->data; + struct ctd_helper_policy *matching = NULL; + int i; + + for (i=0; ipolicy[i].name, + pol->name) != 0) + continue; + + matching = pol; + break; + } + if (matching == NULL) { + print_err(CTD_CFG_ERROR, + "Unknown policy `%s' in helper " + "configuration", pol->name); + exit(EXIT_FAILURE); + } + /* FIXME: First set default policy, then change only + * tuned fields, not everything. + */ + memcpy(&helper->policy[i], pol, + sizeof(struct ctd_helper_policy)); + + stack_item_free(e); + break; + } + default: + print_err(CTD_CFG_ERROR, + "Unexpected symbol parsing helper policy"); + exit(EXIT_FAILURE); + break; + } + } + list_add(&helper_inst->head, &CONFIG(cthelper).list); +}; + +helper_type_list: + | helper_type_list helper_type_line + ; + +helper_type_line: helper_type + ; + +helper_type: T_HELPER_QUEUE_NUM T_NUMBER +{ + int *qnum; + struct stack_item *e; + + e = stack_item_alloc(SYMBOL_HELPER_QUEUE_NUM, sizeof(int)); + qnum = (int *) e->data; + *qnum = $2; + stack_item_push(&symbol_stack, e); +}; + +helper_type: T_HELPER_POLICY T_STRING '{' helper_policy_list '}' +{ + struct stack_item *e; + struct ctd_helper_policy *policy; + + e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); + if (e == NULL) { + print_err(CTD_CFG_ERROR, + "Helper policy configuration empty, fix your " + "configuration file, please"); + exit(EXIT_FAILURE); + break; + } + + policy = (struct ctd_helper_policy *) &e->data; + strncpy(policy->name, $2, CTD_HELPER_NAME_LEN); + policy->name[CTD_HELPER_NAME_LEN-1] = '\0'; + /* Now object is complete. */ + e->type = SYMBOL_HELPER_POLICY_EXPECT_ROOT; + stack_item_push(&symbol_stack, e); +}; + +helper_policy_list: + | helper_policy_list helper_policy_line + ; + +helper_policy_line: helper_policy_expect_max + | helper_policy_expect_timeout + ; + +helper_policy_expect_max: T_HELPER_EXPECT_MAX T_NUMBER +{ + struct stack_item *e; + struct ctd_helper_policy *policy; + + e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); + if (e == NULL) { + e = stack_item_alloc(SYMBOL_HELPER_EXPECT_POLICY_LEAF, + sizeof(struct ctd_helper_policy)); + } + policy = (struct ctd_helper_policy *) &e->data; + policy->expect_max = $2; + stack_item_push(&symbol_stack, e); +}; + +helper_policy_expect_timeout: T_HELPER_EXPECT_TIMEOUT T_NUMBER +{ + struct stack_item *e; + struct ctd_helper_policy *policy; + + e = stack_item_pop(&symbol_stack, SYMBOL_HELPER_EXPECT_POLICY_LEAF); + if (e == NULL) { + e = stack_item_alloc(SYMBOL_HELPER_EXPECT_POLICY_LEAF, + sizeof(struct ctd_helper_policy)); + } + policy = (struct ctd_helper_policy *) &e->data; + policy->expect_timeout = $2; + stack_item_push(&symbol_stack, e); +}; + %% int __attribute__((noreturn)) @@ -1640,6 +1835,11 @@ init_config(char *filename) CONFIG(stats).syslog_facility = -1; CONFIG(netlink).subsys_id = -1; + /* Initialize list of user-space helpers */ + INIT_LIST_HEAD(&CONFIG(cthelper).list); + + stack_init(&symbol_stack); + yyrestart(fp); yyparse(); fclose(fp); diff --git a/src/run.c b/src/run.c index 852bec6..3337694 100644 --- a/src/run.c +++ b/src/run.c @@ -50,6 +50,9 @@ void killer(int foo) if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) ctnl_kill(); + if (CONFIG(flags) & CTD_HELPER) + cthelper_kill(); + destroy_fds(STATE(fds)); unlink(CONFIG(lockfile)); dlog(LOG_NOTICE, "---- shutdown received ----"); @@ -199,6 +202,9 @@ static int local_handler(int fd, void *data) if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE)) return ctnl_local(fd, type, data); + if (CONFIG(flags) & CTD_HELPER) + return cthelper_local(fd, type, data); + return ret; } @@ -250,6 +256,11 @@ init(void) if (ctnl_init() < 0) return -1; + if (CONFIG(flags) & CTD_HELPER) { + if (cthelper_init() < 0) + return -1; + } + time(&STATE(stats).daemon_start_time); dlog(LOG_NOTICE, "initialization completed"); diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..104b7ba --- /dev/null +++ b/src/stack.c @@ -0,0 +1,56 @@ +/* + * (C) 2005-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. + */ + +#include +#include +#include +#include + +#include "stack.h" + +struct stack_item * +stack_item_alloc(int type, size_t data_len) +{ + struct stack_item *e; + + e = calloc(1, sizeof(struct stack_item) + data_len); + if (e == NULL) + return NULL; + + e->data_len = data_len; + e->type = type; + + return e; +} + +void stack_item_free(struct stack_item *e) +{ + free(e); +} + +void stack_item_push(struct stack *s, struct stack_item *e) +{ + list_add(&e->head, &s->list); +} + +struct stack_item *stack_item_pop(struct stack *s, int type) +{ + struct stack_item *cur, *tmp, *found = NULL; + + list_for_each_entry_safe(cur, tmp, &s->list, head) { + if (cur->type != type && type != -1) + continue; + + list_del(&cur->head); + found = cur; + break; + } + + return found; +} diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..fabec24 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,243 @@ +/* The following code has been extracted from the kenrel sources, if there is + * any problem, blame for mangling it. --pablo */ + +/* + * Generic address resultion entity + * + * Authors: + * net_random Alan Cox + * net_ratelimit Andi Kleen + * in{4,6}_pton YOSHIFUJI Hideaki, Copyright (C)2006 USAGI/WIDE Project + * + * Created by Alexey Kuznetsov + * + * 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. + */ + +/* + * lib/hexdump.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. See README and COPYING for + * more details. + */ + +#include +#include +#include /* for memcpy */ + +#include "helper.h" + +static int hex_to_bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + return -1; +} + +#define IN6PTON_XDIGIT 0x00010000 +#define IN6PTON_DIGIT 0x00020000 +#define IN6PTON_COLON_MASK 0x00700000 +#define IN6PTON_COLON_1 0x00100000 /* single : requested */ +#define IN6PTON_COLON_2 0x00200000 /* second : requested */ +#define IN6PTON_COLON_1_2 0x00400000 /* :: requested */ +#define IN6PTON_DOT 0x00800000 /* . */ +#define IN6PTON_DELIM 0x10000000 +#define IN6PTON_NULL 0x20000000 /* first/tail */ +#define IN6PTON_UNKNOWN 0x40000000 + +static inline int xdigit2bin(char c, int delim) +{ + int val; + + if (c == delim || c == '\0') + return IN6PTON_DELIM; + if (c == ':') + return IN6PTON_COLON_MASK; + if (c == '.') + return IN6PTON_DOT; + + val = hex_to_bin(c); + if (val >= 0) + return val | IN6PTON_XDIGIT | (val < 10 ? IN6PTON_DIGIT : 0); + + if (delim == -1) + return IN6PTON_DELIM; + return IN6PTON_UNKNOWN; +} + +int in4_pton(const char *src, int srclen, + uint8_t *dst, + int delim, const char **end) +{ + const char *s; + uint8_t *d; + uint8_t dbuf[4]; + int ret = 0; + int i; + int w = 0; + + if (srclen < 0) + srclen = strlen(src); + s = src; + d = dbuf; + i = 0; + while(1) { + int c; + c = xdigit2bin(srclen > 0 ? *s : '\0', delim); + if (!(c & (IN6PTON_DIGIT | IN6PTON_DOT | IN6PTON_DELIM | IN6PTON_COLON_MASK))) { + goto out; + } + if (c & (IN6PTON_DOT | IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + if (w == 0) + goto out; + *d++ = w & 0xff; + w = 0; + i++; + if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + if (i != 4) + goto out; + break; + } + goto cont; + } + w = (w * 10) + c; + if ((w & 0xffff) > 255) { + goto out; + } +cont: + if (i >= 4) + goto out; + s++; + srclen--; + } + ret = 1; + memcpy(dst, dbuf, sizeof(dbuf)); +out: + if (end) + *end = s; + return ret; +} + +int in6_pton(const char *src, int srclen, + uint8_t *dst, + int delim, const char **end) +{ + const char *s, *tok = NULL; + uint8_t *d, *dc = NULL; + uint8_t dbuf[16]; + int ret = 0; + int i; + int state = IN6PTON_COLON_1_2 | IN6PTON_XDIGIT | IN6PTON_NULL; + int w = 0; + + memset(dbuf, 0, sizeof(dbuf)); + + s = src; + d = dbuf; + if (srclen < 0) + srclen = strlen(src); + + while (1) { + int c; + + c = xdigit2bin(srclen > 0 ? *s : '\0', delim); + if (!(c & state)) + goto out; + if (c & (IN6PTON_DELIM | IN6PTON_COLON_MASK)) { + /* process one 16-bit word */ + if (!(state & IN6PTON_NULL)) { + *d++ = (w >> 8) & 0xff; + *d++ = w & 0xff; + } + w = 0; + if (c & IN6PTON_DELIM) { + /* We've processed last word */ + break; + } + /* + * COLON_1 => XDIGIT + * COLON_2 => XDIGIT|DELIM + * COLON_1_2 => COLON_2 + */ + switch (state & IN6PTON_COLON_MASK) { + case IN6PTON_COLON_2: + dc = d; + state = IN6PTON_XDIGIT | IN6PTON_DELIM; + if (dc - dbuf >= (int)sizeof(dbuf)) + state |= IN6PTON_NULL; + break; + case IN6PTON_COLON_1|IN6PTON_COLON_1_2: + state = IN6PTON_XDIGIT | IN6PTON_COLON_2; + break; + case IN6PTON_COLON_1: + state = IN6PTON_XDIGIT; + break; + case IN6PTON_COLON_1_2: + state = IN6PTON_COLON_2; + break; + default: + state = 0; + } + tok = s + 1; + goto cont; + } + + if (c & IN6PTON_DOT) { + ret = in4_pton(tok ? tok : s, srclen + (int)(s - tok), d, delim, &s); + if (ret > 0) { + d += 4; + break; + } + goto out; + } + + w = (w << 4) | (0xff & c); + state = IN6PTON_COLON_1 | IN6PTON_DELIM; + if (!(w & 0xf000)) { + state |= IN6PTON_XDIGIT; + } + if (!dc && d + 2 < dbuf + sizeof(dbuf)) { + state |= IN6PTON_COLON_1_2; + state &= ~IN6PTON_DELIM; + } + if (d + 2 >= dbuf + sizeof(dbuf)) { + state &= ~(IN6PTON_COLON_1|IN6PTON_COLON_1_2); + } +cont: + if ((dc && d + 4 < dbuf + sizeof(dbuf)) || + d + 4 == dbuf + sizeof(dbuf)) { + state |= IN6PTON_DOT; + } + if (d >= dbuf + sizeof(dbuf)) { + state &= ~(IN6PTON_XDIGIT|IN6PTON_COLON_MASK); + } + s++; + srclen--; + } + + i = 15; d--; + + if (dc) { + while(d >= dc) + dst[i--] = *d--; + while(i >= dc - dbuf) + dst[i--] = 0; + while(i >= 0) + dst[i--] = *d--; + } else + memcpy(dst, dbuf, sizeof(dbuf)); + + ret = 1; +out: + if (end) + *end = s; + return ret; +} -- cgit v1.2.3 From b6fd0dbf41c733c4824c06f2006554f5af4b895a Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Tue, 15 May 2012 14:31:35 +0200 Subject: conntrackd: RPC helper added to cthelper Signed-off-by: Jozsef Kadlecsik Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 14 ++ src/helpers/Makefile.am | 7 +- src/helpers/rpc.c | 488 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 507 insertions(+), 2 deletions(-) create mode 100644 src/helpers/rpc.c diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 711b309..2bf99fa 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -28,6 +28,20 @@ Helper { ExpectTimeout 300 } } + Type rpc inet tcp { + QueueNum 1 + Policy rpc { + ExpectMax 1 + ExpectTimeout 300 + } + } + Type rpc inet udp { + QueueNum 2 + Policy rpc { + ExpectMax 1 + ExpectTimeout 300 + } + } } # diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am index 2c9d63b..f441b29 100644 --- a/src/helpers/Makefile.am +++ b/src/helpers/Makefile.am @@ -1,9 +1,12 @@ include $(top_srcdir)/Make_global.am -pkglib_LTLIBRARIES = ct_helper_ftp.la +pkglib_LTLIBRARIES = ct_helper_ftp.la \ + ct_helper_rpc.la ct_helper_ftp_la_SOURCES = ftp.c ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) - +ct_helper_rpc_la_SOURCES = rpc.c +ct_helper_rpc_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_rpc_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) diff --git a/src/helpers/rpc.c b/src/helpers/rpc.c new file mode 100644 index 0000000..97c1b35 --- /dev/null +++ b/src/helpers/rpc.c @@ -0,0 +1,488 @@ +/* + * (C) 2012 by Jozsef Kadlecsik + * (C) 2012 by Pablo Neira Ayuso + * + * Based on: RPC extension for conntrack. + * + * This port has been sponsored by Vyatta Inc. + * + * Original copyright notice: + * + * (C) 2000 by Marcelo Barbosa Lima + * (C) 2001 by Rusty Russell + * (C) 2002,2003 by Ian (Larry) Latter + * (C) 2004,2005 by David Stes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "conntrackd.h" +#include "network.h" /* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* RFC 1050: RPC: Remote Procedure Call Protocol Specification Version 2 */ +/* RFC 1014: XDR: External Data Representation Standard */ +#define SUPPORTED_RPC_VERSION 2 + +struct rpc_info { + /* XID */ + uint32_t xid; + /* program */ + uint32_t pm_prog; + /* program version */ + uint32_t pm_vers; + /* transport protocol: TCP|UDP */ + uint32_t pm_prot; +}; + +/* So, this packet has hit the connection tracking matching code. + Mangle it, and change the expectation to match the new version. */ +static unsigned int +nf_nat_rpc(struct pkt_buff *pkt, int dir, struct nf_expect *exp, + uint8_t proto, uint32_t *port_ptr) +{ + const struct nf_conntrack *expected; + struct nf_conntrack *nat_tuple; + uint16_t initial_port, port; + + expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + + nat_tuple = nfct_new(); + if (nat_tuple == NULL) + return NF_ACCEPT; + + initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + + nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + + /* libnetfilter_conntrack needs this */ + nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); + nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, proto); + nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + + /* When you see the packet, we need to NAT it the same as the + * this one. */ + nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + + /* Try to get same port: if not, try to change it. */ + for (port = ntohs(initial_port); port != 0; port++) { + int ret; + + nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); + nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + + ret = cthelper_add_expect(exp); + if (ret == 0) + break; + else if (ret != -EBUSY) { + port = 0; + break; + } + } + nfct_destroy(nat_tuple); + + if (port == 0) + return NF_DROP; + + *port_ptr = htonl(port); + + return NF_ACCEPT; +} + +#define OFFSET(o, n) ((o) += n) +#define ROUNDUP(n) ((((n) + 3)/4)*4) + +static int +rpc_call(const uint32_t *data, uint32_t offset, uint32_t datalen, + struct rpc_info *rpc_info) +{ + uint32_t p, r; + + /* RPC CALL message body */ + + /* call_body { + * rpcvers + * prog + * vers + * proc + * opaque_auth cred + * opaque_auth verf + * pmap + * } + * + * opaque_auth { + * flavour + * opaque[len] <= MAX_AUTH_BYTES + * } + */ + if (datalen < OFFSET(offset, 4*4 + 2*2*4)) { + pr_debug("RPC CALL: too short packet: %u < %u\n", + datalen, offset); + return -1; + } + /* Check rpcversion */ + p = IXDR_GET_INT32(data); + if (p != SUPPORTED_RPC_VERSION) { + pr_debug("RPC CALL: wrong rpcvers %u != %u\n", + p, SUPPORTED_RPC_VERSION); + return -1; + } + /* Skip non-portmap requests */ + p = IXDR_GET_INT32(data); + if (p != PMAPPROG) { + pr_debug("RPC CALL: not portmap %u != %lu\n", + p, PMAPPROG); + return -1; + } + /* Check portmap version */ + p = IXDR_GET_INT32(data); + if (p != PMAPVERS) { + pr_debug("RPC CALL: wrong portmap version %u != %lu\n", + p, PMAPVERS); + return -1; + } + /* Skip non PMAPPROC_GETPORT requests */ + p = IXDR_GET_INT32(data); + if (p != PMAPPROC_GETPORT) { + pr_debug("RPC CALL: not PMAPPROC_GETPORT %u != %lu\n", + p, PMAPPROC_GETPORT); + return -1; + } + /* Check and skip credentials */ + r = IXDR_GET_INT32(data); + p = IXDR_GET_INT32(data); + pr_debug("RPC CALL: cred: %u %u (%u, %u)\n", + r, p, datalen, offset); + if (p > MAX_AUTH_BYTES) { + pr_debug("RPC CALL: invalid sized cred %u > %u\n", + p, MAX_AUTH_BYTES); + return -1; + } + r = ROUNDUP(p); + if (datalen < OFFSET(offset, r)) { + pr_debug("RPC CALL: too short to carry cred: %u < %u, %u\n", + datalen, offset, r); + return -1; + } + data += r/4; + /* Check and skip verifier */ + r = IXDR_GET_INT32(data); + p = IXDR_GET_INT32(data); + pr_debug("RPC CALL: verf: %u %u (%u, %u)\n", + r, p, datalen, offset); + if (p > MAX_AUTH_BYTES) { + pr_debug("RPC CALL: invalid sized verf %u > %u\n", + p, MAX_AUTH_BYTES); + return -1; + } + r = ROUNDUP(p); + if (datalen < OFFSET(offset, r)) { + pr_debug("RPC CALL: too short to carry verf: %u < %u, %u\n", + datalen, offset, r); + return -1; + } + data += r/4; + /* pmap { + * prog + * vers + * prot + * port + * } + */ + /* Check CALL size */ + if (datalen != offset + 4*4) { + pr_debug("RPC CALL: invalid size to carry pmap: %u != %u\n", + datalen, offset + 4*4); + return -1; + } + rpc_info->pm_prog = IXDR_GET_INT32(data); + rpc_info->pm_vers = IXDR_GET_INT32(data); + rpc_info->pm_prot = IXDR_GET_INT32(data); + /* Check supported protocols */ + if (!(rpc_info->pm_prot == IPPROTO_TCP + || rpc_info->pm_prot == IPPROTO_UDP)) { + pr_debug("RPC CALL: unsupported protocol %u", + rpc_info->pm_prot); + return -1; + } + p = IXDR_GET_INT32(data); + /* Check port: must be zero */ + if (p != 0) { + pr_debug("RPC CALL: port is nonzero %u\n", + ntohl(p)); + return -1; + } + pr_debug("RPC CALL: processed: xid %u, prog %u, vers %u, prot %u\n", + rpc_info->xid, rpc_info->pm_prog, + rpc_info->pm_vers, rpc_info->pm_prot); + + return 0; +} + +static int +rpc_reply(uint32_t *data, uint32_t offset, uint32_t datalen, + struct rpc_info *rpc_info, uint32_t **port_ptr) +{ + uint16_t port; + uint32_t p, r; + + /* RPC REPLY message body */ + + /* reply_body { + * reply_stat + * xdr_union { + * accepted_reply + * rejected_reply + * } + * } + * accepted_reply { + * opaque_auth verf + * accept_stat + * xdr_union { + * port + * struct mismatch_info + * } + * } + */ + + /* Check size: reply status */ + if (datalen < OFFSET(offset, 4)) { + pr_debug("RPC REPL: too short, missing rp_stat: %u < %u\n", + datalen, offset); + return -1; + } + p = IXDR_GET_INT32(data); + /* Check accepted request */ + if (p != MSG_ACCEPTED) { + pr_debug("RPC REPL: not accepted %u != %u\n", + p, MSG_ACCEPTED); + return -1; + } + /* Check and skip verifier */ + if (datalen < OFFSET(offset, 2*4)) { + pr_debug("RPC REPL: too short, missing verf: %u < %u\n", + datalen, offset); + return -1; + } + r = IXDR_GET_INT32(data); + p = IXDR_GET_INT32(data); + pr_debug("RPC REPL: verf: %u %u (%u, %u)\n", + r, p, datalen, offset); + if (p > MAX_AUTH_BYTES) { + pr_debug("RPC REPL: invalid sized verf %u > %u\n", + p, MAX_AUTH_BYTES); + return -1; + } + r = ROUNDUP(p); + /* verifier + ac_stat + port */ + if (datalen != OFFSET(offset, r) + 2*4) { + pr_debug("RPC REPL: invalid size to carry verf and " + "success: %u != %u\n", + datalen, offset + 2*4); + return -1; + } + data += r/4; + /* Check success */ + p = IXDR_GET_INT32(data); + if (p != SUCCESS) { + pr_debug("RPC REPL: not success %u != %u\n", + p, SUCCESS); + return -1; + } + /* Get port */ + *port_ptr = data; + port = IXDR_GET_INT32(data); /* -Wunused-but-set-parameter */ + if (port == 0) { + pr_debug("RPC REPL: port is zero\n"); + return -1; + } + pr_debug("RPC REPL: processed: xid %u, prog %u, vers %u, " + "prot %u, port %u\n", + rpc_info->xid, rpc_info->pm_prog, rpc_info->pm_vers, + rpc_info->pm_prot, port); + return 0; +} + +static int +rpc_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + int dir = CTINFO2DIR(ctinfo); + unsigned int offset = protoff, datalen; + uint32_t *data, *port_ptr = NULL, xid; + uint16_t port; + uint8_t proto = nfct_get_attr_u8(myct->ct, ATTR_L4PROTO); + enum msg_type rm_dir; + struct rpc_info *rpc_info = myct->priv_data; + union nfct_attr_grp_addr addr, daddr; + struct nf_expect *exp = NULL; + int ret = NF_ACCEPT; + + /* Until there's been traffic both ways, don't look into TCP packets. */ + if (proto == IPPROTO_TCP + && ctinfo != IP_CT_ESTABLISHED + && ctinfo != IP_CT_ESTABLISHED_REPLY) { + pr_debug("TCP RPC: Conntrackinfo = %u\n", ctinfo); + return ret; + } + if (proto == IPPROTO_TCP) { + struct tcphdr *th = + (struct tcphdr *) (pktb_network_header(pkt) + protoff); + offset += th->doff * 4; + } else { + offset += sizeof(struct udphdr); + } + /* Skip broken headers */ + if (offset % 4) { + pr_debug("RPC: broken header: offset %u%%4 != 0\n", offset); + return ret; + } + + /* Take into Record Fragment header */ + if (proto == IPPROTO_TCP) + offset += 4; + + datalen = pktb_len(pkt); + data = (uint32_t *)(pktb_network_header(pkt) + offset); + + /* rpc_msg { + * xid + * direction + * xdr_union { + * call_body + * reply_body + * } + * } + */ + + /* Check minimal msg size: xid + direction */ + if (datalen < OFFSET(offset, 2*4)) { + pr_debug("RPC: too short packet: %u < %u\n", + datalen, offset); + return ret; + } + xid = IXDR_GET_INT32(data); + rm_dir = IXDR_GET_INT32(data); + + /* Check direction */ + if (!((rm_dir == CALL && dir == MYCT_DIR_ORIG) + || (rm_dir == REPLY && dir == MYCT_DIR_REPL))) { + pr_debug("RPC: rm_dir != dir %u != %u\n", rm_dir, dir); + goto out; + } + + if (rm_dir == CALL) { + if (rpc_call(data, offset, datalen, rpc_info) < 0) + goto out; + + rpc_info->xid = xid; + + return ret; + } else { + /* Check XID */ + if (xid != rpc_info->xid) { + pr_debug("RPC REPL: XID does not match: %u != %u\n", + xid, rpc_info->xid); + goto out; + } + if (rpc_reply(data, offset, datalen, rpc_info, &port_ptr) < 0) + goto out; + + port = IXDR_GET_INT32(port_ptr); + port = htons(port); + + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + cthelper_get_addr_dst(myct->ct, !dir, &daddr); + cthelper_get_addr_src(myct->ct, !dir, &addr); + + exp = nfexp_new(); + if (exp == NULL) + goto out; + + if (cthelper_expect_init(exp, myct->ct, 0, &addr, &daddr, + rpc_info->pm_prot, + NULL, &port)) { + pr_debug("RPC: failed to init expectation\n"); + goto out_exp; + } + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. */ + if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { + ret = nf_nat_rpc(pkt, dir, exp, rpc_info->pm_prot, + port_ptr); + goto out_exp; + } + + /* Can't expect this? Best to drop packet now. */ + if (cthelper_add_expect(exp) < 0) { + pr_debug("RPC: cannot add expectation: %s\n", + strerror(errno)); + ret = NF_DROP; + } + } + +out_exp: + nfexp_destroy(exp); +out: + rpc_info->xid = 0; + return ret; +} + +static struct ctd_helper rpc_helper_tcp = { + .name = "rpc", + .l4proto = IPPROTO_TCP, + .cb = rpc_helper_cb, + .priv_data_len = sizeof(struct rpc_info), + .policy = { + { + .name = "rpc", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +static struct ctd_helper rpc_helper_udp = { + .name = "rpc", + .l4proto = IPPROTO_UDP, + .cb = rpc_helper_cb, + .priv_data_len = sizeof(struct rpc_info), + .policy = { + { + .name = "rpc", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) rpc_init(void); + +void rpc_init(void) +{ + helper_register(&rpc_helper_tcp); + helper_register(&rpc_helper_udp); +} -- cgit v1.2.3 From 30c70aa6360ef88395327774d193ea7b6115f86a Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Tue, 15 May 2012 14:43:20 +0200 Subject: conntrackd: TNS helper added to cthelper Signed-off-by: Jozsef Kadlecsik Signed-off-by: Pablo Neira Ayuso --- doc/helper/conntrackd.conf | 7 + src/helpers/Makefile.am | 7 +- src/helpers/tns.c | 396 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 src/helpers/tns.c diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf index 2bf99fa..80f1f92 100644 --- a/doc/helper/conntrackd.conf +++ b/doc/helper/conntrackd.conf @@ -42,6 +42,13 @@ Helper { ExpectTimeout 300 } } + Type tns inet tcp { + QueueNum 3 + Policy tns { + ExpectMax 1 + ExpectTimeout 300 + } + } } # diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am index f441b29..589b4f3 100644 --- a/src/helpers/Makefile.am +++ b/src/helpers/Makefile.am @@ -1,7 +1,8 @@ include $(top_srcdir)/Make_global.am pkglib_LTLIBRARIES = ct_helper_ftp.la \ - ct_helper_rpc.la + ct_helper_rpc.la \ + ct_helper_tns.la ct_helper_ftp_la_SOURCES = ftp.c ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) @@ -10,3 +11,7 @@ ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) ct_helper_rpc_la_SOURCES = rpc.c ct_helper_rpc_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) ct_helper_rpc_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_tns_la_SOURCES = tns.c +ct_helper_tns_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_tns_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) diff --git a/src/helpers/tns.c b/src/helpers/tns.c new file mode 100644 index 0000000..77f01d9 --- /dev/null +++ b/src/helpers/tns.c @@ -0,0 +1,396 @@ +/* + * (C) 2012 by Jozsef Kadlecsik + * (C) 2012 by Pablo Neira Ayuso + * + * Sponsored by Vyatta Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "conntrackd.h" +#include "network.h" /* for before and after */ +#include "helper.h" +#include "myct.h" +#include "log.h" + +#include /* for isdigit */ +#include + +#include + +#include +#include +#include +#include +#include +#include + +/* TNS SQL*Net Version 2 */ +enum tns_types { + TNS_TYPE_CONNECT = 1, + TNS_TYPE_ACCEPT = 2, + TNS_TYPE_ACK = 3, + TNS_TYPE_REFUSE = 4, + TNS_TYPE_REDIRECT = 5, + TNS_TYPE_DATA = 6, + TNS_TYPE_NULL = 7, + TNS_TYPE_ABORT = 9, + TNS_TYPE_RESEND = 11, + TNS_TYPE_MARKER = 12, + TNS_TYPE_ATTENTION = 13, + TNS_TYPE_CONTROL = 14, + TNS_TYPE_MAX = 19, +}; + +struct tns_header { + uint16_t len; + uint16_t csum; + uint8_t type; + uint8_t reserved; + uint16_t header_csum; +}; + +struct tns_redirect { + uint16_t data_len; +}; + +struct tns_info { + /* Scan next DATA|REDIRECT packet */ + bool parse; +}; + +static int try_number(const char *data, size_t dlen, uint32_t array[], + int array_size, char sep, char term) +{ + uint32_t len; + int i; + + memset(array, 0, sizeof(array[0])*array_size); + + /* Keep data pointing at next char. */ + for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) { + if (*data >= '0' && *data <= '9') { + array[i] = array[i]*10 + *data - '0'; + } + else if (*data == sep) + i++; + else { + /* Skip spaces. */ + if (*data == ' ') + continue; + /* Unexpected character; true if it's the + terminator and we're finished. */ + if (*data == term && i == array_size - 1) + return len; + pr_debug("Char %u (got %u nums) `%c' unexpected\n", + len, i, *data); + return 0; + } + } + pr_debug("Failed to fill %u numbers separated by %c\n", + array_size, sep); + return 0; +} + +/* Grab port: number up to delimiter */ +static int get_port(const char *data, size_t dlen, char delim, + struct myct_man *cmd) +{ + uint16_t tmp_port = 0; + uint32_t i; + + for (i = 0; i < dlen; i++) { + /* Finished? */ + if (data[i] == delim) { + if (tmp_port == 0) + break; + cmd->u.port = htons(tmp_port); + pr_debug("get_port: return %d\n", tmp_port); + return i + 1; + } + else if (data[i] >= '0' && data[i] <= '9') + tmp_port = tmp_port*10 + data[i] - '0'; + else if (data[i] == ' ') /* Skip spaces */ + continue; + else { /* Some other crap */ + pr_debug("get_port: invalid char `%c'\n", data[i]); + break; + } + } + return 0; +} + +/* (ADDRESS=(PROTOCOL=tcp)(DEV=x)(HOST=a.b.c.d)(PORT=a)) */ +/* FIXME: handle hostnames */ + +/* Returns 0, or length of port number */ +static unsigned int +find_pattern(struct pkt_buff *pkt, unsigned int dataoff, size_t dlen, + struct myct_man *cmd, unsigned int *numoff) +{ + const char *data = (const char *)pktb_network_header(pkt) + dataoff + + sizeof(struct tns_header); + int length, offset; + uint32_t array[4]; + const char *p; + + p = strstr(data, "("); + if (!p) + return 0; + + p = strstr(p+1, "HOST="); + if (!p) { + pr_debug("HOST= not found\n"); + return 0; + } + + offset = (int)(p - data) + strlen("HOST="); + *numoff = offset; + data += offset; + + length = try_number(data, dlen - offset, array, 4, '.', ')'); + if (length == 0) + return 0; + + cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | + (array[2] << 8) | array[3]); + + p = strstr(data+length, "("); + if (!p) + return 0; + + p = strstr(p, "PORT="); + if (!p) { + pr_debug("PORT= not found\n"); + return 0; + } + + p += strlen("PORT="); + return get_port(p, dlen - offset - length, ')', cmd); +} + +static inline uint16_t +nton(uint16_t len, unsigned int matchoff, unsigned int matchlen) +{ + uint32_t l = (uint32_t)ntohs(len) + matchoff - matchlen; + + return htons(l); +} + +/* So, this packet has hit the connection tracking matching code. + Mangle it, and change the expectation to match the new version. */ +static unsigned int +nf_nat_tns(struct pkt_buff *pkt, struct tns_header *tns, struct nf_expect *exp, + struct nf_conntrack *ct, int dir, + unsigned int matchoff, unsigned int matchlen) +{ + union nfct_attr_grp_addr newip; + char buffer[sizeof("255.255.255.255)(PORT=65535)")]; + unsigned int buflen; + const struct nf_conntrack *expected; + struct nf_conntrack *nat_tuple; + uint16_t initial_port, port; + + /* Connection will come from wherever this packet goes, hence !dir */ + cthelper_get_addr_dst(ct, !dir, &newip); + + expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED); + + nat_tuple = nfct_new(); + if (nat_tuple == NULL) + return NF_ACCEPT; + + initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST); + + nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir); + + /* libnetfilter_conntrack needs this */ + nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0); + nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0); + nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0); + + /* When you see the packet, we need to NAT it the same as the + * this one. */ + nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master"); + + /* Try to get same port: if not, try to change it. */ + for (port = ntohs(initial_port); port != 0; port++) { + int ret; + + nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port)); + nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple); + + ret = cthelper_add_expect(exp); + if (ret == 0) + break; + else if (ret != -EBUSY) { + port = 0; + break; + } + } + nfct_destroy(nat_tuple); + + if (port == 0) + return NF_DROP; + + buflen = snprintf(buffer, sizeof(buffer), + "%pI4)(PORT=%u)", &newip.ip, port); + if (!buflen) + goto out; + + if (!nfq_tcp_mangle(pkt, matchoff, matchlen, buffer, buflen)) + goto out; + + if (buflen != matchlen) { + /* FIXME: recalculate checksum */ + tns->csum = 0; + tns->header_csum = 0; + + tns->len = nton(tns->len, matchlen, buflen); + if (tns->type == TNS_TYPE_REDIRECT) { + struct tns_redirect *r; + + r = (struct tns_redirect *)((char *)tns + sizeof(struct tns_header)); + + r->data_len = nton(r->data_len, matchlen, buflen); + } + } + + return NF_ACCEPT; + +out: + cthelper_del_expect(exp); + return NF_DROP; +} + +static int +tns_helper_cb(struct pkt_buff *pkt, uint32_t protoff, + struct myct *myct, uint32_t ctinfo) +{ + struct tcphdr *th; + struct tns_header *tns; + int dir = CTINFO2DIR(ctinfo); + unsigned int dataoff, datalen, numoff = 0, numlen; + struct tns_info *tns_info = myct->priv_data; + union nfct_attr_grp_addr addr; + struct nf_expect *exp = NULL; + struct myct_man cmd; + int ret = NF_ACCEPT; + + memset(&cmd, 0, sizeof(struct myct_man)); + memset(&addr, 0, sizeof(union nfct_attr_grp_addr)); + + /* Until there's been traffic both ways, don't look into TCP packets. */ + if (ctinfo != IP_CT_ESTABLISHED + && ctinfo != IP_CT_ESTABLISHED_REPLY) { + pr_debug("TNS: Conntrackinfo = %u\n", ctinfo); + goto out; + } + /* Parse server direction only */ + if (dir != MYCT_DIR_REPL) { + pr_debug("TNS: skip client direction\n"); + goto out; + } + + th = (struct tcphdr *) (pktb_network_header(pkt) + protoff); + + dataoff = protoff + th->doff * 4; + datalen = pktb_len(pkt); + + if (datalen < sizeof(struct tns_header)) { + pr_debug("TNS: skip packet with short header\n"); + goto out; + } + + tns = (struct tns_header *)(pktb_network_header(pkt) + dataoff); + + if (tns->type == TNS_TYPE_REDIRECT) { + struct tns_redirect *redirect; + + dataoff += sizeof(struct tns_header); + datalen -= sizeof(struct tns_header); + redirect = (struct tns_redirect *)(pktb_network_header(pkt) + dataoff); + tns_info->parse = false; + + if (ntohs(redirect->data_len) == 0) { + tns_info->parse = true; + goto out; + } + goto parse; + } + + /* Parse only the very next DATA packet */ + if (!(tns_info->parse && tns->type == TNS_TYPE_DATA)) { + tns_info->parse = false; + goto out; + } +parse: + numlen = find_pattern(pkt, dataoff, datalen, &cmd, &numoff); + tns_info->parse = false; + if (!numlen) + goto out; + + /* We refer to the reverse direction ("!dir") tuples here, + * because we're expecting something in the other direction. + * Doesn't matter unless NAT is happening. */ + cthelper_get_addr_src(myct->ct, !dir, &addr); + + exp = nfexp_new(); + if (exp == NULL) + goto out; + + if (cthelper_expect_init(exp, myct->ct, 0, + &addr, &cmd.u3, + IPPROTO_TCP, + NULL, &cmd.u.port)) { + pr_debug("TNS: failed to init expectation\n"); + goto out_exp; + } + + /* Now, NAT might want to mangle the packet, and register the + * (possibly changed) expectation itself. + */ + if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) { + ret = nf_nat_tns(pkt, tns, exp, myct->ct, dir, numoff, numlen); + goto out_exp; + } + + /* Can't expect this? Best to drop packet now. */ + if (cthelper_add_expect(exp) < 0) { + pr_debug("TNS: cannot add expectation: %s\n", + strerror(errno)); + ret = NF_DROP; + goto out_exp; + } + goto out; + +out_exp: + nfexp_destroy(exp); +out: + return ret; +} + +static struct ctd_helper tns_helper = { + .name = "tns", + .l4proto = IPPROTO_TCP, + .cb = tns_helper_cb, + .priv_data_len = sizeof(struct tns_info), + .policy = { + [0] = { + .name = "tns", + .expect_max = 1, + .expect_timeout = 300, + }, + }, +}; + +void __attribute__ ((constructor)) tns_init(void); + +void tns_init(void) +{ + helper_register(&tns_helper); +} -- cgit v1.2.3 From 687fc04ea8de73eb1ec19d933c8d81f054c977dd Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Fri, 25 May 2012 03:03:33 +0200 Subject: tests: conntrackd: add cthelper-test infrastructure This patch adds the automated testing infrastructure the user-space helpers. Basically, this adds the `cthelper-test' program that can be invoked from the command line: ./cthelper-test oracle-tns/oracle-tns-redirect.pcap tns tcp To test the helper with one PCAP file that contains traces of Oracle TNS traffic. This will also allow fuzzy testing of user-space helper, for further validation, not yet implemented. To compile this tool, you have to run: ./configure make check under the qa/cthelper-test/ directory. I'm doing like this because this directory is not included in the standalone tarball that make distcheck generates (I don't want to bloat it with development tools that can be retrieved from the git repository). Signed-off-by: Pablo Neira Ayuso --- tests/conntrackd/cthelper/.gitignore | 14 ++ tests/conntrackd/cthelper/Make_global.am | 7 + tests/conntrackd/cthelper/Makefile.am | 20 +++ tests/conntrackd/cthelper/README | 2 + tests/conntrackd/cthelper/configure.ac | 64 +++++++ tests/conntrackd/cthelper/ct.c | 91 ++++++++++ tests/conntrackd/cthelper/ct.h | 22 +++ tests/conntrackd/cthelper/expect.c | 199 +++++++++++++++++++++ tests/conntrackd/cthelper/l3_ipv4.c | 86 +++++++++ tests/conntrackd/cthelper/l4_tcp.c | 88 +++++++++ tests/conntrackd/cthelper/l4_udp.c | 88 +++++++++ tests/conntrackd/cthelper/main.c | 175 ++++++++++++++++++ tests/conntrackd/cthelper/pcaps/nfsv3.pcap | Bin 0 -> 6824 bytes .../cthelper/pcaps/oracle-tns-redirect.pcap | Bin 0 -> 1095 bytes tests/conntrackd/cthelper/proto.c | 49 +++++ tests/conntrackd/cthelper/proto.h | 50 ++++++ tests/conntrackd/cthelper/run-test.sh | 8 + tests/conntrackd/cthelper/test.h | 13 ++ 18 files changed, 976 insertions(+) create mode 100644 tests/conntrackd/cthelper/.gitignore create mode 100644 tests/conntrackd/cthelper/Make_global.am create mode 100644 tests/conntrackd/cthelper/Makefile.am create mode 100644 tests/conntrackd/cthelper/README create mode 100644 tests/conntrackd/cthelper/configure.ac create mode 100755 tests/conntrackd/cthelper/ct.c create mode 100755 tests/conntrackd/cthelper/ct.h create mode 100644 tests/conntrackd/cthelper/expect.c create mode 100755 tests/conntrackd/cthelper/l3_ipv4.c create mode 100755 tests/conntrackd/cthelper/l4_tcp.c create mode 100755 tests/conntrackd/cthelper/l4_udp.c create mode 100755 tests/conntrackd/cthelper/main.c create mode 100644 tests/conntrackd/cthelper/pcaps/nfsv3.pcap create mode 100644 tests/conntrackd/cthelper/pcaps/oracle-tns-redirect.pcap create mode 100755 tests/conntrackd/cthelper/proto.c create mode 100755 tests/conntrackd/cthelper/proto.h create mode 100644 tests/conntrackd/cthelper/run-test.sh create mode 100644 tests/conntrackd/cthelper/test.h diff --git a/tests/conntrackd/cthelper/.gitignore b/tests/conntrackd/cthelper/.gitignore new file mode 100644 index 0000000..928e44b --- /dev/null +++ b/tests/conntrackd/cthelper/.gitignore @@ -0,0 +1,14 @@ +.deps/ +.libs/ +Makefile +Makefile.in +*.o +*.la +*.lo + +/aclocal.m4 +/autom4te.cache/ +/build-aux/ +/config.* +/configure +/libtool diff --git a/tests/conntrackd/cthelper/Make_global.am b/tests/conntrackd/cthelper/Make_global.am new file mode 100644 index 0000000..06785a1 --- /dev/null +++ b/tests/conntrackd/cthelper/Make_global.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = -I$(top_srcdir)/include -I../../../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 \ + -Wno-unused-parameter \ + ${LIBNETFILTER_CONNTRACK_CFLAGS} \ + ${LIBNETFILTER_CTTIMEOUT_CFLAGS} diff --git a/tests/conntrackd/cthelper/Makefile.am b/tests/conntrackd/cthelper/Makefile.am new file mode 100644 index 0000000..b8f0d42 --- /dev/null +++ b/tests/conntrackd/cthelper/Makefile.am @@ -0,0 +1,20 @@ +include $(top_srcdir)/Make_global.am + +check_PROGRAMS = cthelper-test + +cthelper_test_SOURCES = proto.c \ + ct.c \ + l3_ipv4.c \ + l4_tcp.c \ + l4_udp.c \ + expect.c \ + ../../../src/helpers.c \ + main.c + +cthelper_test_LDFLAGS = -dynamic \ + -lpcap \ + -ldl \ + -lmnl \ + -lnetfilter_queue \ + -lnetfilter_conntrack \ + -export-dynamic diff --git a/tests/conntrackd/cthelper/README b/tests/conntrackd/cthelper/README new file mode 100644 index 0000000..6e8b385 --- /dev/null +++ b/tests/conntrackd/cthelper/README @@ -0,0 +1,2 @@ +This directory contains PCAP files with traffic traces that we can use to test +the user-space helpers. diff --git a/tests/conntrackd/cthelper/configure.ac b/tests/conntrackd/cthelper/configure.ac new file mode 100644 index 0000000..8b3da5c --- /dev/null +++ b/tests/conntrackd/cthelper/configure.ac @@ -0,0 +1,64 @@ +AC_INIT(cthelper-test, 0.0.1, pablo@netfilter.org) +AC_CONFIG_AUX_DIR([build-aux]) + +AC_CANONICAL_HOST +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([-Wall foreign subdir-objects + tar-pax no-dist-gzip dist-bzip2 1.6]) + +dnl kernel style compile messages +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AC_SEARCH_LIBS([dlopen], [dl], [libdl_LIBS="$LIBS"; LIBS=""]) +AC_SUBST([libdl_LIBS]) + +AC_PROG_CC +AC_DISABLE_STATIC +AM_PROG_LIBTOOL +AC_PROG_INSTALL +AC_PROG_LN_S +AM_PROG_LEX +AC_PROG_YACC + +case "$host" in +*-*-linux*) ;; +*) AC_MSG_ERROR([Linux only, dude!]);; +esac + +PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.0]) +PKG_CHECK_MODULES([LIBNETFILTER_QUEUE], [libnetfilter_queue >= 1.0.0]) + +AC_CHECK_HEADERS(arpa/inet.h) +dnl check for inet_pton +AC_CHECK_FUNCS(inet_pton) +dnl Some systems have it, but not IPv6 +if test "$ac_cv_func_inet_pton" = "yes" ; then +AC_MSG_CHECKING(if inet_pton supports IPv6) +AC_RUN_IFELSE([AC_LANG_SOURCE([[ +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_ARPA_INET_H +#include +#endif +int main() + { + struct in6_addr addr6; + if (inet_pton(AF_INET6, "::1", &addr6) < 1) + exit(1); + else + exit(0); + } + ]])],[ AC_MSG_RESULT(yes) + AC_DEFINE_UNQUOTED(HAVE_INET_PTON_IPV6, 1, [Define to 1 if inet_pton supports IPv6.]) + ],[AC_MSG_RESULT(no)],[AC_MSG_RESULT(no)]) +fi + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/tests/conntrackd/cthelper/ct.c b/tests/conntrackd/cthelper/ct.c new file mode 100755 index 0000000..1c17336 --- /dev/null +++ b/tests/conntrackd/cthelper/ct.c @@ -0,0 +1,91 @@ +#include +#include +#include + +#include + +#include + +#include "proto.h" +#include "helper.h" +#include "myct.h" +#include "ct.h" + +static LIST_HEAD(ct_list); + +struct nf_ct_entry * +ct_alloc(const uint8_t *pkt, unsigned int l3hdr_len, + struct cthelper_proto_l2l3_helper *l3h, + struct cthelper_proto_l4_helper *l4h) +{ + struct nf_ct_entry *ct; + + ct = calloc(1, sizeof(struct nf_ct_entry)); + if (ct == NULL) + return NULL; + + ct->myct = calloc(1, sizeof(struct myct)); + if (ct->myct == NULL) { + free(ct); + return NULL; + } + ct->myct->ct = nfct_new(); + if (ct->myct->ct == NULL) { + free(ct->myct); + free(ct); + return NULL; + } + /* FIXME: use good private helper size */ + ct->myct->priv_data = calloc(1, 128); + if (ct->myct->priv_data == NULL) { + nfct_destroy(ct->myct->ct); + free(ct->myct); + free(ct); + return NULL; + } + + l3h->l3ct_build(pkt, ct->myct->ct); + l4h->l4ct_build(pkt + l3hdr_len, ct->myct->ct); + + return ct; +} + +struct nf_ct_entry * +ct_find(const uint8_t *pkt, unsigned int l3hdr_len, + struct cthelper_proto_l2l3_helper *l3h, + struct cthelper_proto_l4_helper *l4h, unsigned int *ctinfo) +{ + struct nf_ct_entry *cur; + + list_for_each_entry(cur, &ct_list, head) { + if (l3h->l3ct_cmp_orig(pkt, cur->myct->ct) && + l4h->l4ct_cmp_orig(pkt + l3hdr_len, cur->myct->ct)) { + *ctinfo = 0; + return cur; + } + if (l3h->l3ct_cmp_repl(pkt, cur->myct->ct) && + l4h->l4ct_cmp_repl(pkt + l3hdr_len, cur->myct->ct)) { + *ctinfo = IP_CT_IS_REPLY; + return cur; + } + } + return NULL; +} + +void ct_add(struct nf_ct_entry *ct) +{ + list_add(&ct->head, &ct_list); +} + +void ct_flush(void) +{ + struct nf_ct_entry *cur, *tmp; + + list_for_each_entry_safe(cur, tmp, &ct_list, head) { + list_del(&cur->head); + free(cur->myct->priv_data); + free(cur->myct->ct); + free(cur->myct); + free(cur); + } +} diff --git a/tests/conntrackd/cthelper/ct.h b/tests/conntrackd/cthelper/ct.h new file mode 100755 index 0000000..f01d49d --- /dev/null +++ b/tests/conntrackd/cthelper/ct.h @@ -0,0 +1,22 @@ +#ifndef _CT_H_ +#define _CT_H_ + +#include "../../../include/linux_list.h" +#include "../../../include/myct.h" + +struct nf_ct_entry { + struct list_head head; + struct myct *myct; +}; + +struct cthelper_proto_l2l3_helper; +struct cthelper_proto_l4_helper; + +struct nf_ct_entry *ct_alloc(const uint8_t *pkt, unsigned int l3hdr_len, struct cthelper_proto_l2l3_helper *l3h, struct cthelper_proto_l4_helper *l4h); + +struct nf_ct_entry *ct_find(const uint8_t *pkt, unsigned int l3hdr_len, struct cthelper_proto_l2l3_helper *l3h, struct cthelper_proto_l4_helper *l4h, unsigned int *ctinfo); + +void ct_add(struct nf_ct_entry *ct); +void ct_flush(void); + +#endif diff --git a/tests/conntrackd/cthelper/expect.c b/tests/conntrackd/cthelper/expect.c new file mode 100644 index 0000000..c667293 --- /dev/null +++ b/tests/conntrackd/cthelper/expect.c @@ -0,0 +1,199 @@ +/* + * (C) 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 version 2 as + * published by the Free Software Foundation (or any later at your option). + * + * This code has been sponsored by Vyatta Inc. + */ + +#include "../../../include/helper.h" +#include "test.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +int +cthelper_expect_init(struct nf_expect *exp, struct nf_conntrack *master, + uint32_t class, + union nfct_attr_grp_addr *saddr, + union nfct_attr_grp_addr *daddr, + uint8_t l4proto, uint16_t *sport, uint16_t *dport) +{ + struct nf_conntrack *expected, *mask; + + expected = nfct_new(); + if (!expected) + return -1; + + mask = nfct_new(); + if (!mask) + return -1; + + if (saddr) { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, saddr->ip); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, saddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } else { + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + int i; + uint32_t addr[4] = {}; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_SRC, 0x00000000); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(mask, ATTR_IPV4_SRC, 0x00000000); + break; + case AF_INET6: + for (i=0; i<4; i++) + memset(addr, 0x00000000, sizeof(uint32_t)); + + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_SRC, addr); + + nfct_set_attr_u8(mask, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(mask, ATTR_IPV6_SRC, addr); + break; + default: + break; + } + } + + if (sport) { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, *sport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0xffff); + break; + default: + break; + } + } else { + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_SRC, 0x0000); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_SRC, 0x0000); + break; + default: + break; + } + } + + switch(nfct_get_attr_u8(master, ATTR_L3PROTO)) { + uint32_t addr[4] = {}; + int i; + + case AF_INET: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(expected, ATTR_IPV4_DST, daddr->ip); + nfct_set_attr_u32(mask, ATTR_IPV4_DST, 0xffffffff); + break; + case AF_INET6: + nfct_set_attr_u8(expected, ATTR_L3PROTO, AF_INET6); + nfct_set_attr(expected, ATTR_IPV6_DST, daddr->ip6); + + for (i=0; i<4; i++) + memset(addr, 0xffffffff, sizeof(uint32_t)); + + nfct_set_attr(mask, ATTR_IPV6_DST, addr); + break; + default: + break; + } + + switch(l4proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + nfct_set_attr_u8(expected, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(expected, ATTR_PORT_DST, *dport); + nfct_set_attr_u8(mask, ATTR_L4PROTO, l4proto); + nfct_set_attr_u16(mask, ATTR_PORT_DST, 0xffff); + break; + default: + break; + } + + nfexp_set_attr(exp, ATTR_EXP_MASTER, master); + nfexp_set_attr(exp, ATTR_EXP_EXPECTED, expected); + nfexp_set_attr(exp, ATTR_EXP_MASK, mask); + + nfct_destroy(expected); + nfct_destroy(mask); + + return 0; +} + +int cthelper_add_expect(struct nf_expect *exp) +{ + cthelper_test_stats.ct_expect_created++; + return 0; +} + +int cthelper_del_expect(struct nf_expect *exp) +{ + return 0; +} + +void +cthelper_get_addr_src(struct nf_conntrack *ct, int dir, + union nfct_attr_grp_addr *addr) +{ + switch (dir) { + case MYCT_DIR_ORIG: + nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_SRC, addr); + break; + case MYCT_DIR_REPL: + nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_SRC, addr); + break; + } +} + +void +cthelper_get_addr_dst(struct nf_conntrack *ct, int dir, + union nfct_attr_grp_addr *addr) +{ + switch (dir) { + case MYCT_DIR_ORIG: + nfct_get_attr_grp(ct, ATTR_GRP_ORIG_ADDR_DST, addr); + break; + case MYCT_DIR_REPL: + nfct_get_attr_grp(ct, ATTR_GRP_REPL_ADDR_DST, addr); + break; + } +} diff --git a/tests/conntrackd/cthelper/l3_ipv4.c b/tests/conntrackd/cthelper/l3_ipv4.c new file mode 100755 index 0000000..8edfd2e --- /dev/null +++ b/tests/conntrackd/cthelper/l3_ipv4.c @@ -0,0 +1,86 @@ +#include +#include +#include + +#include "proto.h" + +#include + +#define PRINT_CMP(...) + +static void +l3_ipv4_ct_build_tuple(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + nfct_set_attr_u16(ct, ATTR_ORIG_L3PROTO, AF_INET); + nfct_set_attr_u16(ct, ATTR_REPL_L3PROTO, AF_INET); + nfct_set_attr_u32(ct, ATTR_ORIG_IPV4_SRC, iph->saddr); + nfct_set_attr_u32(ct, ATTR_ORIG_IPV4_DST, iph->daddr); + nfct_set_attr_u32(ct, ATTR_REPL_IPV4_SRC, iph->daddr); + nfct_set_attr_u32(ct, ATTR_REPL_IPV4_DST, iph->saddr); +} + +static int +l3_ipv4_ct_cmp_tuple_orig(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + PRINT_CMP("cmp_orig iph->saddr: %x == %x\n", + iph->saddr, nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_SRC)); + PRINT_CMP("cmp_orig iph->daddr: %x == %x\n", + iph->daddr, nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_DST)); + + if (iph->saddr == nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_SRC) && + iph->daddr == nfct_get_attr_u32(ct, ATTR_ORIG_IPV4_DST)) + return 1; + + return 0; +} + +static int +l3_ipv4_ct_cmp_tuple_repl(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + PRINT_CMP("cmp_repl iph->saddr: %x == %x\n", + iph->saddr, nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC)); + PRINT_CMP("cmp_repl iph->daddr: %x == %x\n", + iph->daddr, nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST)); + + if (iph->saddr == nfct_get_attr_u32(ct, ATTR_REPL_IPV4_SRC) && + iph->daddr == nfct_get_attr_u32(ct, ATTR_REPL_IPV4_DST)) + return 1; + + return 0; +} + +static int l3_ipv4_pkt_l4proto_num(const uint8_t *pkt) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + return iph->protocol; +} + +static int l3_ipv4_pkt_l3hdr_len(const uint8_t *pkt) +{ + const struct iphdr *iph = (const struct iphdr *)pkt; + + return iph->ihl << 2; +} + +static struct cthelper_proto_l2l3_helper ipv4 = { + .l2protonum = ETH_P_IP, + .l3protonum = AF_INET, + .l2hdr_len = ETH_HLEN, + .l3ct_build = l3_ipv4_ct_build_tuple, + .l3ct_cmp_orig = l3_ipv4_ct_cmp_tuple_orig, + .l3ct_cmp_repl = l3_ipv4_ct_cmp_tuple_repl, + .l3pkt_hdr_len = l3_ipv4_pkt_l3hdr_len, + .l4pkt_proto = l3_ipv4_pkt_l4proto_num, +}; + +void l2l3_ipv4_init(void) +{ + cthelper_proto_l2l3_helper_register(&ipv4); +} diff --git a/tests/conntrackd/cthelper/l4_tcp.c b/tests/conntrackd/cthelper/l4_tcp.c new file mode 100755 index 0000000..f27c85d --- /dev/null +++ b/tests/conntrackd/cthelper/l4_tcp.c @@ -0,0 +1,88 @@ +#include +#include + +#include "proto.h" + +#include + +#define PRINT_CMP(...) + +static void l4_tcp_ct_build_tuple(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct tcphdr *tcph = (const struct tcphdr *)pkt; + + nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, tcph->source); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, tcph->dest); + nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, tcph->dest); + nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, tcph->source); +} + +static int l4_tcp_ct_cmp_tuple_orig(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct tcphdr *tcph = (const struct tcphdr *)pkt; + + PRINT_CMP("cmp_orig tcph->source: %u == %u\n", + tcph->source, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + PRINT_CMP("cmp_orig tcph->dest: %u == %u\n", + tcph->dest, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + + if (tcph->source == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) && + tcph->dest == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)) + return 1; + + return 0; +} + +static int +l4_tcp_ct_cmp_tuple_repl(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct tcphdr *tcph = (const struct tcphdr *)pkt; + + PRINT_CMP("cmp_repl tcph->source: %u == %u\n", + tcph->source, nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC)); + PRINT_CMP("cmp_repl tcph->dest: %u == %u\n", + tcph->dest, nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST)); + + if (tcph->source == nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC) && + tcph->dest == nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST)) + return 1; + + return 0; +} + +static int +l4_tcp_ct_cmp_port(struct nf_conntrack *ct, uint16_t port) +{ + PRINT_CMP("cmp_port src: %u == %u\n", + port, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + PRINT_CMP("cmp_port dst: %u == %u\n", + port, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + + if (port == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) || + port == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)) + return 1; + + return 0; +} + +static int l4_tcp_pkt_no_data(const uint8_t *pkt) +{ + const struct tcphdr *tcph = (const struct tcphdr *)pkt; + return tcph->syn || tcph->fin || tcph->rst || !tcph->psh; +} + +static struct cthelper_proto_l4_helper tcp = { + .l4protonum = IPPROTO_TCP, + .l4ct_build = l4_tcp_ct_build_tuple, + .l4ct_cmp_orig = l4_tcp_ct_cmp_tuple_orig, + .l4ct_cmp_repl = l4_tcp_ct_cmp_tuple_repl, + .l4ct_cmp_port = l4_tcp_ct_cmp_port, + .l4pkt_no_data = l4_tcp_pkt_no_data, +}; + +void l4_tcp_init(void) +{ + cthelper_proto_l4_helper_register(&tcp); +} diff --git a/tests/conntrackd/cthelper/l4_udp.c b/tests/conntrackd/cthelper/l4_udp.c new file mode 100755 index 0000000..4d52d0a --- /dev/null +++ b/tests/conntrackd/cthelper/l4_udp.c @@ -0,0 +1,88 @@ +#include +#include + +#include "proto.h" + +#include + +#define PRINT_CMP(...) + +static void l4_udp_ct_build_tuple(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct udphdr *udph = (const struct udphdr *)pkt; + + nfct_set_attr_u8(ct, ATTR_ORIG_L4PROTO, IPPROTO_UDP); + nfct_set_attr_u8(ct, ATTR_REPL_L4PROTO, IPPROTO_UDP); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, udph->source); + nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, udph->dest); + nfct_set_attr_u16(ct, ATTR_REPL_PORT_SRC, udph->dest); + nfct_set_attr_u16(ct, ATTR_REPL_PORT_DST, udph->source); +} + +static int l4_udp_ct_cmp_tuple_orig(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct udphdr *udph = (const struct udphdr *)pkt; + + PRINT_CMP("cmp_orig udph->source: %u == %u\n", + udph->source, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + PRINT_CMP("cmp_orig udph->dest: %u == %u\n", + udph->dest, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + + if (udph->source == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) && + udph->dest == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)) + return 1; + + return 0; +} + +static int +l4_udp_ct_cmp_tuple_repl(const uint8_t *pkt, struct nf_conntrack *ct) +{ + const struct udphdr *udph = (const struct udphdr *)pkt; + + PRINT_CMP("cmp_repl udph->source: %u == %u\n", + udph->source, nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC)); + PRINT_CMP("cmp_repl udph->dest: %u == %u\n", + udph->dest, nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST)); + + if (udph->source == nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC) && + udph->dest == nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST)) + return 1; + + return 0; +} + +static int +l4_udp_ct_cmp_port(struct nf_conntrack *ct, uint16_t port) +{ + PRINT_CMP("cmp_port src: %u == %u\n", + port, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC)); + PRINT_CMP("cmp_port dst: %u == %u\n", + port, nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)); + + if (port == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) || + port == nfct_get_attr_u16(ct, ATTR_ORIG_PORT_DST)) + return 1; + + return 0; +} + +static int l4_udp_pkt_no_data(const uint8_t *pkt) +{ + /* UDP has no control packets. */ + return 1; +} + +static struct cthelper_proto_l4_helper tcp = { + .l4protonum = IPPROTO_UDP, + .l4ct_build = l4_udp_ct_build_tuple, + .l4ct_cmp_orig = l4_udp_ct_cmp_tuple_orig, + .l4ct_cmp_repl = l4_udp_ct_cmp_tuple_repl, + .l4ct_cmp_port = l4_udp_ct_cmp_port, + .l4pkt_no_data = l4_udp_pkt_no_data, +}; + +void l4_udp_init(void) +{ + cthelper_proto_l4_helper_register(&tcp); +} diff --git a/tests/conntrackd/cthelper/main.c b/tests/conntrackd/cthelper/main.c new file mode 100755 index 0000000..695054a --- /dev/null +++ b/tests/conntrackd/cthelper/main.c @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ct.h" +#include "proto.h" +#include "../../../include/helper.h" +#include "test.h" + +#include + +struct cthelper_test_stats cthelper_test_stats; + +static int +cthelper_process_packet(const uint8_t *pkt, uint32_t pktlen, + struct ctd_helper *h, int proto, uint16_t port) +{ + struct pkt_buff *pktb; + struct cthelper_proto_l2l3_helper *l3h; + struct cthelper_proto_l4_helper *l4h; + unsigned int l3hdr_len, l4protonum; + struct nf_ct_entry *ct; + int ret, this_proto; + uint32_t dataoff, ctinfo = 0; + + l3h = cthelper_proto_l2l3_helper_find(pkt, &l4protonum, &l3hdr_len); + if (l3h == NULL) { + fprintf(stderr, "Unsupported layer 3 protocol, skipping.\n"); + return -1; + } + + l4h = cthelper_proto_l4_helper_find(pkt, l4protonum); + if (l4h == NULL) { + fprintf(stderr, "Unsupported layer 4 protocol, skipping.\n"); + return -1; + } + /* get layer 3 header. */ + pkt += l3h->l2hdr_len; + pktlen -= l3h->l2hdr_len; + + /* skip packet with mismatching protocol */ + this_proto = l3h->l4pkt_proto(pkt); + if (this_proto != proto) { + cthelper_test_stats.pkt_mismatch_proto++; + return 0; + } + + /* Look for the fake conntrack. */ + ct = ct_find(pkt, l3hdr_len, l3h, l4h, &ctinfo); + if (ct == NULL) { + /* It doesn't exist any, create one. */ + ct = ct_alloc(pkt, l3hdr_len, l3h, l4h); + if (ct == NULL) { + fprintf(stderr, "Not enough memory\n"); + return -1; + } + ct_add(ct); + ctinfo += IP_CT_NEW; + } else + ctinfo += IP_CT_ESTABLISHED; + + /* skip packets with mismatching ports */ + if (!l4h->l4ct_cmp_port(ct->myct->ct, ntohs(port))) { + cthelper_test_stats.pkt_mismatch_port++; + return -1; + } + + /* + * FIXME: reminder, implement this below in the kernel for cthelper. + */ + + /* This packet contains no data, skip it. */ +/* if (l4h->l4pkt_no_data && l4h->l4pkt_no_data(pkt + l3hdr_len)) { + NFG_DEBUG("skipping packet with no data\n"); + continue; + } */ + + /* Create the fake network buffer. */ + pktb = pktb_alloc(AF_INET, pkt, pktlen, 128); + if (pktb == NULL) { + fprintf(stderr, "Not enough memory\n"); + return -1; + } + + dataoff = l3h->l3pkt_hdr_len(pkt); + if (dataoff > pktb_len(pktb)) { + fprintf(stderr, "wrong layer 3 offset: %d > %d\n", + dataoff, pktb_len(pktb)); + return -1; + } + + ret = h->cb(pktb, dataoff, ct->myct, ctinfo); + pktb_free(pktb); + + return ret; +} + +static int +cthelper_test(const char *pcapfile, const char *helper_name, + int l4proto, uint16_t port) +{ + struct pcap_pkthdr pcaph; + char errbuf[PCAP_ERRBUF_SIZE]; + const u_char *pkt; + pcap_t *handle; + struct ctd_helper *h; + + h = helper_find("/usr/lib/conntrack-tools", + helper_name, l4proto, RTLD_NOW); + if (h == NULL) { + fprintf(stderr, "couldn't find helper: %s\n", helper_name); + return -1; + } + + handle = pcap_open_offline(pcapfile, errbuf); + if (handle == NULL) { + fprintf(stderr, "couldn't open pcap file %s: %s\n", + pcapfile, errbuf); + return -1; + } + while ((pkt = pcap_next(handle, &pcaph)) != NULL) { + cthelper_test_stats.pkts++; + cthelper_process_packet(pkt, pcaph.caplen, h, l4proto, port); + } + + ct_flush(); + pcap_close(handle); + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret, l4proto; + + if (argc != 5) { + fprintf(stderr, "Wrong usage:\n"); + fprintf(stderr, "%s [pcap_file] [helper-name] [proto] [port]\n", + argv[0]); + fprintf(stderr, "example: %s file.pcap ftp tcp 21\n", argv[0]); + exit(EXIT_FAILURE); + } + if (strncmp("tcp", argv[3], strlen("tcp")) == 0) + l4proto = IPPROTO_TCP; + else if (strncmp("udp", argv[3], strlen("udp")) == 0) + l4proto = IPPROTO_UDP; + else { + fprintf(stderr, "%s not supported, send a patch to Pablo\n", + argv[3]); + exit(EXIT_FAILURE); + } + + /* Initialization of supported layer 3 and 4 protocols here. */ + l2l3_ipv4_init(); + l4_tcp_init(); + l4_udp_init(); + + if (cthelper_test(argv[1], argv[2], l4proto, atoi(argv[4])) < 0) + ret = EXIT_FAILURE; + else + ret = EXIT_SUCCESS; + + printf("\e[1;34mTest results: expect_created=%d packets=%d " + "packets_skipped=%d\e[0m\n", + cthelper_test_stats.ct_expect_created, + cthelper_test_stats.pkts, + cthelper_test_stats.pkt_mismatch_proto + + cthelper_test_stats.pkt_mismatch_port); + + return ret; +} diff --git a/tests/conntrackd/cthelper/pcaps/nfsv3.pcap b/tests/conntrackd/cthelper/pcaps/nfsv3.pcap new file mode 100644 index 0000000..04606bd Binary files /dev/null and b/tests/conntrackd/cthelper/pcaps/nfsv3.pcap differ diff --git a/tests/conntrackd/cthelper/pcaps/oracle-tns-redirect.pcap b/tests/conntrackd/cthelper/pcaps/oracle-tns-redirect.pcap new file mode 100644 index 0000000..32f8952 Binary files /dev/null and b/tests/conntrackd/cthelper/pcaps/oracle-tns-redirect.pcap differ diff --git a/tests/conntrackd/cthelper/proto.c b/tests/conntrackd/cthelper/proto.c new file mode 100755 index 0000000..6a1f345 --- /dev/null +++ b/tests/conntrackd/cthelper/proto.c @@ -0,0 +1,49 @@ +#include +#include +#include + +#include "linux_list.h" +#include "proto.h" + +static LIST_HEAD(l2l3_helper_list); +static LIST_HEAD(l4_helper_list); + +struct cthelper_proto_l2l3_helper * +cthelper_proto_l2l3_helper_find(const uint8_t *pkt, + unsigned int *l4protonum, + unsigned int *l3hdr_len) +{ + const struct ethhdr *eh = (const struct ethhdr *)pkt; + struct cthelper_proto_l2l3_helper *cur; + + list_for_each_entry(cur, &l2l3_helper_list, head) { + if (ntohs(cur->l2protonum) == eh->h_proto) { + *l4protonum = cur->l4pkt_proto(pkt + ETH_HLEN); + *l3hdr_len = cur->l3pkt_hdr_len(pkt + ETH_HLEN); + return cur; + } + } + return NULL; +} + +void cthelper_proto_l2l3_helper_register(struct cthelper_proto_l2l3_helper *h) +{ + list_add(&h->head, &l2l3_helper_list); +} + +struct cthelper_proto_l4_helper * +cthelper_proto_l4_helper_find(const uint8_t *pkt, unsigned int l4protocol) +{ + struct cthelper_proto_l4_helper *cur; + + list_for_each_entry(cur, &l4_helper_list, head) { + if (cur->l4protonum == l4protocol) + return cur; + } + return NULL; +} + +void cthelper_proto_l4_helper_register(struct cthelper_proto_l4_helper *h) +{ + list_add(&h->head, &l4_helper_list); +} diff --git a/tests/conntrackd/cthelper/proto.h b/tests/conntrackd/cthelper/proto.h new file mode 100755 index 0000000..9e99eea --- /dev/null +++ b/tests/conntrackd/cthelper/proto.h @@ -0,0 +1,50 @@ +#ifndef _HELPER_H_ +#define _HELPER_H_ + +#include + +#include "../../../include/linux_list.h" + +struct nf_conntrack; + +struct cthelper_proto_l4_helper { + struct list_head head; + + unsigned int l4protonum; + + void (*l4ct_build)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l4ct_cmp_orig)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l4ct_cmp_repl)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l4ct_cmp_port)(struct nf_conntrack *ct, uint16_t port); + + int (*l4pkt_no_data)(const uint8_t *pkt); +}; + +struct cthelper_proto_l2l3_helper { + struct list_head head; + + unsigned int l2protonum; + unsigned int l2hdr_len; + + unsigned int l3protonum; + + void (*l3ct_build)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l3ct_cmp_orig)(const uint8_t *pkt, struct nf_conntrack *ct); + int (*l3ct_cmp_repl)(const uint8_t *pkt, struct nf_conntrack *ct); + + int (*l3pkt_hdr_len)(const uint8_t *pkt); + int (*l4pkt_proto)(const uint8_t *pkt); +}; + +struct cthelper_proto_l2l3_helper *cthelper_proto_l2l3_helper_find(const uint8_t *pkt, unsigned int *l4protonum, unsigned int *l3hdr_len); +void cthelper_proto_l2l3_helper_register(struct cthelper_proto_l2l3_helper *h); + +struct cthelper_proto_l4_helper *cthelper_proto_l4_helper_find(const uint8_t *pkt, unsigned int l4protonum); +void cthelper_proto_l4_helper_register(struct cthelper_proto_l4_helper *h); + +/* Initialization of supported protocols here. */ +void l2l3_ipv4_init(void); +void l4_tcp_init(void); +void l4_udp_init(void); + +#endif diff --git a/tests/conntrackd/cthelper/run-test.sh b/tests/conntrackd/cthelper/run-test.sh new file mode 100644 index 0000000..ccce3ac --- /dev/null +++ b/tests/conntrackd/cthelper/run-test.sh @@ -0,0 +1,8 @@ +echo "Running test for oracle TNS port 1521" +./cthelper-test pcaps/oracle-tns-redirect.pcap tns tcp 1521 + +echo "Running test for NFSv3 UDP port 111" +./cthelper-test pcaps/nfsv3.pcap rpc udp 111 + +echo "Running test for NFSv3 TCP port 111" +./cthelper-test pcaps/nfsv3.pcap rpc tcp 111 diff --git a/tests/conntrackd/cthelper/test.h b/tests/conntrackd/cthelper/test.h new file mode 100644 index 0000000..4f5a6b6 --- /dev/null +++ b/tests/conntrackd/cthelper/test.h @@ -0,0 +1,13 @@ +#ifndef _CTHELPER_TEST_H_ +#define _CTHELPER_TEST_H_ + +struct cthelper_test_stats { + int pkts; + int pkt_mismatch_proto; + int pkt_mismatch_port; + int ct_expect_created; +}; + +extern struct cthelper_test_stats cthelper_test_stats; + +#endif -- cgit v1.2.3