diff options
| author | Jozsef Kadlecsik <kadlec@blackhole.kfki.hu> | 2012-05-15 14:43:20 +0200 | 
|---|---|---|
| committer | Pablo Neira Ayuso <pablo@netfilter.org> | 2012-08-01 19:20:17 +0200 | 
| commit | 40f4330e6b50ed2b198549b1006c6fcb349f5a3b (patch) | |
| tree | 184a77979ee353748e09f08387ea6b5d74dd72f6 /src | |
| parent | 969d93f14fffadb5cae67a7662484c1e064bbff1 (diff) | |
| download | conntrack-tools-40f4330e6b50ed2b198549b1006c6fcb349f5a3b.tar.gz conntrack-tools-40f4330e6b50ed2b198549b1006c6fcb349f5a3b.zip | |
conntrackd: TNS helper added to cthelper
Signed-off-by: Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Diffstat (limited to 'src')
| -rw-r--r-- | src/helpers/Makefile.am | 7 | ||||
| -rw-r--r-- | src/helpers/tns.c | 407 | 
2 files changed, 413 insertions, 1 deletions
| diff --git a/src/helpers/Makefile.am b/src/helpers/Makefile.am index f441b29..589b4f3 100644 --- a/src/helpers/Makefile.am +++ b/src/helpers/Makefile.am @@ -1,7 +1,8 @@  include $(top_srcdir)/Make_global.am  pkglib_LTLIBRARIES = ct_helper_ftp.la	\ -		     ct_helper_rpc.la +		     ct_helper_rpc.la	\ +		     ct_helper_tns.la  ct_helper_ftp_la_SOURCES = ftp.c  ct_helper_ftp_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) @@ -10,3 +11,7 @@ ct_helper_ftp_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS)  ct_helper_rpc_la_SOURCES = rpc.c  ct_helper_rpc_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS)  ct_helper_rpc_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) + +ct_helper_tns_la_SOURCES = tns.c +ct_helper_tns_la_LDFLAGS = -avoid-version -module $(LIBNETFILTER_CONNTRACK_LIBS) +ct_helper_tns_la_CFLAGS = $(AM_CFLAGS) $(LIBNETFILTER_CONNTRACK_CFLAGS) diff --git a/src/helpers/tns.c b/src/helpers/tns.c new file mode 100644 index 0000000..5833fea --- /dev/null +++ b/src/helpers/tns.c @@ -0,0 +1,407 @@ +/* + * (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, 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); +} | 
