From b84e7a150daab4c9f488120c9fded984ee3f7ec8 Mon Sep 17 00:00:00 2001
From: Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
Date: Tue, 15 May 2012 14:43:20 +0200
Subject: conntrackd: TNS helper added to cthelper

Signed-off-by: Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
 src/helpers/tns.c | 396 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 396 insertions(+)
 create mode 100644 src/helpers/tns.c

(limited to 'src/helpers/tns.c')

diff --git a/src/helpers/tns.c b/src/helpers/tns.c
new file mode 100644
index 0000000..c4bfb91
--- /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_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)) {
+		pr_debug("TNS: failed to init expectation\n");
+		goto out_exp;
+	}
+
+	/* Now, NAT might want to mangle the packet, and register the
+	 * (possibly changed) expectation itself.
+	 */
+	if (nfct_get_attr_u32(myct->ct, ATTR_STATUS) & IPS_NAT_MASK) {
+		ret = nf_nat_tns(pkt, tns, exp, myct->ct, dir, numoff, numlen);
+		goto out_exp;
+	}
+
+	/* Can't expect this?  Best to drop packet now. */
+	if (cthelper_add_expect(exp) < 0) {
+		pr_debug("TNS: cannot add expectation: %s\n",
+			 strerror(errno));
+		ret = NF_DROP;
+		goto out_exp;
+	}
+	goto out;
+
+out_exp:
+	nfexp_destroy(exp);
+out:
+	return ret;
+}
+
+static struct ctd_helper tns_helper = {
+	.name		= "tns",
+	.l4proto	= IPPROTO_TCP,
+	.cb		= tns_helper_cb,
+	.priv_data_len	= sizeof(struct tns_info),
+	.policy		= {
+		[0] = {
+			.name		= "tns",
+			.expect_max	= 1,
+			.expect_timeout	= 300,
+		},
+	},
+};
+
+void __attribute__ ((constructor)) tns_init(void);
+
+void tns_init(void)
+{
+	helper_register(&tns_helper);
+}
-- 
cgit v1.2.3