patch-1.3.81 linux/net/ipv4/ip_masq.c

Next file: linux/net/ipv4/ip_masq_app.c
Previous file: linux/net/ipv4/ip_input.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.80/linux/net/ipv4/ip_masq.c linux/net/ipv4/ip_masq.c
@@ -0,0 +1,672 @@
+/*
+ *
+ * 	Masquerading functionality
+ *
+ * 	Copyright (c) 1994 Pauline Middelink
+ *
+ *	See ip_fw.c for original log
+ *
+ * Fixes:
+ *	Juan Jose Ciarlante	:	Modularized application masquerading (see ip_masq_app.c)
+ *	Juan Jose Ciarlante	:	New struct ip_masq_seq that holds output/input delta seq.
+ *	Juan Jose Ciarlante	:	Added hashed lookup by proto,maddr,mport and proto,saddr,sport
+ *	Juan Jose Ciarlante	:	Fixed deadlock if free ports get exhausted
+ *	Juan Jose Ciarlante	:	Added NO_ADDR status flag.
+ *
+ *	
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/skbuff.h>
+#include <asm/system.h>
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <net/protocol.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <net/ip_masq.h>
+
+#define IP_MASQ_TAB_SIZE 256    /* must be power of 2 */
+
+/*
+ *	Implement IP packet masquerading
+ */
+
+static const char *strProt[] = {"UDP","TCP"};
+
+static __inline__ const char * masq_proto_name(unsigned proto)
+{
+        return strProt[proto==IPPROTO_TCP];
+}
+
+/*
+ *	Last masq_port number in use.
+ *	Will cycle in MASQ_PORT boundaries.
+ */
+static __u16 masq_port = PORT_MASQ_BEGIN;
+
+/*
+ *	free ports counters (UDP & TCP)
+ *
+ *	Their value is _less_ or _equal_ to actual free ports:
+ *	same masq port, diff masq addr (firewall iface address) allocated
+ *	entries are accounted but their actually don't eat a more than 1 port.
+ *
+ *	Greater values could lower MASQ_EXPIRATION setting as a way to
+ *	manage 'masq_entries resource'.
+ *	
+ */
+
+int ip_masq_free_ports[2] = {
+        PORT_MASQ_END - PORT_MASQ_BEGIN, 	/* UDP */
+        PORT_MASQ_END - PORT_MASQ_BEGIN 	/* TCP */
+};
+
+static struct symbol_table ip_masq_syms = {
+#include <linux/symtab_begin.h>
+	X(ip_masq_new),
+        X(ip_masq_set_expire),
+        X(ip_masq_free_ports),
+#include <linux/symtab_end.h>
+};
+
+/*
+ *	2 ip_masq hash tables: for input and output pkts lookups.
+ */
+
+struct ip_masq *ip_masq_m_tab[IP_MASQ_TAB_SIZE];
+struct ip_masq *ip_masq_s_tab[IP_MASQ_TAB_SIZE];
+
+/*
+ *	Returns hash value
+ */
+
+static __inline__ unsigned
+
+ip_masq_hash_key(unsigned proto, __u32 addr, __u16 port)
+{
+        return (proto^ntohl(addr)^ntohs(port)) & (IP_MASQ_TAB_SIZE-1);
+}
+
+/*
+ *	Hashes ip_masq by its proto,addrs,ports.
+ *	should be called with masked interrupts.
+ *	returns bool success.
+ */
+
+static __inline__ int
+ip_masq_hash(struct ip_masq *ms)
+{
+        unsigned hash;
+
+        if (ms->flags & IP_MASQ_F_HASHED) {
+                printk("ip_masq_hash(): request for already hashed\n");
+                return 0;
+        }
+        /*
+         *	Hash by proto,m{addr,port}
+         */
+        hash = ip_masq_hash_key(ms->protocol, ms->maddr, ms->mport);
+        ms->m_link = ip_masq_m_tab[hash];
+        ip_masq_m_tab[hash] = ms;
+        
+        /*
+         *	Hash by proto,s{addr,port}
+         */
+        hash = ip_masq_hash_key(ms->protocol, ms->saddr, ms->sport);
+        ms->s_link = ip_masq_s_tab[hash];
+        ip_masq_s_tab[hash] = ms;
+        
+
+        ms->flags |= IP_MASQ_F_HASHED;
+        return 1;
+}
+
+/*
+ *	UNhashes ip_masq from ip_masq_[ms]_tables.
+ *	should be called with masked interrupts.
+ *	returns bool success.
+ */
+
+static __inline__ int ip_masq_unhash(struct ip_masq *ms)
+{
+        unsigned hash;
+        struct ip_masq ** ms_p;
+        if (!(ms->flags & IP_MASQ_F_HASHED)) {
+                printk("ip_masq_unhash(): request for unhash flagged\n");
+                return 0;
+        }
+        /*
+         *	UNhash by m{addr,port}
+         */
+        hash = ip_masq_hash_key(ms->protocol, ms->maddr, ms->mport);
+        for (ms_p = &ip_masq_m_tab[hash]; *ms_p ; ms_p = &(*ms_p)->m_link)
+                if (ms == (*ms_p))  {
+                        *ms_p = ms->m_link;
+                        break;
+                }
+        /*
+         *	UNhash by s{addr,port}
+         */
+        hash = ip_masq_hash_key(ms->protocol, ms->saddr, ms->sport);
+        for (ms_p = &ip_masq_s_tab[hash]; *ms_p ; ms_p = &(*ms_p)->s_link)
+                if (ms == (*ms_p))  {
+                        *ms_p = ms->s_link;
+                        break;
+                }
+       
+        ms->flags &= ~IP_MASQ_F_HASHED;
+        return 1;
+}
+
+/*
+ *	Returns ip_masq associated with addresses found in iph.
+ *	called for pkts coming from outside-to-INside the firewall
+ *
+ * 	NB. Cannot check destination address, just for the incoming port.
+ * 	reason: archie.doc.ac.uk has 6 interfaces, you send to
+ * 	phoenix and get a reply from any other interface(==dst)!
+ *
+ * 	[Only for UDP] - AC
+ */
+
+struct ip_masq *
+ip_masq_in_get(struct iphdr *iph)
+{
+        unsigned hash;
+        struct ip_masq *ms;
+ 	__u16 *portptr;
+        int protocol;
+        __u32 s_addr, d_addr;
+        __u16 s_port, d_port;
+
+ 	portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+        protocol = iph->protocol;
+        s_addr = iph->saddr;
+        s_port = portptr[0];
+        d_addr = iph->daddr;
+        d_port = portptr[1];
+        
+        hash = ip_masq_hash_key(protocol, d_addr, d_port);
+        for(ms = ip_masq_m_tab[hash]; ms ; ms = ms->m_link) {
+ 		if ( protocol==ms->protocol &&
+		    (s_addr==ms->daddr || ms->flags & IP_MASQ_F_NO_DADDR) &&
+                    (s_port==ms->dport || ms->flags & IP_MASQ_F_NO_DPORT) &&
+                    (d_addr==ms->maddr && d_port==ms->mport))
+                        return ms;
+        }
+        return NULL;
+}
+
+/*
+ *	Returns ip_masq associated with addresses found in iph.
+ *	called for pkts coming from inside-to-OUTside the firewall.
+ */
+
+struct ip_masq *
+ip_masq_out_get(struct iphdr *iph)
+{
+        unsigned hash;
+        struct ip_masq *ms;
+ 	__u16 *portptr;
+        int protocol;
+        __u32 s_addr, d_addr;
+        __u16 s_port, d_port;
+
+ 	portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+        protocol = iph->protocol;
+        s_addr = iph->saddr;
+        s_port = portptr[0];
+        d_addr = iph->daddr;
+        d_port = portptr[1];
+        
+        hash = ip_masq_hash_key(protocol, s_addr, s_port);
+        for(ms = ip_masq_s_tab[hash]; ms ; ms = ms->s_link) {
+		if (protocol == ms->protocol &&
+		    s_addr == ms->saddr && s_port == ms->sport &&
+                    d_addr == ms->daddr && d_port == ms->dport )
+                        return ms;
+        }
+        
+        return NULL;
+}
+
+/*
+ *	Returns ip_masq for given proto,m_addr,m_port.
+ *      called by allocation routine to find an unused m_port.
+ */
+
+struct ip_masq *
+ip_masq_getbym(int protocol, __u32 m_addr, __u16 m_port)
+{
+        unsigned hash;
+        struct ip_masq *ms;
+        
+        hash = ip_masq_hash_key(protocol, m_addr, m_port);
+        for(ms = ip_masq_m_tab[hash]; ms ; ms = ms->m_link) {
+ 		if ( protocol==ms->protocol &&
+                    (m_addr==ms->maddr && m_port==ms->mport))
+                        return ms;
+        }
+        return NULL;
+}
+
+static void masq_expire(unsigned long data)
+{
+	struct ip_masq *ms = (struct ip_masq *)data;
+	unsigned long flags;
+
+#ifdef DEBUG_CONFIG_IP_MASQUERADE
+	printk("Masqueraded %s %lX:%X expired\n",
+			masq_proto_name(ms->protocol),
+			ntohl(ms->src),ntohs(ms->sport));
+#endif
+	
+	save_flags(flags);
+	cli();
+
+        if (ip_masq_unhash(ms)) {
+                ip_masq_free_ports[ms->protocol==IPPROTO_TCP]++;
+                ip_masq_unbind_app(ms);
+                kfree_s(ms,sizeof(*ms));
+        }
+        
+	restore_flags(flags);
+}
+
+/*
+ * 	Create a new masquerade list entry, also allocate an
+ * 	unused mport, keeping the portnumber between the
+ * 	given boundaries MASQ_BEGIN and MASQ_END.
+ */
+
+struct ip_masq * ip_masq_new(struct device *dev, int proto, __u32 saddr, __u16 sport, __u32 daddr, __u16 dport, unsigned mflags)
+{
+        struct ip_masq *ms, *mst;
+        int ports_tried, *free_ports_p;
+	unsigned long flags;
+        static int n_fails = 0;
+        
+        free_ports_p = &ip_masq_free_ports[proto==IPPROTO_TCP];
+        
+        if (*free_ports_p == 0) {
+                if (++n_fails < 5)
+                        printk("ip_masq_new(proto=%s): no free ports.\n",
+                               masq_proto_name(proto));
+                return NULL;
+        }
+        ms = (struct ip_masq *) kmalloc(sizeof(struct ip_masq), GFP_ATOMIC);
+        if (ms == NULL) {
+                if (++n_fails < 5)
+                        printk("ip_masq_new(proto=%s): no memory available.\n",
+                               masq_proto_name(proto));
+                return NULL;
+        }
+        memset(ms, 0, sizeof(*ms));
+	init_timer(&ms->timer);
+	ms->timer.data     = (unsigned long)ms;
+	ms->timer.function = masq_expire;
+        ms->protocol	   = proto;
+        ms->saddr    	   = saddr;
+        ms->sport	   = sport;
+        ms->daddr	   = daddr;
+        ms->dport	   = dport;
+        ms->flags	   = mflags;
+        
+        if (proto == IPPROTO_UDP)
+                ms->flags |= IP_MASQ_F_NO_DADDR;
+        
+        /* get masq address from rif */
+        ms->maddr	   = dev->pa_addr;
+
+        for (ports_tried = 0; ports_tried < *free_ports_p; ports_tried++){
+                save_flags(flags);
+                cli();
+                
+		/*
+                 *	Try the next available port number
+                 */
+                
+		ms->mport = htons(masq_port++);
+		if (masq_port==PORT_MASQ_END) masq_port = PORT_MASQ_BEGIN;
+                
+                restore_flags(flags);
+                
+                /*
+                 *	lookup to find out if this port is used.
+                 */
+                
+                mst = ip_masq_getbym(proto, ms->maddr, ms->mport);
+                if (mst == NULL) {
+                        save_flags(flags);
+                        cli();
+                
+                        if (ip_masq_free_ports == 0) {
+                                restore_flags(flags);
+                                break;
+                        }
+                        (*free_ports_p)--;
+                        ip_masq_hash(ms);
+                        
+                        restore_flags(flags);
+                        
+                        ip_masq_bind_app(ms);
+                        n_fails = 0;
+                        return ms;
+                }
+        }
+        
+        if (++n_fails < 5)
+                printk("ip_masq_new(proto=%s): could not get free masq entry (free=%d).\n",
+                       masq_proto_name(ms->protocol), *free_ports_p);
+        kfree_s(ms, sizeof(*ms));
+        return NULL;
+}
+
+/*
+ * 	Set masq expiration (deletion) and adds timer,
+ *	if timeout==0 cancel expiration.
+ *	Warning: it does not check/delete previous timer!
+ */
+
+void ip_masq_set_expire(struct ip_masq *ms, unsigned long tout)
+{
+        if (tout) {
+                ms->timer.expires = jiffies+tout;
+                add_timer(&ms->timer);
+        } else {
+                del_timer(&ms->timer);
+        }
+}
+
+static void recalc_check(struct udphdr *uh, __u32 saddr,
+	__u32 daddr, int len)
+{
+	uh->check=0;
+	uh->check=csum_tcpudp_magic(saddr,daddr,len,
+		IPPROTO_UDP, csum_partial((char *)uh,len,0));
+	if(uh->check==0)
+		uh->check=0xFFFF;
+}
+	
+void ip_fw_masquerade(struct sk_buff **skb_ptr, struct device *dev)
+{
+	struct sk_buff  *skb=*skb_ptr;
+	struct iphdr	*iph = skb->h.iph;
+	__u16	*portptr;
+	struct ip_masq	*ms;
+	int		size;
+        unsigned long 	timeout;
+
+	/*
+	 * We can only masquerade protocols with ports...
+	 */
+
+	if (iph->protocol!=IPPROTO_UDP && iph->protocol!=IPPROTO_TCP)
+		return;
+ 
+	/*
+	 *	Now hunt the list to see if we have an old entry
+	 */
+
+	portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+#ifdef DEBUG_CONFIG_IP_MASQUERADE
+	printk("Outgoing %s %lX:%X -> %lX:%X\n",
+		masq_proto_name(iph->protocol),
+		ntohl(iph->saddr), ntohs(portptr[0]),
+		ntohl(iph->daddr), ntohs(portptr[1]));
+#endif
+        
+        ms = ip_masq_out_get(iph);
+        if (ms!=NULL)
+                ip_masq_set_expire(ms,0);
+
+	/*
+	 *	Nope, not found, create a new entry for it
+	 */
+	 
+	if (ms==NULL) 
+	{
+                ms = ip_masq_new(dev, iph->protocol,
+                                 iph->saddr, portptr[0],
+                                 iph->daddr, portptr[1],
+                                 0);
+                if (ms == NULL)
+			return;
+ 	}
+ 
+ 	/*
+ 	 *	Change the fragments origin
+ 	 */
+ 	 
+ 	size = skb->len - ((unsigned char *)portptr - skb->h.raw);
+        /*
+         *	Set iph addr and port from ip_masq obj.
+         */
+ 	iph->saddr = ms->maddr;
+ 	portptr[0] = ms->mport;
+ 
+ 	/*
+ 	 *	Attempt ip_masq_app call.
+         *	will fix ip_masq and iph seq stuff
+ 	 */
+        if (ip_masq_app_pkt_out(ms, skb_ptr, dev) != 0)
+	{
+                /*
+                 *	skb has possibly changed, update pointers.
+                 */
+                skb = *skb_ptr;
+                iph = skb->h.iph;
+                portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+                size = skb->len - ((unsigned char *)portptr-skb->h.raw);
+        }
+        
+ 	/*
+ 	 *	Adjust packet accordingly to protocol
+ 	 */
+ 	 
+ 	if (iph->protocol==IPPROTO_UDP) 
+ 	{
+                timeout = MASQUERADE_EXPIRE_UDP;
+ 		recalc_check((struct udphdr *)portptr,iph->saddr,iph->daddr,size);
+ 	}
+ 	else 
+ 	{
+ 		struct tcphdr *th;
+ 		th = (struct tcphdr *)portptr;
+ 
+ 		/*
+ 		 *	Timeout depends if FIN packet was seen
+ 		 */
+ 		if (ms->flags & IP_MASQ_F_SAW_FIN || th->fin)
+                {
+                        timeout = MASQUERADE_EXPIRE_TCP_FIN;
+ 			ms->flags |= IP_MASQ_F_SAW_FIN;
+ 		}
+ 		else timeout = MASQUERADE_EXPIRE_TCP;
+ 
+		skb->csum = csum_partial((void *)(th + 1), size - sizeof(*th), 0);
+ 		tcp_send_check(th,iph->saddr,iph->daddr,size,skb);
+ 	}
+        ip_masq_set_expire(ms, timeout);
+ 	ip_send_check(iph);
+ 
+ #ifdef DEBUG_CONFIG_IP_MASQUERADE
+ 	printk("O-routed from %lX:%X over %s\n",ntohl(ms->maddr),ntohs(ms->mport),dev->name);
+ #endif
+ }
+ 
+ /*
+  *	Check if it's an masqueraded port, look it up,
+  *	and send it on it's way...
+  *
+  *	Better not have many hosts using the designated portrange
+  *	as 'normal' ports, or you'll be spending many time in
+  *	this function.
+  */
+
+int ip_fw_demasquerade(struct sk_buff **skb_p, struct device *dev)
+{
+        struct sk_buff 	*skb = *skb_p;
+ 	struct iphdr	*iph = skb->h.iph;
+ 	__u16	*portptr;
+ 	struct ip_masq	*ms;
+ 
+ 	if (iph->protocol!=IPPROTO_UDP && iph->protocol!=IPPROTO_TCP)
+ 		return 0;
+ 
+ 	portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+ 	if (ntohs(portptr[1]) < PORT_MASQ_BEGIN ||
+ 	    ntohs(portptr[1]) > PORT_MASQ_END)
+ 		return 0;
+ 
+#ifdef DEBUG_CONFIG_IP_MASQUERADE
+ 	printk("Incoming %s %lX:%X -> %lX:%X\n",
+ 		masq_proto_name(iph->protocol),
+ 		ntohl(iph->saddr), ntohs(portptr[0]),
+ 		ntohl(iph->daddr), ntohs(portptr[1]));
+#endif
+ 	/*
+ 	 * reroute to original host:port if found...
+         */
+        
+        ms = ip_masq_in_get(iph);
+        
+        if (ms != NULL)
+        {
+                int size;
+        
+                /*
+                 *	Set dport if not defined yet.
+                 */
+                
+                if ( ms->flags & IP_MASQ_F_NO_DPORT && ms->protocol == IPPROTO_TCP ) {
+                        ms->flags &= ~IP_MASQ_F_NO_DPORT;
+                        ms->dport = portptr[0];
+#if DEBUG_CONFIG_IP_MASQUERADE
+                        printk("ip_fw_demasquerade(): filled dport=%d\n",
+                               ntohs(ms->dport));
+#endif
+                }
+                if (ms->flags & IP_MASQ_F_NO_DADDR && ms->protocol == IPPROTO_TCP)  {
+                        ms->flags &= ~IP_MASQ_F_NO_DADDR;
+                        ms->daddr = iph->saddr;
+#if DEBUG_CONFIG_IP_MASQUERADE
+                        printk("ip_fw_demasquerade(): filled daddr=%X\n",
+                               ntohs(ms->daddr));
+#endif
+                }
+                size = skb->len - ((unsigned char *)portptr - skb->h.raw);
+                iph->daddr = ms->saddr;
+                portptr[1] = ms->sport;
+                
+                /*
+                 *	Attempt ip_masq_app call.
+                 *	will fix ip_masq and iph ack_seq stuff
+                 */
+                
+                if (ip_masq_app_pkt_in(ms, skb_p, dev) != 0)
+                {
+                        /*
+                         *	skb has changed, update pointers.
+                         */
+                        
+                        skb = *skb_p;
+                        iph = skb->h.iph;
+                        portptr = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+                        size = skb->len - ((unsigned char *)portptr-skb->h.raw);
+                }
+                
+                /*
+                 * Yug! adjust UDP/TCP and IP checksums
+                 */
+                if (iph->protocol==IPPROTO_UDP)
+                        recalc_check((struct udphdr *)portptr,iph->saddr,iph->daddr,size);
+                else
+                {
+                        skb->csum = csum_partial((void *)(((struct tcphdr *)portptr) + 1),
+                                                 size - sizeof(struct tcphdr), 0);
+                        tcp_send_check((struct tcphdr *)portptr,iph->saddr,iph->daddr,size,skb);
+                }
+                ip_send_check(iph);
+#ifdef DEBUG_CONFIG_IP_MASQUERADE
+                printk("I-routed to %lX:%X\n",ntohl(iph->daddr),ntohs(portptr[1]));
+#endif
+                return 1;
+ 	}
+        
+ 	/* sorry, all this trouble for a no-hit :) */
+ 	return 0;
+}
+
+/*
+ *	/proc/net entry
+ */
+
+static int ip_msqhst_procinfo(char *buffer, char **start, off_t offset,
+			      int length, int unused)
+{
+	off_t pos=0, begin=0;
+	struct ip_masq *ms;
+	unsigned long flags;
+        int idx = 0;
+	int len=0;
+	
+	len=sprintf(buffer,"Prc FromIP   FPrt ToIP     TPrt Masq Init-seq  Delta PDelta Expires (free=%d,%d)\n",
+                    ip_masq_free_ports[0], ip_masq_free_ports[1]); 
+	save_flags(flags);
+	cli();
+        
+        for(idx = 0; idx < IP_MASQ_TAB_SIZE; idx++)
+        for(ms = ip_masq_m_tab[idx]; ms ; ms = ms->m_link)
+	{
+		int timer_active = del_timer(&ms->timer);
+		if (!timer_active)
+			ms->timer.expires = jiffies;
+		len+=sprintf(buffer+len,"%s %08lX:%04X %08lX:%04X %04X %08X %6d %6d %lu\n",
+			masq_proto_name(ms->protocol),
+			ntohl(ms->saddr),ntohs(ms->sport),
+			ntohl(ms->daddr),ntohs(ms->dport),
+			ntohs(ms->mport),
+			ms->out_seq.init_seq,ms->out_seq.delta,ms->out_seq.previous_delta,ms->timer.expires-jiffies);
+		if (timer_active)
+			add_timer(&ms->timer);
+
+		pos=begin+len;
+		if(pos<offset) 
+		{
+ 			len=0;
+			begin=pos;
+		}
+		if(pos>offset+length)
+			break;
+        }
+	restore_flags(flags);
+	*start=buffer+(offset-begin);
+	len-=(offset-begin);
+	if(len>length)
+		len=length;
+	return len;
+}
+
+/*
+ *	Initialize ip masquerading
+ */
+int ip_masq_init(void)
+{  
+        register_symtab (&ip_masq_syms);
+	proc_net_register(&(struct proc_dir_entry) {
+		PROC_NET_IPMSQHST, 13, "ip_masquerade",
+		S_IFREG | S_IRUGO, 1, 0, 0,
+		0, &proc_net_inode_operations,
+		ip_msqhst_procinfo
+	});
+        ip_masq_app_init();
+        
+        return 0;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this