diff options
| author | Gaurav Sinha <gaurav.sinha@vyatta.com> | 2012-05-30 07:54:05 -0700 | 
|---|---|---|
| committer | Gaurav Sinha <gaurav.sinha@vyatta.com> | 2012-05-30 07:54:05 -0700 | 
| commit | a608049a22dc23676c85bbf443e45cbbf0e9b83c (patch) | |
| tree | 1b82fa315337a8503390384c2684fdbb27b58294 /src/helpers | |
| parent | 775fea07517af4b68cb2ce75e25ee5af09af0f05 (diff) | |
| parent | 687fc04ea8de73eb1ec19d933c8d81f054c977dd (diff) | |
| download | conntrack-tools-a608049a22dc23676c85bbf443e45cbbf0e9b83c.tar.gz conntrack-tools-a608049a22dc23676c85bbf443e45cbbf0e9b83c.zip  | |
Merge branch 'cthelper9' of git://git.netfilter.org/conntrack-tools into user_space_helpers
Conflicts:
	.gitignore
	src/run.c
Diffstat (limited to 'src/helpers')
| -rw-r--r-- | src/helpers/Makefile.am | 17 | ||||
| -rw-r--r-- | src/helpers/ftp.c | 599 | ||||
| -rw-r--r-- | src/helpers/rpc.c | 488 | ||||
| -rw-r--r-- | src/helpers/tns.c | 396 | 
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); +}  | 
