diff options
author | Alex Harpin <development@landsofshadow.co.uk> | 2015-10-02 07:43:42 +0100 |
---|---|---|
committer | Alex Harpin <development@landsofshadow.co.uk> | 2015-10-02 07:43:42 +0100 |
commit | ef5ae91676c8ada2a12ea72f889a54452dd94981 (patch) | |
tree | 0a962905ab9d0c2322f627928521a10c4cb5e20f | |
parent | 9f9a63cecdc6ac4f449d3eacda6c591f0de9fbf3 (diff) | |
parent | 8845f3db20c951fcf1db3229a818cfd185f17f2e (diff) | |
download | conntrack-tools-ef5ae91676c8ada2a12ea72f889a54452dd94981.tar.gz conntrack-tools-ef5ae91676c8ada2a12ea72f889a54452dd94981.zip |
Merge remote-tracking branch 'source/master' into upstreamupstream
113 files changed, 8390 insertions, 1008 deletions
@@ -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> - >= 1.0.0. This document will evolve in the future to cover new features + >= 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 >= 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 >= 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; @@ -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; + } +} @@ -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; } @@ -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); @@ -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); @@ -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) { @@ -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 |