summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Harpin <development@landsofshadow.co.uk>2015-10-02 07:43:42 +0100
committerAlex Harpin <development@landsofshadow.co.uk>2015-10-02 07:43:42 +0100
commitef5ae91676c8ada2a12ea72f889a54452dd94981 (patch)
tree0a962905ab9d0c2322f627928521a10c4cb5e20f
parent9f9a63cecdc6ac4f449d3eacda6c591f0de9fbf3 (diff)
parent8845f3db20c951fcf1db3229a818cfd185f17f2e (diff)
downloadconntrack-tools-upstream.tar.gz
conntrack-tools-upstream.zip
Merge remote-tracking branch 'source/master' into upstreamupstream
-rw-r--r--.gitignore1
-rw-r--r--Make_global.am9
-rw-r--r--Makefile.am5
-rw-r--r--README.nfct62
-rw-r--r--configure.ac41
-rw-r--r--conntrack.846
-rw-r--r--conntrackd.814
-rw-r--r--doc/debian.conntrackd.init.d48
-rw-r--r--doc/helper/conntrackd.conf132
-rw-r--r--doc/manual/conntrack-tools.tmpl172
-rw-r--r--doc/sync/alarm/conntrackd.conf7
-rw-r--r--doc/sync/ftfw/conntrackd.conf7
-rw-r--r--doc/sync/notrack/conntrackd.conf7
-rw-r--r--extensions/libct_proto_dccp.c4
-rw-r--r--extensions/libct_proto_gre.c2
-rw-r--r--extensions/libct_proto_icmp.c8
-rw-r--r--extensions/libct_proto_icmpv6.c8
-rw-r--r--extensions/libct_proto_sctp.c6
-rw-r--r--extensions/libct_proto_tcp.c4
-rw-r--r--extensions/libct_proto_udp.c4
-rw-r--r--extensions/libct_proto_udplite.c4
-rw-r--r--include/Makefile.am4
-rw-r--r--include/bitops.h14
-rw-r--r--include/channel.h11
-rw-r--r--include/conntrack.h4
-rw-r--r--include/conntrackd.h32
-rw-r--r--include/fds.h4
-rw-r--r--include/filter.h1
-rw-r--r--include/helper.h114
-rw-r--r--include/internal.h1
-rw-r--r--include/linux/Makefile.am1
-rw-r--r--include/linux/netfilter/Makefile.am1
-rw-r--r--include/linux/netfilter/nfnetlink.h66
-rw-r--r--include/linux/netfilter/nfnetlink_cthelper.h55
-rw-r--r--include/linux/netfilter/nfnetlink_cttimeout.h116
-rw-r--r--include/linux/netfilter/nfnetlink_queue.h115
-rw-r--r--include/linux_list.h2
-rw-r--r--include/mcast.h1
-rw-r--r--include/myct.h44
-rw-r--r--include/netlink.h2
-rw-r--r--include/network.h16
-rw-r--r--include/nfct.h46
-rw-r--r--include/stack.h28
-rw-r--r--include/tcp.h1
-rw-r--r--include/udp.h1
-rw-r--r--nfct.868
-rw-r--r--qa/testsuite/04zone8
-rw-r--r--src/.gitignore1
-rw-r--r--src/Makefile.am49
-rw-r--r--src/build.c64
-rw-r--r--src/cache-ct.c13
-rw-r--r--src/cache-exp.c2
-rw-r--r--src/cache.c5
-rw-r--r--src/channel.c6
-rw-r--r--src/channel_tcp.c1
-rw-r--r--src/conntrack.c891
-rw-r--r--src/cthelper.c561
-rw-r--r--src/ctnl.c524
-rw-r--r--src/expect.c227
-rw-r--r--src/fds.c61
-rw-r--r--src/filter.c78
-rw-r--r--src/helpers.c76
-rw-r--r--src/helpers/Makefile.am42
-rw-r--r--src/helpers/amanda.c203
-rw-r--r--src/helpers/dhcpv6.c123
-rw-r--r--src/helpers/ftp.c605
-rw-r--r--src/helpers/rpc.c488
-rw-r--r--src/helpers/sane.c173
-rw-r--r--src/helpers/ssdp.c134
-rw-r--r--src/helpers/tftp.c138
-rw-r--r--src/helpers/tns.c408
-rw-r--r--src/internal_bypass.c12
-rw-r--r--src/internal_cache.c11
-rw-r--r--src/local.c12
-rw-r--r--src/main.c28
-rw-r--r--src/multichannel.c7
-rw-r--r--src/netlink.c49
-rw-r--r--src/nfct-extensions/helper.c474
-rw-r--r--src/nfct-extensions/timeout.c499
-rw-r--r--src/nfct.c252
-rw-r--r--src/parse.c88
-rw-r--r--src/process.c14
-rw-r--r--src/read_config_lex.l12
-rw-r--r--src/read_config_yy.y233
-rw-r--r--src/run.c582
-rw-r--r--src/stack.c56
-rw-r--r--src/stats-mode.c1
-rw-r--r--src/sync-mode.c173
-rw-r--r--src/sync-notrack.c2
-rw-r--r--src/tcp.c42
-rw-r--r--src/udp.c8
-rw-r--r--src/utils.c243
-rw-r--r--tests/conntrack/run-test.sh19
-rw-r--r--tests/conntrack/test-conntrack.c (renamed from qa/test-conntrack.c)0
-rw-r--r--tests/conntrack/testsuite/00create (renamed from qa/testsuite/00create)0
-rw-r--r--tests/conntrack/testsuite/01delete (renamed from qa/testsuite/01delete)0
-rw-r--r--tests/conntrack/testsuite/02filter (renamed from qa/testsuite/02filter)0
-rw-r--r--tests/conntrack/testsuite/03nat (renamed from qa/testsuite/03nat)0
-rw-r--r--tests/conntrack/testsuite/04zone24
-rw-r--r--tests/conntrack/testsuite/05mark (renamed from qa/testsuite/05mark)0
-rw-r--r--tests/conntrack/testsuite/06update (renamed from qa/testsuite/06update)0
-rw-r--r--tests/nfct/run-test.sh20
-rw-r--r--tests/nfct/test-live.sh73
-rw-r--r--tests/nfct/test.c100
-rw-r--r--tests/nfct/timeout/00tcp16
-rw-r--r--tests/nfct/timeout/01udp16
-rw-r--r--tests/nfct/timeout/02generic16
-rw-r--r--tests/nfct/timeout/03udplite16
-rw-r--r--tests/nfct/timeout/04icmp16
-rw-r--r--tests/nfct/timeout/05icmpv616
-rw-r--r--tests/nfct/timeout/06sctp16
-rw-r--r--tests/nfct/timeout/07dccp16
-rw-r--r--tests/nfct/timeout/08gre16
113 files changed, 8390 insertions, 1008 deletions
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/Make_global.am b/Make_global.am
index e8f603a..8084249 100644
--- a/Make_global.am
+++ b/Make_global.am
@@ -1,6 +1,9 @@
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}
+ -Wmissing-prototypes -Wwrite-strings -Wfloat-equal -Wshadow -Wpointer-arith -Wbad-function-cast -Wsign-compare -Waggregate-return -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -Wstrict-prototypes -Wundef \
+ -Wno-unused-parameter ${LIBNFNETLINK_CFLAGS} ${LIBMNL_CFLAGS} \
+ ${LIBNETFILTER_CONNTRACK_CFLAGS} \
+ ${LIBNETFILTER_CTTIMEOUT_CFLAGS} \
+ ${LIBNETFILTER_QUEUE_CFLAGS} \
+ ${LIBNETFILTER_CTHELPER_CFLAGS}
diff --git a/Makefile.am b/Makefile.am
index afb4595..975c538 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
@@ -10,4 +10,5 @@ DIST_SUBDIRS = include src extensions
LIBS = @LIBNETFILTER_CONNTRACK_LIBS@
dist-hook:
- rm -rf `find $(distdir)/doc -name .svn`
+ rm -rf `find $(distdir)/doc -name *.orig`
+ rm -rf `find $(distdir)/doc -name *.rej`
diff --git a/README.nfct b/README.nfct
new file mode 100644
index 0000000..89dd328
--- /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 add timeout custom-tcp-policy1 inet tcp established 100
+
+You can also retrieve the existing timeout policies with:
+
+# nfct list timeout
+.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 add timeout 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 <pablo@netfilter.org>
diff --git a/configure.ac b/configure.ac
index 26a7e02..70d3729 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.4.3, pablo@netfilter.org)
AC_CONFIG_AUX_DIR([build-aux])
AC_CANONICAL_HOST
@@ -9,8 +9,12 @@ 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_AR
AM_PROG_LIBTOOL
AC_PROG_INSTALL
AC_PROG_LN_S
@@ -51,8 +55,27 @@ else
flex.])
fi
-PKG_CHECK_MODULES([LIBNFNETLINK], [libnfnetlink >= 1.0.0])
-PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.0])
+AC_ARG_ENABLE([cthelper],
+ AS_HELP_STRING([--disable-cthelper], [Do not build userspace helper support]),
+ [enable_cthelper="no"], [enable_cthelper="yes"])
+AC_ARG_ENABLE([cttimeout],
+ AS_HELP_STRING([--disable-cttimeout], [Do not build timeout support]),
+ [enable_cttimeout="no"], [enable_cttimeout="yes"])
+
+PKG_CHECK_MODULES([LIBNFNETLINK], [libnfnetlink >= 1.0.1])
+PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.3])
+PKG_CHECK_MODULES([LIBNETFILTER_CONNTRACK], [libnetfilter_conntrack >= 1.0.4])
+AS_IF([test "x$enable_cttimeout" = "xyes"], [
+ PKG_CHECK_MODULES([LIBNETFILTER_CTTIMEOUT], [libnetfilter_cttimeout >= 1.0.0])
+])
+AM_CONDITIONAL([HAVE_CTTIMEOUT], [test "x$enable_cttimeout" = "xyes"])
+
+AS_IF([test "x$enable_cthelper" = "xyes"], [
+ PKG_CHECK_MODULES([LIBNETFILTER_CTHELPER], [libnetfilter_cthelper >= 1.0.0])
+ PKG_CHECK_MODULES([LIBNETFILTER_QUEUE], [libnetfilter_queue >= 1.0.2])
+ AC_DEFINE([BUILD_CTHELPER], [1], [Building cthelper support])
+])
+AM_CONDITIONAL([HAVE_CTHELPER], [test "x$enable_cthelper" = "xyes"])
AC_CHECK_HEADERS([linux/capability.h],, [AC_MSG_ERROR([Cannot find linux/capabibility.h])])
@@ -112,5 +135,15 @@ dnl debug/src/Makefile
dnl extensions/Makefile
dnl src/Makefile])
-AC_CONFIG_FILES([Makefile src/Makefile include/Makefile extensions/Makefile])
+if test ! -z "$libdir"; then
+ MODULE_DIR="\\\"$libdir/conntrack-tools/\\\""
+ CFLAGS="$CFLAGS -DCONNTRACKD_LIB_DIR=$MODULE_DIR"
+fi
+
+AC_CONFIG_FILES([Makefile src/Makefile include/Makefile include/linux/Makefile include/linux/netfilter/Makefile extensions/Makefile src/helpers/Makefile])
AC_OUTPUT
+
+echo "
+conntrack-tools configuration:
+ userspace conntrack helper support: ${enable_cthelper}
+ conntrack timeout support: ${enable_cttimeout}"
diff --git a/conntrack.8 b/conntrack.8
index 6525123..a981a76 100644
--- a/conntrack.8
+++ b/conntrack.8
@@ -1,4 +1,4 @@
-.TH CONNTRACK 8 "Jul 5, 2010" "" ""
+.TH CONNTRACK 8 "Aug 24, 2015" "" ""
.\" Man page written by Harald Welte <laforge@netfilter.org (Jun 2005)
.\" Maintained by Pablo Neira Ayuso <pablo@netfilter.org (May 2007)
@@ -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
@@ -86,20 +86,22 @@ Show the in-kernel connection tracking system statistics.
.TP
.BI "-z, --zero "
Atomically zero counters after reading them. This option is only valid in
-combination with the "-L, --dump" command options.
+combination with the "\-L, \-\-dump" command options.
.TP
-.BI "-o, --output [extended,xml,timestamp,id,ktimestamp] "
+.BI "-o, --output [extended,xml,timestamp,id,ktimestamp,labels] "
Display output in a certain format. With the extended output option, this tool
displays the layer 3 information. With ktimestamp, it displays the in-kernel
timestamp available since 2.6.38 (you can enable it via echo 1 >
/proc/sys/net/netfilter/nf_conntrack_timestamp).
+The labels output option tells conntrack to show the names of connection
+tracking labels that might be present.
.TP
.BI "-e, --event-mask " "[ALL|NEW|UPDATES|DESTROY][,...]"
Set the bitmask of events that are to be generated by the in-kernel ctnetlink
event code. Using this parameter, you can reduce the event messages generated
by the kernel to those types to those that you are actually interested in.
.
-This option can only be used in conjunction with "-E, --event".
+This option can only be used in conjunction with "\-E, \-\-event".
.TP
.BI "-b, --buffer-size " "value (in bytes)"
Set the Netlink socket buffer size. This option is useful if the command line
@@ -110,7 +112,7 @@ other words, if the amount of events are big enough to overrun the socket
buffer. Note that using a big buffer reduces the chances to hit ENOBUFS,
however, this results in more memory consumption.
.
-This option can only be used in conjunction with "-E, --event".
+This option can only be used in conjunction with "\-E, \-\-event".
.SS FILTER PARAMETERS
.TP
.BI "-s, --orig-src " IP_ADDRESS
@@ -130,17 +132,31 @@ Specify layer four (TCP, UDP, ...) protocol.
.TP
.BI "-f, --family " "PROTO"
Specify layer three (ipv4, ipv6) protocol
-This option is only required in conjunction with "-L, --dump". If this option is not passed, the default layer 3 protocol will be IPv4.
+This option is only required in conjunction with "\-L, \-\-dump". If this option is not passed, the default layer 3 protocol will be IPv4.
.TP
.BI "-t, --timeout " "TIMEOUT"
Specify the timeout.
.TP
.BI "-m, --mark " "MARK[/MASK]"
Specify the conntrack mark. Optionally, a mask value can be specified.
-In "--update" mode, this mask specifies the bits that should be zeroed before XORing
+In "\-\-update" mode, this mask specifies the bits that should be zeroed before XORing
the MARK value into the ctmark.
Otherwise, the mask is logically ANDed with the existing mark before the comparision.
-In "--create" mode, the mask is ignored.
+In "\-\-create" mode, the mask is ignored.
+.TP
+.BI "-l, --label " "LABEL"
+Specify a conntrack label.
+This option is only available in conjunction with "\-L, \-\-dump", "\-E, \-\-event", or "\-U \-\-update".
+Match entries whose labels match at least those specified.
+Use multiple \-l commands to specify multiple labels that need to be set.
+Match entries whose labels matches at least those specified as arguments.
+.BI "--label-add " "LABEL"
+Specify the conntrack label to add to to the selected conntracks.
+This option is only available in conjunction with "\-I, \-\-create" or "\-U, \-\-update".
+.BI "--label-del " "[LABEL]"
+Specify the conntrack label to delete from the selected conntracks.
+If no label is given, all labels are deleted.
+This option is only available in conjunction with "\-U, \-\-update".
.TP
.BI "-c, --secmark " "SECMARK"
Specify the conntrack selinux security mark.
@@ -160,6 +176,14 @@ Filter any NAT connections.
.BI "-w, --zone "
Filter by conntrack zone. See iptables CT target for more information.
.TP
+.BI "--orig-zone "
+Filter by conntrack zone in original direction.
+See iptables CT target for more information.
+.TP
+.BI "--reply-zone "
+Filter by conntrack zone in reply direction.
+See iptables CT target for more information.
+.TP
.BI "--tuple-src " IP_ADDRESS
Specify the tuple source address of an expectation.
.TP
diff --git a/conntrackd.8 b/conntrackd.8
index f07ad7a..455f6c5 100644
--- a/conntrackd.8
+++ b/conntrackd.8
@@ -1,4 +1,4 @@
-.TH CONNTRACKD 8 "Oct 21, 2008" "" ""
+.TH CONNTRACKD 8 "Sep 25, 2014" "" ""
.\" Man page written by Pablo Neira Ayuso <pablo@netfilter.org> (Dec 2007)
@@ -32,7 +32,7 @@ Dump the external cache, i.e. show foreign states
.TP
.BI "-x "
Display output in XML format. This option is only valid in combination
-with "-i" and "-e" parameters.
+with "\-i" and "\-e" parameters.
.TP
.BI "-f " "[|internal|external]"
Flush the internal and/or external cache
@@ -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/doc/debian.conntrackd.init.d b/doc/debian.conntrackd.init.d
deleted file mode 100644
index ba847dd..0000000
--- a/doc/debian.conntrackd.init.d
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/bin/sh
-#
-# /etc/init.d/conntrackd
-#
-# Maximilian Wilhelm <max@rfc2324.org>
-# -- Mon, 06 Nov 2006 18:39:07 +0100
-#
-
-export PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
-
-NAME="conntrackd"
-DAEMON=`command -v conntrackd`
-CONFIG="/etc/conntrack/conntrackd.conf"
-PIDFILE="/var/run/${NAME}.pid"
-
-
-# Gracefully exit if there is no daemon (debian way of life)
-if [ ! -x "${DAEMON}" ]; then
- exit 0
-fi
-
-# Check for config file
-if [ ! -f /etc/conntrackd/conntrackd.conf ]; then
- echo "Error: There is no config file for $NAME" >&2
- exit 1;
-fi
-
-case "$1" in
- start)
- echo -n "Starting $NAME: "
- start-stop-daemon --start --quiet --make-pidfile --pidfile "/var/run/${NAME}.pid" --background --exec "${DAEMON}" && echo "done." || echo "FAILED!"
- ;;
- stop)
- echo -n "Stopping $NAME:"
- start-stop-daemon --stop --quiet --oknodo --pidfile "/var/run/${NAME}.pid" && echo "done." || echo "FAILED!"
- ;;
-
- restart)
- $0 start
- $0 stop
- ;;
-
- *)
- echo "Usage: /etc/init.d/conntrackd {start|stop|restart}"
- exit 1
-esac
-
-exit 0
diff --git a/doc/helper/conntrackd.conf b/doc/helper/conntrackd.conf
new file mode 100644
index 0000000..5c07509
--- /dev/null
+++ b/doc/helper/conntrackd.conf
@@ -0,0 +1,132 @@
+#
+# Helper settings
+#
+
+Helper {
+ # Before this, you have to make sure you have registered the `ftp'
+ # user-space helper stub via:
+ #
+ # nfct add helper ftp inet tcp
+ #
+ Type ftp inet tcp {
+ #
+ # Set NFQUEUE number you want to use to receive traffic from
+ # the kernel.
+ #
+ QueueNum 0
+
+ #
+ # Maximum number of packets waiting in the queue to receive
+ # a verdict from user-space. Default is 1024.
+ #
+ # Rise value if you hit the following error message:
+ # "nf_queue: full at X entries, dropping packets(s)"
+ #
+ QueueLen 10240
+
+ #
+ # 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
+ }
+ }
+ Type rpc inet tcp {
+ QueueNum 1
+ QueueLen 10240
+ Policy rpc {
+ ExpectMax 1
+ ExpectTimeout 300
+ }
+ }
+ Type rpc inet udp {
+ QueueNum 2
+ QueueLen 10240
+ Policy rpc {
+ ExpectMax 1
+ ExpectTimeout 300
+ }
+ }
+ Type tns inet tcp {
+ QueueNum 3
+ QueueLen 10240
+ Policy tns {
+ ExpectMax 1
+ ExpectTimeout 300
+ }
+ }
+ Type dhcpv6 inet6 udp {
+ QueueNum 4
+ QueueLen 10240
+ Policy dhcpv6 {
+ ExpectMax 1
+ ExpectTimeout 300
+ }
+ }
+ Type ssdp inet udp {
+ QueueNum 5
+ QueueLen 10240
+ Policy ssdp {
+ ExpectMax 1
+ 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/doc/manual/conntrack-tools.tmpl b/doc/manual/conntrack-tools.tmpl
index 4936a76..87a792e 100644
--- a/doc/manual/conntrack-tools.tmpl
+++ b/doc/manual/conntrack-tools.tmpl
@@ -19,7 +19,7 @@
</authorgroup>
<copyright>
- <year>2008-2011</year>
+ <year>2008-2012</year>
<holder>Pablo Neira Ayuso</holder>
</copyright>
@@ -37,7 +37,7 @@
<releaseinfo>
This document details how to install and configure the
<ulink url="http://conntrack-tools.netfilter.org">conntrack-tools</ulink>
- &gt;= 1.0.0. This document will evolve in the future to cover new features
+ &gt;= 1.4.0. This document will evolve in the future to cover new features
and changes.</releaseinfo>
</bookinfo>
@@ -641,10 +641,11 @@ Sync {
broken.</para>
<para>The following example shows how to only generate the
- <emphasis>assured</emphasis> event:</para>
+ <emphasis>assured</emphasis> and <emphasis>destroy</emphasis>
+ events:</para>
<programlisting>
- # iptables -I PREROUTING -t raw -j CT --ctevents assured
+ # iptables -I PREROUTING -t raw -j CT --ctevents assured,destroy
</programlisting>
<note><title>Assured flows</title>
@@ -659,6 +660,13 @@ Sync {
<sect3 id="sync-expect"><title>Synchronization of expectations</title>
+ <note><title>Check your Linux kernel version first</title>
+ <para>
+ The synchronization of expectations require a Linux kernel &gt;= 3.5
+ to work appropriately.
+ </para>
+ </note>
+
<para>The connection tracking system provides helpers that allows you to
filter multi-flow application protocols like FTP, H.323 and SIP among many
others. These protocols usually split the control and data traffic in
@@ -688,7 +696,9 @@ Sync {
ExpectationSync {
ftp
sip
- h323
+ ras # for H.323
+ q.931 # for H.323
+ h.245 # for H.323
}
}
}</programlisting>
@@ -817,7 +827,155 @@ Sync {
</sect2>
-<sect2 id="sync-trouble"><title>Troubleshooting</title>
+</sect1>
+
+<sect1 id="helpers"><title>User-space helpers</title>
+
+ <note><title>Check your Linux kernel version first</title>
+ <para>
+ The user-space helper infrastructure requires a Linux kernel &gt;= 3.6
+ to work appropriately.
+ </para>
+ </note>
+
+<para>Connection tracking helpers allows you to filter multi-flow protocols
+that usually separate control and data traffic into different flows.
+These protocols usually violate network layering by including layer 3/4
+details, eg. IP address and TCP/UDP ports, in their application protocol
+(which resides in layer 7). This is problematic for gateways since they
+operate at packet-level, ie. layers 3/4, and therefore they miss this
+important information to filter these protocols appropriately.</para>
+
+<para>Helpers inspect packet content (at layer 7) and create the so-called
+expectations. These expectations are added to one internal table
+that resides in the gateway. For each new packet arriving to the
+gateway, the gateway first looks up for matching expectations. If
+there is any, then this flow is accepted since it's been expected.
+Note this lookup only occurs for the first packet that is part of one
+newly established flow, not for all packets.</para>
+
+<para>Since 1.4.0, conntrackd provides the infrastructure to develop
+helpers in user-space. The main features of the user-space infrastructure
+for helpers are:</para>
+
+<itemizedlist>
+
+<listitem><para>Rapid connection tracking helper development, as developing code
+in user-space is usually faster.</para></listitem>
+
+<listitem><para>Reliability: A buggy helper does not crash the kernel. If the helper
+fails, ie. the conntrackd crashes, Moreover, we can monitor the helper process
+and restart it in case of problems.</para></listitem>
+
+<listitem><para>Security: Avoid complex string matching and mangling in
+kernel-space running in privileged mode. Going further, we can even think
+about running user-space helper as a non-root process.</para></listitem>
+
+<listitem><para>It allows the development of very specific helpers for
+proprietary protocols that are not standard. This is the case of the SQL*net
+helper. Implementing this in kernel-space may be problematic, since
+this may not be accepted for ainline inclusion in the Linux kernel.
+As an alternative, we can still distribute this support as separate
+patches. However, my personal experience is that, given that the
+kernel API/ABI is not stable, changes in the interface lead to the
+breakage of the patch. This highly increase the overhead in the
+maintainance.</para></listitem>
+
+</itemizedlist>
+
+<para>Currently, the infrastructure supports the following user-space helpers:
+</para>
+
+<itemizedlist>
+<listitem><para>Oracle*TNS, to support its special <emphasis>Redirect</emphasis> message.</para></listitem>
+<listitem><para>NFSv3, mind that version 4 does not require this helper.</para></listitem>
+<listitem><para>FTP (this helper is also available in kernel-space).</para></listitem>
+<listitem><para>SSDP.</para></listitem>
+</itemizedlist>
+
+<para>The following steps describe how to enable the RPC portmapper helper for NFSv3 (this is similar for other helpers):</para>
+
+<orderedlist>
+<listitem><para>Register user-space helper:
+
+<programlisting>
+nfct add helper rpc inet udp
+nfct add helper rpc inet tcp
+</programlisting>
+
+This registers the portmapper helper for both UDP and TCP (NFSv3 traffic goes both over TCP and UDP).
+</para></listitem>
+
+<listitem><para>Add iptables rule using the CT target:
+
+<programlisting>
+# iptables -I OUTPUT -t raw -p udp --dport 111 -j CT --helper rpc
+# iptables -I OUTPUT -t raw -p tcp --dport 111 -j CT --helper rpc
+</programlisting>
+
+With this, packets matching port TCP/UDP/111 are passed to user-space for
+inspection. If there is no instance of conntrackd configured to support
+user-space helpers, no inspection happens and packets are not sent to
+user-space.</para></listitem>
+
+<listitem><para>Add configuration to conntrackd.conf:
+
+<programlisting>
+Helper {
+ Type rpc inet udp {
+ QueueNum 1
+ QueueLen 10240
+ Policy rpc {
+ ExpectMax 1
+ ExpectTimeout 300
+ }
+ }
+ Type rpc inet tcp {
+ QueueNum 2
+ QueueLen 10240
+ Policy rpc {
+ ExpectMax 1
+ ExpectTimeout 300
+ }
+ }
+}
+</programlisting>
+
+This configures conntrackd to use NFQUEUE queue numbers 1 and 2 to send traffic
+for inspection to user-space</para>
+
+ <note><title>If you have some custom libnetfilter_queue application</title>
+ <para>
+ Make sure your queue numbers do not collide with those used in your
+ conntrackd.conf file.
+ </para>
+ </note>
+
+</listitem>
+
+</orderedlist>
+
+<para>Now you can test this (assuming you have some working NFSv3 setup) with:
+
+<programlisting>
+mount -t nfs -onfsvers=3 mynfs.server.info:/srv/cvs /mnt/
+</programlisting>
+
+</para>
+
+<para>You should see new expectations being added via:
+
+<programlisting>
+# conntrack -E expect
+ [NEW] 300 proto=17 src=1.2.3.4 dst=1.2.3.4 sport=0 dport=54834 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=1.2.3.4 master-dst=1.2.3.4 sport=58190 dport=111 PERMANENT class=0 helper=rpc
+ [NEW] 300 proto=6 src=1.2.3.4 dst=1.2.3.4 sport=0 dport=2049 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=1.2.3.4 master-dst=1.2.3.4 sport=55450 dport=111 PERMANENT class=0 helper=rpc
+ [NEW] 300 proto=17 src=1.2.3.4 dst=1.2.3.4 sport=0 dport=58031 mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535 master-src=1.2.3.4 master-dst=1.2.3.4 sport=56309 dport=111 PERMANENT class=0 helper=rpc
+</programlisting>
+</para>
+
+</sect1>
+
+<sect1 id="sync-trouble"><title>Troubleshooting</title>
<para>Problems with <emphasis>conntrackd</emphasis>? The following list
of questions should help for troubleshooting:</para>
@@ -1023,8 +1181,6 @@ not enough space errors: 0
</qandaset>
-</sect2>
-
</sect1>
</chapter>
diff --git a/doc/sync/alarm/conntrackd.conf b/doc/sync/alarm/conntrackd.conf
index deed291..0223745 100644
--- a/doc/sync/alarm/conntrackd.conf
+++ b/doc/sync/alarm/conntrackd.conf
@@ -194,11 +194,14 @@ Sync {
# Set this option on if you want to enable the synchronization
# of expectations. You have to specify the list of helpers that
- # you want to enable. Default is off.
+ # you want to enable. Default is off. This feature requires
+ # a Linux kernel >= 3.5.
#
# 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..65e7b77 100644
--- a/doc/sync/ftfw/conntrackd.conf
+++ b/doc/sync/ftfw/conntrackd.conf
@@ -217,11 +217,14 @@ Sync {
# Set this option on if you want to enable the synchronization
# of expectations. You have to specify the list of helpers that
- # you want to enable. Default is off.
+ # you want to enable. Default is off. This feature requires
+ # a Linux kernel >= 3.5.
#
# 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..3d036fb 100644
--- a/doc/sync/notrack/conntrackd.conf
+++ b/doc/sync/notrack/conntrackd.conf
@@ -256,11 +256,14 @@ Sync {
# Set this option on if you want to enable the synchronization
# of expectations. You have to specify the list of helpers that
- # you want to enable. Default is off.
+ # you want to enable. Default is off. This feature requires
+ # a Linux kernel >= 3.5.
#
# ExpectationSync {
# ftp
- # h323
+ # ras
+ # q.931
+ # h.245
# sip
# }
#
diff --git a/extensions/libct_proto_dccp.c b/extensions/libct_proto_dccp.c
index 586c4cc..f6258ad 100644
--- a/extensions/libct_proto_dccp.c
+++ b/extensions/libct_proto_dccp.c
@@ -78,7 +78,7 @@ static char dccp_commands_v_options[NUMBER_OF_CMD][DCCP_OPT_MAX] =
/*CT_VERSION*/ {0,0,0,0,0,0,0,0,0,0},
/*CT_HELP*/ {0,0,0,0,0,0,0,0,0,0},
/*EXP_LIST*/ {0,0,0,0,0,0,0,0,0,0},
-/*EXP_CREATE*/ {1,1,1,1,1,1,0,1,1,1},
+/*EXP_CREATE*/ {1,1,0,0,1,1,0,1,1,1},
/*EXP_DELETE*/ {1,1,1,1,0,0,0,0,0,0},
/*EXP_GET*/ {1,1,1,1,0,0,0,0,0,0},
/*EXP_FLUSH*/ {0,0,0,0,0,0,0,0,0,0},
@@ -118,7 +118,7 @@ static int parse_options(char c,
unsigned int *flags)
{
int i;
- u_int16_t port;
+ uint16_t port;
switch(c) {
case 1:
diff --git a/extensions/libct_proto_gre.c b/extensions/libct_proto_gre.c
index 0274a37..2dc63d1 100644
--- a/extensions/libct_proto_gre.c
+++ b/extensions/libct_proto_gre.c
@@ -91,7 +91,7 @@ static int parse_options(char c,
unsigned int *flags)
{
switch(c) {
- u_int16_t port;
+ uint16_t port;
case '1':
port = htons(strtoul(optarg, NULL, 0));
nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, port);
diff --git a/extensions/libct_proto_icmp.c b/extensions/libct_proto_icmp.c
index 3a346ed..2ce1c65 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},
@@ -72,8 +72,8 @@ static int parse(char c,
unsigned int *flags)
{
switch(c) {
- u_int8_t tmp;
- u_int16_t id;
+ uint8_t tmp;
+ uint16_t id;
case '1':
tmp = atoi(optarg);
nfct_set_attr_u8(ct, ATTR_ICMP_TYPE, tmp);
diff --git a/extensions/libct_proto_icmpv6.c b/extensions/libct_proto_icmpv6.c
index 070eb7f..18dd3e5 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},
@@ -75,8 +75,8 @@ static int parse(char c,
unsigned int *flags)
{
switch(c) {
- u_int8_t tmp;
- u_int16_t id;
+ uint8_t tmp;
+ uint16_t id;
case '1':
tmp = atoi(optarg);
nfct_set_attr_u8(ct, ATTR_ICMP_TYPE, tmp);
diff --git a/extensions/libct_proto_sctp.c b/extensions/libct_proto_sctp.c
index f4c94df..04828bf 100644
--- a/extensions/libct_proto_sctp.c
+++ b/extensions/libct_proto_sctp.c
@@ -81,7 +81,7 @@ static char sctp_commands_v_options[NUMBER_OF_CMD][SCTP_OPT_MAX] =
/*CT_VERSION*/ {0,0,0,0,0,0,0,0,0,0,0},
/*CT_HELP*/ {0,0,0,0,0,0,0,0,0,0,0},
/*EXP_LIST*/ {0,0,0,0,0,0,0,0,0,0,0},
-/*EXP_CREATE*/ {1,1,1,1,1,1,0,1,1,1,1},
+/*EXP_CREATE*/ {1,1,0,0,1,1,0,1,1,1,1},
/*EXP_DELETE*/ {1,1,1,1,0,0,0,0,0,0,0},
/*EXP_GET*/ {1,1,1,1,0,0,0,0,0,0,0},
/*EXP_FLUSH*/ {0,0,0,0,0,0,0,0,0,0,0},
@@ -120,8 +120,8 @@ parse_options(char c, struct nf_conntrack *ct,
unsigned int *flags)
{
int i;
- u_int16_t port;
- u_int32_t vtag;
+ uint16_t port;
+ uint32_t vtag;
switch(c) {
case 1:
diff --git a/extensions/libct_proto_tcp.c b/extensions/libct_proto_tcp.c
index 0b43bf5..8a37a55 100644
--- a/extensions/libct_proto_tcp.c
+++ b/extensions/libct_proto_tcp.c
@@ -65,7 +65,7 @@ static char tcp_commands_v_options[NUMBER_OF_CMD][TCP_NUMBER_OF_OPT] =
/*CT_VERSION*/ {0,0,0,0,0,0,0,0,0},
/*CT_HELP*/ {0,0,0,0,0,0,0,0,0},
/*EXP_LIST*/ {0,0,0,0,0,0,0,0,0},
-/*EXP_CREATE*/ {1,1,1,1,1,1,0,1,1},
+/*EXP_CREATE*/ {1,1,0,0,1,1,0,1,1},
/*EXP_DELETE*/ {1,1,1,1,0,0,0,0,0},
/*EXP_GET*/ {1,1,1,1,0,0,0,0,0},
/*EXP_FLUSH*/ {0,0,0,0,0,0,0,0,0},
@@ -106,7 +106,7 @@ static int parse_options(char c,
unsigned int *flags)
{
int i;
- u_int16_t port;
+ uint16_t port;
switch(c) {
case '1':
diff --git a/extensions/libct_proto_udp.c b/extensions/libct_proto_udp.c
index d7c4da1..e30637c 100644
--- a/extensions/libct_proto_udp.c
+++ b/extensions/libct_proto_udp.c
@@ -73,7 +73,7 @@ static char udp_commands_v_options[NUMBER_OF_CMD][UDP_NUMBER_OF_OPT] =
/*CT_VERSION*/ {0,0,0,0,0,0,0,0},
/*CT_HELP*/ {0,0,0,0,0,0,0,0},
/*EXP_LIST*/ {0,0,0,0,0,0,0,0},
-/*EXP_CREATE*/ {1,1,1,1,1,1,1,1},
+/*EXP_CREATE*/ {1,1,0,0,1,1,1,1},
/*EXP_DELETE*/ {1,1,1,1,0,0,0,0},
/*EXP_GET*/ {1,1,1,1,0,0,0,0},
/*EXP_FLUSH*/ {0,0,0,0,0,0,0,0},
@@ -87,7 +87,7 @@ static int parse_options(char c,
unsigned int *flags)
{
switch(c) {
- u_int16_t port;
+ uint16_t port;
case '1':
port = htons(atoi(optarg));
nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, port);
diff --git a/extensions/libct_proto_udplite.c b/extensions/libct_proto_udplite.c
index bffd5fe..f46cef0 100644
--- a/extensions/libct_proto_udplite.c
+++ b/extensions/libct_proto_udplite.c
@@ -81,7 +81,7 @@ static char udplite_commands_v_options[NUMBER_OF_CMD][UDP_OPT_MAX] =
/*CT_VERSION*/ {0,0,0,0,0,0,0,0},
/*CT_HELP*/ {0,0,0,0,0,0,0,0},
/*EXP_LIST*/ {0,0,0,0,0,0,0,0},
-/*EXP_CREATE*/ {1,1,1,1,1,1,1,1},
+/*EXP_CREATE*/ {1,1,0,0,1,1,1,1},
/*EXP_DELETE*/ {1,1,1,1,0,0,0,0},
/*EXP_GET*/ {1,1,1,1,0,0,0,0},
/*EXP_FLUSH*/ {0,0,0,0,0,0,0,0},
@@ -95,7 +95,7 @@ static int parse_options(char c,
unsigned int *flags)
{
switch(c) {
- u_int16_t port;
+ uint16_t port;
case '1':
port = htons(atoi(optarg));
nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, port);
diff --git a/include/Makefile.am b/include/Makefile.am
index cbbca6b..6bd0f7f 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,8 +1,10 @@
+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 \
+ helper.h myct.h stack.h
diff --git a/include/bitops.h b/include/bitops.h
index 51f4289..27fe58d 100644
--- a/include/bitops.h
+++ b/include/bitops.h
@@ -1,34 +1,34 @@
#ifndef _BITOPS_H_
#define _BITOPS_H_
-#include <stdlib.h>
+#include <stdint.h>
-static inline void set_bit_u32(int nr, u_int32_t *addr)
+static inline void set_bit_u32(int nr, uint32_t *addr)
{
addr[nr >> 5] |= (1UL << (nr & 31));
}
-static inline void unset_bit_u32(int nr, u_int32_t *addr)
+static inline void unset_bit_u32(int nr, uint32_t *addr)
{
addr[nr >> 5] &= ~(1UL << (nr & 31));
}
-static inline int test_bit_u32(int nr, const u_int32_t *addr)
+static inline int test_bit_u32(int nr, const uint32_t *addr)
{
return ((1UL << (nr & 31)) & (addr[nr >> 5])) != 0;
}
-static inline void set_bit_u16(int nr, u_int16_t *addr)
+static inline void set_bit_u16(int nr, uint16_t *addr)
{
addr[nr >> 4] |= (1UL << (nr & 15));
}
-static inline void unset_bit_u16(int nr, u_int16_t *addr)
+static inline void unset_bit_u16(int nr, uint16_t *addr)
{
addr[nr >> 4] &= ~(1UL << (nr & 15));
}
-static inline int test_bit_u16(int nr, const u_int16_t *addr)
+static inline int test_bit_u16(int nr, const uint16_t *addr)
{
return ((1UL << (nr & 15)) & (addr[nr >> 4])) != 0;
}
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/conntrack.h b/include/conntrack.h
index 3882de7..6659a64 100644
--- a/include/conntrack.h
+++ b/include/conntrack.h
@@ -9,8 +9,8 @@
#include <netinet/in.h>
-#define NUMBER_OF_CMD 18
-#define NUMBER_OF_OPT 24
+#define NUMBER_OF_CMD 19
+#define NUMBER_OF_OPT 29
struct ctproto_handler {
struct list_head head;
diff --git a/include/conntrackd.h b/include/conntrackd.h
index 9359dfa..d338fc4 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
@@ -103,7 +104,6 @@ struct ct_conf {
unsigned int netlink_buffer_size_max_grown;
int nl_overrun_resync;
unsigned int flags;
- int family; /* protocol family */
unsigned int resend_queue_size; /* FTFW protocol */
unsigned int window_size;
int poll_kernel_secs;
@@ -134,6 +134,9 @@ struct ct_conf {
int syslog_facility;
size_t buffer_size;
} stats;
+ struct {
+ struct list_head list;
+ } cthelper;
};
#define STATE(x) st.x
@@ -252,24 +255,41 @@ 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 {
struct internal_handler *internal;
int (*init)(void);
- void (*run)(fd_set *readfds);
int (*local)(int fd, int type, void *data);
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);
+
+/* 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;
@@ -278,7 +298,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/include/filter.h b/include/filter.h
index 3c7c8cc..d0acd96 100644
--- a/include/filter.h
+++ b/include/filter.h
@@ -51,6 +51,7 @@ void ct_filter_set_logic(struct ct_filter *f,
enum ct_filter_type type,
enum ct_filter_logic logic);
int ct_filter_conntrack(const struct nf_conntrack *ct, int userspace);
+int ct_filter_master(const struct nf_conntrack *master);
struct exp_filter;
struct nf_expect;
diff --git a/include/helper.h b/include/helper.h
new file mode 100644
index 0000000..f412e55
--- /dev/null
+++ b/include/helper.h
@@ -0,0 +1,114 @@
+#ifndef _CTD_HELPER_H_
+#define _CTD_HELPER_H_
+
+#include <stdint.h>
+#include "linux_list.h"
+#include "myct.h"
+
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+
+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,
+ uint32_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;
+ uint32_t queue_len;
+ 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, uint32_t flags);
+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);
+
+void cthelper_get_port_src(struct nf_conntrack *ct, int dir, uint16_t *port);
+void cthelper_get_port_dst(struct nf_conntrack *ct, int dir, uint16_t *port);
+
+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)
+
+#if 0
+#define pr_debug(fmt, arg...) \
+ printf(fmt, ##arg)
+#else
+#define pr_debug(fmt, arg...) \
+ ({ if (0) printf(fmt, ##arg); 0; })
+#endif
+
+#endif
diff --git a/include/internal.h b/include/internal.h
index 2ba9714..1a796a7 100644
--- a/include/internal.h
+++ b/include/internal.h
@@ -40,6 +40,7 @@ struct internal_handler {
void (*new)(struct nf_expect *exp, int origin_type);
void (*upd)(struct nf_expect *exp, int origin_type);
int (*del)(struct nf_expect *exp, int origin_type);
+ int (*find)(const struct nf_conntrack *master);
void (*dump)(int fd, int type);
void (*populate)(struct nf_expect *exp);
diff --git a/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..6574060
--- /dev/null
+++ b/include/linux/netfilter/Makefile.am
@@ -0,0 +1 @@
+noinst_HEADERS = nfnetlink.h nfnetlink_cttimeout.h nfnetlink_queue.h nfnetlink_cthelper.h
diff --git a/include/linux/netfilter/nfnetlink.h b/include/linux/netfilter/nfnetlink.h
new file mode 100644
index 0000000..c755646
--- /dev/null
+++ b/include/linux/netfilter/nfnetlink.h
@@ -0,0 +1,66 @@
+#ifndef _NFNETLINK_H
+#define _NFNETLINK_H
+#include <linux/types.h>
+#include <linux/netfilter/nfnetlink_compat.h>
+
+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_NFTABLES,
+#define NFNLGRP_NFTABLES NFNLGRP_NFTABLES
+ NFNLGRP_ACCT_QUOTA,
+#define NFNLGRP_ACCT_QUOTA NFNLGRP_ACCT_QUOTA
+ __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_CTNETLINK_TIMEOUT 8
+#define NFNL_SUBSYS_CTHELPER 9
+#define NFNL_SUBSYS_NFTABLES 10
+#define NFNL_SUBSYS_NFT_COMPAT 11
+#define NFNL_SUBSYS_COUNT 12
+
+/* Reserved control nfnetlink messages */
+#define NFNL_MSG_BATCH_BEGIN NLMSG_MIN_TYPE
+#define NFNL_MSG_BATCH_END NLMSG_MIN_TYPE+1
+
+#endif /* _NFNETLINK_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_cttimeout.h b/include/linux/netfilter/nfnetlink_cttimeout.h
new file mode 100644
index 0000000..1ab0b97
--- /dev/null
+++ b/include/linux/netfilter/nfnetlink_cttimeout.h
@@ -0,0 +1,116 @@
+#ifndef _CTTIMEOUT_NETLINK_H
+#define _CTTIMEOUT_NETLINK_H
+#include <linux/netfilter/nfnetlink.h>
+
+enum ctnl_timeout_msg_types {
+ IPCTNL_MSG_TIMEOUT_NEW,
+ IPCTNL_MSG_TIMEOUT_GET,
+ IPCTNL_MSG_TIMEOUT_DELETE,
+ IPCTNL_MSG_TIMEOUT_DEFAULT_SET,
+ IPCTNL_MSG_TIMEOUT_DEFAULT_GET,
+
+ 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/linux/netfilter/nfnetlink_queue.h b/include/linux/netfilter/nfnetlink_queue.h
new file mode 100644
index 0000000..8dd819e
--- /dev/null
+++ b/include/linux/netfilter/nfnetlink_queue.h
@@ -0,0 +1,115 @@
+#ifndef _NFNETLINK_QUEUE_H
+#define _NFNETLINK_QUEUE_H
+
+#include <linux/types.h>
+#include <linux/netfilter/nfnetlink.h>
+
+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_CAP_LEN, /* __u32 length of captured packet */
+ NFQA_SKB_INFO, /* __u32 skb meta information */
+ NFQA_EXP, /* nf_conntrack_netlink.h */
+ NFQA_UID, /* __u32 sk uid */
+ NFQA_GID, /* __u32 sk gid */
+
+ __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_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_MASK, /* identify which flags to change */
+ NFQA_CFG_FLAGS, /* value of these flags (__u32) */
+ __NFQA_CFG_MAX
+};
+#define NFQA_CFG_MAX (__NFQA_CFG_MAX-1)
+
+/* Flags for NFQA_CFG_FLAGS */
+#define NFQA_CFG_F_FAIL_OPEN (1 << 0)
+#define NFQA_CFG_F_CONNTRACK (1 << 1)
+#define NFQA_CFG_F_GSO (1 << 2)
+#define NFQA_CFG_F_UID_GID (1 << 3)
+#define NFQA_CFG_F_MAX (1 << 4)
+
+/* flags for NFQA_SKB_INFO */
+/* packet appears to have wrong checksums, but they are ok */
+#define NFQA_SKB_CSUMNOTREADY (1 << 0)
+/* packet is GSO (i.e., exceeds device mtu) */
+#define NFQA_SKB_GSO (1 << 1)
+/* csum not validated (incoming device doesn't support hw checksum, etc.) */
+#define NFQA_SKB_CSUM_NOTVERIFIED (1 << 2)
+
+#endif /* _NFNETLINK_QUEUE_H */
diff --git a/include/linux_list.h b/include/linux_list.h
index de182a4..efffb91 100644
--- a/include/linux_list.h
+++ b/include/linux_list.h
@@ -29,7 +29,7 @@
1; \
})
-#define prefetch(x) 1
+#define prefetch(x) ((void)0)
/* empty define to make this work in userspace -HW */
#ifndef smp_wmb
diff --git a/include/mcast.h b/include/mcast.h
index 402a033..f0225aa 100644
--- a/include/mcast.h
+++ b/include/mcast.h
@@ -4,6 +4,7 @@
#include <stdint.h>
#include <netinet/in.h>
#include <net/if.h>
+#include <sys/select.h>
struct mcast_conf {
int ipproto;
diff --git a/include/myct.h b/include/myct.h
new file mode 100644
index 0000000..02d695c
--- /dev/null
+++ b/include/myct.h
@@ -0,0 +1,44 @@
+#ifndef _MYCT_H_
+#define _MYCT_H_
+
+#include "linux_list.h"
+
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+
+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;
+ struct nf_expect *exp;
+ void *priv_data;
+};
+
+#endif
diff --git a/include/netlink.h b/include/netlink.h
index 3bde30c..9a33083 100644
--- a/include/netlink.h
+++ b/include/netlink.h
@@ -12,7 +12,7 @@ struct nlif_handle *nl_init_interface_handler(void);
int nl_send_resync(struct nfct_handle *h);
void nl_resize_socket_buffer(struct nfct_handle *h);
int nl_dump_conntrack_table(struct nfct_handle *h);
-int nl_flush_conntrack_table(struct nfct_handle *h);
+int nl_flush_conntrack_table_selective(void);
int nl_get_conntrack(struct nfct_handle *h, const struct nf_conntrack *ct);
int nl_create_conntrack(struct nfct_handle *h, const struct nf_conntrack *ct, int timeout);
int nl_update_conntrack(struct nfct_handle *h, const struct nf_conntrack *ct, int timeout);
diff --git a/include/network.h b/include/network.h
index 41c35af..cc312cb 100644
--- a/include/network.h
+++ b/include/network.h
@@ -173,18 +173,6 @@ static inline int between(uint32_t seq1, uint32_t seq2, uint32_t seq3)
return seq3 - seq2 >= seq1 - seq2;
}
-#define PLD_NETWORK2HOST(x) \
-({ \
- x->len = ntohs(x->len); \
- x->query = ntohs(x->query); \
-})
-
-#define PLD_HOST2NETWORK(x) \
-({ \
- x->len = htons(x->len); \
- x->query = htons(x->query); \
-})
-
struct netattr {
uint16_t nta_len;
uint16_t nta_attr;
@@ -240,9 +228,13 @@ enum nta_attr {
NTA_TCP_WSCALE_ORIG, /* uint8_t */
NTA_TCP_WSCALE_REPL, /* uint8_t */
NTA_HELPER_NAME, /* string (variable length) */
+ NTA_LABELS, /* array of uint32_t (variable length) */
NTA_MAX
};
+/* allow to serialize/replicate up to 4k labels per flow */
+#define NTA_LABELS_MAX_SIZE (4096/sizeof(uint32_t))
+
struct nta_attr_natseqadj {
uint32_t orig_seq_correction_pos;
uint32_t orig_seq_offset_before;
diff --git a/include/nfct.h b/include/nfct.h
new file mode 100644
index 0000000..bfffdd6
--- /dev/null
+++ b/include/nfct.h
@@ -0,0 +1,46 @@
+#ifndef _NFCT_H_
+#define _NFCT_H_
+
+#include "linux_list.h"
+
+enum {
+ NFCT_SUBSYS_NONE = 0,
+ NFCT_SUBSYS_TIMEOUT,
+ NFCT_SUBSYS_HELPER,
+ NFCT_SUBSYS_VERSION,
+ NFCT_SUBSYS_HELP,
+ NFCT_SUBSYS_MAX
+};
+
+enum {
+ NFCT_CMD_NONE = 0,
+ NFCT_CMD_LIST,
+ NFCT_CMD_ADD,
+ NFCT_CMD_DELETE,
+ NFCT_CMD_GET,
+ NFCT_CMD_FLUSH,
+ NFCT_CMD_DISABLE,
+ NFCT_CMD_DEFAULT_SET,
+ NFCT_CMD_DEFAULT_GET,
+ NFCT_CMD_MAX,
+};
+
+#define __init __attribute__((constructor))
+
+void nfct_perror(const char *msg);
+
+struct nfct_extension {
+ struct list_head head;
+ int type;
+ int (*parse_params)(struct mnl_socket *nl, int argc, char *argv[], int cmd);
+};
+
+void nfct_extension_register(struct nfct_extension *ext);
+
+struct mnl_socket *nfct_mnl_open(void);
+int nfct_mnl_talk(struct mnl_socket *nl, struct nlmsghdr *nlh,
+ uint32_t seq, uint32_t portid,
+ int (*cb)(const struct nlmsghdr *nlh, void *data),
+ void *data);
+
+#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/include/tcp.h b/include/tcp.h
index 2f0fd0a..068d43a 100644
--- a/include/tcp.h
+++ b/include/tcp.h
@@ -3,6 +3,7 @@
#include <stdint.h>
#include <netinet/in.h>
+#include <sys/select.h>
struct tcp_conf {
int ipproto;
diff --git a/include/udp.h b/include/udp.h
index 9f9c17a..53d713d 100644
--- a/include/udp.h
+++ b/include/udp.h
@@ -3,6 +3,7 @@
#include <stdint.h>
#include <netinet/in.h>
+#include <sys/select.h>
struct udp_conf {
int ipproto;
diff --git a/nfct.8 b/nfct.8
new file mode 100644
index 0000000..336d9cd
--- /dev/null
+++ b/nfct.8
@@ -0,0 +1,68 @@
+.TH NFCT 8 "Feb 29, 2012" "" ""
+
+.\" Man page written by Pablo Neira Ayuso <pablo@netfilter.org> (Feb 2012)
+
+.SH NAME
+nfct \- command line tool to configure with the connection tracking system
+.SH SYNOPSIS
+.BR "nfct command subsystem [parameters]"
+.SH DESCRIPTION
+.B nfct
+is the command line tool that allows to configure the Connection Tracking
+System.
+.SH COMMANDS
+.TP
+.BI "list "
+List the existing objects.
+.TP
+.BI "add "
+Add new object.
+.TP
+.BI "delete "
+Delete an object.
+.TP
+.BI "get "
+Get an existing object.
+.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 "helper "
+The helper subsystem allows you to configure userspace helpers.
+.TP
+.BI "version "
+Displays the version information.
+.TP
+.BI "help "
+Displays the help message.
+.SH EXAMPLE
+.TP
+.B nfct add timeout 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 <pablo@netfilter.org>.
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/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
diff --git a/src/Makefile.am b/src/Makefile.am
index 7d7b2ac..a1d00f8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,19 +1,48 @@
include $(top_srcdir)/Make_global.am
+if HAVE_CTHELPER
+SUBDIRS = helpers
+endif
+
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}
+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} ${LIBMNL_LIBS} ${LIBNFNETLINK_LIBS}
+
+nfct_SOURCES = nfct.c
+
+if HAVE_CTHELPER
+nfct_SOURCES += helpers.c \
+ nfct-extensions/helper.c
+endif
+
+if HAVE_CTTIMEOUT
+nfct_SOURCES += nfct-extensions/timeout.c
+endif
+
+nfct_LDADD = ${LIBMNL_LIBS} \
+ ${libdl_LIBS}
+
+if HAVE_CTTIMEOUT
+nfct_LDADD += ${LIBNETFILTER_CTTIMEOUT_LIBS}
+endif
+
+if HAVE_CTHELPER
+nfct_LDADD += ${LIBNETFILTER_CTHELPER_LIBS}
+endif
+
+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 \
sync-mode.c sync-alarm.c sync-ftfw.c sync-notrack.c \
traffic_stats.c stats-mode.c \
network.c cidr.c \
@@ -22,11 +51,23 @@ 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
+
+if HAVE_CTHELPER
+conntrackd_SOURCES += cthelper.c helpers.c utils.c expect.c
+endif
# 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} \
+ ${libdl_LIBS} ${LIBNFNETLINK_LIBS}
+
+if HAVE_CTHELPER
+conntrackd_LDADD += ${LIBNETFILTER_CTHELPER_LIBS} ${LIBNETFILTER_QUEUE_LIBS}
+endif
+
+conntrackd_LDFLAGS = -export-dynamic
EXTRA_DIST = read_config_yy.h
diff --git a/src/build.c b/src/build.c
index 7d4ef12..9ba8b57 100644
--- a/src/build.c
+++ b/src/build.c
@@ -105,14 +105,14 @@ static enum nf_conntrack_attr nat_type[] =
ATTR_ORIG_NAT_SEQ_OFFSET_AFTER, ATTR_REPL_NAT_SEQ_CORRECTION_POS,
ATTR_REPL_NAT_SEQ_OFFSET_BEFORE, ATTR_REPL_NAT_SEQ_OFFSET_AFTER };
+/* ICMP, UDP and TCP are always loaded with nf_conntrack_ipv4 */
static void build_l4proto_tcp(const struct nf_conntrack *ct, struct nethdr *n)
{
- ct_build_group(ct, ATTR_GRP_ORIG_PORT, n, NTA_PORT,
- sizeof(struct nfct_attr_grp_port));
-
if (!nfct_attr_is_set(ct, ATTR_TCP_STATE))
return;
+ ct_build_group(ct, ATTR_GRP_ORIG_PORT, n, NTA_PORT,
+ sizeof(struct nfct_attr_grp_port));
ct_build_u8(ct, ATTR_TCP_STATE, n, NTA_TCP_STATE);
if (CONFIG(sync).tcp_window_tracking) {
ct_build_u8(ct, ATTR_TCP_WSCALE_ORIG, n, NTA_TCP_WSCALE_ORIG);
@@ -122,12 +122,12 @@ static void build_l4proto_tcp(const struct nf_conntrack *ct, struct nethdr *n)
static void build_l4proto_sctp(const struct nf_conntrack *ct, struct nethdr *n)
{
- ct_build_group(ct, ATTR_GRP_ORIG_PORT, n, NTA_PORT,
- sizeof(struct nfct_attr_grp_port));
-
+ /* SCTP is optional, make sure nf_conntrack_sctp is loaded */
if (!nfct_attr_is_set(ct, ATTR_SCTP_STATE))
return;
+ ct_build_group(ct, ATTR_GRP_ORIG_PORT, n, NTA_PORT,
+ sizeof(struct nfct_attr_grp_port));
ct_build_u8(ct, ATTR_SCTP_STATE, n, NTA_SCTP_STATE);
ct_build_u32(ct, ATTR_SCTP_VTAG_ORIG, n, NTA_SCTP_VTAG_ORIG);
ct_build_u32(ct, ATTR_SCTP_VTAG_REPL, n, NTA_SCTP_VTAG_REPL);
@@ -135,18 +135,22 @@ static void build_l4proto_sctp(const struct nf_conntrack *ct, struct nethdr *n)
static void build_l4proto_dccp(const struct nf_conntrack *ct, struct nethdr *n)
{
- ct_build_group(ct, ATTR_GRP_ORIG_PORT, n, NTA_PORT,
- sizeof(struct nfct_attr_grp_port));
-
+ /* DCCP is optional, make sure nf_conntrack_dccp is loaded */
if (!nfct_attr_is_set(ct, ATTR_DCCP_STATE))
return;
+ ct_build_group(ct, ATTR_GRP_ORIG_PORT, n, NTA_PORT,
+ sizeof(struct nfct_attr_grp_port));
ct_build_u8(ct, ATTR_DCCP_STATE, n, NTA_DCCP_STATE);
ct_build_u8(ct, ATTR_DCCP_ROLE, n, NTA_DCCP_ROLE);
}
static void build_l4proto_icmp(const struct nf_conntrack *ct, struct nethdr *n)
{
+ /* This is also used by ICMPv6 and nf_conntrack_ipv6 is optional */
+ if (!nfct_attr_is_set(ct, ATTR_ICMP_TYPE))
+ return;
+
ct_build_u8(ct, ATTR_ICMP_TYPE, n, NTA_ICMP_TYPE);
ct_build_u8(ct, ATTR_ICMP_CODE, n, NTA_ICMP_CODE);
ct_build_u16(ct, ATTR_ICMP_ID, n, NTA_ICMP_ID);
@@ -158,6 +162,42 @@ static void build_l4proto_udp(const struct nf_conntrack *ct, struct nethdr *n)
sizeof(struct nfct_attr_grp_port));
}
+static void ct_build_clabel(const struct nf_conntrack *ct, struct nethdr *n)
+{
+ const struct nfct_bitmask *b;
+ uint32_t *words;
+ unsigned int wordcount, i, maxbit;
+
+ if (!nfct_attr_is_set(ct, ATTR_CONNLABELS))
+ return;
+
+ b = nfct_get_attr(ct, ATTR_CONNLABELS);
+
+ maxbit = nfct_bitmask_maxbit(b);
+ for (i=0; i <= maxbit; i++) {
+ if (nfct_bitmask_test_bit(b, i))
+ break;
+ }
+
+ if (i > maxbit)
+ return;
+
+ wordcount = (nfct_bitmask_maxbit(b) / 32) + 1;
+ words = put_header(n, NTA_LABELS, wordcount * sizeof(*words));
+
+ for (i=0; i < wordcount; i++) {
+ int bit = 31;
+ uint32_t tmp = 0;
+
+ do {
+ if (nfct_bitmask_test_bit(b, (32 * i) + bit))
+ tmp |= (1 << bit);
+ } while (--bit >= 0);
+
+ words[i] = htonl(tmp);
+ }
+}
+
#ifndef IPPROTO_DCCP
#define IPPROTO_DCCP 33
#endif
@@ -233,6 +273,9 @@ void ct2msg(const struct nf_conntrack *ct, struct nethdr *n)
if (nfct_attr_is_set(ct, ATTR_HELPER_NAME))
ct_build_str(ct, ATTR_HELPER_NAME, n, NTA_HELPER_NAME);
+
+ if (nfct_attr_is_set(ct, ATTR_CONNLABELS))
+ ct_build_clabel(ct, n);
}
static void
@@ -356,7 +399,8 @@ void exp2msg(const struct nf_expect *exp, struct nethdr *n)
exp_build_u32(exp, ATTR_EXP_NAT_DIR, n, NTA_EXP_NAT_DIR);
}
- exp_build_str(exp, ATTR_EXP_HELPER_NAME, n, NTA_EXP_HELPER_NAME);
+ if (nfexp_attr_is_set(exp, ATTR_EXP_HELPER_NAME))
+ exp_build_str(exp, ATTR_EXP_HELPER_NAME, n, NTA_EXP_HELPER_NAME);
if (nfexp_attr_is_set(exp, ATTR_EXP_FN))
exp_build_str(exp, ATTR_EXP_FN, n, NTA_EXP_FN);
}
diff --git a/src/cache-ct.c b/src/cache-ct.c
index 0ad8d2a..f86d143 100644
--- a/src/cache-ct.c
+++ b/src/cache-ct.c
@@ -59,7 +59,7 @@ cache_hash6_ct(const struct nf_conntrack *ct, const struct hashtable *table)
uint32_t a[10];
memcpy(&a[0], nfct_get_attr(ct, ATTR_IPV6_SRC), sizeof(uint32_t)*4);
- memcpy(&a[4], nfct_get_attr(ct, ATTR_IPV6_SRC), sizeof(uint32_t)*4);
+ memcpy(&a[4], nfct_get_attr(ct, ATTR_IPV6_DST), sizeof(uint32_t)*4);
a[8] = nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO) << 16 |
nfct_get_attr_u8(ct, ATTR_ORIG_L4PROTO);
a[9] = nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) << 16 |
@@ -88,14 +88,21 @@ cache_ct_hash(const void *data, const struct hashtable *table)
return ret;
}
+/* master conntrack of expectations have no ID */
+static inline int
+cache_ct_cmp_id(const struct nf_conntrack *ct1, const struct nf_conntrack *ct2)
+{
+ return nfct_attr_is_set(ct2, ATTR_ID) ?
+ nfct_get_attr_u32(ct1, ATTR_ID) == nfct_get_attr_u32(ct2, ATTR_ID) : 1;
+}
+
static int cache_ct_cmp(const void *data1, const void *data2)
{
const struct cache_object *obj = data1;
const struct nf_conntrack *ct = data2;
return nfct_cmp(obj->ptr, ct, NFCT_CMP_ORIG) &&
- nfct_get_attr_u32(obj->ptr, ATTR_ID) ==
- nfct_get_attr_u32(ct, ATTR_ID);
+ cache_ct_cmp_id(obj->ptr, ct);
}
static void *cache_ct_alloc(void)
diff --git a/src/cache-exp.c b/src/cache-exp.c
index e88877a..9183b2c 100644
--- a/src/cache-exp.c
+++ b/src/cache-exp.c
@@ -59,7 +59,7 @@ cache_hash6_exp(const struct nf_conntrack *ct, const struct hashtable *table)
uint32_t a[10];
memcpy(&a[0], nfct_get_attr(ct, ATTR_IPV6_SRC), sizeof(uint32_t)*4);
- memcpy(&a[4], nfct_get_attr(ct, ATTR_IPV6_SRC), sizeof(uint32_t)*4);
+ memcpy(&a[4], nfct_get_attr(ct, ATTR_IPV6_DST), sizeof(uint32_t)*4);
a[8] = nfct_get_attr_u8(ct, ATTR_ORIG_L3PROTO) << 16 |
nfct_get_attr_u8(ct, ATTR_ORIG_L4PROTO);
a[9] = nfct_get_attr_u16(ct, ATTR_ORIG_PORT_SRC) << 16 |
diff --git a/src/cache.c b/src/cache.c
index 7c41e54..79a024f 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -34,7 +34,7 @@ struct cache_feature *cache_feature[CACHE_MAX_FEATURE] = {
};
struct cache *cache_create(const char *name, enum cache_type type,
- unsigned int features,
+ unsigned int features,
struct cache_extra *extra,
struct cache_ops *ops)
{
@@ -53,7 +53,8 @@ struct cache *cache_create(const char *name, enum cache_type type,
return NULL;
memset(c, 0, sizeof(struct cache));
- strcpy(c->name, name);
+ strncpy(c->name, name, CACHE_MAX_NAMELEN);
+ c->name[CACHE_MAX_NAMELEN - 1] = '\0';
c->type = type;
for (i = 0; i < CACHE_MAX_FEATURE; i++) {
diff --git a/src/channel.c b/src/channel.c
index 818bb01..acbfa7d 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -109,6 +109,7 @@ channel_open(struct channel_conf *cfg)
if (ioctl(fd, SIOCGIFMTU, &ifr) == -1) {
free(c);
+ close(fd);
return NULL;
}
close(fd);
@@ -310,3 +311,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/conntrack.c b/src/conntrack.c
index f808994..9421360 100644
--- a/src/conntrack.c
+++ b/src/conntrack.c
@@ -1,5 +1,6 @@
/*
- * (C) 2005-2008 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2005-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2012 by Intra2net AG <http://www.intra2net.com>
*
* 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
@@ -33,6 +34,8 @@
* Ported to the new libnetfilter_conntrack API
* 2008-04-13 Pablo Neira Ayuso <pablo@netfilter.org>:
* Way more flexible update and delete operations
+ *
+ * Part of this code has been funded by Sophos Astaro <http://www.sophos.com>
*/
#include "conntrack.h"
@@ -56,6 +59,7 @@
#include <netdb.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include <libmnl/libmnl.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
struct u32_mask {
@@ -72,6 +76,15 @@ 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;
+
+ /* Allows filtering by ctlabels */
+ struct nfct_bitmask *label;
+
+ /* Allows setting/removing specific ctlabels */
+ struct nfct_bitmask *label_modify;
} tmpl;
static int alloc_tmpl_objects(void)
@@ -97,6 +110,10 @@ static void free_tmpl_objects(void)
nfct_destroy(tmpl.mask);
if (tmpl.exp)
nfexp_destroy(tmpl.exp);
+ if (tmpl.label)
+ nfct_bitmask_destroy(tmpl.label);
+ if (tmpl.label_modify)
+ nfct_bitmask_destroy(tmpl.label_modify);
}
enum ct_command {
@@ -153,8 +170,11 @@ enum ct_command {
EXP_COUNT_BIT = 16,
EXP_COUNT = (1 << EXP_COUNT_BIT),
- X_STATS_BIT = 17,
- X_STATS = (1 << X_STATS_BIT),
+ CT_STATS_BIT = 17,
+ CT_STATS = (1 << CT_STATS_BIT),
+
+ EXP_STATS_BIT = 18,
+ EXP_STATS = (1 << EXP_STATS_BIT),
};
/* If you add a new command, you have to update NUMBER_OF_CMD in conntrack.h */
@@ -237,13 +257,29 @@ enum ct_options {
CT_OPT_ZONE_BIT = 23,
CT_OPT_ZONE = (1 << CT_OPT_ZONE_BIT),
+
+ CT_OPT_LABEL_BIT = 24,
+ CT_OPT_LABEL = (1 << CT_OPT_LABEL_BIT),
+
+ CT_OPT_ADD_LABEL_BIT = 25,
+ CT_OPT_ADD_LABEL = (1 << CT_OPT_ADD_LABEL_BIT),
+
+ CT_OPT_DEL_LABEL_BIT = 26,
+ CT_OPT_DEL_LABEL = (1 << CT_OPT_DEL_LABEL_BIT),
+
+ CT_OPT_ORIG_ZONE_BIT = 27,
+ CT_OPT_ORIG_ZONE = (1 << CT_OPT_ORIG_ZONE_BIT),
+
+ CT_OPT_REPL_ZONE_BIT = 28,
+ CT_OPT_REPL_ZONE = (1 << CT_OPT_REPL_ZONE_BIT),
};
/* If you add a new option, you have to update NUMBER_OF_OPT in conntrack.h */
/* Update this mask to allow to filter based on new options. */
#define CT_COMPARISON (CT_OPT_PROTO | CT_OPT_ORIG | CT_OPT_REPL | \
CT_OPT_MARK | CT_OPT_SECMARK | CT_OPT_STATUS | \
- CT_OPT_ID | CT_OPT_ZONE)
+ CT_OPT_ID | CT_OPT_ZONE | CT_OPT_LABEL | \
+ CT_OPT_ORIG_ZONE | CT_OPT_REPL_ZONE)
static const char *optflags[NUMBER_OF_OPT] = {
[CT_OPT_ORIG_SRC_BIT] = "src",
@@ -270,6 +306,11 @@ static const char *optflags[NUMBER_OF_OPT] = {
[CT_OPT_BUFFERSIZE_BIT] = "buffer-size",
[CT_OPT_ANY_NAT_BIT] = "any-nat",
[CT_OPT_ZONE_BIT] = "zone",
+ [CT_OPT_LABEL_BIT] = "label",
+ [CT_OPT_ADD_LABEL_BIT] = "label-add",
+ [CT_OPT_DEL_LABEL_BIT] = "label-del",
+ [CT_OPT_ORIG_ZONE_BIT] = "orig-zone",
+ [CT_OPT_REPL_ZONE_BIT] = "reply-zone",
};
static struct option original_opts[] = {
@@ -310,12 +351,17 @@ static struct option original_opts[] = {
{"buffer-size", 1, 0, 'b'},
{"any-nat", 2, 0, 'j'},
{"zone", 1, 0, 'w'},
+ {"label", 1, 0, 'l'},
+ {"label-add", 1, 0, '<'},
+ {"label-del", 2, 0, '>'},
+ {"orig-zone", 1, 0, '('},
+ {"reply-zone", 1, 0, ')'},
{0, 0, 0, 0}
};
-static const char *getopt_str = "L::I::U::D::G::E::F::hVs:d:r:q:"
+static const char *getopt_str = ":L::I::U::D::G::E::F::hVs:d:r:q:"
"p:t:u:e:a:z[:]:{:}:m:i:f:o:n::"
- "g::c:b:C::Sj::w:";
+ "g::c:b:C::Sj::w:l:<:>::(:):";
/* Table of legal combinations of commands and options. If any of the
* given commands make an option legal, that option is legal (applies to
@@ -330,25 +376,26 @@ static const char *getopt_str = "L::I::U::D::G::E::F::hVs:d:r:q:"
static char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] =
/* Well, it's better than "Re: Linux vs FreeBSD" */
{
- /* s d r q p t u z e [ ] { } a m i f n g o c b j w*/
-/*CT_LIST*/ {2,2,2,2,2,0,2,2,0,0,0,0,0,0,2,0,2,2,2,2,2,0,2,2},
-/*CT_CREATE*/ {3,3,3,3,1,1,2,0,0,0,0,0,0,2,2,0,0,2,2,0,0,0,0,2},
-/*CT_UPDATE*/ {2,2,2,2,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0},
-/*CT_DELETE*/ {2,2,2,2,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,2},
-/*CT_GET*/ {3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0},
-/*CT_FLUSH*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
-/*CT_EVENT*/ {2,2,2,2,2,0,0,0,2,0,0,0,0,0,2,0,0,2,2,2,2,2,2,2},
-/*VERSION*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
-/*HELP*/ {0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
-/*EXP_LIST*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0},
-/*EXP_CREATE*/{1,1,2,2,1,1,2,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0},
-/*EXP_DELETE*/{1,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
-/*EXP_GET*/ {1,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
-/*EXP_FLUSH*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
-/*EXP_EVENT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0},
-/*CT_COUNT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
-/*EXP_COUNT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
-/*X_STATS*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ /* s d r q p t u z e [ ] { } a m i f n g o c b j w l < > ( ) */
+/*CT_LIST*/ {2,2,2,2,2,0,2,2,0,0,0,0,0,0,2,0,2,2,2,2,2,0,2,2,2,0,0,2,2},
+/*CT_CREATE*/ {3,3,3,3,1,1,2,0,0,0,0,0,0,2,2,0,0,2,2,0,0,0,0,2,0,2,0,2,2},
+/*CT_UPDATE*/ {2,2,2,2,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,2,2,2,0,0},
+/*CT_DELETE*/ {2,2,2,2,2,2,2,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,2,2,0,0,2,2},
+/*CT_GET*/ {3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,0,2,0,0,0,0},
+/*CT_FLUSH*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*CT_EVENT*/ {2,2,2,2,2,0,0,0,2,0,0,0,0,0,2,0,0,2,2,2,2,2,2,2,2,0,0,2,2},
+/*VERSION*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*HELP*/ {0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*EXP_LIST*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,2,0,0,0,0,0,0,0,0,0},
+/*EXP_CREATE*/{1,1,2,2,1,1,2,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*EXP_DELETE*/{1,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*EXP_GET*/ {1,1,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*EXP_FLUSH*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*EXP_EVENT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0},
+/*CT_COUNT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*EXP_COUNT*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*CT_STATS*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+/*EXP_STATS*/ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
};
static const int cmd2type[][2] = {
@@ -361,6 +408,7 @@ static const int cmd2type[][2] = {
['V'] = { CT_VERSION, CT_VERSION },
['h'] = { CT_HELP, CT_HELP },
['C'] = { CT_COUNT, EXP_COUNT },
+ ['S'] = { CT_STATS, EXP_STATS },
};
static const int opt2type[] = {
@@ -379,6 +427,11 @@ static const int opt2type[] = {
['i'] = CT_OPT_ID,
['j'] = CT_OPT_ANY_NAT,
['w'] = CT_OPT_ZONE,
+ ['l'] = CT_OPT_LABEL,
+ ['<'] = CT_OPT_ADD_LABEL,
+ ['>'] = CT_OPT_DEL_LABEL,
+ ['('] = CT_OPT_ORIG_ZONE,
+ [')'] = CT_OPT_REPL_ZONE,
};
static const int opt2family_attr[][2] = {
@@ -397,10 +450,19 @@ static const int opt2attr[] = {
['d'] = ATTR_ORIG_L3PROTO,
['r'] = ATTR_REPL_L3PROTO,
['q'] = ATTR_REPL_L3PROTO,
+ ['{'] = ATTR_ORIG_L3PROTO,
+ ['}'] = ATTR_ORIG_L3PROTO,
+ ['['] = ATTR_ORIG_L3PROTO,
+ [']'] = ATTR_ORIG_L3PROTO,
['m'] = ATTR_MARK,
['c'] = ATTR_SECMARK,
['i'] = ATTR_ID,
['w'] = ATTR_ZONE,
+ ['l'] = ATTR_CONNLABELS,
+ ['<'] = ATTR_CONNLABELS,
+ ['>'] = ATTR_CONNLABELS,
+ ['('] = ATTR_ORIG_ZONE,
+ [')'] = ATTR_REPL_ZONE,
};
static char exit_msg[NUMBER_OF_CMD][64] = {
@@ -438,7 +500,8 @@ static const char usage_conntrack_parameters[] =
" -c, --secmark secmark\t\t\tSet selinux secmark\n"
" -e, --event-mask eventmask\t\tEvent mask, eg. NEW,DESTROY\n"
" -z, --zero \t\t\t\tZero counters while listing\n"
- " -o, --output type[,...]\t\tOutput format, eg. xml\n";
+ " -o, --output type[,...]\t\tOutput format, eg. xml\n"
+ " -l, --label label[,...]\t\tconntrack labels\n";
static const char usage_expectation_parameters[] =
"Expectation parameters and options:\n"
@@ -447,6 +510,11 @@ static const char usage_expectation_parameters[] =
" --mask-src ip\t\tSource mask address\n"
" --mask-dst ip\t\tDestination mask address\n";
+static const char usage_update_parameters[] =
+ "Updating parameters and options:\n"
+ " --label-add label\tAdd label\n"
+ " --label-del label\tDelete label\n";
+
static const char usage_parameters[] =
"Common parameters and options:\n"
" -s, --orig-src ip\t\tSource address from original direction\n"
@@ -458,6 +526,8 @@ static const char usage_parameters[] =
" -t, --timeout timeout\t\tSet timeout\n"
" -u, --status status\t\tSet status, eg. ASSURED\n"
" -w, --zone value\t\tSet conntrack zone\n"
+ " --orig-zone value\t\tSet zone for original direction\n"
+ " --reply-zone value\t\tSet zone for reply direction\n"
" -b, --buffer-size\t\tNetlink socket buffer size\n"
;
@@ -476,6 +546,7 @@ static unsigned int addr_valid_flags[ADDR_VALID_FLAGS_MAX] = {
static LIST_HEAD(proto_list);
static unsigned int options;
+static struct nfct_labelmap *labelmap;
void register_proto(struct ctproto_handler *h)
{
@@ -497,7 +568,7 @@ static struct ctproto_handler *findproto(char *name, int *pnum)
/* is it in the list of supported protocol? */
list_for_each_entry(cur, &proto_list, head) {
- if (strcmp(cur->name, name) == 0) {
+ if (strcasecmp(cur->name, name) == 0) {
*pnum = cur->protonum;
return cur;
}
@@ -717,6 +788,7 @@ enum {
_O_TMS = (1 << 2),
_O_ID = (1 << 3),
_O_KTMS = (1 << 4),
+ _O_CL = (1 << 5),
};
enum {
@@ -735,8 +807,8 @@ static struct parse_parameter {
{ IPS_ASSURED, IPS_SEEN_REPLY, 0, IPS_FIXED_TIMEOUT, IPS_EXPECTED} },
{ {"ALL", "NEW", "UPDATES", "DESTROY"}, 4,
{ CT_EVENT_F_ALL, CT_EVENT_F_NEW, CT_EVENT_F_UPD, CT_EVENT_F_DEL } },
- { {"xml", "extended", "timestamp", "id", "ktimestamp"}, 5,
- { _O_XML, _O_EXT, _O_TMS, _O_ID, _O_KTMS },
+ { {"xml", "extended", "timestamp", "id", "ktimestamp", "labels", }, 6,
+ { _O_XML, _O_EXT, _O_TMS, _O_ID, _O_KTMS, _O_CL },
},
};
@@ -800,6 +872,59 @@ parse_u32_mask(const char *arg, struct u32_mask *m)
m->mask = ~0;
}
+static int
+get_label(char *name)
+{
+ int bit = nfct_labelmap_get_bit(labelmap, name);
+ if (bit < 0)
+ exit_error(PARAMETER_PROBLEM, "unknown label '%s'", name);
+ return bit;
+}
+
+static void
+set_label(struct nfct_bitmask *b, char *name)
+{
+ int bit = get_label(name);
+ nfct_bitmask_set_bit(b, bit);
+}
+
+static unsigned int
+set_max_label(char *name, unsigned int current_max)
+{
+ int bit = get_label(name);
+ if ((unsigned int) bit > current_max)
+ return (unsigned int) bit;
+ return current_max;
+}
+
+static unsigned int
+parse_label_get_max(char *arg)
+{
+ unsigned int max = 0;
+ char *parse;
+
+ while ((parse = strchr(arg, ',')) != NULL) {
+ parse[0] = '\0';
+ max = set_max_label(arg, max);
+ arg = &parse[1];
+ }
+
+ max = set_max_label(arg, max);
+ return max;
+}
+
+static void
+parse_label(struct nfct_bitmask *b, char *arg)
+{
+ char * parse;
+ while ((parse = strchr(arg, ',')) != NULL) {
+ parse[0] = '\0';
+ set_label(b, arg);
+ arg = &parse[1];
+ }
+ set_label(b, arg);
+}
+
static void
add_command(unsigned int *cmd, const int newcmd)
{
@@ -808,27 +933,45 @@ add_command(unsigned int *cmd, const int newcmd)
*cmd |= newcmd;
}
-static unsigned int
-check_type(int argc, char *argv[])
+static char *get_optional_arg(int argc, char *argv[])
{
- char *table = NULL;
+ char *arg = NULL;
- /* Nasty bug or feature in getopt_long ?
+ /* Nasty bug or feature in getopt_long ?
* It seems that it behaves badly with optional arguments.
* Fortunately, I just stole the fix from iptables ;) */
if (optarg)
- return 0;
- else if (optind < argc && argv[optind][0] != '-'
- && argv[optind][0] != '!')
- table = argv[optind++];
-
- if (!table)
- return 0;
-
+ return arg;
+ else if (optind < argc && argv[optind][0] != '-' &&
+ argv[optind][0] != '!')
+ arg = argv[optind++];
+
+ return arg;
+}
+
+enum {
+ CT_TABLE_CONNTRACK,
+ CT_TABLE_EXPECT,
+ CT_TABLE_DYING,
+ CT_TABLE_UNCONFIRMED,
+};
+
+static unsigned int check_type(int argc, char *argv[])
+{
+ const char *table = get_optional_arg(argc, argv);
+
+ /* default to conntrack subsystem if nothing has been specified. */
+ if (table == NULL)
+ return CT_TABLE_CONNTRACK;
+
if (strncmp("expect", table, strlen(table)) == 0)
- return 1;
+ return CT_TABLE_EXPECT;
else if (strncmp("conntrack", table, strlen(table)) == 0)
- return 0;
+ return CT_TABLE_CONNTRACK;
+ else if (strncmp("dying", table, strlen(table)) == 0)
+ return CT_TABLE_DYING;
+ else if (strncmp("unconfirmed", table, strlen(table)) == 0)
+ return CT_TABLE_UNCONFIRMED;
else
exit_error(PARAMETER_PROBLEM, "unknown type `%s'", table);
@@ -945,11 +1088,30 @@ usage(char *prog)
fprintf(stdout, "\n%s", usage_tables);
fprintf(stdout, "\n%s", usage_conntrack_parameters);
fprintf(stdout, "\n%s", usage_expectation_parameters);
+ fprintf(stdout, "\n%s", usage_update_parameters);
fprintf(stdout, "\n%s\n", usage_parameters);
}
static unsigned int output_mask;
+static int
+filter_label(const struct nf_conntrack *ct)
+{
+ if (tmpl.label == NULL)
+ return 0;
+
+ const struct nfct_bitmask *ctb = nfct_get_attr(ct, ATTR_CONNLABELS);
+ if (ctb == NULL)
+ return 1;
+
+ for (unsigned int i = 0; i <= nfct_bitmask_maxbit(tmpl.label); i++) {
+ if (nfct_bitmask_test_bit(tmpl.label, i) &&
+ !nfct_bitmask_test_bit(ctb, i))
+ return 1;
+ }
+
+ return 0;
+}
static int
filter_mark(const struct nf_conntrack *ct)
@@ -960,7 +1122,6 @@ filter_mark(const struct nf_conntrack *ct)
return 0;
}
-
static int
filter_nat(const struct nf_conntrack *obj, const struct nf_conntrack *ct)
{
@@ -1091,6 +1252,9 @@ static int event_cb(enum nf_conntrack_msg_type type,
if (filter_mark(ct))
return NFCT_CB_CONTINUE;
+ if (filter_label(ct))
+ return NFCT_CB_CONTINUE;
+
if (options & CT_COMPARISON &&
!nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK))
return NFCT_CB_CONTINUE;
@@ -1109,7 +1273,7 @@ static int event_cb(enum nf_conntrack_msg_type type,
if (!(output_mask & _O_XML)) {
struct timeval tv;
gettimeofday(&tv, NULL);
- printf("[%-8ld.%-6ld]\t", tv.tv_sec, tv.tv_usec);
+ printf("[%-.8ld.%-.6ld]\t", tv.tv_sec, tv.tv_usec);
} else
op_flags |= NFCT_OF_TIME;
}
@@ -1118,7 +1282,7 @@ static int event_cb(enum nf_conntrack_msg_type type,
if (output_mask & _O_ID)
op_flags |= NFCT_OF_ID;
- nfct_snprintf(buf, sizeof(buf), ct, type, op_type, op_flags);
+ nfct_snprintf_labels(buf, sizeof(buf), ct, type, op_type, op_flags, labelmap);
printf("%s\n", buf);
fflush(stdout);
@@ -1143,6 +1307,9 @@ static int dump_cb(enum nf_conntrack_msg_type type,
if (filter_mark(ct))
return NFCT_CB_CONTINUE;
+ if (filter_label(ct))
+ return NFCT_CB_CONTINUE;
+
if (options & CT_COMPARISON &&
!nfct_cmp(obj, ct, NFCT_CMP_ALL | NFCT_CMP_MASK))
return NFCT_CB_CONTINUE;
@@ -1162,7 +1329,7 @@ static int dump_cb(enum nf_conntrack_msg_type type,
if (output_mask & _O_ID)
op_flags |= NFCT_OF_ID;
- nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags);
+ nfct_snprintf_labels(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags, labelmap);
printf("%s\n", buf);
counter++;
@@ -1226,7 +1393,7 @@ static int print_cb(enum nf_conntrack_msg_type type,
if (output_mask & _O_ID)
op_flags |= NFCT_OF_ID;
- nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags);
+ nfct_snprintf_labels(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, op_type, op_flags, labelmap);
printf("%s\n", buf);
return NFCT_CB_CONTINUE;
@@ -1253,6 +1420,72 @@ static void copy_status(struct nf_conntrack *tmp, const struct nf_conntrack *ct)
}
}
+static struct nfct_bitmask *xnfct_bitmask_clone(const struct nfct_bitmask *a)
+{
+ struct nfct_bitmask *b = nfct_bitmask_clone(a);
+ if (!b)
+ exit_error(OTHER_PROBLEM, "out of memory");
+ return b;
+}
+
+static void copy_label(struct nf_conntrack *tmp, const struct nf_conntrack *ct)
+{
+ struct nfct_bitmask *ctb, *newmask;
+ unsigned int i;
+
+ if ((options & (CT_OPT_ADD_LABEL|CT_OPT_DEL_LABEL)) == 0)
+ return;
+
+ nfct_copy_attr(tmp, ct, ATTR_CONNLABELS);
+ ctb = (void *) nfct_get_attr(tmp, ATTR_CONNLABELS);
+
+ if (options & CT_OPT_ADD_LABEL) {
+ if (ctb == NULL) {
+ nfct_set_attr(tmp, ATTR_CONNLABELS,
+ xnfct_bitmask_clone(tmpl.label_modify));
+ return;
+ }
+ /* If we send a bitmask shorter than the kernel sent to us, the bits we
+ * omit will be cleared (as "padding"). So we always have to send the
+ * same sized bitmask as we received.
+ *
+ * Mask has to have the same size as the labels, otherwise it will not
+ * be encoded by libnetfilter_conntrack, as different sizes are not
+ * accepted by the kernel.
+ */
+ newmask = nfct_bitmask_new(nfct_bitmask_maxbit(ctb));
+
+ for (i = 0; i <= nfct_bitmask_maxbit(ctb); i++) {
+ if (nfct_bitmask_test_bit(tmpl.label_modify, i)) {
+ nfct_bitmask_set_bit(ctb, i);
+ nfct_bitmask_set_bit(newmask, i);
+ } else if (nfct_bitmask_test_bit(ctb, i)) {
+ /* Kernel only retains old bit values that are sent as
+ * zeroes in BOTH labels and mask.
+ */
+ nfct_bitmask_unset_bit(ctb, i);
+ }
+ }
+ nfct_set_attr(tmp, ATTR_CONNLABELS_MASK, newmask);
+ } else if (ctb != NULL) {
+ /* CT_OPT_DEL_LABEL */
+ if (tmpl.label_modify == NULL) {
+ newmask = nfct_bitmask_new(0);
+ if (newmask)
+ nfct_set_attr(tmp, ATTR_CONNLABELS, newmask);
+ return;
+ }
+
+ for (i = 0; i <= nfct_bitmask_maxbit(ctb); i++) {
+ if (nfct_bitmask_test_bit(tmpl.label_modify, i))
+ nfct_bitmask_unset_bit(ctb, i);
+ }
+
+ newmask = xnfct_bitmask_clone(tmpl.label_modify);
+ nfct_set_attr(tmp, ATTR_CONNLABELS_MASK, newmask);
+ }
+}
+
static int update_cb(enum nf_conntrack_msg_type type,
struct nf_conntrack *ct,
void *data)
@@ -1272,6 +1505,9 @@ static int update_cb(enum nf_conntrack_msg_type type,
if (options & CT_OPT_TUPLE_REPL && !nfct_cmp(obj, ct, NFCT_CMP_REPL))
return NFCT_CB_CONTINUE;
+ if (filter_label(ct))
+ return NFCT_CB_CONTINUE;
+
tmp = nfct_new();
if (tmp == NULL)
exit_error(OTHER_PROBLEM, "out of memory");
@@ -1280,6 +1516,7 @@ static int update_cb(enum nf_conntrack_msg_type type,
nfct_copy(tmp, obj, NFCT_CP_META);
copy_mark(tmp, ct, &tmpl.mark);
copy_status(tmp, ct);
+ copy_label(tmp, ct);
/* do not send NFCT_Q_UPDATE if ct appears unchanged */
if (nfct_cmp(tmp, ct, NFCT_CMP_ALL | NFCT_CMP_MASK)) {
@@ -1288,12 +1525,10 @@ static int update_cb(enum nf_conntrack_msg_type type,
}
res = nfct_query(ith, NFCT_Q_UPDATE, tmp);
- if (res < 0) {
- nfct_destroy(tmp);
- exit_error(OTHER_PROBLEM,
- "Operation failed: %s",
+ if (res < 0)
+ fprintf(stderr,
+ "Operation failed: %s\n",
err2str(errno, CT_UPDATE));
- }
nfct_callback_register(ith, NFCT_T_ALL, print_cb, NULL);
res = nfct_query(ith, NFCT_Q_GET, tmp);
@@ -1374,6 +1609,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;
@@ -1455,8 +1691,305 @@ out_err:
return ret;
}
+static struct nfct_mnl_socket {
+ struct mnl_socket *mnl;
+ uint32_t portid;
+} sock;
+
+static int nfct_mnl_socket_open(void)
+{
+ sock.mnl = mnl_socket_open(NETLINK_NETFILTER);
+ if (sock.mnl == NULL) {
+ perror("mnl_socket_open");
+ return -1;
+ }
+ if (mnl_socket_bind(sock.mnl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ perror("mnl_socket_bind");
+ return -1;
+ }
+ sock.portid = mnl_socket_get_portid(sock.mnl);
+
+ return 0;
+}
+
+static struct nlmsghdr *
+nfct_mnl_nlmsghdr_put(char *buf, uint16_t subsys, uint16_t type,
+ uint8_t family)
+{
+ struct nlmsghdr *nlh;
+ struct nfgenmsg *nfh;
+
+ nlh = mnl_nlmsg_put_header(buf);
+ nlh->nlmsg_type = (subsys << 8) | type;
+ nlh->nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP;
+ nlh->nlmsg_seq = time(NULL);
+
+ nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg));
+ nfh->nfgen_family = family;
+ nfh->version = NFNETLINK_V0;
+ nfh->res_id = 0;
+
+ return nlh;
+}
+
+static void nfct_mnl_socket_close(void)
+{
+ mnl_socket_close(sock.mnl);
+}
+
+static int
+nfct_mnl_dump(uint16_t subsys, uint16_t type, mnl_cb_t cb, uint8_t family)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ int res;
+
+ nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type, family);
+
+ res = mnl_socket_sendto(sock.mnl, nlh, nlh->nlmsg_len);
+ if (res < 0)
+ return res;
+
+ res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf));
+ while (res > 0) {
+ res = mnl_cb_run(buf, res, nlh->nlmsg_seq, sock.portid,
+ cb, NULL);
+ if (res <= MNL_CB_STOP)
+ break;
+
+ res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf));
+ }
+
+ return res;
+}
+
+static int
+nfct_mnl_get(uint16_t subsys, uint16_t type, mnl_cb_t cb, uint8_t family)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ int res;
+
+ nlh = nfct_mnl_nlmsghdr_put(buf, subsys, type, family);
+
+ res = mnl_socket_sendto(sock.mnl, nlh, nlh->nlmsg_len);
+ if (res < 0)
+ return res;
+
+ res = mnl_socket_recvfrom(sock.mnl, buf, sizeof(buf));
+ if (res < 0)
+ return res;
+
+ return mnl_cb_run(buf, res, nlh->nlmsg_seq, sock.portid, cb, NULL);
+}
+
+static int nfct_stats_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, CTA_STATS_MAX) < 0)
+ return MNL_CB_OK;
+
+ if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
+ perror("mnl_attr_validate");
+ return MNL_CB_ERROR;
+ }
+
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int nfct_stats_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[CTA_STATS_MAX+1] = {};
+ struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh);
+ const char *attr2name[CTA_STATS_MAX+1] = {
+ [CTA_STATS_SEARCHED] = "searched",
+ [CTA_STATS_FOUND] = "found",
+ [CTA_STATS_NEW] = "new",
+ [CTA_STATS_INVALID] = "invalid",
+ [CTA_STATS_IGNORE] = "ignore",
+ [CTA_STATS_DELETE] = "delete",
+ [CTA_STATS_DELETE_LIST] = "delete_list",
+ [CTA_STATS_INSERT] = "insert",
+ [CTA_STATS_INSERT_FAILED] = "insert_failed",
+ [CTA_STATS_DROP] = "drop",
+ [CTA_STATS_EARLY_DROP] = "early_drop",
+ [CTA_STATS_ERROR] = "error",
+ [CTA_STATS_SEARCH_RESTART] = "search_restart",
+ };
+ int i;
+
+ mnl_attr_parse(nlh, sizeof(*nfg), nfct_stats_attr_cb, tb);
+
+ printf("cpu=%-4u\t", ntohs(nfg->res_id));
+
+ for (i=0; i<CTA_STATS_MAX+1; i++) {
+ if (tb[i]) {
+ printf("%s=%u ",
+ attr2name[i], ntohl(mnl_attr_get_u32(tb[i])));
+ }
+ }
+ printf("\n");
+ return MNL_CB_OK;
+}
+
+static int nfexp_stats_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, CTA_STATS_EXP_MAX) < 0)
+ return MNL_CB_OK;
+
+ if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
+ perror("mnl_attr_validate");
+ return MNL_CB_ERROR;
+ }
+
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int nfexp_stats_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[CTA_STATS_EXP_MAX+1] = {};
+ struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh);
+ const char *attr2name[CTA_STATS_EXP_MAX+1] = {
+ [CTA_STATS_EXP_NEW] = "expect_new",
+ [CTA_STATS_EXP_CREATE] = "expect_create",
+ [CTA_STATS_EXP_DELETE] = "expect_delete",
+ };
+ int i;
+
+ mnl_attr_parse(nlh, sizeof(*nfg), nfexp_stats_attr_cb, tb);
+
+ printf("cpu=%-4u\t", ntohs(nfg->res_id));
+
+ for (i=0; i<CTA_STATS_EXP_MAX+1; i++) {
+ if (tb[i]) {
+ printf("%s=%u ",
+ attr2name[i], ntohl(mnl_attr_get_u32(tb[i])));
+ }
+ }
+ printf("\n");
+ return MNL_CB_OK;
+}
+
+static int nfct_stats_global_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, CTA_STATS_GLOBAL_MAX) < 0)
+ return MNL_CB_OK;
+
+ if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
+ perror("mnl_attr_validate");
+ return MNL_CB_ERROR;
+ }
+
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int nfct_global_stats_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nlattr *tb[CTA_STATS_GLOBAL_MAX+1] = {};
+ struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*nfg), nfct_stats_global_attr_cb, tb);
+
+ if (tb[CTA_STATS_GLOBAL_ENTRIES]) {
+ printf("%d\n",
+ ntohl(mnl_attr_get_u32(tb[CTA_STATS_GLOBAL_ENTRIES])));
+ }
+ return MNL_CB_OK;
+}
+
+static int mnl_nfct_dump_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct nf_conntrack *ct;
+ char buf[4096];
+
+ ct = nfct_new();
+ if (ct == NULL)
+ return MNL_CB_OK;
+
+ nfct_nlmsg_parse(nlh, ct);
+
+ nfct_snprintf(buf, sizeof(buf), ct, NFCT_T_UNKNOWN, NFCT_O_DEFAULT, 0);
+ printf("%s\n", buf);
+
+ nfct_destroy(ct);
+
+ counter++;
+
+ return MNL_CB_OK;
+}
+
static struct ctproto_handler *h;
+static void labelmap_init(void)
+{
+ if (labelmap)
+ return;
+ labelmap = nfct_labelmap_new(NULL);
+ if (!labelmap)
+ perror("nfct_labelmap_new");
+}
+
+static void merge_bitmasks(struct nfct_bitmask **current,
+ struct nfct_bitmask *src)
+{
+ unsigned int i;
+
+ if (*current == NULL) {
+ *current = src;
+ return;
+ }
+
+ /* "current" must be the larger bitmask object */
+ if (nfct_bitmask_maxbit(src) > nfct_bitmask_maxbit(*current)) {
+ struct nfct_bitmask *tmp = *current;
+ *current = src;
+ src = tmp;
+ }
+
+ for (i = 0; i <= nfct_bitmask_maxbit(src); i++) {
+ if (nfct_bitmask_test_bit(src, i))
+ nfct_bitmask_set_bit(*current, i);
+ }
+
+ nfct_bitmask_destroy(src);
+}
+
+static void
+nfct_set_addr_from_opt(int opt, struct nf_conntrack *ct, union ct_address *ad,
+ int *family)
+{
+ int l3protonum;
+
+ options |= opt2type[opt];
+ l3protonum = parse_addr(optarg, ad);
+ if (l3protonum == AF_UNSPEC) {
+ exit_error(PARAMETER_PROBLEM,
+ "Invalid IP address `%s'", optarg);
+ }
+ set_family(family, l3protonum);
+ if (l3protonum == AF_INET) {
+ nfct_set_attr_u32(ct,
+ opt2family_attr[opt][0],
+ ad->v4);
+ } else if (l3protonum == AF_INET6) {
+ nfct_set_attr(ct,
+ opt2family_attr[opt][1],
+ &ad->v6);
+ }
+ nfct_set_attr_u8(ct, opt2attr[opt], l3protonum);
+}
+
int main(int argc, char *argv[])
{
int c, cmd;
@@ -1464,7 +1997,7 @@ int main(int argc, char *argv[])
int res = 0, partial;
size_t socketbuffersize = 0;
int family = AF_UNSPEC;
- int l3protonum, protonum = 0;
+ int protonum = 0;
union ct_address ad;
unsigned int command = 0;
@@ -1489,6 +2022,16 @@ int main(int argc, char *argv[])
switch(c) {
/* commands */
case 'L':
+ type = check_type(argc, argv);
+ /* Special case: dumping dying and unconfirmed list
+ * are handled like normal conntrack dumps.
+ */
+ if (type == CT_TABLE_DYING ||
+ type == CT_TABLE_UNCONFIRMED)
+ add_command(&command, cmd2type[c][0]);
+ else
+ add_command(&command, cmd2type[c][type]);
+ break;
case 'I':
case 'D':
case 'G':
@@ -1497,66 +2040,43 @@ int main(int argc, char *argv[])
case 'V':
case 'h':
case 'C':
+ case 'S':
type = check_type(argc, argv);
+ if (type == CT_TABLE_DYING ||
+ type == CT_TABLE_UNCONFIRMED) {
+ exit_error(PARAMETER_PROBLEM,
+ "Can't do that command with "
+ "tables `dying' and `unconfirmed'");
+ }
add_command(&command, cmd2type[c][type]);
break;
case 'U':
type = check_type(argc, argv);
- if (type == 0)
+ if (type == CT_TABLE_DYING ||
+ type == CT_TABLE_UNCONFIRMED) {
+ exit_error(PARAMETER_PROBLEM,
+ "Can't do that command with "
+ "tables `dying' and `unconfirmed'");
+ } else if (type == CT_TABLE_CONNTRACK)
add_command(&command, CT_UPDATE);
else
- exit_error(PARAMETER_PROBLEM,
+ exit_error(PARAMETER_PROBLEM,
"Can't update expectations");
break;
- case 'S':
- add_command(&command, X_STATS);
- break;
/* options */
case 's':
case 'd':
case 'r':
case 'q':
- options |= opt2type[c];
-
- l3protonum = parse_addr(optarg, &ad);
- if (l3protonum == AF_UNSPEC) {
- exit_error(PARAMETER_PROBLEM,
- "Invalid IP address `%s'", optarg);
- }
- set_family(&family, l3protonum);
- if (l3protonum == AF_INET) {
- nfct_set_attr_u32(tmpl.ct,
- opt2family_attr[c][0],
- ad.v4);
- } else if (l3protonum == AF_INET6) {
- nfct_set_attr(tmpl.ct,
- opt2family_attr[c][1],
- &ad.v6);
- }
- nfct_set_attr_u8(tmpl.ct, opt2attr[c], l3protonum);
+ nfct_set_addr_from_opt(c, tmpl.ct, &ad, &family);
break;
case '{':
case '}':
+ nfct_set_addr_from_opt(c, tmpl.exptuple, &ad, &family);
+ break;
case '[':
case ']':
- options |= opt2type[c];
- l3protonum = parse_addr(optarg, &ad);
- if (l3protonum == AF_UNSPEC) {
- exit_error(PARAMETER_PROBLEM,
- "Invalid IP address `%s'", optarg);
- }
- set_family(&family, l3protonum);
- if (l3protonum == AF_INET) {
- nfct_set_attr_u32(tmpl.mask,
- opt2family_attr[c][0],
- ad.v4);
- } else if (l3protonum == AF_INET6) {
- nfct_set_attr(tmpl.mask,
- opt2family_attr[c][1],
- &ad.v6);
- }
- nfct_set_attr_u8(tmpl.mask,
- ATTR_ORIG_L3PROTO, l3protonum);
+ nfct_set_addr_from_opt(c, tmpl.mask, &ad, &family);
break;
case 'p':
options |= CT_OPT_PROTO;
@@ -1590,6 +2110,8 @@ int main(int argc, char *argv[])
case 'o':
options |= CT_OPT_OUTPUT;
parse_parameter(optarg, &output_mask, PARSE_OUTPUT);
+ if (output_mask & _O_CL)
+ labelmap_init();
break;
case 'z':
options |= CT_OPT_ZERO;
@@ -1601,12 +2123,7 @@ int main(int argc, char *argv[])
options |= opt2type[c];
- if (optarg)
- continue;
- else if (optind < argc && argv[optind][0] != '-'
- && argv[optind][0] != '!')
- tmp = argv[optind++];
-
+ tmp = get_optional_arg(argc, argv);
if (tmp == NULL)
continue;
@@ -1615,6 +2132,8 @@ int main(int argc, char *argv[])
break;
}
case 'w':
+ case '(':
+ case ')':
options |= opt2type[c];
nfct_set_attr_u16(tmpl.ct,
opt2attr[c],
@@ -1630,6 +2149,44 @@ 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 'l':
+ case '<':
+ case '>':
+ options |= opt2type[c];
+
+ labelmap_init();
+
+ if ((options & (CT_OPT_DEL_LABEL|CT_OPT_ADD_LABEL)) ==
+ (CT_OPT_DEL_LABEL|CT_OPT_ADD_LABEL))
+ exit_error(OTHER_PROBLEM, "cannot use --label-add and "
+ "--label-del at the same time");
+
+ if (c == '>') { /* DELETE */
+ char *tmp = get_optional_arg(argc, argv);
+ if (tmp == NULL) /* delete all labels */
+ break;
+ optarg = tmp;
+ }
+
+ char *optarg2 = strdup(optarg);
+ unsigned int max = parse_label_get_max(optarg);
+ struct nfct_bitmask * b = nfct_bitmask_new(max);
+ if (!b)
+ exit_error(OTHER_PROBLEM, "out of memory");
+
+ parse_label(b, optarg2);
+
+ /* join "-l foo -l bar" into single bitmask object */
+ if (c == 'l') {
+ merge_bitmasks(&tmpl.label, b);
+ } else {
+ merge_bitmasks(&tmpl.label_modify, b);
+ }
+
+ free(optarg2);
break;
case 'a':
fprintf(stderr, "WARNING: ignoring -%c, "
@@ -1650,15 +2207,13 @@ int main(int argc, char *argv[])
socketbuffersize = atol(optarg);
options |= CT_OPT_BUFFERSIZE;
break;
+ case ':':
+ exit_error(PARAMETER_PROBLEM,
+ "option `%s' requires an "
+ "argument", argv[optind-1]);
case '?':
- if (optopt)
- exit_error(PARAMETER_PROBLEM,
- "option `%s' requires an "
- "argument", argv[optind-1]);
- else
- exit_error(PARAMETER_PROBLEM,
- "unknown option `%s'",
- argv[optind-1]);
+ exit_error(PARAMETER_PROBLEM,
+ "unknown option `%s'", argv[optind-1]);
break;
default:
if (h && h->parse_opts
@@ -1703,8 +2258,31 @@ int main(int argc, char *argv[])
h->final_check(l4flags, cmd, tmpl.ct);
switch(command) {
+ struct nfct_filter_dump *filter_dump;
case CT_LIST:
+ if (type == CT_TABLE_DYING) {
+ if (nfct_mnl_socket_open() < 0)
+ exit_error(OTHER_PROBLEM, "Can't open handler");
+
+ res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK,
+ IPCTNL_MSG_CT_GET_DYING,
+ mnl_nfct_dump_cb, family);
+
+ nfct_mnl_socket_close();
+ break;
+ } else if (type == CT_TABLE_UNCONFIRMED) {
+ if (nfct_mnl_socket_open() < 0)
+ exit_error(OTHER_PROBLEM, "Can't open handler");
+
+ res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK,
+ IPCTNL_MSG_CT_GET_UNCONFIRMED,
+ mnl_nfct_dump_cb, family);
+
+ nfct_mnl_socket_close();
+ break;
+ }
+
cth = nfct_open(CONNTRACK, 0);
if (!cth)
exit_error(OTHER_PROBLEM, "Can't open handler");
@@ -1716,10 +2294,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("</conntrack>\n");
@@ -1753,6 +2344,10 @@ int main(int argc, char *argv[])
if (options & CT_OPT_MARK)
nfct_set_attr_u32(tmpl.ct, ATTR_MARK, tmpl.mark.value);
+ if (options & CT_OPT_ADD_LABEL)
+ nfct_set_attr(tmpl.ct, ATTR_CONNLABELS,
+ xnfct_bitmask_clone(tmpl.label_modify));
+
cth = nfct_open(CONNTRACK, 0);
if (!cth)
exit_error(OTHER_PROBLEM, "Can't open handler");
@@ -1798,7 +2393,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;
@@ -1929,7 +2537,25 @@ int main(int argc, char *argv[])
res = nfexp_catch(cth);
nfct_close(cth);
break;
- case CT_COUNT: {
+ case CT_COUNT:
+ /* If we fail with netlink, fall back to /proc to ensure
+ * backward compatibility.
+ */
+ if (nfct_mnl_socket_open() < 0)
+ goto try_proc_count;
+
+ res = nfct_mnl_get(NFNL_SUBSYS_CTNETLINK,
+ IPCTNL_MSG_CT_GET_STATS,
+ nfct_global_stats_cb, AF_UNSPEC);
+
+ nfct_mnl_socket_close();
+
+ /* don't look at /proc, we got the information via ctnetlink */
+ if (res >= 0)
+ break;
+
+try_proc_count:
+ {
#define NF_CONNTRACK_COUNT_PROC "/proc/sys/net/netfilter/nf_conntrack_count"
FILE *fd;
int count;
@@ -1956,7 +2582,42 @@ int main(int argc, char *argv[])
nfct_close(cth);
printf("%d\n", counter);
break;
- case X_STATS:
+ case CT_STATS:
+ /* If we fail with netlink, fall back to /proc to ensure
+ * backward compatibility.
+ */
+ if (nfct_mnl_socket_open() < 0)
+ goto try_proc;
+
+ res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK,
+ IPCTNL_MSG_CT_GET_STATS_CPU,
+ nfct_stats_cb, AF_UNSPEC);
+
+ nfct_mnl_socket_close();
+
+ /* don't look at /proc, we got the information via ctnetlink */
+ if (res >= 0)
+ break;
+
+ goto try_proc;
+
+ case EXP_STATS:
+ /* If we fail with netlink, fall back to /proc to ensure
+ * backward compatibility.
+ */
+ if (nfct_mnl_socket_open() < 0)
+ goto try_proc;
+
+ res = nfct_mnl_dump(NFNL_SUBSYS_CTNETLINK_EXP,
+ IPCTNL_MSG_EXP_GET_STATS_CPU,
+ nfexp_stats_cb, AF_UNSPEC);
+
+ nfct_mnl_socket_close();
+
+ /* don't look at /proc, we got the information via ctnetlink */
+ if (res >= 0)
+ break;
+try_proc:
if (display_proc_conntrack_stats() < 0)
exit_error(OTHER_PROBLEM, "Can't open /proc interface");
break;
@@ -1979,6 +2640,8 @@ int main(int argc, char *argv[])
free_tmpl_objects();
free_options();
+ if (labelmap)
+ nfct_labelmap_destroy(labelmap);
if (command && exit_msg[cmd][0]) {
fprintf(stderr, "%s v%s (conntrack-tools): ",PROGNAME,VERSION);
diff --git a/src/cthelper.c b/src/cthelper.c
new file mode 100644
index 0000000..54eb830
--- /dev/null
+++ b/src/cthelper.c
@@ -0,0 +1,561 @@
+/*
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ */
+
+#include "conntrackd.h"
+#include "log.h"
+#include "fds.h"
+#include "helper.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#define _GNU_SOURCE
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <net/ethernet.h>
+
+#ifndef __aligned_be64
+#define __aligned_be64 unsigned long long __attribute__((aligned(8)))
+#endif
+
+#include <linux/netfilter/nfnetlink_queue.h>
+
+#include <libmnl/libmnl.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <libnetfilter_cthelper/libnetfilter_cthelper.h>
+#include <linux/netfilter.h>
+#include <libnetfilter_queue/pktbuff.h>
+
+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_hdr_put(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)
+{
+ uint8_t protocol;
+
+ 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;
+ protocol = ip->protocol;
+ break;
+ }
+ case ETHERTYPE_IPV6: {
+ struct iphdr *ip = (struct iphdr *) pkt;
+ struct ip6_hdr *ip6 = (struct ip6_hdr *) pkt;
+
+ /* No room for IPv6 header. */
+ if (pktlen < sizeof(struct ip6_hdr)) {
+ dlog(LOG_ERR, "no room for IPv6 header");
+ return -1;
+ }
+
+ /* this is not IPv6, skip. */
+ if (ip->version != 6) {
+ dlog(LOG_ERR, "not IPv6, skipping");
+ return -1;
+ }
+
+ *protoff = sizeof(struct ip6_hdr);
+ protocol = ip6->ip6_nxt;
+ break;
+ }
+ default:
+ /* Unknown layer 3 protocol. */
+ dlog(LOG_ERR, "unknown layer 3 protocol (%d), skipping", proto);
+ return -1;
+ }
+
+ switch (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;
+ }
+
+ 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 pkt_buff *pktb)
+{
+ struct nlmsghdr *nlh;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlattr *nest;
+
+ nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num);
+
+ /* save private data and send it back to kernel-space, if any. */
+ if (myct->priv_data) {
+ nfct_set_attr_l(myct->ct, ATTR_HELPER_INFO, myct->priv_data,
+ cur->helper->priv_data_len);
+ }
+
+ nfq_nlmsg_verdict_put(nlh, id, verdict);
+ if (pktb_mangled(pktb))
+ nfq_nlmsg_verdict_put_pkt(nlh, pktb_data(pktb), pktb_len(pktb));
+
+ 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 (myct->exp) {
+ nest = mnl_attr_nest_start(nlh, NFQA_EXP);
+ if (nest == NULL)
+ return -1;
+
+ nfexp_nlmsg_build(nlh, myct->exp);
+ 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_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num);
+ nfq_nlmsg_verdict_put(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(struct pkt_buff *pktb, 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;
+
+ /* 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);
+ }
+
+ *verdict = cur->helper->cb(pktb, protoff, myct, ctinfo);
+ 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;
+ struct pkt_buff *pktb;
+ 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 err1;
+ }
+ if (!attr[NFQA_CT] || !attr[NFQA_CT_INFO]) {
+ dlog(LOG_ERR, "no CT attached to this packet");
+ goto err1;
+ }
+
+ 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 err1;
+
+ ct = nfct_new();
+ if (ct == NULL)
+ goto err1;
+
+ 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 err2;
+ }
+
+ myct = calloc(1, sizeof(struct myct));
+ if (myct == NULL)
+ goto err2;
+
+ myct->ct = ct;
+ ctinfo = ntohl(mnl_attr_get_u32(attr[NFQA_CT_INFO]));
+
+ /* XXX: 256 bytes enough for possible NAT mangling in helpers? */
+ pktb = pktb_alloc(AF_INET, pkt, pktlen, 256);
+ if (pktb == NULL)
+ goto err3;
+
+ /* Misconfiguration: if no helper found, accept the packet. */
+ helper = helper_run(pktb, protoff, myct, ctinfo, queue_num, &verdict);
+ if (!helper)
+ goto err4;
+
+ if (pkt_verdict_issue(helper, myct, queue_num, id, verdict, pktb) < 0)
+ goto err4;
+
+ nfct_destroy(ct);
+ if (myct->exp != NULL)
+ nfexp_destroy(myct->exp);
+ if (myct->priv_data != NULL)
+ free(myct->priv_data);
+ free(myct);
+
+ return MNL_CB_OK;
+err4:
+ pktb_free(pktb);
+err3:
+ free(myct);
+err2:
+ nfct_destroy(ct);
+err1:
+ /* 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);
+
+ 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 and "
+ "queuelen=%d", cur->helper->name, cur->queue_num,
+ cur->queue_len);
+
+ for (j=0; j<CTD_HELPER_POLICY_MAX; j++) {
+ struct nfct_helper_policy *p;
+
+ if (!cur->helper->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_hdr_put(buf, NFQNL_MSG_CONFIG, cur->queue_num);
+ nfq_nlmsg_cfg_put_cmd(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_hdr_put(buf, NFQNL_MSG_CONFIG, cur->queue_num);
+ nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);
+ mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_CONNTRACK));
+ mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(0xffffffff));
+ if (cur->queue_len > 0) {
+ mnl_attr_put_u32(nlh, NFQA_CFG_QUEUE_MAXLEN,
+ htonl(cur->queue_len));
+ }
+
+ 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_hdr_put(buf, NFQNL_MSG_CONFIG, 0);
+ nfq_nlmsg_cfg_put_cmd(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_hdr_put(buf, NFQNL_MSG_CONFIG, 0);
+ nfq_nlmsg_cfg_put_cmd(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/ctnl.c b/src/ctnl.c
new file mode 100644
index 0000000..10b5f4c
--- /dev/null
+++ b/src/ctnl.c
@@ -0,0 +1,524 @@
+/*
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Part of this code has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ */
+
+#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 <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+
+void ctnl_kill(void)
+{
+ if (!(CONFIG(flags) & CTD_POLL))
+ nfct_close(STATE(event));
+
+ nfct_close(STATE(resync));
+ nfct_close(STATE(get));
+ origin_unregister(STATE(flush));
+ nfct_close(STATE(flush));
+
+ if (STATE(us_filter))
+ ct_filter_destroy(STATE(us_filter));
+ STATE(mode)->kill();
+
+ if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) {
+ nfct_close(STATE(dump));
+ }
+}
+
+static void local_flush_master(void)
+{
+ STATE(stats).nl_kernel_table_flush++;
+ dlog(LOG_NOTICE, "flushing kernel conntrack table");
+
+ /* fork a child process that performs the flush operation,
+ * meanwhile the parent process handles events. */
+ if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL,
+ NULL, NULL) == 0) {
+ nl_flush_conntrack_table_selective();
+ exit(EXIT_SUCCESS);
+ }
+}
+
+static void local_resync_master(void)
+{
+ if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) {
+ STATE(stats).nl_kernel_table_resync++;
+ dlog(LOG_NOTICE, "resync with master conntrack table");
+ nl_dump_conntrack_table(STATE(dump));
+ } else {
+ dlog(LOG_NOTICE, "resync is unsupported in this mode");
+ }
+}
+
+static void local_exp_flush_master(void)
+{
+ if (!(CONFIG(flags) & CTD_EXPECT))
+ return;
+
+ STATE(stats).nl_kernel_table_flush++;
+ dlog(LOG_NOTICE, "flushing kernel expect table");
+
+ /* fork a child process that performs the flush operation,
+ * meanwhile the parent process handles events. */
+ if (fork_process_new(CTD_PROC_FLUSH, CTD_PROC_F_EXCL,
+ NULL, NULL) == 0) {
+ nl_flush_expect_table(STATE(flush));
+ exit(EXIT_SUCCESS);
+ }
+}
+
+static void local_exp_resync_master(void)
+{
+ if (!(CONFIG(flags) & CTD_EXPECT))
+ return;
+
+ if (STATE(mode)->internal->flags & INTERNAL_F_POPULATE) {
+ STATE(stats).nl_kernel_table_resync++;
+ dlog(LOG_NOTICE, "resync with master expect table");
+ nl_dump_expect_table(STATE(dump));
+ } else {
+ dlog(LOG_NOTICE, "resync is unsupported in this mode");
+ }
+}
+
+int ctnl_local(int fd, int type, void *data)
+{
+ int ret = LOCAL_RET_OK;
+
+ switch(type) {
+ case CT_FLUSH_MASTER:
+ local_flush_master();
+ break;
+ case CT_RESYNC_MASTER:
+ local_resync_master();
+ break;
+ case EXP_FLUSH_MASTER:
+ local_exp_flush_master();
+ break;
+ case EXP_RESYNC_MASTER:
+ local_exp_resync_master();
+ break;
+ case ALL_FLUSH_MASTER:
+ local_flush_master();
+ local_exp_flush_master();
+ break;
+ case ALL_RESYNC_MASTER:
+ local_resync_master();
+ local_exp_resync_master();
+ break;
+ }
+
+ ret = STATE(mode)->local(fd, type, data);
+ if (ret == LOCAL_RET_ERROR) {
+ STATE(stats).local_unknown_request++;
+ return LOCAL_RET_ERROR;
+ }
+ return ret;
+}
+
+static void do_overrun_resync_alarm(struct alarm_block *a, void *data)
+{
+ nl_send_resync(STATE(resync));
+ STATE(stats).nl_kernel_table_resync++;
+}
+
+static void do_polling_alarm(struct alarm_block *a, void *data)
+{
+ if (STATE(mode)->internal->ct.purge)
+ STATE(mode)->internal->ct.purge();
+
+ if (STATE(mode)->internal->exp.purge)
+ STATE(mode)->internal->exp.purge();
+
+ nl_send_resync(STATE(resync));
+ if (CONFIG(flags) & CTD_EXPECT)
+ 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_master(master))
+ 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_master(master))
+ 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_master(master))
+ 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/expect.c b/src/expect.c
new file mode 100644
index 0000000..5add7be
--- /dev/null
+++ b/src/expect.c
@@ -0,0 +1,227 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation (or any later at your option).
+ *
+ * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ */
+
+#include "helper.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+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,
+ uint32_t flags)
+{
+ 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)) {
+ 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);
+ memset(addr, 0xff, sizeof(addr));
+ 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)) {
+ 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:
+ memset(addr, 0x00, sizeof(addr));
+ 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];
+
+ 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);
+ memset(addr, 0xff, sizeof(addr));
+ 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);
+ nfexp_set_attr_u32(exp, ATTR_EXP_FLAGS, flags);
+
+ 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_UPDATE);
+}
+
+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;
+ }
+}
+
+void cthelper_get_port_src(struct nf_conntrack *ct, int dir, uint16_t *port)
+{
+ switch (dir) {
+ case MYCT_DIR_ORIG:
+ *port = nfct_get_attr_u16(ct, ATTR_PORT_SRC);
+ break;
+ case MYCT_DIR_REPL:
+ *port = nfct_get_attr_u16(ct, ATTR_REPL_PORT_SRC);
+ break;
+ }
+}
+
+void cthelper_get_port_dst(struct nf_conntrack *ct, int dir, uint16_t *port)
+{
+ switch (dir) {
+ case MYCT_DIR_ORIG:
+ *port = nfct_get_attr_u16(ct, ATTR_PORT_DST);
+ break;
+ case MYCT_DIR_REPL:
+ *port = nfct_get_attr_u16(ct, ATTR_REPL_PORT_DST);
+ break;
+ }
+}
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 <pablo@netfilter.org>
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -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. <http://www.vyatta.com>
*/
#include <stdlib.h>
#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+#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/filter.c b/src/filter.c
index afefbfa..1ae2cc5 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -1,6 +1,7 @@
/*
- * (C) 2006-2008 by Pablo Neira Ayuso <pablo@netfilter.org>
- *
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2011-2012 by Vyatta Inc <http://www.vyatta.com>
+ *
* 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
@@ -32,8 +33,8 @@
struct ct_filter {
int logic[CT_FILTER_MAX];
- u_int32_t l4protomap[IPPROTO_MAX/32];
- u_int16_t statemap[IPPROTO_MAX];
+ uint32_t l4protomap[IPPROTO_MAX/32];
+ uint16_t statemap[IPPROTO_MAX];
struct hashtable *h;
struct hashtable *h6;
struct vector *v;
@@ -372,8 +373,8 @@ static inline int ct_filter_sanity_check(const struct nf_conntrack *ct)
switch(nfct_get_attr_u8(ct, ATTR_L3PROTO)) {
case AF_INET:
- if (!nfct_attr_is_set(ct, ATTR_IPV4_SRC) ||
- !nfct_attr_is_set(ct, ATTR_IPV4_DST)) {
+ if (!nfct_attr_is_set(ct, ATTR_ORIG_IPV4_SRC) ||
+ !nfct_attr_is_set(ct, ATTR_REPL_IPV4_SRC)) {
dlog(LOG_ERR, "missing IPv4 address. "
"You forgot to load "
"nf_conntrack_ipv4?");
@@ -381,8 +382,8 @@ static inline int ct_filter_sanity_check(const struct nf_conntrack *ct)
}
break;
case AF_INET6:
- if (!nfct_attr_is_set(ct, ATTR_IPV6_SRC) ||
- !nfct_attr_is_set(ct, ATTR_IPV6_DST)) {
+ if (!nfct_attr_is_set(ct, ATTR_ORIG_IPV6_SRC) ||
+ !nfct_attr_is_set(ct, ATTR_REPL_IPV6_SRC)) {
dlog(LOG_ERR, "missing IPv6 address. "
"You forgot to load "
"nf_conntrack_ipv6?");
@@ -406,6 +407,51 @@ int ct_filter_conntrack(const struct nf_conntrack *ct, int userspace)
return 0;
}
+static inline int
+ct_filter_master_sanity_check(const struct nf_conntrack *master)
+{
+ if (master == NULL) {
+ dlog(LOG_ERR, "no master tuple in expectation");
+ return 0;
+ }
+
+ if (!nfct_attr_is_set(master, ATTR_L3PROTO)) {
+ dlog(LOG_ERR, "missing layer 3 protocol");
+ return 0;
+ }
+
+ switch (nfct_get_attr_u8(master, ATTR_L3PROTO)) {
+ case AF_INET:
+ if (!nfct_attr_is_set(master, ATTR_IPV4_SRC) ||
+ !nfct_attr_is_set(master, ATTR_IPV4_DST)) {
+ dlog(LOG_ERR, "missing IPv4 address. "
+ "You forgot to load nf_conntrack_ipv4?");
+ return 0;
+ }
+ break;
+ case AF_INET6:
+ if (!nfct_attr_is_set(master, ATTR_IPV6_SRC) ||
+ !nfct_attr_is_set(master, ATTR_IPV6_DST)) {
+ dlog(LOG_ERR, "missing IPv6 address. "
+ "You forgot to load nf_conntrack_ipv6?");
+ return 0;
+ }
+ break;
+ }
+ return 1;
+}
+
+int ct_filter_master(const struct nf_conntrack *master)
+{
+ if (!ct_filter_master_sanity_check(master))
+ return 1;
+
+ /* Check if we've got a master conntrack for this expectation in our
+ * caches. If there is not, we don't want this expectation either.
+ */
+ return STATE(mode)->internal->exp.find(master) ? 0 : 1;
+}
+
struct exp_filter {
struct list_head list;
};
@@ -449,7 +495,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;
}
@@ -472,10 +518,20 @@ int exp_filter_find(struct exp_filter *f, const struct nf_expect *exp)
return 1;
list_for_each_entry(item, &f->list, head) {
- const char *name = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME);
+ const char *name;
+
+ if (nfexp_attr_is_set(exp, ATTR_EXP_HELPER_NAME))
+ name = nfexp_get_attr(exp, ATTR_EXP_HELPER_NAME);
+ else {
+ /* No helper name, this is likely to be a kernel older
+ * which does not include the helper name, just skip
+ * this so we don't crash.
+ */
+ return 0;
+ }
/* 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;
}
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 <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation (or any later at your option).
+ *
+ * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ */
+
+#include "helper.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+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..78ef7aa
--- /dev/null
+++ b/src/helpers/Makefile.am
@@ -0,0 +1,42 @@
+include $(top_srcdir)/Make_global.am
+
+pkglib_LTLIBRARIES = ct_helper_amanda.la \
+ ct_helper_dhcpv6.la \
+ ct_helper_ftp.la \
+ ct_helper_rpc.la \
+ ct_helper_tftp.la \
+ ct_helper_tns.la \
+ ct_helper_sane.la \
+ ct_helper_ssdp.la
+
+ct_helper_amanda_la_SOURCES = amanda.c
+ct_helper_amanda_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)
+ct_helper_amanda_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS)
+
+ct_helper_dhcpv6_la_SOURCES = dhcpv6.c
+ct_helper_dhcpv6_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)
+ct_helper_dhcpv6_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS)
+
+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)
+
+ct_helper_tftp_la_SOURCES = tftp.c
+ct_helper_tftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)
+ct_helper_tftp_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)
+
+ct_helper_sane_la_SOURCES = sane.c
+ct_helper_sane_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)
+ct_helper_sane_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS)
+
+ct_helper_ssdp_la_SOURCES = ssdp.c
+ct_helper_ssdp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)
+ct_helper_ssdp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS)
diff --git a/src/helpers/amanda.c b/src/helpers/amanda.c
new file mode 100644
index 0000000..9e6c4e7
--- /dev/null
+++ b/src/helpers/amanda.c
@@ -0,0 +1,203 @@
+/*
+ * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Adapted from:
+ *
+ * Amanda extension for IP connection tracking
+ *
+ * (C) 2002 by Brian J. Murrell <netfilter@interlinx.bc.ca>
+ * based on HW's ip_conntrack_irc.c as well as other modules
+ * (C) 2006 Patrick McHardy <kaber@trash.net>
+ *
+ * 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 "conntrackd.h"
+#include "helper.h"
+#include "myct.h"
+#include "log.h"
+#include <errno.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_udp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+static int nat_amanda(struct pkt_buff *pkt, uint32_t ctinfo,
+ unsigned int matchoff, unsigned int matchlen,
+ struct nf_expect *exp)
+{
+ char buffer[sizeof("65535")];
+ uint16_t port, initial_port;
+ unsigned int ret;
+ const struct nf_conntrack *expected;
+ struct nf_conntrack *nat_tuple;
+
+ nat_tuple = nfct_new();
+ if (nat_tuple == NULL)
+ return NF_ACCEPT;
+
+ expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED);
+
+ /* Connection comes from client. */
+ initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST);
+ nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, IP_CT_DIR_ORIGINAL);
+
+ /* 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 (ie. same IP: it will be TCP and master is UDP). */
+ 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 res;
+
+ nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port));
+ nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple);
+
+ res = cthelper_add_expect(exp);
+ if (res == 0)
+ break;
+ else if (res != -EBUSY) {
+ port = 0;
+ break;
+ }
+ }
+
+ if (port == 0) {
+ pr_debug("all ports in use\n");
+ return NF_DROP;
+ }
+
+ sprintf(buffer, "%u", port);
+ ret = nfq_udp_mangle_ipv4(pkt, matchoff, matchlen, buffer,
+ strlen(buffer));
+ if (ret != NF_ACCEPT) {
+ pr_debug("cannot mangle packet\n");
+ cthelper_del_expect(exp);
+ }
+ return ret;
+}
+
+static char amanda_buffer[65536];
+static unsigned int master_timeout = 300;
+
+enum amanda_strings {
+ SEARCH_CONNECT,
+ SEARCH_NEWLINE,
+ SEARCH_DATA,
+ SEARCH_MESG,
+ SEARCH_INDEX,
+};
+
+static const char *conns[] = { "DATA ", "MESG ", "INDEX " };
+
+static int
+amanda_helper_cb(struct pkt_buff *pkt, uint32_t protoff,
+ struct myct *myct, uint32_t ctinfo)
+{
+ struct nf_expect *exp;
+ char *data, *data_limit, *tmp;
+ unsigned int dataoff, i;
+ uint16_t port, len;
+ int ret = NF_ACCEPT;
+ struct iphdr *iph;
+ union nfct_attr_grp_addr saddr, daddr;
+
+ /* Only look at packets from the Amanda server */
+ if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL)
+ return NF_ACCEPT;
+
+ /* increase the UDP timeout of the master connection as replies from
+ * Amanda clients to the server can be quite delayed */
+ nfct_set_attr_u32(myct->ct, ATTR_TIMEOUT, master_timeout);
+
+ /* No data? */
+ iph = (struct iphdr *)pktb_network_header(pkt);
+ dataoff = iph->ihl*4 + sizeof(struct udphdr);
+ if (dataoff >= pktb_len(pkt)) {
+ pr_debug("amanda_help: pktlen = %u\n", pktb_len(pkt));
+ return NF_ACCEPT;
+ }
+
+ memcpy(amanda_buffer, pktb_network_header(pkt) + dataoff,
+ pktb_len(pkt) - dataoff);
+ data = amanda_buffer;
+ data_limit = amanda_buffer + pktb_len(pkt) - dataoff;
+ *data_limit = '\0';
+
+ /* Search for the CONNECT string */
+ data = strstr(data, "CONNECT ");
+ if (!data)
+ goto out;
+ data += strlen("CONNECT ");
+
+ /* Only search first line. */
+ if ((tmp = strchr(data, '\n')))
+ *tmp = '\0';
+
+ for (i = 0; i < ARRAY_SIZE(conns); i++) {
+ char *match = strstr(data, conns[i]);
+ if (!match)
+ continue;
+ tmp = data = match + strlen(conns[i]);
+ port = strtoul(data, &data, 10);
+ len = data - tmp;
+ if (port == 0 || len > 5)
+ break;
+
+ exp = nfexp_new();
+ if (exp == NULL)
+ return NF_DROP;
+
+ cthelper_get_addr_src(myct->ct, MYCT_DIR_ORIG, &saddr);
+ cthelper_get_addr_dst(myct->ct, MYCT_DIR_ORIG, &daddr);
+ cthelper_get_port_src(myct->ct, MYCT_DIR_ORIG, &port);
+
+ if (cthelper_expect_init(exp, myct->ct, 0, &saddr, &daddr,
+ IPPROTO_TCP, NULL, &port, 0)) {
+ nfexp_destroy(exp);
+ return NF_DROP;
+ }
+
+ if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) {
+ ret = nat_amanda(pkt, ctinfo, tmp - amanda_buffer,
+ len, exp);
+ } else
+ myct->exp = exp;
+ }
+out:
+ return ret;
+}
+
+static struct ctd_helper amanda_helper = {
+ .name = "amanda",
+ .l4proto = IPPROTO_UDP,
+ .cb = amanda_helper_cb,
+ .policy = {
+ [0] = {
+ .name = "amanda",
+ .expect_max = ARRAY_SIZE(conns),
+ .expect_timeout = 180,
+ },
+ },
+};
+
+void __attribute__ ((constructor)) amanda_init(void);
+
+void amanda_init(void)
+{
+ helper_register(&amanda_helper);
+}
diff --git a/src/helpers/dhcpv6.c b/src/helpers/dhcpv6.c
new file mode 100644
index 0000000..73632ec
--- /dev/null
+++ b/src/helpers/dhcpv6.c
@@ -0,0 +1,123 @@
+/*
+ * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Adapted from:
+ *
+ * DHCPv6 multicast connection tracking helper.
+ *
+ * (c) 2012 Google Inc.
+ *
+ * Original author: Darren Willis <djw@google.com>
+ *
+ * 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 "conntrackd.h"
+#include "helper.h"
+#include "myct.h"
+#include "log.h"
+#include <errno.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_udp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+#define DHCPV6_CLIENT_PORT 546
+
+static uint16_t dhcpv6_port;
+
+/* Timeouts for DHCPv6 replies, in seconds, indexed by message type. */
+static const int dhcpv6_timeouts[] = {
+ 0, /* No message has type 0. */
+ 120, /* Solicit. */
+ 0, /* Advertise. */
+ 30, /* Request. */
+ 4, /* Confirm. */
+ 600, /* Renew. */
+ 600, /* Rebind. */
+ 0, /* Reply. */
+ 1, /* Release. */
+ 1, /* Decline. */
+ 0, /* Reconfigure. */
+ 120, /* Information Request. */
+ 0, /* Relay-forward. */
+ 0 /* Relay-reply. */
+};
+
+static inline int ipv6_addr_is_multicast(const struct in6_addr *addr)
+{
+ return (addr->s6_addr32[0] & htonl(0xFF000000)) == htonl(0xFF000000);
+}
+
+static int
+dhcpv6_helper_cb(struct pkt_buff *pkt, uint32_t protoff,
+ struct myct *myct, uint32_t ctinfo)
+{
+ struct iphdr *iph = (struct iphdr *)pktb_network_header(pkt);
+ struct ip6_hdr *ip6h = (struct ip6_hdr *)pktb_network_header(pkt);
+ int dir = CTINFO2DIR(ctinfo);
+ union nfct_attr_grp_addr addr;
+ struct nf_expect *exp;
+ uint8_t *dhcpv6_msg_type;
+
+ if (iph->version != 6 || !ipv6_addr_is_multicast(&ip6h->ip6_dst))
+ return NF_ACCEPT;
+
+ dhcpv6_msg_type = pktb_network_header(pkt) + protoff + sizeof(struct udphdr);
+ if (*dhcpv6_msg_type > ARRAY_SIZE(dhcpv6_timeouts)) {
+ printf("Dropping DHCPv6 message with bad type %u\n",
+ *dhcpv6_msg_type);
+ return NF_DROP;
+ }
+
+ exp = nfexp_new();
+ if (exp == NULL)
+ return NF_ACCEPT;
+
+ cthelper_get_addr_src(myct->ct, dir, &addr);
+
+ if (cthelper_expect_init(exp, myct->ct, 0, NULL, &addr,
+ IPPROTO_UDP, NULL, &dhcpv6_port,
+ NF_CT_EXPECT_PERMANENT)) {
+ nfexp_destroy(exp);
+ return NF_DROP;
+ }
+
+ myct->exp = exp;
+
+ if (dhcpv6_timeouts[*dhcpv6_msg_type] > 0) {
+ nfct_set_attr_u32(myct->ct, ATTR_TIMEOUT,
+ dhcpv6_timeouts[*dhcpv6_msg_type]);
+ }
+
+ return NF_ACCEPT;
+}
+
+static struct ctd_helper dhcpv6_helper = {
+ .name = "dhcpv6",
+ .l4proto = IPPROTO_UDP,
+ .cb = dhcpv6_helper_cb,
+ .policy = {
+ [0] = {
+ .name = "dhcpv6",
+ .expect_max = 1,
+ .expect_timeout = 300,
+ },
+ },
+};
+
+void __attribute__ ((constructor)) dhcpv6_init(void);
+
+void dhcpv6_init(void)
+{
+ dhcpv6_port = htons(DHCPV6_CLIENT_PORT);
+ helper_register(&dhcpv6_helper);
+}
diff --git a/src/helpers/ftp.c b/src/helpers/ftp.c
new file mode 100644
index 0000000..24ee877
--- /dev/null
+++ b/src/helpers/ftp.c
@@ -0,0 +1,605 @@
+/*
+ * (C) 2010-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Based on: kernel-space FTP extension for connection tracking.
+ *
+ * This port has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ *
+ * Original copyright notice:
+ *
+ * (C) 1999-2001 Paul `Rusty' Russell
+ * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
+ * (C) 2003,2004 USAGI/WIDE Project <http://www.linux-ipv6.org>
+ *
+ * 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 <ctype.h> /* for isdigit */
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <netinet/tcp.h>
+
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+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, uint8_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|%u.%u.%u.%u|%u|",
+ ((unsigned char *)&addr)[0],
+ ((unsigned char *)&addr)[1],
+ ((unsigned char *)&addr)[2],
+ ((unsigned char *)&addr)[3],
+ 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_ipv4(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
+ <lincoln@cesar.org.br> 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, 0)) {
+ 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/helpers/rpc.c b/src/helpers/rpc.c
new file mode 100644
index 0000000..82493c2
--- /dev/null
+++ b/src/helpers/rpc.c
@@ -0,0 +1,488 @@
+/*
+ * (C) 2012 by Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Based on: RPC extension for conntrack.
+ *
+ * This port has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ *
+ * Original copyright notice:
+ *
+ * (C) 2000 by Marcelo Barbosa Lima <marcelo.lima@dcc.unicamp.br>
+ * (C) 2001 by Rusty Russell <rusty@rustcorp.com.au>
+ * (C) 2002,2003 by Ian (Larry) Latter <Ian.Latter@mq.edu.au>
+ * (C) 2004,2005 by David Stes <stes@pandora.be>
+ *
+ * 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 <errno.h>
+
+#include <rpc/rpc_msg.h>
+#include <rpc/pmap_prot.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+/* 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, NF_CT_EXPECT_PERMANENT)) {
+ 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);
+}
diff --git a/src/helpers/sane.c b/src/helpers/sane.c
new file mode 100644
index 0000000..c30f4ba
--- /dev/null
+++ b/src/helpers/sane.c
@@ -0,0 +1,173 @@
+/*
+ * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Port this helper to userspace.
+ */
+
+/* SANE connection tracking helper
+ * (SANE = Scanner Access Now Easy)
+ * For documentation about the SANE network protocol see
+ * http://www.sane-project.org/html/doc015.html
+ */
+
+/*
+ * Copyright (C) 2007 Red Hat, Inc.
+ * Author: Michal Schmidt <mschmidt@redhat.com>
+ * Based on the FTP conntrack helper (net/netfilter/nf_conntrack_ftp.c):
+ * (C) 1999-2001 Paul `Rusty' Russell
+ * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
+ * (C) 2003,2004 USAGI/WIDE Project <http://www.linux-ipv6.org>
+ * (C) 2003 Yasuyuki Kozakai @USAGI <yasuyuki.kozakai@toshiba.co.jp>
+ *
+ * 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 "helper.h"
+#include "myct.h"
+#include "log.h"
+#include <errno.h>
+#include <netinet/ip.h>
+#define _GNU_SOURCE
+#include <netinet/tcp.h>
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+enum sane_state {
+ SANE_STATE_NORMAL,
+ SANE_STATE_START_REQUESTED,
+};
+
+struct sane_request {
+ uint32_t RPC_code;
+#define SANE_NET_START 7 /* RPC code */
+
+ uint32_t handle;
+};
+
+struct sane_reply_net_start {
+ uint32_t status;
+#define SANE_STATUS_SUCCESS 0
+
+ uint16_t zero;
+ uint16_t port;
+ /* other fields aren't interesting for conntrack */
+};
+
+struct nf_ct_sane_master {
+ enum sane_state state;
+};
+
+static int
+sane_helper_cb(struct pkt_buff *pkt, uint32_t protoff,
+ struct myct *myct, uint32_t ctinfo)
+{
+ unsigned int dataoff, datalen;
+ const struct tcphdr *th;
+ void *sb_ptr;
+ int ret = NF_ACCEPT;
+ int dir = CTINFO2DIR(ctinfo);
+ struct nf_ct_sane_master *ct_sane_info = myct->priv_data;
+ struct nf_expect *exp;
+ struct sane_request *req;
+ struct sane_reply_net_start *reply;
+ union nfct_attr_grp_addr saddr;
+ union nfct_attr_grp_addr daddr;
+
+ /* Until there's been traffic both ways, don't look in packets. */
+ if (ctinfo != IP_CT_ESTABLISHED &&
+ ctinfo != IP_CT_ESTABLISHED_REPLY)
+ return NF_ACCEPT;
+
+ th = (struct tcphdr *)(pktb_network_header(pkt) + protoff);
+
+ /* No data? */
+ dataoff = protoff + th->doff * 4;
+ if (dataoff >= pktb_len(pkt))
+ return NF_ACCEPT;
+
+ datalen = pktb_len(pkt) - dataoff;
+
+ sb_ptr = pktb_network_header(pkt) + dataoff;
+
+ if (dir == MYCT_DIR_ORIG) {
+ if (datalen != sizeof(struct sane_request))
+ goto out;
+
+ req = sb_ptr;
+ if (req->RPC_code != htonl(SANE_NET_START)) {
+ /* Not an interesting command */
+ ct_sane_info->state = SANE_STATE_NORMAL;
+ goto out;
+ }
+
+ /* We're interested in the next reply */
+ ct_sane_info->state = SANE_STATE_START_REQUESTED;
+ goto out;
+ }
+
+ /* Is it a reply to an uninteresting command? */
+ if (ct_sane_info->state != SANE_STATE_START_REQUESTED)
+ goto out;
+
+ /* It's a reply to SANE_NET_START. */
+ ct_sane_info->state = SANE_STATE_NORMAL;
+
+ if (datalen < sizeof(struct sane_reply_net_start)) {
+ pr_debug("nf_ct_sane: NET_START reply too short\n");
+ goto out;
+ }
+
+ reply = sb_ptr;
+ if (reply->status != htonl(SANE_STATUS_SUCCESS)) {
+ /* saned refused the command */
+ pr_debug("nf_ct_sane: unsuccessful SANE_STATUS = %u\n",
+ ntohl(reply->status));
+ goto out;
+ }
+
+ /* Invalid saned reply? Ignore it. */
+ if (reply->zero != 0)
+ goto out;
+
+ exp = nfexp_new();
+ if (exp == NULL)
+ return NF_DROP;
+
+ cthelper_get_addr_src(myct->ct, MYCT_DIR_ORIG, &saddr);
+ cthelper_get_addr_dst(myct->ct, MYCT_DIR_ORIG, &daddr);
+
+ if (cthelper_expect_init(exp, myct->ct, 0, &saddr, &daddr,
+ IPPROTO_TCP, NULL, &reply->port, 0)) {
+ nfexp_destroy(exp);
+ return NF_DROP;
+ }
+ myct->exp = exp;
+out:
+ return ret;
+}
+
+static struct ctd_helper sane_helper = {
+ .name = "sane",
+ .l4proto = IPPROTO_TCP,
+ .priv_data_len = sizeof(struct nf_ct_sane_master),
+ .cb = sane_helper_cb,
+ .policy = {
+ [0] = {
+ .name = "sane",
+ .expect_max = 1,
+ .expect_timeout = 5 * 60,
+ },
+ },
+};
+
+static void __attribute__ ((constructor)) sane_init(void)
+{
+ helper_register(&sane_helper);
+}
diff --git a/src/helpers/ssdp.c b/src/helpers/ssdp.c
new file mode 100644
index 0000000..bc41087
--- /dev/null
+++ b/src/helpers/ssdp.c
@@ -0,0 +1,134 @@
+/*
+ * SSDP connection tracking helper
+ * (SSDP = Simple Service Discovery Protocol)
+ * For documentation about SSDP see
+ * http://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol
+ *
+ * Copyright (C) 2014 Ashley Hughes <ashley.hughes@blueyonder.co.uk>
+ * Based on the SSDP conntrack helper (nf_conntrack_ssdp.c),
+ * :http://marc.info/?t=132945775100001&r=1&w=2
+ * (C) 2012 Ian Pilcher <arequipeno@gmail.com>
+ *
+ * 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 "helper.h"
+#include "myct.h"
+#include "log.h"
+#include <errno.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+#define SSDP_MCAST_ADDR "239.255.255.250"
+#define UPNP_MCAST_LL_ADDR "FF02::C" /* link-local */
+#define UPNP_MCAST_SL_ADDR "FF05::C" /* site-local */
+
+#define SSDP_M_SEARCH "M-SEARCH"
+#define SSDP_M_SEARCH_SIZE (sizeof SSDP_M_SEARCH - 1)
+
+static int ssdp_helper_cb(struct pkt_buff *pkt, uint32_t protoff,
+ struct myct *myct, uint32_t ctinfo)
+{
+ int ret = NF_ACCEPT;
+ union nfct_attr_grp_addr daddr, saddr, taddr;
+ struct iphdr *net_hdr = (struct iphdr *)pktb_network_header(pkt);
+ int good_packet = 0;
+ struct nf_expect *exp;
+ uint16_t port;
+ unsigned int dataoff;
+ void *sb_ptr;
+
+ cthelper_get_addr_dst(myct->ct, MYCT_DIR_ORIG, &daddr);
+ switch (nfct_get_attr_u8(myct->ct, ATTR_L3PROTO)) {
+ case AF_INET:
+ inet_pton(AF_INET, SSDP_MCAST_ADDR, &(taddr.ip));
+ if (daddr.ip == taddr.ip)
+ good_packet = 1;
+ break;
+ case AF_INET6:
+ inet_pton(AF_INET6, UPNP_MCAST_LL_ADDR, &(taddr.ip6));
+ if (daddr.ip6[0] == taddr.ip6[0] &&
+ daddr.ip6[1] == taddr.ip6[1] &&
+ daddr.ip6[2] == taddr.ip6[2] &&
+ daddr.ip6[3] == taddr.ip6[3]) {
+ good_packet = 1;
+ break;
+ }
+ inet_pton(AF_INET6, UPNP_MCAST_SL_ADDR, &(taddr.ip6));
+ if (daddr.ip6[0] == taddr.ip6[0] &&
+ daddr.ip6[1] == taddr.ip6[1] &&
+ daddr.ip6[2] == taddr.ip6[2] &&
+ daddr.ip6[3] == taddr.ip6[3]) {
+ good_packet = 1;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (!good_packet) {
+ pr_debug("ssdp_help: destination address not multicast; ignoring\n");
+ return NF_ACCEPT;
+ }
+
+ /* No data? Ignore */
+ dataoff = net_hdr->ihl*4 + sizeof(struct udphdr);
+ if (dataoff >= pktb_len(pkt)) {
+ pr_debug("ssdp_help: UDP payload too small for M-SEARCH; ignoring\n");
+ return NF_ACCEPT;
+ }
+
+ sb_ptr = pktb_network_header(pkt) + dataoff;
+
+ if (memcmp(sb_ptr, SSDP_M_SEARCH, SSDP_M_SEARCH_SIZE) != 0) {
+ pr_debug("ssdp_help: UDP payload does not begin with 'M-SEARCH'; ignoring\n");
+ return NF_ACCEPT;
+ }
+
+ cthelper_get_addr_src(myct->ct, MYCT_DIR_ORIG, &saddr);
+ cthelper_get_port_src(myct->ct, MYCT_DIR_ORIG, &port);
+
+ exp = nfexp_new();
+ if (exp == NULL)
+ return NF_DROP;
+
+ if (cthelper_expect_init(exp, myct->ct, 0, NULL, &saddr,
+ IPPROTO_UDP, NULL, &port,
+ NF_CT_EXPECT_PERMANENT)) {
+ nfexp_destroy(exp);
+ return NF_DROP;
+ }
+ myct->exp = exp;
+
+ return ret;
+}
+
+static struct ctd_helper ssdp_helper = {
+ .name = "ssdp",
+ .l4proto = IPPROTO_UDP,
+ .priv_data_len = 0,
+ .cb = ssdp_helper_cb,
+ .policy = {
+ [0] = {
+ .name = "ssdp",
+ .expect_max = 1,
+ .expect_timeout = 5 * 60,
+ },
+ },
+};
+
+static void __attribute__ ((constructor)) ssdp_init(void)
+{
+ helper_register(&ssdp_helper);
+}
diff --git a/src/helpers/tftp.c b/src/helpers/tftp.c
new file mode 100644
index 0000000..45591c6
--- /dev/null
+++ b/src/helpers/tftp.c
@@ -0,0 +1,138 @@
+/*
+ * (C) 2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Adapted from:
+ *
+ * (C) 2001-2002 Magnus Boden <mb@ozaba.mine.nu>
+ * (C) 2006-2012 Patrick McHardy <kaber@trash.net>
+ * 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 "helper.h"
+#include "myct.h"
+#include "log.h"
+#include <errno.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_udp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+struct tftphdr {
+ uint16_t opcode;
+};
+
+#define TFTP_OPCODE_READ 1
+#define TFTP_OPCODE_WRITE 2
+#define TFTP_OPCODE_DATA 3
+#define TFTP_OPCODE_ACK 4
+#define TFTP_OPCODE_ERROR 5
+
+static unsigned int nat_tftp(struct pkt_buff *pkt, uint32_t ctinfo,
+ struct nf_conntrack *ct, struct nf_expect *exp)
+{
+ struct nf_conntrack *nat_tuple;
+ static uint32_t zero[4] = { 0, 0, 0, 0 };
+
+ nat_tuple = nfct_new();
+ if (nat_tuple == NULL)
+ return NF_ACCEPT;
+
+ switch (nfct_get_attr_u8(ct, ATTR_L3PROTO)) {
+ case AF_INET:
+ 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);
+ break;
+ case AF_INET6:
+ nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET6);
+ nfct_set_attr(nat_tuple, ATTR_IPV6_SRC, &zero);
+ nfct_set_attr(nat_tuple, ATTR_IPV6_DST, &zero);
+ break;
+ }
+ nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_UDP);
+ nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC,
+ nfct_get_attr_u16(ct, ATTR_PORT_SRC));
+ nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0);
+
+ nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, MYCT_DIR_REPL);
+ nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master");
+ nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple);
+
+ return NF_ACCEPT;
+}
+
+static int
+tftp_helper_cb(struct pkt_buff *pkt, uint32_t protoff,
+ struct myct *myct, uint32_t ctinfo)
+{
+ const struct tftphdr *tfh;
+ struct nf_expect *exp;
+ unsigned int ret = NF_ACCEPT;
+ union nfct_attr_grp_addr saddr, daddr;
+ uint16_t dport;
+
+ tfh = (struct tftphdr *)(pktb_network_header(pkt) + protoff + sizeof(struct udphdr));
+
+ switch (ntohs(tfh->opcode)) {
+ case TFTP_OPCODE_READ:
+ case TFTP_OPCODE_WRITE:
+ /* RRQ and WRQ works the same way */
+ exp = nfexp_new();
+ if (exp == NULL) {
+ pr_debug("cannot alloc expectation\n");
+ return NF_DROP;
+ }
+
+ cthelper_get_addr_src(myct->ct, MYCT_DIR_REPL, &saddr);
+ cthelper_get_addr_dst(myct->ct, MYCT_DIR_REPL, &daddr);
+ cthelper_get_port_dst(myct->ct, MYCT_DIR_REPL, &dport);
+
+ if (cthelper_expect_init(exp, myct->ct, 0, &saddr, &daddr,
+ IPPROTO_UDP, NULL, &dport, 0)) {
+ nfexp_destroy(exp);
+ return NF_DROP;
+ }
+
+ if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK)
+ ret = nat_tftp(pkt, ctinfo, myct->ct, exp);
+
+ myct->exp = exp;
+ break;
+ case TFTP_OPCODE_DATA:
+ case TFTP_OPCODE_ACK:
+ pr_debug("Data/ACK opcode\n");
+ break;
+ case TFTP_OPCODE_ERROR:
+ pr_debug("Error opcode\n");
+ break;
+ default:
+ pr_debug("Unknown opcode\n");
+ }
+ return ret;
+}
+
+static struct ctd_helper tftp_helper = {
+ .name = "tftp",
+ .l4proto = IPPROTO_UDP,
+ .cb = tftp_helper_cb,
+ .policy = {
+ [0] = {
+ .name = "tftp",
+ .expect_max = 1,
+ .expect_timeout = 5 * 60,
+ },
+ },
+};
+
+static void __attribute__ ((constructor)) tftp_init(void)
+{
+ helper_register(&tftp_helper);
+}
diff --git a/src/helpers/tns.c b/src/helpers/tns.c
new file mode 100644
index 0000000..2b4fed4
--- /dev/null
+++ b/src/helpers/tns.c
@@ -0,0 +1,408 @@
+/*
+ * (C) 2012 by Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * Sponsored by Vyatta Inc. <http://www.vyatta.com>
+ *
+ * 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 <ctype.h> /* for isdigit */
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <netinet/tcp.h>
+
+#include <libmnl/libmnl.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
+#include <libnetfilter_queue/pktbuff.h>
+#include <linux/netfilter.h>
+
+/* 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, ret;
+ uint32_t array[4];
+ const char *p, *start;
+
+ p = strstr(data, "(");
+ if (!p)
+ return 0;
+
+ p = strstr(p+1, "HOST=");
+ if (!p) {
+ pr_debug("HOST= not found\n");
+ return 0;
+ }
+
+ start = p + strlen("HOST=");
+ offset = (int)(p - data) + strlen("HOST=");
+ *numoff = offset + sizeof(struct tns_header);
+ 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=");
+ ret = get_port(p, dlen - offset - length, ')', cmd);
+ if (ret == 0)
+ return 0;
+
+ p += ret;
+ return (int)(p - start);
+}
+
+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),
+ "%u.%u.%u.%u)(PORT=%u)",
+ ((unsigned char *)&newip.ip)[0],
+ ((unsigned char *)&newip.ip)[1],
+ ((unsigned char *)&newip.ip)[2],
+ ((unsigned char *)&newip.ip)[3], port);
+ if (!buflen)
+ goto out;
+
+ if (!nfq_tcp_mangle_ipv4(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, 0)) {
+ 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 + sizeof(struct tns_header), 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);
+}
diff --git a/src/internal_bypass.c b/src/internal_bypass.c
index 5c83c21..61988c7 100644
--- a/src/internal_bypass.c
+++ b/src/internal_bypass.c
@@ -49,7 +49,7 @@ internal_bypass_ct_dump_cb(enum nf_conntrack_msg_type type,
static void internal_bypass_ct_dump(int fd, int type)
{
struct nfct_handle *h;
- u_int32_t family = AF_UNSPEC;
+ uint32_t family = AF_UNSPEC;
int ret;
h = nfct_open(CONFIG(netlink).subsys_id, 0);
@@ -67,7 +67,7 @@ static void internal_bypass_ct_dump(int fd, int type)
static void internal_bypass_ct_flush(void)
{
- nl_flush_conntrack_table(STATE(flush));
+ nl_flush_conntrack_table_selective();
}
struct {
@@ -180,7 +180,7 @@ internal_bypass_exp_dump_cb(enum nf_conntrack_msg_type type,
static void internal_bypass_exp_dump(int fd, int type)
{
struct nfct_handle *h;
- u_int32_t family = AF_UNSPEC;
+ uint32_t family = AF_UNSPEC;
int ret;
h = nfct_open(CONFIG(netlink).subsys_id, 0);
@@ -283,6 +283,11 @@ static int internal_bypass_exp_event_del(struct nf_expect *exp, int origin)
return 1;
}
+static int internal_bypass_exp_master_find(const struct nf_conntrack *master)
+{
+ return nl_get_conntrack(STATE(get), master) == 0;
+}
+
struct internal_handler internal_bypass = {
.init = internal_bypass_init,
.close = internal_bypass_close,
@@ -309,5 +314,6 @@ struct internal_handler internal_bypass = {
.new = internal_bypass_exp_event_new,
.upd = internal_bypass_exp_event_upd,
.del = internal_bypass_exp_event_del,
+ .find = internal_bypass_exp_master_find,
},
};
diff --git a/src/internal_cache.c b/src/internal_cache.c
index ba2d74b..bad31f3 100644
--- a/src/internal_cache.c
+++ b/src/internal_cache.c
@@ -364,6 +364,16 @@ static int internal_cache_exp_event_del(struct nf_expect *exp, int origin)
return 1;
}
+static int internal_cache_exp_master_find(const struct nf_conntrack *master)
+{
+ struct cache_object *obj;
+ int id;
+
+ obj = cache_find(STATE(mode)->internal->ct.data,
+ (struct nf_conntrack *)master, &id);
+ return obj ? 1 : 0;
+}
+
struct internal_handler internal_cache = {
.flags = INTERNAL_F_POPULATE | INTERNAL_F_RESYNC,
.init = internal_cache_init,
@@ -391,5 +401,6 @@ struct internal_handler internal_cache = {
.new = internal_cache_exp_event_new,
.upd = internal_cache_exp_event_upd,
.del = internal_cache_exp_event_del,
+ .find = internal_cache_exp_master_find,
},
};
diff --git a/src/local.c b/src/local.c
index feff608..3395b4c 100644
--- a/src/local.c
+++ b/src/local.c
@@ -77,7 +77,7 @@ int do_local_server_step(struct local_server *server, void *data,
int rfd;
struct sockaddr_un local;
socklen_t sin_size = sizeof(struct sockaddr_un);
-
+
rfd = accept(server->fd, (struct sockaddr *) &local, &sin_size);
if (rfd == -1)
return -1;
@@ -117,11 +117,10 @@ void local_client_destroy(int fd)
int do_local_client_step(int fd, void (*process)(char *buf))
{
- int numbytes;
char buf[1024];
memset(buf, 0, sizeof(buf));
- while ((numbytes = recv(fd, buf, sizeof(buf)-1, 0)) > 0) {
+ while (recv(fd, buf, sizeof(buf)-1, 0) > 0) {
buf[sizeof(buf)-1] = '\0';
if (process)
process(buf);
@@ -148,11 +147,14 @@ int do_local_request(int request,
ret = send(fd, &request, sizeof(int), 0);
if (ret == -1)
- return -1;
+ goto err1;
do_local_client_step(fd, step);
local_client_destroy(fd);
-
+
return 0;
+err1:
+ local_client_destroy(fd);
+ return -1;
}
diff --git a/src/main.c b/src/main.c
index 0850a29..dafeaee 100644
--- a/src/main.c
+++ b/src/main.c
@@ -19,9 +19,11 @@
#include "conntrackd.h"
#include "log.h"
+#include "helper.h"
#include <sys/types.h>
#include <sys/stat.h>
+#include <errno.h>
#include <fcntl.h>
#include <sys/utsname.h>
#include <string.h>
@@ -31,7 +33,7 @@
#include <limits.h>
struct ct_general_state st;
-union ct_state state;
+struct ct_state state;
static const char usage_daemon_commands[] =
"Daemon mode commands:\n"
@@ -49,6 +51,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"
@@ -110,6 +113,23 @@ set_action_by_table(int i, int argc, char *argv[],
return i;
}
+static void
+set_nice_value(int nv)
+{
+ errno = 0;
+ if (nice(nv) == -1 && errno) /* warn only */
+ fprintf(stderr, "Cannot set nice level %d: %s\n",
+ nv, strerror(errno));
+}
+
+static void
+do_chdir(const char *d)
+{
+ if (chdir(d))
+ fprintf(stderr, "Cannot change current directory to %s: %s\n",
+ d, strerror(errno));
+}
+
int main(int argc, char *argv[])
{
int ret, i, action = -1;
@@ -354,7 +374,7 @@ int main(int argc, char *argv[])
/*
* Setting process priority and scheduler
*/
- nice(CONFIG(nice));
+ set_nice_value(CONFIG(nice));
if (CONFIG(sched).type != SCHED_OTHER) {
struct sched_param schedparam = {
@@ -380,7 +400,7 @@ int main(int argc, char *argv[])
exit(EXIT_FAILURE);
}
- chdir("/");
+ do_chdir("/");
close(STDIN_FILENO);
/* Daemonize conntrackd */
@@ -405,6 +425,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/netlink.c b/src/netlink.c
index fe979e3..189f55a 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -26,7 +26,7 @@
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
-#include <sys/fcntl.h>
+#include <fcntl.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack_tcp.h>
struct nfct_handle *nl_init_event_handler(void)
@@ -146,19 +146,53 @@ void nl_resize_socket_buffer(struct nfct_handle *h)
"to %u bytes", CONFIG(netlink_buffer_size));
}
+static const int family = AF_UNSPEC;
+
int nl_dump_conntrack_table(struct nfct_handle *h)
{
- return nfct_query(h, NFCT_Q_DUMP, &CONFIG(family));
+ return nfct_query(h, NFCT_Q_DUMP, &family);
}
-int nl_flush_conntrack_table(struct nfct_handle *h)
+static int
+nl_flush_selective_cb(enum nf_conntrack_msg_type type,
+ struct nf_conntrack *ct, void *data)
{
- return nfct_query(h, NFCT_Q_FLUSH, &CONFIG(family));
+ /* don't delete this conntrack, it's in the ignore filter */
+ if (ct_filter_conntrack(ct, 1))
+ return NFCT_CB_CONTINUE;
+
+ switch(type) {
+ case NFCT_T_UPDATE:
+ nl_destroy_conntrack(STATE(flush), ct);
+ break;
+ default:
+ STATE(stats).nl_dump_unknown_type++;
+ break;
+ }
+ return NFCT_CB_CONTINUE;
+}
+
+int nl_flush_conntrack_table_selective(void)
+{
+ struct nfct_handle *h;
+ int ret;
+
+ h = nfct_open(CONNTRACK, 0);
+ if (h == NULL) {
+ dlog(LOG_ERR, "cannot open handle");
+ return -1;
+ }
+ nfct_callback_register(h, NFCT_T_ALL, nl_flush_selective_cb, NULL);
+
+ ret = nfct_query(h, NFCT_Q_DUMP, &family);
+
+ nfct_close(h);
+
+ return ret;
}
int nl_send_resync(struct nfct_handle *h)
{
- int family = CONFIG(family);
return nfct_send(h, NFCT_Q_DUMP, &family);
}
@@ -347,16 +381,15 @@ int nl_get_expect(struct nfct_handle *h, const struct nf_expect *exp)
int nl_dump_expect_table(struct nfct_handle *h)
{
- return nfexp_query(h, NFCT_Q_DUMP, &CONFIG(family));
+ return nfexp_query(h, NFCT_Q_DUMP, &family);
}
int nl_flush_expect_table(struct nfct_handle *h)
{
- return nfexp_query(h, NFCT_Q_FLUSH, &CONFIG(family));
+ return nfexp_query(h, NFCT_Q_FLUSH, &family);
}
int nl_send_expect_resync(struct nfct_handle *h)
{
- int family = CONFIG(family);
return nfexp_send(h, NFCT_Q_DUMP, &family);
}
diff --git a/src/nfct-extensions/helper.c b/src/nfct-extensions/helper.c
new file mode 100644
index 0000000..dfc55e7
--- /dev/null
+++ b/src/nfct-extensions/helper.c
@@ -0,0 +1,474 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/netfilter/nfnetlink_cthelper.h>
+#include <libnetfilter_cthelper/libnetfilter_cthelper.h>
+
+#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]);
+}
+
+static int nfct_cmd_helper_list(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_helper_add(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_helper_delete(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_helper_get(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_helper_flush(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_helper_disable(struct mnl_socket *nl, int argc, char *argv[]);
+
+static int
+nfct_helper_parse_params(struct mnl_socket *nl, int argc, char *argv[], int cmd)
+{
+ int ret;
+
+ if (argc < 3) {
+ nfct_cmd_helper_usage(argv);
+ return -1;
+ }
+
+ switch (cmd) {
+ case NFCT_CMD_LIST:
+ case NFCT_CMD_ADD:
+ case NFCT_CMD_DELETE:
+ case NFCT_CMD_GET:
+ case NFCT_CMD_FLUSH:
+ case NFCT_CMD_DISABLE:
+ break;
+ default:
+ 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(nl, argc, argv);
+ break;
+ case NFCT_CMD_ADD:
+ ret = nfct_cmd_helper_add(nl, argc, argv);
+ break;
+ case NFCT_CMD_DELETE:
+ ret = nfct_cmd_helper_delete(nl, argc, argv);
+ break;
+ case NFCT_CMD_GET:
+ ret = nfct_cmd_helper_get(nl, argc, argv);
+ break;
+ case NFCT_CMD_FLUSH:
+ ret = nfct_cmd_helper_flush(nl, argc, argv);
+ break;
+ case NFCT_CMD_DISABLE:
+ ret = nfct_cmd_helper_disable(nl, argc, argv);
+ break;
+ default:
+ nfct_cmd_helper_usage(argv);
+ return -1;
+ }
+
+ 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;
+}
+
+static int nfct_cmd_helper_list(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ unsigned int seq, portid;
+
+ 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, nfct_helper_cb, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int nfct_cmd_helper_add(struct mnl_socket *nl, int argc, char *argv[])
+{
+ 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 j;
+
+ if (argc < 6) {
+ nfct_perror("missing parameters\n"
+ "syntax: nfct add helper 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;
+ }
+
+ helper = helper_find(CONNTRACKD_LIB_DIR, 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; j<CTD_HELPER_POLICY_MAX; j++) {
+ struct nfct_helper_policy *p;
+
+ if (!helper->policy[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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, NULL, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+nfct_cmd_helper_delete(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_helper *t;
+
+ 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, NULL, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int nfct_cmd_helper_get(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_helper *t;
+
+ 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, nfct_helper_cb, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+nfct_cmd_helper_flush(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+
+ 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, NULL, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+nfct_cmd_helper_disable(struct mnl_socket *nl, int argc, char *argv[])
+{
+ 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;
+
+ if (argc < 6) {
+ nfct_perror("missing parameters\n"
+ "syntax: nfct add helper 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;
+ }
+
+ helper = helper_find(CONNTRACKD_LIB_DIR, 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, NULL, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct nfct_extension helper = {
+ .type = NFCT_SUBSYS_HELPER,
+ .parse_params = nfct_helper_parse_params,
+};
+
+static void __init helper_init(void)
+{
+ nfct_extension_register(&helper);
+}
diff --git a/src/nfct-extensions/timeout.c b/src/nfct-extensions/timeout.c
new file mode 100644
index 0000000..1cb04a1
--- /dev/null
+++ b/src/nfct-extensions/timeout.c
@@ -0,0 +1,499 @@
+/*
+ * (C) 2012-2013 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include <libmnl/libmnl.h>
+#include <linux/netfilter/nfnetlink_cttimeout.h>
+#include <libnetfilter_cttimeout/libnetfilter_cttimeout.h>
+
+#include "nfct.h"
+
+static void
+nfct_cmd_timeout_usage(char *argv[])
+{
+ fprintf(stderr, "nfct v%s: Missing command\n"
+ "%s <list|add|delete|get|flush|set> timeout "
+ "[<parameters>, ...]\n", VERSION, argv[0]);
+}
+
+static int nfct_cmd_timeout_list(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_timeout_add(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_timeout_delete(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_timeout_get(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_timeout_flush(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_timeout_default_set(struct mnl_socket *nl, int argc, char *argv[]);
+static int nfct_cmd_timeout_default_get(struct mnl_socket *nl, int argc, char *argv[]);
+
+static int
+nfct_timeout_parse_params(struct mnl_socket *nl, int argc, char *argv[], int cmd)
+{
+ int ret;
+
+ if (argc < 3) {
+ nfct_cmd_timeout_usage(argv);
+ return -1;
+ }
+
+ switch (cmd) {
+ case NFCT_CMD_LIST:
+ case NFCT_CMD_ADD:
+ case NFCT_CMD_DELETE:
+ case NFCT_CMD_GET:
+ case NFCT_CMD_FLUSH:
+ case NFCT_CMD_DEFAULT_SET:
+ case NFCT_CMD_DEFAULT_GET:
+ break;
+ default:
+ nfct_cmd_timeout_usage(argv);
+ return -1;
+ }
+
+ switch (cmd) {
+ case NFCT_CMD_LIST:
+ ret = nfct_cmd_timeout_list(nl, argc, argv);
+ break;
+ case NFCT_CMD_ADD:
+ ret = nfct_cmd_timeout_add(nl, argc, argv);
+ break;
+ case NFCT_CMD_DELETE:
+ ret = nfct_cmd_timeout_delete(nl, argc, argv);
+ break;
+ case NFCT_CMD_GET:
+ ret = nfct_cmd_timeout_get(nl, argc, argv);
+ break;
+ case NFCT_CMD_FLUSH:
+ ret = nfct_cmd_timeout_flush(nl, argc, argv);
+ break;
+ case NFCT_CMD_DEFAULT_SET:
+ ret = nfct_cmd_timeout_default_set(nl, argc, argv);
+ break;
+ case NFCT_CMD_DEFAULT_GET:
+ ret = nfct_cmd_timeout_default_get(nl, argc, argv);
+ break;
+ default:
+ nfct_cmd_timeout_usage(argv);
+ return -1;
+ }
+
+ return ret;
+}
+
+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, NFCT_TIMEOUT_O_DEFAULT, 0);
+ printf("%s\n", buf);
+
+err_free:
+ nfct_timeout_free(t);
+err:
+ return MNL_CB_OK;
+}
+
+static int nfct_cmd_timeout_list(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ unsigned int seq, portid;
+
+ 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, nfct_timeout_cb, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ 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,
+};
+
+static int nfct_cmd_get_l3proto(char *argv[])
+{
+ int l3proto;
+
+ if (strcmp(*argv, "inet") == 0)
+ l3proto = AF_INET;
+ else if (strcmp(*argv, "inet6") == 0)
+ l3proto = AF_INET6;
+ else {
+ nfct_perror("unknown layer 3 protocol");
+ return -1;
+ }
+ return l3proto;
+}
+
+static int nfct_cmd_get_l4proto(char *argv[])
+{
+ int l4proto;
+ struct protoent *pent;
+
+ pent = getprotobyname(*argv);
+ if (!pent) {
+ /* In Debian, /etc/protocols says ipv6-icmp. Support icmpv6
+ * as well not to break backward compatibility.
+ */
+ if (strcmp(*argv, "icmpv6") == 0)
+ l4proto = IPPROTO_ICMPV6;
+ else if (strcmp(*argv, "generic") == 0)
+ l4proto = IPPROTO_RAW;
+ else {
+ nfct_perror("unknown layer 4 protocol");
+ return -1;
+ }
+ } else
+ l4proto = pent->p_proto;
+
+ return l4proto;
+}
+
+static int
+nfct_cmd_timeout_parse(struct nfct_timeout *t, int argc, char *argv[])
+{
+ int l3proto, l4proto;
+ unsigned int j;
+ const char *proto_name;
+
+ l3proto = nfct_cmd_get_l3proto(argv);
+ if (l3proto < 0)
+ return -1;
+
+ nfct_timeout_attr_set_u16(t, NFCT_TIMEOUT_ATTR_L3PROTO, l3proto);
+
+ argc--;
+ argv++;
+ proto_name = *argv;
+
+ l4proto = nfct_cmd_get_l4proto(argv);
+ if (l4proto < 0)
+ return -1;
+
+ nfct_timeout_attr_set_u8(t, NFCT_TIMEOUT_ATTR_L4PROTO, l4proto);
+ argc--;
+ argv++;
+
+ for (; argc>1; argc-=2, argv+=2) {
+ int matching = -1;
+
+ for (j=0; j<nfct_timeout_attr_max[l4proto]; j++) {
+ const char *state_name;
+
+ state_name =
+ nfct_timeout_policy_attr_to_name(l4proto, j);
+ if (state_name == NULL) {
+ nfct_perror("state name is NULL");
+ return -1;
+ }
+ if (strcasecmp(*argv, state_name) != 0)
+ continue;
+
+ matching = j;
+ break;
+ }
+ if (matching != -1) {
+ nfct_timeout_policy_attr_set_u32(t, matching,
+ atoi(*(argv+1)));
+ } else {
+ fprintf(stderr, "nfct v%s: Wrong state name: `%s' "
+ "for protocol `%s'\n",
+ VERSION, *argv, proto_name);
+ return -1;
+ }
+ }
+ if (argc > 0) {
+ nfct_perror("missing value for this timeout");
+ return -1;
+ }
+
+ return 0;
+}
+
+int nfct_cmd_timeout_add(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_timeout *t;
+
+ if (argc < 6) {
+ nfct_perror("missing parameters\n"
+ "syntax: nfct add timeout name family protocol state1 timeout1 ...");
+ 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 (nfct_cmd_timeout_parse(t, argc-4, &argv[4]) < 0)
+ 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, NULL, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+int nfct_cmd_timeout_delete(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_timeout *t;
+
+ 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, NULL, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+int nfct_cmd_timeout_get(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_timeout *t;
+
+ 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, nfct_timeout_cb, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+int nfct_cmd_timeout_flush(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+
+ 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);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, NULL, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+nfct_cmd_timeout_default_set(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_timeout *t;
+
+ if (argc < 6) {
+ nfct_perror("missing parameters\n"
+ "syntax: nfct default-set timeout family protocol state1 timeout1...");
+ return -1;
+ }
+
+ t = nfct_timeout_alloc();
+ if (t == NULL)
+ return -1;
+
+ if (nfct_cmd_timeout_parse(t, argc-3, &argv[3]) < 0)
+ return -1;
+
+ seq = time(NULL);
+ nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_DEFAULT_SET,
+ NLM_F_ACK, seq);
+ nfct_timeout_nlmsg_build_payload(nlh, t);
+ nfct_timeout_free(t);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, nfct_timeout_cb, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+nfct_cmd_timeout_default_get(struct mnl_socket *nl, int argc, char *argv[])
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh;
+ uint32_t portid, seq;
+ struct nfct_timeout *t;
+ int l3proto, l4proto;
+
+ if (argc < 5) {
+ nfct_perror("missing parameters\n"
+ "syntax: nfct default-get timeout family protocol");
+ return -1;
+ }
+
+ t = nfct_timeout_alloc();
+ if (t == NULL)
+ return -1;
+
+ argc-=3;
+ argv+=3;
+
+ l3proto = nfct_cmd_get_l3proto(argv);
+ if (l3proto < 0)
+ return -1;
+
+ nfct_timeout_attr_set_u16(t, NFCT_TIMEOUT_ATTR_L3PROTO, l3proto);
+ argc--;
+ argv++;
+
+ l4proto = nfct_cmd_get_l4proto(argv);
+ if (l4proto < 0)
+ return -1;
+
+ nfct_timeout_attr_set_u8(t, NFCT_TIMEOUT_ATTR_L4PROTO, l4proto);
+
+ seq = time(NULL);
+ nlh = nfct_timeout_nlmsg_build_hdr(buf, IPCTNL_MSG_TIMEOUT_DEFAULT_GET,
+ NLM_F_ACK, seq);
+ nfct_timeout_nlmsg_build_payload(nlh, t);
+ nfct_timeout_free(t);
+
+ portid = mnl_socket_get_portid(nl);
+ if (nfct_mnl_talk(nl, nlh, seq, portid, nfct_timeout_cb, NULL) < 0) {
+ nfct_perror("netlink error");
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct nfct_extension timeout = {
+ .type = NFCT_SUBSYS_TIMEOUT,
+ .parse_params = nfct_timeout_parse_params,
+};
+
+static void __init timeout_init(void)
+{
+ nfct_extension_register(&timeout);
+}
diff --git a/src/nfct.c b/src/nfct.c
new file mode 100644
index 0000000..3331e5b
--- /dev/null
+++ b/src/nfct.c
@@ -0,0 +1,252 @@
+/*
+ * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com>
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <errno.h>
+
+#include <libmnl/libmnl.h>
+
+#include "linux_list.h"
+#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 command subsystem [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));
+ }
+}
+
+static LIST_HEAD(nfct_extension_list);
+
+void nfct_extension_register(struct nfct_extension *ext)
+{
+ list_add(&ext->head, &nfct_extension_list);
+}
+
+static struct nfct_extension *nfct_extension_lookup(int type)
+{
+ struct nfct_extension *ext;
+
+ list_for_each_entry(ext, &nfct_extension_list, head) {
+ if (ext->type == type)
+ return ext;
+ }
+ return NULL;
+}
+
+static const char *nfct_cmd_array[NFCT_CMD_MAX] = {
+ [NFCT_CMD_LIST] = "list",
+ [NFCT_CMD_ADD] = "add",
+ [NFCT_CMD_DELETE] = "delete",
+ [NFCT_CMD_GET] = "get",
+ [NFCT_CMD_FLUSH] = "flush",
+ [NFCT_CMD_DISABLE] = "disable",
+ [NFCT_CMD_DEFAULT_SET] = "default-set",
+ [NFCT_CMD_DEFAULT_GET] = "default-get",
+};
+
+static int nfct_cmd_parse(const char *cmdstr)
+{
+ int i;
+
+ for (i = 1; i < NFCT_CMD_MAX; i++) {
+ if (strncmp(nfct_cmd_array[i], cmdstr, strlen(cmdstr)) == 0)
+ return i;
+ }
+ return -1;
+}
+
+static int nfct_cmd_error(char *argv[])
+{
+ fprintf(stderr, "nfct v%s: Unknown command: %s\n", VERSION, argv[1]);
+ usage(argv);
+
+ return EXIT_FAILURE;
+}
+
+static const char *nfct_subsys_array[NFCT_SUBSYS_MAX] = {
+ [NFCT_SUBSYS_TIMEOUT] = "timeout",
+ [NFCT_SUBSYS_HELPER] = "helper",
+ [NFCT_SUBSYS_VERSION] = "version",
+ [NFCT_SUBSYS_HELP] = "help",
+};
+
+static int nfct_subsys_parse(const char *cmdstr)
+{
+ int i;
+
+ for (i = 1; i < NFCT_SUBSYS_MAX; i++) {
+ if (strncmp(nfct_subsys_array[i], cmdstr, strlen(cmdstr)) == 0)
+ return i;
+ }
+ return -1;
+}
+
+static int nfct_subsys_error(char *argv[])
+{
+ fprintf(stderr, "nfct v%s: Unknown subsystem: %s\n", VERSION, argv[1]);
+ usage(argv);
+
+ return EXIT_FAILURE;
+}
+
+int main(int argc, char *argv[])
+{
+ int subsys, cmd, ret = 0;
+ struct nfct_extension *ext;
+ struct mnl_socket *nl;
+
+ if (argc < 3) {
+ usage(argv);
+ exit(EXIT_FAILURE);
+ }
+
+ cmd = nfct_cmd_parse(argv[1]);
+ if (cmd < 0) {
+ /* Workaround not to break backward compatibility and to get
+ * the syntax in sync with nft. Old nfct versions allow to
+ * specify the subsystem before the command.
+ */
+ subsys = nfct_subsys_parse(argv[1]);
+ if (subsys < 0)
+ return nfct_subsys_error(argv);
+
+ cmd = nfct_cmd_parse(argv[2]);
+ if (cmd < 0)
+ return nfct_cmd_error(argv);
+ } else {
+ subsys = nfct_subsys_parse(argv[2]);
+ if (subsys < 0)
+ return nfct_subsys_error(argv);
+ }
+
+ switch (subsys) {
+ case NFCT_SUBSYS_VERSION:
+ ret = nfct_cmd_version(argc, argv);
+ break;
+ case NFCT_SUBSYS_HELP:
+ ret = nfct_cmd_help(argc, argv);
+ break;
+ default:
+ ext = nfct_extension_lookup(subsys);
+ if (ext == NULL) {
+ fprintf(stderr, "nfct v%s: subsystem %s not supported\n",
+ VERSION, argv[1]);
+ return EXIT_FAILURE;
+ }
+
+ nl = nfct_mnl_open();
+ if (nl == NULL) {
+ nfct_perror("cannot open netlink");
+ return -1;
+ }
+
+ ret = ext->parse_params(nl, argc, argv, cmd);
+ mnl_socket_close(nl);
+ 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 <pablo@netfilter.org>\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"
+ " 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"
+ "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;
+}
+
+int nfct_mnl_talk(struct mnl_socket *nl, struct nlmsghdr *nlh,
+ uint32_t seq, uint32_t portid,
+ int (*cb)(const struct nlmsghdr *nlh, void *data),
+ void *data)
+{
+ int ret;
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+
+ if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0)
+ return -1;
+
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ while (ret > 0) {
+ ret = mnl_cb_run(buf, ret, seq, portid, cb, data);
+ if (ret <= 0)
+ break;
+
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ }
+ if (ret == -1)
+ return -1;
+
+ return 0;
+}
+
+struct mnl_socket *nfct_mnl_open(void)
+{
+ struct mnl_socket *nl;
+
+ nl = mnl_socket_open(NETLINK_NETFILTER);
+ if (nl == NULL)
+ return NULL;
+
+ if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0)
+ return NULL;
+
+ return nl;
+}
diff --git a/src/parse.c b/src/parse.c
index 732bc44..919d36c 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -19,6 +19,7 @@
#include "network.h"
+#include <stdlib.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
#ifndef ssizeof
@@ -28,15 +29,19 @@
static void ct_parse_u8(struct nf_conntrack *ct, int attr, void *data);
static void ct_parse_u16(struct nf_conntrack *ct, int attr, void *data);
static void ct_parse_u32(struct nf_conntrack *ct, int attr, void *data);
-static void ct_parse_str(struct nf_conntrack *ct, int attr, void *data);
+static void ct_parse_str(struct nf_conntrack *ct,
+ const struct netattr *, void *data);
static void ct_parse_group(struct nf_conntrack *ct, int attr, void *data);
static void ct_parse_nat_seq_adj(struct nf_conntrack *ct, int attr, void *data);
+static void ct_parse_clabel(struct nf_conntrack *ct,
+ const struct netattr *, void *data);
struct ct_parser {
void (*parse)(struct nf_conntrack *ct, int attr, void *data);
- int attr;
- int size;
- int max_size;
+ void (*parse2)(struct nf_conntrack *ct, const struct netattr *, void *);
+ uint16_t attr;
+ uint16_t size;
+ uint16_t max_size;
};
static struct ct_parser h[NTA_MAX] = {
@@ -175,10 +180,15 @@ static struct ct_parser h[NTA_MAX] = {
.size = NTA_SIZE(sizeof(uint8_t)),
},
[NTA_HELPER_NAME] = {
- .parse = ct_parse_str,
+ .parse2 = ct_parse_str,
.attr = ATTR_HELPER_NAME,
.max_size = NFCT_HELPER_NAME_MAX,
},
+ [NTA_LABELS] = {
+ .parse2 = ct_parse_clabel,
+ .attr = ATTR_CONNLABELS,
+ .max_size = NTA_SIZE(NTA_LABELS_MAX_SIZE),
+ },
};
static void
@@ -203,9 +213,9 @@ ct_parse_u32(struct nf_conntrack *ct, int attr, void *data)
}
static void
-ct_parse_str(struct nf_conntrack *ct, int attr, void *data)
+ct_parse_str(struct nf_conntrack *ct, const struct netattr *attr, void *data)
{
- nfct_set_attr(ct, h[attr].attr, data);
+ nfct_set_attr(ct, h[attr->nta_attr].attr, data);
}
static void
@@ -215,6 +225,44 @@ ct_parse_group(struct nf_conntrack *ct, int attr, void *data)
}
static void
+ct_parse_clabel(struct nf_conntrack *ct, const struct netattr *attr, void *data)
+{
+ struct nfct_bitmask *bitm;
+ unsigned int i, wordcount;
+ const uint32_t *words;
+ unsigned int len;
+
+ len = attr->nta_len - NTA_LENGTH(0);
+ wordcount = len / sizeof(*words);
+ if (!wordcount)
+ return;
+
+ if (len & (sizeof(*words) - 1))
+ return;
+
+ bitm = nfct_bitmask_new((len * 8) - 1);
+ if (!bitm)
+ return;
+
+ words = data;
+ for (i=0; i < wordcount; i++) {
+ uint32_t word;
+ int bit;
+
+ if (words[i] == 0)
+ continue;
+
+ word = htonl(words[i]);
+ bit = 31;
+ do {
+ if (word & (1 << bit))
+ nfct_bitmask_set_bit(bitm, (32 * i) + bit);
+ } while (--bit >= 0);
+ }
+ nfct_set_attr(ct, ATTR_CONNLABELS, bitm);
+}
+
+static void
ct_parse_nat_seq_adj(struct nf_conntrack *ct, int attr, void *data)
{
struct nta_attr_natseqadj *this = data;
@@ -247,14 +295,22 @@ int msg2ct(struct nf_conntrack *ct, struct nethdr *net, size_t remain)
ATTR_NETWORK2HOST(attr);
if (attr->nta_len > len)
return -1;
- if (attr->nta_attr > NTA_MAX)
+ if (attr->nta_len < NTA_LENGTH(0))
+ return -1;
+ if (attr->nta_attr >= NTA_MAX)
return -1;
if (h[attr->nta_attr].size &&
attr->nta_len != h[attr->nta_attr].size)
return -1;
- if (h[attr->nta_attr].max_size &&
- attr->nta_len > h[attr->nta_attr].max_size)
- return -1;
+
+ if (h[attr->nta_attr].max_size) {
+ if (attr->nta_len > h[attr->nta_attr].max_size)
+ return -1;
+ h[attr->nta_attr].parse2(ct, attr, NTA_DATA(attr));
+ attr = NTA_NEXT(attr, len);
+ continue;
+ }
+
if (h[attr->nta_attr].parse == NULL) {
attr = NTA_NEXT(attr, len);
continue;
@@ -396,7 +452,7 @@ static struct exp_parser {
[NTA_EXP_FN] = {
.parse = exp_parse_str,
.exp_attr = ATTR_EXP_FN,
- .max_size = NFCT_HELPER_NAME_MAX,
+ .max_size = 32, /* XXX: artificial limit */
},
};
@@ -454,7 +510,9 @@ int msg2exp(struct nf_expect *exp, struct nethdr *net, size_t remain)
ATTR_NETWORK2HOST(attr);
if (attr->nta_len > len)
goto err;
- if (attr->nta_attr > NTA_MAX)
+ if (attr->nta_attr >= NTA_EXP_MAX)
+ goto err;
+ if (attr->nta_len < NTA_LENGTH(0))
goto err;
if (exp_h[attr->nta_attr].size &&
attr->nta_len != exp_h[attr->nta_attr].size)
@@ -466,13 +524,15 @@ int msg2exp(struct nf_expect *exp, struct nethdr *net, size_t remain)
attr = NTA_NEXT(attr, len);
continue;
}
- switch(exp_h[attr->nta_attr].exp_attr) {
+ switch (exp_h[attr->nta_attr].exp_attr) {
case ATTR_EXP_MASTER:
exp_h[attr->nta_attr].parse(master, attr->nta_attr,
NTA_DATA(attr));
+ break;
case ATTR_EXP_EXPECTED:
exp_h[attr->nta_attr].parse(expected, attr->nta_attr,
NTA_DATA(attr));
+ break;
case ATTR_EXP_MASK:
exp_h[attr->nta_attr].parse(mask, attr->nta_attr,
NTA_DATA(attr));
diff --git a/src/process.c b/src/process.c
index 9b9734c..3ddad5f 100644
--- a/src/process.c
+++ b/src/process.c
@@ -27,25 +27,19 @@ int fork_process_new(int type, int flags, void (*cb)(void *data), void *data)
struct child_process *c, *this;
int pid;
- /* block SIGCHLD to avoid the access of the list concurrently */
- sigprocmask(SIG_BLOCK, &STATE(block), NULL);
-
/* We only want one process of this type at the same time. This is
* useful if you want to prevent two child processes from accessing
* a shared descriptor at the same time. */
if (flags & CTD_PROC_F_EXCL) {
list_for_each_entry(this, &process_list, head) {
if (this->type == type) {
- sigprocmask(SIG_UNBLOCK, &STATE(block), NULL);
return -1;
}
}
}
c = calloc(sizeof(struct child_process), 1);
- if (c == NULL) {
- sigprocmask(SIG_UNBLOCK, &STATE(block), NULL);
+ if (c == NULL)
return -1;
- }
c->type = type;
c->cb = cb;
@@ -54,8 +48,8 @@ int fork_process_new(int type, int flags, void (*cb)(void *data), void *data)
if (c->pid > 0)
list_add(&c->head, &process_list);
-
- sigprocmask(SIG_UNBLOCK, &STATE(block), NULL);
+ else
+ free(c);
return pid;
}
@@ -89,7 +83,6 @@ void fork_process_dump(int fd)
char buf[4096];
int size = 0;
- sigprocmask(SIG_BLOCK, &STATE(block), NULL);
list_for_each_entry(this, &process_list, head) {
size += snprintf(buf+size, sizeof(buf),
"PID=%u type=%s\n",
@@ -97,7 +90,6 @@ void fork_process_dump(int fd)
this->type < CTD_PROC_MAX ?
process_type_to_name[this->type] : "unknown");
}
- sigprocmask(SIG_UNBLOCK, &STATE(block), NULL);
send(fd, buf, size, 0);
}
diff --git a/src/read_config_lex.l b/src/read_config_lex.l
index 01fe4fc..8350069 100644
--- a/src/read_config_lex.l
+++ b/src/read_config_lex.l
@@ -44,10 +44,10 @@ ip4 {ip4_part}\.{ip4_part}\.{ip4_part}\.{ip4_part}{ip4_cidr}?
hex_255 [0-9a-fA-F]{1,4}
ip6_cidr \/[0-1]*[0-9]*[0-9]+
ip6_part {hex_255}":"?
-ip6_form1 {ip6_part}{0,16}"::"{ip6_part}{0,16}
-ip6_form2 ({hex_255}":"){16}{hex_255}
+ip6_form1 {ip6_part}{0,7}"::"{ip6_part}{0,7}
+ip6_form2 ({hex_255}":"){0,7}{hex_255}
ip6 {ip6_form1}{ip6_cidr}?|{ip6_form2}{ip6_cidr}?
-string [a-zA-Z][a-zA-Z0-9\.\-]*
+string [a-zA-Z][a-zA-Z0-9\.\-\_]*
persistent [P|p][E|e][R|r][S|s][I|i][S|s][T|t][E|e][N|n][T|T]
nack [N|n][A|a][C|c][K|k]
alarm [A|a][L|l][A|a][R|r][M|m]
@@ -142,6 +142,12 @@ 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; }
+"QueueLen" { return T_HELPER_QUEUE_LEN; }
+"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 d94bd85..dd5aeb0 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 <syslog.h>
#include <sched.h>
+#include <dlfcn.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
#include <libnetfilter_conntrack/libnetfilter_conntrack_tcp.h>
@@ -48,6 +51,16 @@ 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_QUEUE_LEN,
+ SYMBOL_HELPER_POLICY_EXPECT_ROOT,
+ SYMBOL_HELPER_EXPECT_POLICY_LEAF,
+};
+
%}
%union {
@@ -74,6 +87,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_QUEUE_LEN T_HELPER_POLICY
+%token T_HELPER_EXPECT_TIMEOUT T_HELPER_EXPECT_MAX
%token <string> T_IP T_PATH_VAL
%token <val> T_NUMBER
@@ -96,6 +111,7 @@ line : ignore_protocol
| general
| sync
| stats
+ | helper
;
logfile_bool : T_LOG T_ON
@@ -1177,10 +1193,7 @@ scheduler_line : T_PRIO T_NUMBER
family : T_FAMILY T_STRING
{
- if (strncmp($2, "IPv6", strlen("IPv6")) == 0)
- conf.family = AF_INET6;
- else
- conf.family = AF_INET;
+ print_err(CTD_CFG_WARN, "`Family' is deprecated, ignoring");
};
event_iterations_limit : T_EVENT_ITER_LIMIT T_NUMBER
@@ -1580,6 +1593,209 @@ 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);
+ }
+
+#ifdef BUILD_CTHELPER
+ helper = helper_find(CONNTRACKD_LIB_DIR, $2, l4proto, RTLD_NOW);
+ if (helper == NULL) {
+ print_err(CTD_CFG_ERROR, "Unknown `%s' helper", $2);
+ exit(EXIT_FAILURE);
+ }
+#else
+ print_err(CTD_CFG_ERROR, "Helper support is disabled, recompile "
+ "conntrackd");
+ exit(EXIT_FAILURE);
+#endif
+
+ 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_QUEUE_LEN: {
+ int *qlen = (int *) &e->data;
+
+ helper_inst->queue_len = *qlen;
+ 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; i<CTD_HELPER_POLICY_MAX; i++) {
+ if (strcmp(helper->policy[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_QUEUE_LEN T_NUMBER
+{
+ int *qlen;
+ struct stack_item *e;
+
+ e = stack_item_alloc(SYMBOL_HELPER_QUEUE_LEN, sizeof(int));
+ qlen = (int *) e->data;
+ *qlen = $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))
@@ -1659,14 +1875,15 @@ 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);
- /* default to IPv4 */
- if (CONFIG(family) == 0)
- CONFIG(family) = AF_INET;
-
/* set to default is not specified */
if (strcmp(CONFIG(lockfile), "") == 0)
strncpy(CONFIG(lockfile), DEFAULT_LOCKFILE, FILENAME_MAXLEN);
diff --git a/src/run.c b/src/run.c
index 159ef30..a9d4862 100644
--- a/src/run.c
+++ b/src/run.c
@@ -1,5 +1,5 @@
/*
- * (C) 2006-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
* (C) 2011 by Vyatta Inc. <http://www.vyatta.com>
*
* 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. <http://www.vyatta.com>
*/
#include "conntrackd.h"
@@ -40,35 +40,30 @@
#include <time.h>
#include <fcntl.h>
-void killer(int foo)
+void killer(int signo)
{
- /* 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));
+ /* Signals are re-entrant, disable signal handling to avoid problems
+ * in case we receive SIGINT and SIGTERM in a row. This function is
+ * also called via -k from the unix socket context, we already disabled
+ * signals in that path, so don't do it.
+ */
+ if (signo)
+ sigprocmask(SIG_BLOCK, &STATE(block), NULL);
- 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();
+#ifdef BUILD_CTHELPER
+ if (CONFIG(flags) & CTD_HELPER)
+ cthelper_kill();
+#endif
+ destroy_fds(STATE(fds));
unlink(CONFIG(lockfile));
dlog(LOG_NOTICE, "---- shutdown received ----");
close_log();
- sigprocmask(SIG_UNBLOCK, &STATE(block), NULL);
-
exit(0);
}
@@ -187,62 +182,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 +192,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,182 +203,20 @@ 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:
- 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;
+ if (CONFIG(flags) & (CTD_SYNC_MODE | CTD_STATS_MODE))
+ return ctnl_local(fd, type, data);
- STATE(get_retval) = 1;
- return NFCT_CB_CONTINUE;
+#ifdef BUILD_CTHELPER
+ if (CONFIG(flags) & CTD_HELPER)
+ return cthelper_local(fd, type, data);
+#endif
+ return ret;
}
-static int exp_get_handler(enum nf_conntrack_msg_type type,
- struct nf_expect *exp, void *data)
+/* order received via UNIX socket */
+static void local_cb(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;
+ do_local_server_step(&STATE(local), NULL, local_handler);
}
int
@@ -464,133 +224,18 @@ 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!");
return -1;
}
- register_fd(STATE(local).fd, 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);
- register_fd(nfct_fd(STATE(resync)), 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)), STATE(fds));
- }
+ register_fd(STATE(local).fd, local_cb, NULL, STATE(fds));
/* Signals handling */
sigemptyset(&STATE(block));
@@ -611,163 +256,20 @@ 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;
+
+#ifdef BUILD_CTHELPER
+ if (CONFIG(flags) & CTD_HELPER) {
+ if (cthelper_init() < 0)
+ return -1;
+ }
+#endif
time(&STATE(stats).daemon_start_time);
dlog(LOG_NOTICE, "initialization completed");
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/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 <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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/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..e69ecfe 100644
--- a/src/sync-mode.c
+++ b/src/sync-mode.c
@@ -1,5 +1,5 @@
/*
- * (C) 2006-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
+ * (C) 2006-2012 by Pablo Neira Ayuso <pablo@netfilter.org>
* (C) 2011 by Vyatta Inc. <http://www.vyatta.com>
*
* 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; k<CONFIG(event_iterations_limit); k++) {
- if (channel_handler_routine(m, i) == -1) {
+ if (channel_handler_routine(c) == -1) {
break;
}
}
@@ -284,7 +285,7 @@ static void interface_candidate(void)
dlog(LOG_ERR, "no dedicated links available!");
}
-static void interface_handler(void)
+static void interface_handler(void *data)
{
int idx = multichannel_get_current_ifindex(STATE_SYNC(channel));
unsigned int flags;
@@ -304,13 +305,60 @@ static void do_reset_cache_alarm(struct alarm_block *a, void *data)
* 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));
+ nl_flush_conntrack_table_selective();
exit(EXIT_SUCCESS);
}
/* this is not required if events don't get lost */
STATE(mode)->internal->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; i<STATE_SYNC(channel)->channel_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; i<STATE_SYNC(channel)->channel_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();
@@ -608,6 +613,12 @@ static int local_handler_sync(int fd, int type, void *data)
}
break;
case CT_FLUSH_CACHE:
+ /* if we're still committing, abort this command */
+ if (STATE_SYNC(commit).clientfd != -1) {
+ dlog(LOG_ERR, "ignoring flush command, "
+ "commit still in progress");
+ break;
+ }
/* inmediate flush, remove pending flush scheduled if any */
del_alarm(&STATE_SYNC(reset_cache_alarm));
dlog(LOG_NOTICE, "flushing caches");
@@ -621,12 +632,15 @@ static int local_handler_sync(int fd, int type, void *data)
STATE(mode)->internal->ct.flush();
break;
case CT_FLUSH_EXT_CACHE:
+ /* if we're still committing, abort this command */
+ if (STATE_SYNC(commit).clientfd != -1) {
+ dlog(LOG_ERR, "ignoring flush command, "
+ "commit still in progress");
+ break;
+ }
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);
@@ -684,9 +698,15 @@ static int local_handler_sync(int fd, int type, void *data)
dlog(LOG_NOTICE, "committing expectation cache");
STATE_SYNC(commit).rq[0].cb = STATE_SYNC(external)->exp.commit;
STATE_SYNC(commit).rq[1].cb = NULL;
- local_commit(fd);
+ ret = local_commit(fd);
break;
case ALL_FLUSH_CACHE:
+ /* if we're still committing, abort this command */
+ if (STATE_SYNC(commit).clientfd != -1) {
+ dlog(LOG_ERR, "ignoring flush command, "
+ "commit still in progress");
+ break;
+ }
dlog(LOG_NOTICE, "flushing caches");
STATE(mode)->internal->ct.flush();
STATE_SYNC(external)->ct.flush();
@@ -704,7 +724,7 @@ static int local_handler_sync(int fd, int type, void *data)
} else {
STATE_SYNC(commit).rq[1].cb = NULL;
}
- local_commit(fd);
+ ret = local_commit(fd);
break;
case EXP_DUMP_INT_XML:
if (fork_process_new(CTD_PROC_ANY, 0, NULL, NULL) == 0) {
@@ -729,7 +749,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/sync-notrack.c b/src/sync-notrack.c
index a7df4e7..c810bbb 100644
--- a/src/sync-notrack.c
+++ b/src/sync-notrack.c
@@ -99,7 +99,7 @@ static int kernel_resync_cb(enum nf_conntrack_msg_type type,
static void kernel_resync(void)
{
struct nfct_handle *h;
- u_int32_t family = AF_UNSPEC;
+ uint32_t family = AF_UNSPEC;
int ret;
h = nfct_open(CONFIG(netlink).subsys_id, 0);
diff --git a/src/tcp.c b/src/tcp.c
index c551c54..c8f2544 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;
}
@@ -270,13 +247,11 @@ int tcp_accept(struct tcp_sock *m)
/* the other peer wants to connect ... */
ret = accept(m->fd, NULL, NULL);
if (ret == -1) {
- if (errno != EAGAIN) {
- /* unexpected error. Give us another try. */
- m->state = TCP_SERVER_ACCEPTING;
- } else {
- /* waiting for new connections. */
- m->state = TCP_SERVER_ACCEPTING;
- }
+ /* unexpected error: Give us another try. Or we have hit
+ * -EAGAIN, in that case we remain in the accepting connections
+ * state.
+ */
+ m->state = TCP_SERVER_ACCEPTING;
} else {
/* the peer finally got connected. */
if (fcntl(ret, F_SETFL, O_NONBLOCK) == -1) {
@@ -287,7 +262,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;
}
@@ -367,7 +341,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 +350,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) {
diff --git a/src/udp.c b/src/udp.c
index ecaa46e..d0a7f5b 100644
--- a/src/udp.c
+++ b/src/udp.c
@@ -136,14 +136,18 @@ struct udp_sock *udp_client_create(struct udp_conf *conf)
m->addr.ipv4.sin_family = AF_INET;
m->addr.ipv4.sin_port = htons(conf->port);
m->addr.ipv4.sin_addr = conf->client.inet_addr;
- m->sockaddr_len = sizeof(struct sockaddr_in);
+ m->sockaddr_len = sizeof(struct sockaddr_in);
break;
case AF_INET6:
m->addr.ipv6.sin6_family = AF_INET6;
m->addr.ipv6.sin6_port = htons(conf->port);
memcpy(&m->addr.ipv6.sin6_addr, &conf->client.inet_addr6,
sizeof(struct in6_addr));
- m->sockaddr_len = sizeof(struct sockaddr_in6);
+ m->sockaddr_len = sizeof(struct sockaddr_in6);
+ /* Bind the sender side to the same interface that we use to
+ * receive sync messages.
+ */
+ m->addr.ipv6.sin6_scope_id = conf->server.ipv6.scope_id;
break;
default:
ret = -1;
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 <kuznet@ms2.inr.ac.ru>
+ *
+ * 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 <stdint.h>
+#include <ctype.h>
+#include <string.h> /* 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;
+}
diff --git a/tests/conntrack/run-test.sh b/tests/conntrack/run-test.sh
new file mode 100644
index 0000000..1403e2c
--- /dev/null
+++ b/tests/conntrack/run-test.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+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
diff --git a/qa/test-conntrack.c b/tests/conntrack/test-conntrack.c
index c9097b6..c9097b6 100644
--- a/qa/test-conntrack.c
+++ b/tests/conntrack/test-conntrack.c
diff --git a/qa/testsuite/00create b/tests/conntrack/testsuite/00create
index 40e2c19..40e2c19 100644
--- a/qa/testsuite/00create
+++ b/tests/conntrack/testsuite/00create
diff --git a/qa/testsuite/01delete b/tests/conntrack/testsuite/01delete
index 3c38ac5..3c38ac5 100644
--- a/qa/testsuite/01delete
+++ b/tests/conntrack/testsuite/01delete
diff --git a/qa/testsuite/02filter b/tests/conntrack/testsuite/02filter
index 204c4e8..204c4e8 100644
--- a/qa/testsuite/02filter
+++ b/tests/conntrack/testsuite/02filter
diff --git a/qa/testsuite/03nat b/tests/conntrack/testsuite/03nat
index f94e8ff..f94e8ff 100644
--- a/qa/testsuite/03nat
+++ b/tests/conntrack/testsuite/03nat
diff --git a/tests/conntrack/testsuite/04zone b/tests/conntrack/testsuite/04zone
new file mode 100644
index 0000000..dc8b691
--- /dev/null
+++ b/tests/conntrack/testsuite/04zone
@@ -0,0 +1,24 @@
+# 1) zone, 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
+# 2) orig-zone, 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 --orig-zone 2; OK
+# display dummy
+-L --orig-zone 2; OK
+# display dummy
+-L --orig-zone 0; OK
+# delete dummy
+-D --orig-zone 2; OK
+# 3) reply-zone, 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 --reply-zone 3; OK
+# display dummy
+-L --reply-zone 3; OK
+# display dummy
+-L --reply-zone 0; OK
+# delete dummy
+-D --reply-zone 3; OK
diff --git a/qa/testsuite/05mark b/tests/conntrack/testsuite/05mark
index 4d99dea..4d99dea 100644
--- a/qa/testsuite/05mark
+++ b/tests/conntrack/testsuite/05mark
diff --git a/qa/testsuite/06update b/tests/conntrack/testsuite/06update
index 0408303..0408303 100644
--- a/qa/testsuite/06update
+++ b/tests/conntrack/testsuite/06update
diff --git a/tests/nfct/run-test.sh b/tests/nfct/run-test.sh
new file mode 100644
index 0000000..851ee75
--- /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..2257087
--- /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 flush timeout
+
+# flush the conntrack table
+conntrack -F
+
+#
+# No.1: test generic timeout policy
+#
+
+echo "---- test no. 1 ----"
+
+conntrack -E -p 13 &
+
+nfct add timeout 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 del timeout test-generic
+
+#
+# No.2: test TCP timeout policy
+#
+
+echo "---- test no. 2 ----"
+
+conntrack -E -p tcp &
+
+nfct add timeout 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 del timeout 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 add timeout 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 del timeout 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 <pablo@netfilter.org>
+ *
+ * Extremely simple test utility for the command line tools.
+ *
+ * Based on test-conntrack.c
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dirent.h>
+
+#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..ab2e6fc
--- /dev/null
+++ b/tests/nfct/timeout/00tcp
@@ -0,0 +1,16 @@
+# add policy object `test'
+nfct add timeout test inet tcp established 100 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
+# get unexistent policy object `dummy'
+nfct get timeout test ; BAD
+# delete policy object `test', however, it does not exists anymore
+nfct delete timeout test ; BAD
+# add policy object `test'
+nfct add timeout 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 get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
diff --git a/tests/nfct/timeout/01udp b/tests/nfct/timeout/01udp
new file mode 100644
index 0000000..f8097d6
--- /dev/null
+++ b/tests/nfct/timeout/01udp
@@ -0,0 +1,16 @@
+# add policy object `test'
+nfct add timeout test inet udp unreplied 10 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
+# get unexistent policy object `dummy'
+nfct get timeout test ; BAD
+# delete policy object `test', however, it does not exists anymore
+nfct delete timeout test ; BAD
+# add policy object `test'
+nfct add timeout test inet udp unreplied 1 replied 2 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
diff --git a/tests/nfct/timeout/02generic b/tests/nfct/timeout/02generic
new file mode 100644
index 0000000..ffba138
--- /dev/null
+++ b/tests/nfct/timeout/02generic
@@ -0,0 +1,16 @@
+# add policy object `test'
+nfct add timeout test inet generic timeout 10 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
+# get unexistent policy object `dummy'
+nfct get timeout test ; BAD
+# delete policy object `test', however, it does not exists anymore
+nfct delete timeout test ; BAD
+# add policy object `test'
+nfct add timeout test inet generic timeout 1 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
diff --git a/tests/nfct/timeout/03udplite b/tests/nfct/timeout/03udplite
new file mode 100644
index 0000000..8ed3459
--- /dev/null
+++ b/tests/nfct/timeout/03udplite
@@ -0,0 +1,16 @@
+# add policy object `test'
+nfct add timeout test inet udplite unreplied 10 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
+# get unexistent policy object `dummy'
+nfct get timeout test ; BAD
+# delete policy object `test', however, it does not exists anymore
+nfct delete timeout test ; BAD
+# add policy object `test'
+nfct add timeout test inet udplite unreplied 1 replied 2 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
diff --git a/tests/nfct/timeout/04icmp b/tests/nfct/timeout/04icmp
new file mode 100644
index 0000000..edb1c99
--- /dev/null
+++ b/tests/nfct/timeout/04icmp
@@ -0,0 +1,16 @@
+# add policy object `test'
+nfct add timeout test inet icmp timeout 10 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
+# get unexistent policy object `dummy'
+nfct get timeout test ; BAD
+# delete policy object `test', however, it does not exists anymore
+nfct delete timeout test ; BAD
+# add policy object `test'
+nfct add timeout test inet icmp timeout 1 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
diff --git a/tests/nfct/timeout/05icmpv6 b/tests/nfct/timeout/05icmpv6
new file mode 100644
index 0000000..40ccc49
--- /dev/null
+++ b/tests/nfct/timeout/05icmpv6
@@ -0,0 +1,16 @@
+# add policy object `test'
+nfct add timeout test inet6 icmpv6 timeout 10 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
+# get unexistent policy object `dummy'
+nfct get timeout test ; BAD
+# delete policy object `test', however, it does not exists anymore
+nfct delete timeout test ; BAD
+# add policy object `test'
+nfct add timeout test inet6 icmpv6 timeout 1 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
diff --git a/tests/nfct/timeout/06sctp b/tests/nfct/timeout/06sctp
new file mode 100644
index 0000000..62b44c6
--- /dev/null
+++ b/tests/nfct/timeout/06sctp
@@ -0,0 +1,16 @@
+# add policy object `test'
+nfct add timeout test inet sctp established 100 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
+# get unexistent policy object `dummy'
+nfct get timeout test ; BAD
+# delete policy object `test', however, it does not exists anymore
+nfct delete timeout test ; BAD
+# add policy object `test'
+nfct add timeout 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 get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
diff --git a/tests/nfct/timeout/07dccp b/tests/nfct/timeout/07dccp
new file mode 100644
index 0000000..1d88585
--- /dev/null
+++ b/tests/nfct/timeout/07dccp
@@ -0,0 +1,16 @@
+# add policy object `test'
+nfct add timeout test inet dccp request 100 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
+# get unexistent policy object `dummy'
+nfct get timeout test ; BAD
+# delete policy object `test', however, it does not exists anymore
+nfct delete timeout test ; BAD
+# add policy object `test'
+nfct add timeout test inet dccp request 1 respond 2 partopen 3 open 4 closereq 5 closing 6 timewait 7 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
diff --git a/tests/nfct/timeout/08gre b/tests/nfct/timeout/08gre
new file mode 100644
index 0000000..709b943
--- /dev/null
+++ b/tests/nfct/timeout/08gre
@@ -0,0 +1,16 @@
+# add policy object `test'
+nfct add timeout test inet gre unreplied 10 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK
+# get unexistent policy object `dummy'
+nfct get timeout test ; BAD
+# delete policy object `test', however, it does not exists anymore
+nfct delete timeout test ; BAD
+# add policy object `test'
+nfct add timeout test inet gre unreplied 1 replied 2 ; OK
+# get policy object `test'
+nfct get timeout test ; OK
+# delete policy object `test'
+nfct delete timeout test ; OK