patch-2.3.99-pre2 linux/net/ipv4/netfilter/ip_fw_compat_redir.c

Next file: linux/net/ipv4/netfilter/ip_nat_core.c
Previous file: linux/net/ipv4/netfilter/ip_fw_compat_masq.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.99-pre1/linux/net/ipv4/netfilter/ip_fw_compat_redir.c linux/net/ipv4/netfilter/ip_fw_compat_redir.c
@@ -0,0 +1,284 @@
+/* This is a file to handle the "simple" NAT cases (redirect and
+   masquerade) required for the compatibility layer.
+
+   `bind to foreign address' and `getpeername' hacks are not
+   supported.
+
+   FIXME: Timing is overly simplistic.  If anyone complains, make it
+   use conntrack.
+*/
+#include <linux/config.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/tcp.h>
+#include <net/checksum.h>
+#include <linux/timer.h>
+#include <linux/netdevice.h>
+#include <linux/if.h>
+#include <linux/in.h>
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+static DECLARE_LOCK(redir_lock);
+#define ASSERT_READ_LOCK(x) MUST_BE_LOCKED(&redir_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_LOCKED(&redir_lock)
+
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#ifdef CONFIG_NETFILTER_DEBUG
+#define IP_NF_ASSERT(x)							 \
+do {									 \
+	if (!(x))							 \
+		/* Wooah!  I'm tripping my conntrack in a frenzy of	 \
+		   netplay... */					 \
+		printk("ASSERT: %s:%i(%s)\n",				 \
+		       __FILE__, __LINE__, __FUNCTION__);		 \
+} while(0);
+#else
+#define IP_NF_ASSERT(x)
+#endif
+
+static u_int16_t
+cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
+{
+	u_int32_t diffs[] = { oldvalinv, newval };
+	return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
+				      oldcheck^0xFFFF));
+}
+
+struct redir_core {
+	u_int32_t orig_srcip, orig_dstip;
+	u_int16_t orig_sport, orig_dport;
+
+	u_int32_t new_dstip;
+	u_int16_t new_dport;
+};
+
+struct redir
+{
+	struct list_head list;
+	struct redir_core core;
+	struct timer_list destroyme;
+};
+
+static LIST_HEAD(redirs);
+
+static int
+redir_cmp(const struct redir *i,
+	  u_int32_t orig_srcip, u_int32_t orig_dstip,
+	  u_int16_t orig_sport, u_int16_t orig_dport)
+{
+	return (i->core.orig_srcip == orig_srcip
+		&& i->core.orig_dstip == orig_dstip
+		&& i->core.orig_sport == orig_sport
+		&& i->core.orig_dport == orig_dport);
+}
+
+/* Search for an existing redirection of the TCP packet. */
+static struct redir *
+find_redir(u_int32_t orig_srcip, u_int32_t orig_dstip,
+	   u_int16_t orig_sport, u_int16_t orig_dport)
+{
+	return LIST_FIND(&redirs, redir_cmp, struct redir *,
+			 orig_srcip, orig_dstip, orig_sport, orig_dport);
+}
+
+static void do_tcp_redir(struct sk_buff *skb, struct redir *redir)
+{
+	struct iphdr *iph = skb->nh.iph;
+	struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+						+ iph->ihl);
+
+	tcph->check = cheat_check(~redir->core.orig_dstip,
+				  redir->core.new_dstip,
+				  cheat_check(redir->core.orig_dport ^ 0xFFFF,
+					      redir->core.new_dport,
+					      tcph->check));
+	iph->check = cheat_check(~redir->core.orig_dstip,
+				 redir->core.new_dstip, iph->check);
+	tcph->dest = redir->core.new_dport;
+	iph->daddr = redir->core.new_dstip;
+
+	skb->nfcache |= NFC_ALTERED;
+}
+
+static int
+unredir_cmp(const struct redir *i,
+	    u_int32_t new_dstip, u_int32_t orig_srcip,
+	    u_int16_t new_dport, u_int16_t orig_sport)
+{
+	return (i->core.orig_srcip == orig_srcip
+		&& i->core.new_dstip == new_dstip
+		&& i->core.orig_sport == orig_sport
+		&& i->core.new_dport == new_dport);
+}
+
+/* Match reply packet against redir */
+static struct redir *
+find_unredir(u_int32_t new_dstip, u_int32_t orig_srcip,
+	     u_int16_t new_dport, u_int16_t orig_sport)
+{
+	return LIST_FIND(&redirs, unredir_cmp, struct redir *,
+			 new_dstip, orig_srcip, new_dport, orig_sport);
+}
+
+/* `unredir' a reply packet. */
+static void do_tcp_unredir(struct sk_buff *skb, struct redir *redir)
+{
+	struct iphdr *iph = skb->nh.iph;
+	struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+						+ iph->ihl);
+
+	tcph->check = cheat_check(~redir->core.new_dstip,
+				  redir->core.orig_dstip,
+				  cheat_check(redir->core.new_dport ^ 0xFFFF,
+					      redir->core.orig_dport,
+					      tcph->check));
+	iph->check = cheat_check(~redir->core.new_dstip,
+				 redir->core.orig_dstip,
+				 iph->check);
+	tcph->source = redir->core.orig_dport;
+	iph->saddr = redir->core.orig_dstip;
+
+	skb->nfcache |= NFC_ALTERED;
+}
+
+/* REDIRECT a packet. */
+unsigned int
+do_redirect(struct sk_buff *skb,
+	    const struct net_device *dev,
+	    u_int16_t redirpt)
+{
+	struct iphdr *iph = skb->nh.iph;
+	u_int32_t newdst;
+
+	/* Figure out address: not loopback. */
+	if (!dev)
+		return NF_DROP;
+
+	/* Grab first address on interface. */
+	newdst = ((struct in_device *)dev->ip_ptr)->ifa_list->ifa_local;
+
+	switch (iph->protocol) {
+	case IPPROTO_UDP: {
+		/* Simple mangle. */
+		struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph
+							+ iph->ihl);
+
+		udph->check = cheat_check(~iph->daddr, newdst,
+					  cheat_check(udph->dest ^ 0xFFFF,
+						      redirpt,
+						      udph->check));
+		iph->check = cheat_check(~iph->daddr, newdst, iph->check);
+		udph->dest = redirpt;
+		iph->daddr = newdst;
+
+		skb->nfcache |= NFC_ALTERED;
+		return NF_ACCEPT;
+	}
+	case IPPROTO_TCP: {
+		/* Mangle, maybe record. */
+		struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+							+ iph->ihl);
+		struct redir *redir;
+		int ret;
+
+		DEBUGP("Doing tcp redirect. %08X:%u %08X:%u -> %08X:%u\n",
+		       iph->saddr, tcph->source, iph->daddr, tcph->dest,
+		       newdst, redirpt);
+		LOCK_BH(&redir_lock);
+		redir = find_redir(iph->saddr, iph->daddr,
+				   tcph->source, tcph->dest);
+
+		if (!redir) {
+			redir = kmalloc(sizeof(struct redir), GFP_ATOMIC);
+			if (!redir) {
+				ret = NF_DROP;
+				goto out;
+			}
+			list_prepend(&redirs, redir);
+			init_timer(&redir->destroyme);
+		}
+		/* In case mangling has changed, rewrite this part. */
+		redir->core = ((struct redir_core)
+			       { iph->saddr, iph->daddr,
+				 tcph->source, tcph->dest,
+				 newdst, redirpt });
+		do_tcp_redir(skb, redir);
+		ret = NF_ACCEPT;
+
+	out:
+		UNLOCK_BH(&redir_lock);
+		return ret;
+	}
+
+	default: /* give up if not TCP or UDP. */
+		return NF_DROP;
+	}
+}
+
+static void destroyme(unsigned long me)
+{
+	LOCK_BH(&redir_lock);
+	LIST_DELETE(&redirs, (struct redir *)me);
+	UNLOCK_BH(&redir_lock);
+}
+
+/* Incoming packet: is it a reply to a masqueraded connection, or
+   part of an already-redirected TCP connection? */
+void
+check_for_redirect(struct sk_buff *skb)
+{
+	struct iphdr *iph = skb->nh.iph;
+	struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+						+ iph->ihl);
+	struct redir *redir;
+
+	if (iph->protocol != IPPROTO_TCP)
+		return;
+
+	LOCK_BH(&redir_lock);
+	redir = find_redir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
+	if (redir) {
+		DEBUGP("Doing tcp redirect again.\n");
+		do_tcp_redir(skb, redir);
+		if (tcph->rst || tcph->fin) {
+			redir->destroyme.function = destroyme;
+			redir->destroyme.data = (unsigned long)redir;
+			mod_timer(&redir->destroyme, 75*HZ);
+		}
+	}
+	UNLOCK_BH(&redir_lock);
+}
+
+void
+check_for_unredirect(struct sk_buff *skb)
+{
+	struct iphdr *iph = skb->nh.iph;
+	struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+						+ iph->ihl);
+	struct redir *redir;
+
+	if (iph->protocol != IPPROTO_TCP)
+		return;
+
+	LOCK_BH(&redir_lock);
+	redir = find_unredir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
+	if (redir) {
+		DEBUGP("Doing tcp unredirect.\n");
+		do_tcp_unredir(skb, redir);
+		if (tcph->rst || tcph->fin) {
+			redir->destroyme.function = destroyme;
+			redir->destroyme.data = (unsigned long)redir;
+			mod_timer(&redir->destroyme, 75*HZ);
+		}
+	}
+	UNLOCK_BH(&redir_lock);
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)