summaryrefslogtreecommitdiff
path: root/src/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'src/helpers')
-rw-r--r--src/helpers/Makefile.am17
-rw-r--r--src/helpers/ftp.c599
-rw-r--r--src/helpers/rpc.c488
-rw-r--r--src/helpers/tns.c396
4 files changed, 1500 insertions, 0 deletions
diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am
new file mode 100644
index 0000000..589b4f3
--- /dev/null
+++ b/src/helpers/Makefile.am
@@ -0,0 +1,17 @@
+include $(top_srcdir)/Make_global.am
+
+pkglib_LTLIBRARIES = ct_helper_ftp.la \
+ ct_helper_rpc.la \
+ ct_helper_tns.la
+
+ct_helper_ftp_la_SOURCES = ftp.c
+ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)
+ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS)
+
+ct_helper_rpc_la_SOURCES = rpc.c
+ct_helper_rpc_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)
+ct_helper_rpc_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS)
+
+ct_helper_tns_la_SOURCES = tns.c
+ct_helper_tns_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)
+ct_helper_tns_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS)
diff --git a/src/helpers/ftp.c b/src/helpers/ftp.c
new file mode 100644
index 0000000..c6ad7da
--- /dev/null
+++ b/src/helpers/ftp.c
@@ -0,0 +1,599 @@
+/*
+ * (C) 2010-2012 by Pablo Neira Ayuso <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>
+
+#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, u_int8_t term)
+{
+ const char *end;
+ int ret = in6_pton(src, min_t(size_t, dlen, 0xffff),
+ (uint8_t *)dst, term, &end);
+ if (ret > 0)
+ return (int)(end - src);
+ return 0;
+}
+
+static int try_number(const char *data, size_t dlen, uint32_t array[],
+ int array_size, char sep, char term)
+{
+ uint32_t len;
+ int i;
+
+ memset(array, 0, sizeof(array[0])*array_size);
+
+ /* Keep data pointing at next char. */
+ for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) {
+ if (*data >= '0' && *data <= '9') {
+ array[i] = array[i]*10 + *data - '0';
+ }
+ else if (*data == sep)
+ i++;
+ else {
+ /* Unexpected character; true if it's the
+ terminator and we're finished. */
+ if (*data == term && i == array_size - 1)
+ return len;
+ pr_debug("Char %u (got %u nums) `%u' unexpected\n",
+ len, i, *data);
+ return 0;
+ }
+ }
+ pr_debug("Failed to fill %u numbers separated by %c\n",
+ array_size, sep);
+ return 0;
+}
+
+/* Grab port: number up to delimiter */
+static int get_port(const char *data, int start, size_t dlen, char delim,
+ struct myct_man *cmd)
+{
+ uint16_t tmp_port = 0;
+ uint32_t i;
+
+ for (i = start; i < dlen; i++) {
+ /* Finished? */
+ if (data[i] == delim) {
+ if (tmp_port == 0)
+ break;
+ cmd->u.port = htons(tmp_port);
+ pr_debug("get_port: return %d\n", tmp_port);
+ return i + 1;
+ }
+ else if (data[i] >= '0' && data[i] <= '9')
+ tmp_port = tmp_port*10 + data[i] - '0';
+ else { /* Some other crap */
+ pr_debug("get_port: invalid char.\n");
+ break;
+ }
+ }
+ return 0;
+}
+
+/* Returns 0, or length of numbers: 192,168,1,1,5,6 */
+static int try_rfc959(const char *data, size_t dlen, struct myct_man *cmd,
+ uint16_t l3protonum, char term)
+{
+ int length;
+ uint32_t array[6];
+
+ length = try_number(data, dlen, array, 6, ',', term);
+ if (length == 0)
+ return 0;
+
+ cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) |
+ (array[2] << 8) | array[3]);
+ cmd->u.port = htons((array[4] << 8) | array[5]);
+ return length;
+}
+
+/* Returns 0, or length of numbers: |1|132.235.1.2|6275| or |2|3ffe::1|6275| */
+static int try_eprt(const char *data, size_t dlen,
+ struct myct_man *cmd, uint16_t l3protonum, char term)
+{
+ char delim;
+ int length;
+
+ /* First character is delimiter, then "1" for IPv4 or "2" for IPv6,
+ then delimiter again. */
+ if (dlen <= 3) {
+ pr_debug("EPRT: too short\n");
+ return 0;
+ }
+ delim = data[0];
+ if (isdigit(delim) || delim < 33 || delim > 126 || data[2] != delim) {
+ pr_debug("try_eprt: invalid delimitter.\n");
+ return 0;
+ }
+
+ if ((l3protonum == PF_INET && data[1] != '1') ||
+ (l3protonum == PF_INET6 && data[1] != '2')) {
+ pr_debug("EPRT: invalid protocol number.\n");
+ return 0;
+ }
+
+ pr_debug("EPRT: Got %c%c%c\n", delim, data[1], delim);
+ if (data[1] == '1') {
+ uint32_t array[4];
+
+ /* Now we have IP address. */
+ length = try_number(data + 3, dlen - 3, array, 4, '.', delim);
+ if (length != 0)
+ cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16)
+ | (array[2] << 8) | array[3]);
+ } else {
+ /* Now we have IPv6 address. */
+ length = get_ipv6_addr(data + 3, dlen - 3,
+ (struct in6_addr *)cmd->u3.ip6, delim);
+ }
+
+ if (length == 0)
+ return 0;
+ pr_debug("EPRT: Got IP address!\n");
+ /* Start offset includes initial "|1|", and trailing delimiter */
+ return get_port(data, 3 + length + 1, dlen, delim, cmd);
+}
+
+/* Returns 0, or length of numbers: |||6446| */
+static int try_epsv_response(const char *data, size_t dlen,
+ struct myct_man *cmd,
+ uint16_t l3protonum, char term)
+{
+ char delim;
+
+ /* Three delimiters. */
+ if (dlen <= 3) return 0;
+ delim = data[0];
+ if (isdigit(delim) || delim < 33 || delim > 126 ||
+ data[1] != delim || data[2] != delim)
+ return 0;
+
+ return get_port(data, 3, dlen, delim, cmd);
+}
+
+static struct ftp_search {
+ const char *pattern;
+ size_t plen;
+ char skip;
+ char term;
+ enum nf_ct_ftp_type ftptype;
+ int (*getnum)(const char *, size_t, struct myct_man *, uint16_t, char);
+} search[MYCT_DIR_MAX][2] = {
+ [MYCT_DIR_ORIG] = {
+ {
+ .pattern = "PORT",
+ .plen = sizeof("PORT") - 1,
+ .skip = ' ',
+ .term = '\r',
+ .ftptype = NF_CT_FTP_PORT,
+ .getnum = try_rfc959,
+ },
+ {
+ .pattern = "EPRT",
+ .plen = sizeof("EPRT") - 1,
+ .skip = ' ',
+ .term = '\r',
+ .ftptype = NF_CT_FTP_EPRT,
+ .getnum = try_eprt,
+ },
+ },
+ [MYCT_DIR_REPL] = {
+ {
+ .pattern = "227 ",
+ .plen = sizeof("227 ") - 1,
+ .skip = '(',
+ .term = ')',
+ .ftptype = NF_CT_FTP_PASV,
+ .getnum = try_rfc959,
+ },
+ {
+ .pattern = "229 ",
+ .plen = sizeof("229 ") - 1,
+ .skip = '(',
+ .term = ')',
+ .ftptype = NF_CT_FTP_EPSV,
+ .getnum = try_epsv_response,
+ },
+ },
+};
+
+static int ftp_find_pattern(struct pkt_buff *pkt,
+ unsigned int dataoff, unsigned int dlen,
+ const char *pattern, size_t plen,
+ char skip, char term,
+ unsigned int *matchoff, unsigned int *matchlen,
+ struct myct_man *cmd,
+ int (*getnum)(const char *, size_t,
+ struct myct_man *cmd,
+ uint16_t, char),
+ int dir)
+{
+ char *data = (char *)pktb_network_header(pkt) + dataoff;
+ int numlen;
+ uint32_t i;
+
+ if (dlen == 0)
+ return 0;
+
+ /* short packet, skip partial matching. */
+ if (dlen <= plen)
+ return 0;
+
+ if (strncmp(data, pattern, plen) != 0)
+ return 0;
+
+ pr_debug("Pattern matches!\n");
+
+ /* Now we've found the constant string, try to skip
+ to the 'skip' character */
+ for (i = plen; data[i] != skip; i++)
+ if (i == dlen - 1) return 0;
+
+ /* Skip over the last character */
+ i++;
+
+ pr_debug("Skipped up to `%c'!\n", skip);
+
+ numlen = getnum(data + i, dlen - i, cmd, PF_INET, term);
+ if (!numlen)
+ return 0;
+
+ pr_debug("Match succeded!\n");
+ return 1;
+}
+
+/* Look up to see if we're just after a \n. */
+static int find_nl_seq(uint32_t seq, struct ftp_info *info, int dir)
+{
+ int i;
+
+ for (i = 0; i < info->seq_aft_nl_num[dir]; i++)
+ if (info->seq_aft_nl[dir][i] == seq)
+ return 1;
+ return 0;
+}
+
+/* We don't update if it's older than what we have. */
+static void update_nl_seq(uint32_t nl_seq, struct ftp_info *info, int dir)
+{
+ int i, oldest;
+
+ /* Look for oldest: if we find exact match, we're done. */
+ for (i = 0; i < info->seq_aft_nl_num[dir]; i++) {
+ if (info->seq_aft_nl[dir][i] == nl_seq)
+ return;
+ }
+
+ if (info->seq_aft_nl_num[dir] < NUM_SEQ_TO_REMEMBER) {
+ info->seq_aft_nl[dir][info->seq_aft_nl_num[dir]++] = nl_seq;
+ } else {
+ if (before(info->seq_aft_nl[dir][0], info->seq_aft_nl[dir][1]))
+ oldest = 0;
+ else
+ oldest = 1;
+
+ if (after(nl_seq, info->seq_aft_nl[dir][oldest]))
+ info->seq_aft_nl[dir][oldest] = nl_seq;
+ }
+}
+
+static int nf_nat_ftp_fmt_cmd(enum nf_ct_ftp_type type,
+ char *buffer, size_t buflen,
+ uint32_t addr, uint16_t port)
+{
+ switch (type) {
+ case NF_CT_FTP_PORT:
+ case NF_CT_FTP_PASV:
+ return snprintf(buffer, buflen, "%u,%u,%u,%u,%u,%u",
+ ((unsigned char *)&addr)[0],
+ ((unsigned char *)&addr)[1],
+ ((unsigned char *)&addr)[2],
+ ((unsigned char *)&addr)[3],
+ port >> 8,
+ port & 0xFF);
+ case NF_CT_FTP_EPRT:
+ return snprintf(buffer, buflen, "|1|%pI4|%u|", &addr, port);
+ case NF_CT_FTP_EPSV:
+ return snprintf(buffer, buflen, "|||%u|", port);
+ }
+
+ return 0;
+}
+
+/* So, this packet has hit the connection tracking matching code.
+ Mangle it, and change the expectation to match the new version. */
+static unsigned int nf_nat_ftp(struct pkt_buff *pkt,
+ int dir,
+ int ctinfo,
+ enum nf_ct_ftp_type type,
+ unsigned int matchoff,
+ unsigned int matchlen,
+ struct nf_conntrack *ct,
+ struct nf_expect *exp)
+{
+ union nfct_attr_grp_addr newip;
+ uint16_t port;
+ char buffer[sizeof("|1|255.255.255.255|65535|")];
+ unsigned int buflen;
+ const struct nf_conntrack *expected;
+ struct nf_conntrack *nat_tuple;
+ uint16_t initial_port;
+
+ pr_debug("FTP_NAT: type %i, off %u len %u\n", type, matchoff, matchlen);
+
+ /* Connection will come from wherever this packet goes, hence !dir */
+ cthelper_get_addr_dst(ct, !dir, &newip);
+
+ expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED);
+
+ nat_tuple = nfct_new();
+ if (nat_tuple == NULL)
+ return NF_ACCEPT;
+
+ initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST);
+
+ nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir);
+
+ /* libnetfilter_conntrack needs this */
+ nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET);
+ nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0);
+ nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0);
+ nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP);
+ nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0);
+
+ /* When you see the packet, we need to NAT it the same as the
+ * this one. */
+ nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master");
+
+ /* Try to get same port: if not, try to change it. */
+ for (port = ntohs(initial_port); port != 0; port++) {
+ int ret;
+
+ nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port));
+ nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple);
+
+ ret = cthelper_add_expect(exp);
+ if (ret == 0)
+ break;
+ else if (ret != -EBUSY) {
+ port = 0;
+ break;
+ }
+ }
+
+ if (port == 0)
+ return NF_DROP;
+
+ buflen = nf_nat_ftp_fmt_cmd(type, buffer, sizeof(buffer),
+ newip.ip, port);
+ if (!buflen)
+ goto out;
+
+ if (!nfq_tcp_mangle(pkt, matchoff, matchlen, buffer, buflen))
+ goto out;
+
+ return NF_ACCEPT;
+
+out:
+ cthelper_del_expect(exp);
+ return NF_DROP;
+}
+
+static int
+ftp_helper_cb(struct pkt_buff *pkt, uint32_t protoff,
+ struct myct *myct, uint32_t ctinfo)
+{
+ struct tcphdr *th;
+ unsigned int dataoff;
+ unsigned int matchoff = 0, matchlen = 0; /* makes gcc happy. */
+ unsigned int datalen;
+ unsigned int i;
+ int found = 0, ends_in_nl;
+ uint32_t seq;
+ int ret = NF_ACCEPT;
+ struct myct_man cmd;
+ union nfct_attr_grp_addr addr;
+ union nfct_attr_grp_addr daddr;
+ int dir = CTINFO2DIR(ctinfo);
+ struct ftp_info *ftp_info = myct->priv_data;
+ struct nf_expect *exp = NULL;
+
+ memset(&cmd, 0, sizeof(struct myct_man));
+ memset(&addr, 0, sizeof(union nfct_attr_grp_addr));
+
+ /* Until there's been traffic both ways, don't look in packets. */
+ if (ctinfo != IP_CT_ESTABLISHED &&
+ ctinfo != IP_CT_ESTABLISHED_REPLY) {
+ pr_debug("ftp: Conntrackinfo = %u\n", ctinfo);
+ goto out;
+ }
+
+ th = (struct tcphdr *) (pktb_network_header(pkt) + protoff);
+
+ dataoff = protoff + th->doff * 4;
+ datalen = pktb_len(pkt) - dataoff;
+
+ ends_in_nl = (pktb_network_header(pkt)[pktb_len(pkt) - 1] == '\n');
+ seq = ntohl(th->seq) + datalen;
+
+ /* Look up to see if we're just after a \n. */
+ if (!find_nl_seq(ntohl(th->seq), ftp_info, dir)) {
+ /* Now if this ends in \n, update ftp info. */
+ pr_debug("nf_conntrack_ftp: wrong seq pos %s(%u) or %s(%u)\n",
+ ftp_info->seq_aft_nl_num[dir] > 0 ? "" : "(UNSET)",
+ ftp_info->seq_aft_nl[dir][0],
+ ftp_info->seq_aft_nl_num[dir] > 1 ? "" : "(UNSET)",
+ ftp_info->seq_aft_nl[dir][1]);
+ goto out_update_nl;
+ }
+
+ /* Initialize IP/IPv6 addr to expected address (it's not mentioned
+ in EPSV responses) */
+ cmd.l3num = nfct_get_attr_u16(myct->ct, ATTR_L3PROTO);
+ nfct_get_attr_grp(myct->ct, ATTR_GRP_ORIG_ADDR_SRC, &cmd.u3);
+
+ for (i = 0; i < ARRAY_SIZE(search[dir]); i++) {
+ found = ftp_find_pattern(pkt, dataoff, datalen,
+ search[dir][i].pattern,
+ search[dir][i].plen,
+ search[dir][i].skip,
+ search[dir][i].term,
+ &matchoff, &matchlen,
+ &cmd,
+ search[dir][i].getnum,
+ dir);
+ if (found) break;
+ }
+ if (found == 0) /* No match */
+ goto out_update_nl;
+
+ pr_debug("conntrack_ftp: match `%.*s' (%u bytes at %u)\n",
+ matchlen, pktb_network_header(pkt) + matchoff,
+ matchlen, ntohl(th->seq) + matchoff);
+
+ /* We refer to the reverse direction ("!dir") tuples here,
+ * because we're expecting something in the other direction.
+ * Doesn't matter unless NAT is happening. */
+ cthelper_get_addr_dst(myct->ct, !dir, &daddr);
+
+ cthelper_get_addr_src(myct->ct, dir, &addr);
+
+ /* Update the ftp info */
+ if ((cmd.l3num == nfct_get_attr_u16(myct->ct, ATTR_L3PROTO)) &&
+ memcmp(&cmd.u3, &addr, sizeof(addr)) != 0) {
+ /* Enrico Scholz's passive FTP to partially RNAT'd ftp
+ server: it really wants us to connect to a
+ different IP address. Simply don't record it for
+ NAT. */
+ if (cmd.l3num == PF_INET) {
+ pr_debug("conntrack_ftp: NOT RECORDING: %pI4 != %pI4\n",
+ &cmd.u3.ip, &addr);
+ } else {
+ pr_debug("conntrack_ftp: NOT RECORDING: %pI6 != %pI6\n",
+ cmd.u3.ip6, &addr);
+ }
+ /* Thanks to Cristiano Lincoln Mattos
+ <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)) {
+ 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..97c1b35
--- /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)) {
+ 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/tns.c b/src/helpers/tns.c
new file mode 100644
index 0000000..77f01d9
--- /dev/null
+++ b/src/helpers/tns.c
@@ -0,0 +1,396 @@
+/*
+ * (C) 2012 by Jozsef Kadlecsik <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>
+
+#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;
+ uint32_t array[4];
+ const char *p;
+
+ p = strstr(data, "(");
+ if (!p)
+ return 0;
+
+ p = strstr(p+1, "HOST=");
+ if (!p) {
+ pr_debug("HOST= not found\n");
+ return 0;
+ }
+
+ offset = (int)(p - data) + strlen("HOST=");
+ *numoff = offset;
+ data += offset;
+
+ length = try_number(data, dlen - offset, array, 4, '.', ')');
+ if (length == 0)
+ return 0;
+
+ cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) |
+ (array[2] << 8) | array[3]);
+
+ p = strstr(data+length, "(");
+ if (!p)
+ return 0;
+
+ p = strstr(p, "PORT=");
+ if (!p) {
+ pr_debug("PORT= not found\n");
+ return 0;
+ }
+
+ p += strlen("PORT=");
+ return get_port(p, dlen - offset - length, ')', cmd);
+}
+
+static inline uint16_t
+nton(uint16_t len, unsigned int matchoff, unsigned int matchlen)
+{
+ uint32_t l = (uint32_t)ntohs(len) + matchoff - matchlen;
+
+ return htons(l);
+}
+
+/* So, this packet has hit the connection tracking matching code.
+ Mangle it, and change the expectation to match the new version. */
+static unsigned int
+nf_nat_tns(struct pkt_buff *pkt, struct tns_header *tns, struct nf_expect *exp,
+ struct nf_conntrack *ct, int dir,
+ unsigned int matchoff, unsigned int matchlen)
+{
+ union nfct_attr_grp_addr newip;
+ char buffer[sizeof("255.255.255.255)(PORT=65535)")];
+ unsigned int buflen;
+ const struct nf_conntrack *expected;
+ struct nf_conntrack *nat_tuple;
+ uint16_t initial_port, port;
+
+ /* Connection will come from wherever this packet goes, hence !dir */
+ cthelper_get_addr_dst(ct, !dir, &newip);
+
+ expected = nfexp_get_attr(exp, ATTR_EXP_EXPECTED);
+
+ nat_tuple = nfct_new();
+ if (nat_tuple == NULL)
+ return NF_ACCEPT;
+
+ initial_port = nfct_get_attr_u16(expected, ATTR_PORT_DST);
+
+ nfexp_set_attr_u32(exp, ATTR_EXP_NAT_DIR, !dir);
+
+ /* libnetfilter_conntrack needs this */
+ nfct_set_attr_u8(nat_tuple, ATTR_L3PROTO, AF_INET);
+ nfct_set_attr_u32(nat_tuple, ATTR_IPV4_SRC, 0);
+ nfct_set_attr_u32(nat_tuple, ATTR_IPV4_DST, 0);
+ nfct_set_attr_u8(nat_tuple, ATTR_L4PROTO, IPPROTO_TCP);
+ nfct_set_attr_u16(nat_tuple, ATTR_PORT_DST, 0);
+
+ /* When you see the packet, we need to NAT it the same as the
+ * this one. */
+ nfexp_set_attr(exp, ATTR_EXP_FN, "nat-follow-master");
+
+ /* Try to get same port: if not, try to change it. */
+ for (port = ntohs(initial_port); port != 0; port++) {
+ int ret;
+
+ nfct_set_attr_u16(nat_tuple, ATTR_PORT_SRC, htons(port));
+ nfexp_set_attr(exp, ATTR_EXP_NAT_TUPLE, nat_tuple);
+
+ ret = cthelper_add_expect(exp);
+ if (ret == 0)
+ break;
+ else if (ret != -EBUSY) {
+ port = 0;
+ break;
+ }
+ }
+ nfct_destroy(nat_tuple);
+
+ if (port == 0)
+ return NF_DROP;
+
+ buflen = snprintf(buffer, sizeof(buffer),
+ "%pI4)(PORT=%u)", &newip.ip, port);
+ if (!buflen)
+ goto out;
+
+ if (!nfq_tcp_mangle(pkt, matchoff, matchlen, buffer, buflen))
+ goto out;
+
+ if (buflen != matchlen) {
+ /* FIXME: recalculate checksum */
+ tns->csum = 0;
+ tns->header_csum = 0;
+
+ tns->len = nton(tns->len, matchlen, buflen);
+ if (tns->type == TNS_TYPE_REDIRECT) {
+ struct tns_redirect *r;
+
+ r = (struct tns_redirect *)((char *)tns + sizeof(struct tns_header));
+
+ r->data_len = nton(r->data_len, matchlen, buflen);
+ }
+ }
+
+ return NF_ACCEPT;
+
+out:
+ cthelper_del_expect(exp);
+ return NF_DROP;
+}
+
+static int
+tns_helper_cb(struct pkt_buff *pkt, uint32_t protoff,
+ struct myct *myct, uint32_t ctinfo)
+{
+ struct tcphdr *th;
+ struct tns_header *tns;
+ int dir = CTINFO2DIR(ctinfo);
+ unsigned int dataoff, datalen, numoff = 0, numlen;
+ struct tns_info *tns_info = myct->priv_data;
+ union nfct_attr_grp_addr addr;
+ struct nf_expect *exp = NULL;
+ struct myct_man cmd;
+ int ret = NF_ACCEPT;
+
+ memset(&cmd, 0, sizeof(struct myct_man));
+ memset(&addr, 0, sizeof(union nfct_attr_grp_addr));
+
+ /* Until there's been traffic both ways, don't look into TCP packets. */
+ if (ctinfo != IP_CT_ESTABLISHED
+ && ctinfo != IP_CT_ESTABLISHED_REPLY) {
+ pr_debug("TNS: Conntrackinfo = %u\n", ctinfo);
+ goto out;
+ }
+ /* Parse server direction only */
+ if (dir != MYCT_DIR_REPL) {
+ pr_debug("TNS: skip client direction\n");
+ goto out;
+ }
+
+ th = (struct tcphdr *) (pktb_network_header(pkt) + protoff);
+
+ dataoff = protoff + th->doff * 4;
+ datalen = pktb_len(pkt);
+
+ if (datalen < sizeof(struct tns_header)) {
+ pr_debug("TNS: skip packet with short header\n");
+ goto out;
+ }
+
+ tns = (struct tns_header *)(pktb_network_header(pkt) + dataoff);
+
+ if (tns->type == TNS_TYPE_REDIRECT) {
+ struct tns_redirect *redirect;
+
+ dataoff += sizeof(struct tns_header);
+ datalen -= sizeof(struct tns_header);
+ redirect = (struct tns_redirect *)(pktb_network_header(pkt) + dataoff);
+ tns_info->parse = false;
+
+ if (ntohs(redirect->data_len) == 0) {
+ tns_info->parse = true;
+ goto out;
+ }
+ goto parse;
+ }
+
+ /* Parse only the very next DATA packet */
+ if (!(tns_info->parse && tns->type == TNS_TYPE_DATA)) {
+ tns_info->parse = false;
+ goto out;
+ }
+parse:
+ numlen = find_pattern(pkt, dataoff, datalen, &cmd, &numoff);
+ tns_info->parse = false;
+ if (!numlen)
+ goto out;
+
+ /* We refer to the reverse direction ("!dir") tuples here,
+ * because we're expecting something in the other direction.
+ * Doesn't matter unless NAT is happening. */
+ cthelper_get_addr_src(myct->ct, !dir, &addr);
+
+ exp = nfexp_new();
+ if (exp == NULL)
+ goto out;
+
+ if (cthelper_expect_init(exp, myct->ct, 0,
+ &addr, &cmd.u3,
+ IPPROTO_TCP,
+ NULL, &cmd.u.port)) {
+ pr_debug("TNS: failed to init expectation\n");
+ goto out_exp;
+ }
+
+ /* Now, NAT might want to mangle the packet, and register the
+ * (possibly changed) expectation itself.
+ */
+ if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) {
+ ret = nf_nat_tns(pkt, tns, exp, myct->ct, dir, numoff, numlen);
+ goto out_exp;
+ }
+
+ /* Can't expect this? Best to drop packet now. */
+ if (cthelper_add_expect(exp) < 0) {
+ pr_debug("TNS: cannot add expectation: %s\n",
+ strerror(errno));
+ ret = NF_DROP;
+ goto out_exp;
+ }
+ goto out;
+
+out_exp:
+ nfexp_destroy(exp);
+out:
+ return ret;
+}
+
+static struct ctd_helper tns_helper = {
+ .name = "tns",
+ .l4proto = IPPROTO_TCP,
+ .cb = tns_helper_cb,
+ .priv_data_len = sizeof(struct tns_info),
+ .policy = {
+ [0] = {
+ .name = "tns",
+ .expect_max = 1,
+ .expect_timeout = 300,
+ },
+ },
+};
+
+void __attribute__ ((constructor)) tns_init(void);
+
+void tns_init(void)
+{
+ helper_register(&tns_helper);
+}