Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e8beb21f authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "Support full CONE NAT and ADDRESS RESTRICTED CONE NAT"

parents f51ec01c e1e6576c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -8,3 +8,4 @@ header-y += ipt_TTL.h
header-y += ipt_ah.h
header-y += ipt_ecn.h
header-y += ipt_ttl.h
header-y += ipt_NATTYPE.h
+25 −0
Original line number Diff line number Diff line
#ifndef _IPT_NATTYPE_H_target
#define _IPT_NATTYPE_H_target

#define NATTYPE_TIMEOUT 300

enum nattype_mode {
	MODE_DNAT,
	MODE_FORWARD_IN,
	MODE_FORWARD_OUT
};

enum nattype_type {
	TYPE_PORT_ADDRESS_RESTRICTED,
	TYPE_ENDPOINT_INDEPENDENT,
	TYPE_ADDRESS_RESTRICTED
};


struct ipt_nattype_info {
	u_int16_t mode;
	u_int16_t type;
};

#endif /*_IPT_NATTYPE_H_target*/
+11 −0
Original line number Diff line number Diff line
@@ -282,6 +282,17 @@ config IP_NF_TARGET_MASQUERADE

	  To compile it as a module, choose M here.  If unsure, say N.

config IP_NF_TARGET_NATTYPE_MODULE
	tristate "NATTYPE target support"
	depends on NF_NAT
	default m if NETFILTER_ADVANCED=n
	help
	  NATTYPE is a special case of NAT: used to support FULL Cone NAT
	  and ADDRESS Restricted Cone NAT. All incoming connections are
	  allowed if there is an outgoing connection using that port.

	  To compile it as a module, choose M here.  If unsure, say N.

config IP_NF_TARGET_NETMAP
	tristate "NETMAP target support"
	depends on NETFILTER_ADVANCED
+1 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ obj-$(CONFIG_IP_NF_MATCH_RPFILTER) += ipt_rpfilter.o
obj-$(CONFIG_IP_NF_TARGET_CLUSTERIP) += ipt_CLUSTERIP.o
obj-$(CONFIG_IP_NF_TARGET_ECN) += ipt_ECN.o
obj-$(CONFIG_IP_NF_TARGET_MASQUERADE) += ipt_MASQUERADE.o
obj-$(CONFIG_IP_NF_TARGET_NATTYPE_MODULE) += ipt_NATTYPE.o
obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o
obj-$(CONFIG_IP_NF_TARGET_SYNPROXY) += ipt_SYNPROXY.o

+595 −0
Original line number Diff line number Diff line
/* netfilter NATTYPE
 * net/ipv4/netfilter/ipt_NATTYPE.c
 * Endpoint Independent, Address Restricted and Port-Address Restricted
 * NAT types' kernel side implementation.
 *
 * (C) Copyright 2011, Ubicom, Inc.
 *
 * This file is part of the Ubicom32 Linux Kernel Port.
 *
 * The Ubicom32 Linux Kernel Port is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 *
 * The Ubicom32 Linux Kernel Port is distributed in the hope that it
 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
 * the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Ubicom32 Linux Kernel Port.  If not,
 * see <http://www.gnu.org/licenses/>.
 *
 * Ubicom32 implementation derived from
 * Cameo's implementation(with many thanks):
 */
#include <linux/types.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/module.h>
#include <net/protocol.h>
#include <net/checksum.h>
#include <net/ip.h>
#include <linux/tcp.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_nat_rule.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter_ipv4/ipt_NATTYPE.h>
#include <linux/atomic.h>

#if !defined(NATTYPE_DEBUG)
#define DEBUGP(type, args...)
#else
static const char * const types[] = {"TYPE_PORT_ADDRESS_RESTRICTED",
			"TYPE_ENDPOINT_INDEPENDENT",
			"TYPE_ADDRESS_RESTRICTED"};
static const char * const modes[] = {"MODE_DNAT", "MODE_FORWARD_IN",
			"MODE_FORWARD_OUT"};
#define DEBUGP(args...) pr_debug(args)
#endif

/* netfilter NATTYPE TODO:
 * Add magic value checks to data structure.
 */
struct ipt_nattype {
	struct list_head list;
	struct timer_list timeout;
	unsigned short proto;		/* Protocol: TCP or UDP */
	struct nf_nat_ipv4_range range;	/* LAN side src info*/
	unsigned short nat_port;	/* Routed NAT port */
	unsigned int dest_addr;	/* Original egress packets dst addr */
	unsigned short dest_port;/* Original egress packets destination port */
};

/* TODO: It might be better to use a hash table for performance in
 * heavy traffic.
 */
static LIST_HEAD(nattype_list);
static DEFINE_SPINLOCK(nattype_lock);

/* netfilter NATTYPE
 * nattype_nte_debug_print()
 */
static void nattype_nte_debug_print(const struct ipt_nattype *nte,
				    const char *s)
{
	DEBUGP("%p: %s - proto[%d], src[%pI4:%d], nat[<x>:%d], dest[%pI4:%d]\n",
	       nte, s, nte->proto,
		&nte->range.min_ip, ntohs(nte->range.min.all),
		ntohs(nte->nat_port),
		&nte->dest_addr, ntohs(nte->dest_port));
}

/* netfilter NATTYPE nattype_free()
 * Free the object.
 */
static void nattype_free(struct ipt_nattype *nte)
{
	nattype_nte_debug_print(nte, "free");
	kfree(nte);
}

/* netfilter NATTYPE nattype_refresh_timer()
 * Refresh the timer for this object.
 */
static bool nattype_refresh_timer(struct ipt_nattype *nte)
{
	if (del_timer(&nte->timeout)) {
		nte->timeout.expires = jiffies + NATTYPE_TIMEOUT * HZ;
		add_timer(&nte->timeout);
		return true;
	}
	return false;
}

/* netfilter NATTYPE nattype_timer_timeout()
 * The timer has gone off, self-destruct
 */
static void nattype_timer_timeout(unsigned long in_nattype)
{
	struct ipt_nattype *nte = (void *)in_nattype;

	/* netfilter NATTYPE
	 * The race with list deletion is solved by ensuring
	 * that either this code or the list deletion code
	 * but not both will remove the oject.
	 */
	nattype_nte_debug_print(nte, "timeout");
	spin_lock_bh(&nattype_lock);
	list_del(&nte->list);
	spin_unlock_bh(&nattype_lock);
	nattype_free(nte);
}

/* netfilter NATTYPE nattype_packet_in_match()
 * Ingress packet, try to match with this nattype entry.
 */
static bool nattype_packet_in_match(const struct ipt_nattype *nte,
				    struct sk_buff *skb,
				    const struct ipt_nattype_info *info)
{
	const struct iphdr *iph = ip_hdr(skb);
	u16 dst_port = 0;

	/* If the protocols are not the same, no sense in looking
	 * further.
	 */
	if (nte->proto != iph->protocol) {
		DEBUGP("nattype_packet_in_match: protocol failed: nte proto:"
		DEBUGP(" %d, packet proto: %d\n",
		       nte->proto, iph->protocol);
		return false;
	}

	 /* In ADDRESS_RESTRICT, the egress destination must match the source
	  * of this ingress packet.
	  */
	if (info->type == TYPE_ADDRESS_RESTRICTED) {
		if (nte->dest_addr != iph->saddr) {
			DEBUGP("nattype_packet_in_match: dest/src check");
			DEBUGP(" failed: dest_addr: %pI4, src dest: %pI4\n",
			       &nte->dest_addr, &iph->saddr);
			return false;
		}
	}

	/* Obtain the destination port value for TCP or UDP.  The nattype
	 * entries are stored in native (not host).
	 */
	if (iph->protocol == IPPROTO_TCP) {
		struct tcphdr _tcph;
		struct tcphdr *tcph;

		tcph = skb_header_pointer(skb, ip_hdrlen(skb),
					  sizeof(_tcph), &_tcph);
		if (!tcph)
			return false;
		dst_port = tcph->dest;
	} else if (iph->protocol == IPPROTO_UDP) {
		struct udphdr _udph;
		struct udphdr *udph;

		udph = skb_header_pointer(skb, ip_hdrlen(skb),
					  sizeof(_udph), &_udph);
		if (!udph)
			return false;
		dst_port = udph->dest;
	}

	/* Our NAT port must match the ingress pacekt's
	 * destination packet.
	 */
	if (nte->nat_port != dst_port) {
		DEBUGP("nattype_packet_in_match fail: ");
		DEBUGP(" nat port: %d,dest_port: %d\n",
		       ntohs(nte->nat_port), ntohs(dst_port));
		return false;
	}

	/* In either EI or AR mode, the ingress packet's src port
	 * can be anything.
	 */
	nattype_nte_debug_print(nte, "INGRESS MATCH");
	return true;
}

/* netfilter NATTYPE nattype_compare
 * Compare two entries, return true if relevant fields are the same.
 */
static bool nattype_compare(struct ipt_nattype *n1, struct ipt_nattype *n2)
{
	/* netfilter NATTYPE Protocol
	 * compare.
	 */
	if (n1->proto != n2->proto) {
		DEBUGP("nattype_compare: protocol mismatch: %d:%d\n",
		       n1->proto, n2->proto);
		return false;
	}

	 /* netfilter NATTYPE LAN Source compare.
	  * Since we always keep min/max values the same,
	  * just compare the min values.
	  */
	if (n1->range.min_ip != n2->range.min_ip) {
		DEBUGP("nattype_compare: r.min_ip mismatch: %pI4:%pI4\n",
		       &n1->range.min_ip, &n2->range.min_ip);
		return false;
	}

	if (n1->range.min.all != n2->range.min.all) {
		DEBUGP("nattype_compare: r.min mismatch: %d:%d\n",
		       ntohs(n1->range.min.all),
		       ntohs(n2->range.min.all));
		return false;
	}

	/* netfilter NATTYPE
	 * NAT port
	 */
	if (n1->nat_port != n2->nat_port) {
		DEBUGP("nattype_compare: nat_port mistmatch: %d:%d\n",
		       ntohs(n1->nat_port), ntohs(n2->nat_port));
		return false;
	}

	/* netfilter NATTYPE
	 * Destination compare
	 */
	if (n1->dest_addr != n2->dest_addr) {
		DEBUGP("nattype_compare: dest_addr mismatch: %pI4:%pI4\n",
		       &n1->dest_addr, &n2->dest_addr);
		return false;
	}

	if (n1->dest_port != n2->dest_port) {
		DEBUGP("nattype_compare: dest_port mismatch: %d:%d\n",
		       ntohs(n1->dest_port), ntohs(n2->dest_port));
		return false;
	}
	return true;
}

 /**
  *  netfilter NATTYPE nattype_nat()
  * Ingress packet on PRE_ROUTING hook, find match, update conntrack
  * to allow
  **/
static unsigned int nattype_nat(struct sk_buff *skb,
				const struct xt_action_param *par)
{
	struct ipt_nattype *nte;

	if (par->hooknum != NF_INET_PRE_ROUTING)
		return XT_CONTINUE;
	spin_lock_bh(&nattype_lock);
	list_for_each_entry(nte, &nattype_list, list) {
		struct nf_conn *ct;
		enum ip_conntrack_info ctinfo;
		struct nf_nat_ipv4_range newrange;
		unsigned int ret;

		if (!nattype_packet_in_match(nte, skb, par->targinfo))
			continue;

		/* Copy the LAN source data into the ingress' pacekts
		 * conntrack in the reply direction.
		 */
		newrange = nte->range;
		spin_unlock_bh(&nattype_lock);

		/* netfilter NATTYPE Find the
		 * ingress packet's conntrack.
		 */
		ct = nf_ct_get(skb, &ctinfo);
		if (!ct) {
			DEBUGP("ingress packet conntrack not found\n");
			return XT_CONTINUE;
		}

		/* Expand the ingress conntrack
		 * to include the reply as source
		 */
		DEBUGP("Expand ingress conntrack=%p, type=%d, src[%pI4:%d]\n",
		       ct, ctinfo, &newrange.min_ip, ntohs(newrange.min.all));
		ret = nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
		DEBUGP("Expand returned: %d\n", ret);
		return ret;
	}
	spin_unlock_bh(&nattype_lock);
	return XT_CONTINUE;
}

/* netfilter NATTYPE nattype_forward()
 * Ingress and Egress packet forwarding hook
 */
static unsigned int nattype_forward(struct sk_buff *skb,
				    const struct xt_action_param *par)
{
	const struct iphdr *iph = ip_hdr(skb);
	void *protoh = (void *)iph + iph->ihl * 4;
	struct ipt_nattype *nte;
	struct ipt_nattype *nte2;
	struct nf_conn *ct;
	enum ip_conntrack_info ctinfo;
	const struct ipt_nattype_info *info = par->targinfo;
	u16 nat_port;

	if (par->hooknum != NF_INET_FORWARD)
		return XT_CONTINUE;

	/* Ingress packet,
	 * refresh the timer if we find an entry.
	 */
	if (info->mode == MODE_FORWARD_IN) {
		spin_lock_bh(&nattype_lock);
		list_for_each_entry(nte, &nattype_list, list) {
			/* netfilter NATTYPE
			 * Compare the ingress packet with the existing
			 * entries looking for a match.
			 */
			if (!nattype_packet_in_match(nte, skb, info))
				continue;

			/* netfilter NATTYPE
			 * Refresh the timer, if we fail, break
			 * out and forward fail as though we never
			 * found the entry.
			 */
			if (!nattype_refresh_timer(nte))
				break;

			/* netfilter NATTYPE
			 * The entry is found and refreshed, the
			 * entry values should not change so print
			 * them outside the lock.
			 */
			spin_unlock_bh(&nattype_lock);
			nattype_nte_debug_print(nte, "refresh");
			DEBUGP("FORWARD_IN_ACCEPT\n");
			return NF_ACCEPT;
		}
		spin_unlock_bh(&nattype_lock);
		DEBUGP("FORWARD_IN_FAIL\n");
		return XT_CONTINUE;
	}

	/* netfilter NATTYPE
	 * Egress packet, create a new rule in our list.  If conntrack does
	 * not have an entry, skip this packet.
	 */
	ct = nf_ct_get(skb, &ctinfo);
	if (!ct || (ctinfo == IP_CT_NEW && ctinfo == IP_CT_RELATED))
		return XT_CONTINUE;

	nat_port = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.all;

	/* netfilter NATTYPE
	 * Allocate a new entry
	 */
	nte = kzalloc(sizeof(*nte), GFP_ATOMIC | __GFP_NOWARN);
	if (!nte) {
		DEBUGP("kernel malloc fail\n");
		return XT_CONTINUE;
	}

	INIT_LIST_HEAD(&nte->list);

	nte->proto = iph->protocol;
	nte->nat_port = nat_port;
	nte->dest_addr = iph->daddr;
	nte->range.min_ip = iph->saddr;
	nte->range.max_ip = nte->range.min_ip;

	/* netfilter NATTYPE
	 * TOOD: Would it be better to get this information from the
	 * conntrack instead of the headers.
	 */
	if (iph->protocol == IPPROTO_TCP) {
		nte->range.min.tcp.port = ((struct tcphdr *)protoh)->source;
		nte->range.max.tcp.port = nte->range.min.tcp.port;
		nte->dest_port = ((struct tcphdr *)protoh)->dest;
	} else if (iph->protocol == IPPROTO_UDP) {
		nte->range.min.udp.port = ((struct udphdr *)protoh)->source;
		nte->range.max.udp.port = nte->range.min.udp.port;
		nte->dest_port = ((struct udphdr *)protoh)->dest;
	}
	nte->range.flags = (NF_NAT_RANGE_MAP_IPS |
			NF_NAT_RANGE_PROTO_SPECIFIED);

	/* netfilter NATTYPE
	 * Initialize the self-destruct timer.
	 */
	init_timer(&nte->timeout);
	nte->timeout.data = (unsigned long)nte;
	nte->timeout.function = nattype_timer_timeout;

	/* netfilter NATTYPE
	 * We have created the new nte; however, it might not be unique.
	 * Search the list for a matching entry.  If found, throw away
	 * the new entry and refresh the old.  If not found, atomically
	 * insert the new entry on the list.
	 */
	spin_lock_bh(&nattype_lock);
	list_for_each_entry(nte2, &nattype_list, list) {
		if (!nattype_compare(nte, nte2))
			continue;

		/* netfilter NATTYPE
		 * If we can not refresh this entry, insert our new
		 * entry as this one is timed out and will be removed
		 * from the list shortly.
		 */
		if (!nattype_refresh_timer(nte2))
			break;

		/* netfilter NATTYPE
		 * Found and refreshed an existing entry.  Its values
		 * do not change so print the values outside of the lock.
		 *
		 * Free up the new entry.
		 */
		spin_unlock_bh(&nattype_lock);
		nattype_nte_debug_print(nte2, "refresh");
		nattype_free(nte);
		return XT_CONTINUE;
	}

	/* netfilter NATTYPE
	 * Add the new entry to the list.
	 */
	nte->timeout.expires = jiffies + (NATTYPE_TIMEOUT  * HZ);
	add_timer(&nte->timeout);
	list_add(&nte->list, &nattype_list);
	spin_unlock_bh(&nattype_lock);
	nattype_nte_debug_print(nte, "ADD");
	return XT_CONTINUE;
}

/* netfilter NATTYPE
 * nattype_target()
 *	One of the iptables hooks has a packet for us to analyze, do so.
 */
static unsigned int nattype_target(struct sk_buff *skb,
				   const struct xt_action_param *par)
{
	const struct ipt_nattype_info *info = par->targinfo;
	const struct iphdr *iph = ip_hdr(skb);

	/* netfilter NATTYPE
	 * The default behavior for Linux is PORT and ADDRESS restricted. So
	 * we do not need to create rules/entries if we are in that mode.
	 */
	if (info->type == TYPE_PORT_ADDRESS_RESTRICTED)
		return XT_CONTINUE;

	/* netfilter NATTYPE
	 * Check if we have enough data in the skb.
	 */
	if (skb->len < ip_hdrlen(skb))
		return XT_CONTINUE;

	/* netfilter NATTYPE
	 * We can not perform endpoint filtering on anything but UDP and TCP.
	 */
	if ((iph->protocol != IPPROTO_TCP) && (iph->protocol != IPPROTO_UDP))
		return XT_CONTINUE;

	/* netfilter NATTYPE
	 * Check for LAND attack and ignore.
	 */
	if (iph->daddr == iph->saddr)
		return XT_CONTINUE;

	/* netfilter NATTYPE
	 * Check that we have valid source and destination addresses.
	 */
	if ((iph->daddr == (__be32)0) || (iph->saddr == (__be32)0))
		return XT_CONTINUE;

	DEBUGP("nattype_target: type = %s, mode = %s\n",
	       types[info->type], modes[info->mode]);

	/* netfilter NATTYPE
	 * TODO: why have mode at all since par->hooknum provides
	 * this information?
	 */
	switch (info->mode) {
	case MODE_DNAT:
		return nattype_nat(skb, par);
	case MODE_FORWARD_OUT:
	case MODE_FORWARD_IN:
		return nattype_forward(skb, par);
	}
	return XT_CONTINUE;
}

/* netfilter NATTYPE
 * nattype_check()
 *	check info (mode/type) set by iptables.
 */
static int nattype_check(const struct xt_tgchk_param *par)
{
	const struct ipt_nattype_info *info = par->targinfo;
	struct list_head *cur, *tmp;

	if ((info->type != TYPE_PORT_ADDRESS_RESTRICTED) &&
	    (info->type != TYPE_ENDPOINT_INDEPENDENT) &&
		(info->type != TYPE_ADDRESS_RESTRICTED)) {
		DEBUGP("nattype_check: unknown type: %d\n", info->type);
		return -EINVAL;
	}

	if (info->mode != MODE_DNAT && info->mode != MODE_FORWARD_IN &&
	    info->mode != MODE_FORWARD_OUT) {
		DEBUGP("nattype_check: unknown mode - %d.\n", info->mode);
		return -EINVAL;
	}

	DEBUGP("nattype_check: type = %s, mode = %s\n",
	       types[info->type], modes[info->mode]);

	if (par->hook_mask & ~((1 << NF_INET_PRE_ROUTING) |
		(1 << NF_INET_FORWARD))) {
		DEBUGP("nattype_check: bad hooks %x.\n", par->hook_mask);
		return -EINVAL;
	}

	/* netfilter NATTYPE
	 * Remove all entries from the nattype list.
	 */
drain:
	spin_lock_bh(&nattype_lock);
	list_for_each_safe(cur, tmp, &nattype_list) {
		struct ipt_nattype *nte = (void *)cur;

		/* netfilter NATTYPE
		 * If the timeout is in process, it will tear
		 * us down.  Since it is waiting on the spinlock
		 * we have to give up the spinlock to give the
		 * timeout on another CPU a chance to run.
		 */
		if (!del_timer(&nte->timeout)) {
			spin_unlock_bh(&nattype_lock);
			goto drain;
		}

		DEBUGP("%p: removing from list\n", nte);
		list_del(&nte->list);
		spin_unlock_bh(&nattype_lock);
		nattype_free(nte);
		goto drain;
	}
	spin_unlock_bh(&nattype_lock);
	return 0;
}

static struct xt_target nattype = {
	.name		= "NATTYPE",
	.family		= NFPROTO_IPV4,
	.target		= nattype_target,
	.checkentry	= nattype_check,
	.targetsize	= sizeof(struct ipt_nattype_info),
	.hooks		= ((1 << NF_INET_PRE_ROUTING) |
				(1 << NF_INET_FORWARD)),
	.me		= THIS_MODULE,
};

static int __init init(void)
{
	return xt_register_target(&nattype);
}

static void __exit fini(void)
{
	xt_unregister_target(&nattype);
}

module_init(init);
module_exit(fini);

MODULE_LICENSE("GPL");