/*
 *
 *			   IPSEC for Linux
 *		         Preliminary Release
 * 
 *	 Copyright (C) 1996, 1997, John Ioannidis <ji@hol.gr>
 *
 * Changes by Angelos D. Keromytis and Niels Provos
 * ported from OpenBSD 2.2 by Petr Novak, <pn@i.cz>
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

/*
 * IPSEC Tunneling code. Heavily based on drivers/net/new_tunnel.c
 */


/* Things I wish I had known when writing the tunnel driver:

	When the tunnel_xmit() function is called, the skb contains the
	packet to be sent (plus a great deal of extra info), and dev
	contains the tunnel device that _we_ are.

	When we are passed a packet, we are expected to fill in the
	source address with our source IP address.

	What is the proper way to allocate, copy and free a buffer?
	After you allocate it, it is a "0 length" chunk of memory
	starting at zero.  If you want to add headers to the buffer
	later, you'll have to call "skb_reserve(skb, amount)" with
	the amount of memory you want reserved.  Then, you call
	"skb_put(skb, amount)" with the amount of space you want in
	the buffer.  skb_put() returns a pointer to the top (#0) of
	that buffer.  skb->len is set to the amount of space you have
	"allocated" with skb_put().  You can then write up to skb->len
	bytes to that buffer.  If you need more, you can call skb_put()
	again with the additional amount of space you need.  You can
	find out how much more space you can allocate by calling 
	"skb_tailroom(skb)".
	Now, to add header space, call "skb_push(skb, header_len)".
	This creates space at the beginning of the buffer and returns
	a pointer to this new space.  If later you need to strip a
	header from a buffer, call "skb_pull(skb, header_len)".
	skb_headroom() will return how much space is left at the top
	of the buffer (before the main data).  Remember, this headroom
	space must be reserved before the skb_put() function is called.
*/

#define __NO_VERSION__
#include <linux/module.h>
#include <linux/config.h>	/* for CONFIG_IP_FORWARD */

#include <asm/segment.h>
#include <asm/system.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/config.h>

#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/icmp.h>
#include <linux/udp.h>
#include <linux/timer.h>
#include <net/ip.h>
#include <net/protocol.h>
#include <net/route.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <net/sock.h>
#include <net/icmp.h>

#include <net/checksum.h>

#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/miscdevice.h>

#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>

#include <net/netlink.h>
#include <unistd.h>
#include "radij.h"
#include "ipsec_encap.h"
#include "ipsec_radij.h"
#include "ipsec_netlink.h"
#include "ipsec_xform.h"
#include "ipsec_tunnel.h"
#include "ipsec_ipe4.h"

/* Only two headers!! :-) */
#include <net/ip.h>
#include <linux/if_arp.h>


#ifdef DEBUG_IPSEC_TUNNEL
int debug_tunnel = -1;
#endif

#define TUNL_HLEN	96		/* should be 96 XXXXX big enough, multiple of 8 */

/*
 *	This is mostly skbuff.c:skb_copy().
 */
 
struct sk_buff *skb_copy_expand(struct sk_buff *skb, int priority, int headroom, int tailroom)
{
	struct sk_buff *n;
	unsigned long offset;

	/*
	 *	Allocate the copy buffer
	 */
	 
	IS_SKB(skb);
	
	n=alloc_skb(skb->truesize-sizeof(struct sk_buff) + headroom+tailroom, priority);
	if(n==NULL)
		return NULL;

	/*
	 *	Shift between the two data areas in bytes
	 */
	 
	/* offset=n->head-skb->head; */ /* moved down a few lines */

	/* Set the data pointer */
	skb_reserve(n,skb->data-skb->head+headroom);
	/* Set the tail pointer and length */
	skb_put(n,skb->len);

	offset=n->head + headroom - skb->head;

	/* Copy the bytes */
	memcpy(n->head + headroom, skb->head,skb->end-skb->head);
	n->link3=NULL;
	n->list=NULL;
	n->sk=NULL;
	n->when=skb->when;
	n->dev=skb->dev;
	n->h.raw=skb->h.raw+offset;
	n->mac.raw=skb->mac.raw+offset;
	n->ip_hdr=(struct iphdr *)(((char *)skb->ip_hdr)+offset);
	n->saddr=skb->saddr;
	n->daddr=skb->daddr;
	n->raddr=skb->raddr;
	n->seq=skb->seq;
	n->end_seq=skb->end_seq;
	n->ack_seq=skb->ack_seq;
	n->acked=skb->acked;
	memcpy(n->proto_priv, skb->proto_priv, sizeof(skb->proto_priv));
	n->used=skb->used;
	n->free=1;
	n->arp=skb->arp;
	n->tries=0;
	n->lock=0;
	n->users=0;
	n->pkt_type=skb->pkt_type;
	n->stamp=skb->stamp;
	
	IS_SKB(n);
	return n;
}

#ifdef DEBUG_IPSEC_TUNNEL
static void print_ip(struct iphdr *ip)
{
	unsigned char *ipaddr;
	int i;

	/*	printk("IP packet:\n"); */
	printk("header len = %d, ", ip->ihl*4);
	printk("ip version: %d, ", ip->version);
	printk("ip protocol: %d\n", ip->protocol);
	ipaddr=(unsigned char *)&ip->saddr;
	printk("ipsec_print_ip: src addr: %u.%u.%u.%u, ", 
			*ipaddr, *(ipaddr+1), *(ipaddr+2), *(ipaddr+3));
	ipaddr=(unsigned char *)&ip->daddr;
	printk("dst addr: %u.%u.%u.%u, ", 
			*ipaddr, *(ipaddr+1), *(ipaddr+2), *(ipaddr+3));
	printk("pkt len: %d\n", ntohs(ip->tot_len));
	printk("payload:");
	for (i=0; i < ntohs(ip->tot_len)-sizeof(struct iphdr); i++)
	{
		printk(" %02x", *((unsigned char *)(ip+1)+i));
	}
	printk("\n");
}
#endif DEBUG_IPSEC_TUNNEL

/*
 *	This function assumes it is being called from dev_queue_xmit()
 *	and that skb is filled properly by that function.
 */

static int
ipsec_tunnel_do_xmit(struct sk_buff *oskb, struct device *odev)
{
	struct device *dev;			/* Our device */
	struct ipsecpriv *prv;
	struct sk_buff *skb;
	struct enet_statistics *stats;		/* This device's statistics */
	struct iphdr  *iph;			/* Our new IP header */
	__u32          target;			/* The other host's IP address */
	__u32	orgdst;			/* Original IP destinatin address */
	int	iphlen;
	int	pyldsz;
	int      max_headroom;			/* The extra header space needed */
	int	max_tailroom;		/*  The extra stuffing needed */
	__u8	savedheader[16];	/* should be plenty of room */
	int i;

	struct udphdr *udpp;
	struct tcphdr *tcpp;
	

	struct sockaddr_encap matcher, *gw;
	struct eroute *er;
	struct tdb *tdbp, *tdbq;
	int oerror;
	
#ifdef DEBUG_IPSEC_TUNNEL
	struct timeval tv;
#endif DEBUG_IPSEC_TUNNEL
	stats = (struct enet_statistics *)odev->priv;

	/*
	 *	Return if there is nothing to do.  (Does this ever happen?)
	 */
	if (oskb == NULL || odev == NULL) {
#ifdef DEBUG_IPSEC_TUNNEL
		if (debug_tunnel & DB_TN_XMIT)
			printk ( KERN_INFO "tunnel: Nothing to do!\n" );
#endif DEBUG_IPSEC_TUNNEL
		return 0;
	}

	prv = odev->priv;
	dev = prv->dev;

#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_XMIT)
	{
		int i;
		char c;
		
		printk("ipsec_tunnel_do_xmit: hard_header_len = %d", dev->hard_header_len);
		c = ' ';
		
		for (i=0; i < dev->hard_header_len; i++)
		{
			printk("%c%02x", c, oskb->data[i]);
			c = ':';
		}
		printk("\n");
	}
#endif DEBUG_IPSEC_TUNNEL
	skb = skb_copy_expand(oskb, GFP_ATOMIC, 64, 32);
	if (skb)
	{
		skb->dev = prv->dev;
		skb->arp = 1;
	}
	else
		return 0;

	iph = (struct iphdr *) (skb->data + dev->hard_header_len);
	iphlen = iph->ihl << 2;
	pyldsz = ntohs(iph->tot_len) - iphlen;

#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_XMIT)
	{
		printk("ipsec_tunnel_do_xmit: packet contents:");
		print_ip(iph);
	}
#endif DEBUG_IPSEC_TUNNEL

	/*
	 * First things first -- look us up in the erouting tables.
	 */

/*
	matcher.sen_len = sizeof (struct sockaddr_encap);
*/
	matcher.sen_len = SENT_IP4_LEN;
	matcher.sen_family = AF_ENCAP;
	matcher.sen_type = SENT_IP4;
	
	matcher.sen_ip_src.s_addr = iph->saddr;
	matcher.sen_ip_dst.s_addr = iph->daddr;
	matcher.sen_proto = iph->protocol;

	switch (iph->protocol)
	{
	  case IPPROTO_UDP:
		udpp = (struct udphdr *)(skb->data + iphlen);
		matcher.sen_sport = ntohs(udpp->source);
		matcher.sen_dport = ntohs(udpp->dest);
		break;

	  case IPPROTO_TCP:
		tcpp = (struct tcphdr *)(skb->data + iphlen);
		matcher.sen_sport = ntohs(tcpp->source);
		matcher.sen_dport = ntohs(tcpp->dest);
		break;

	  default:
		matcher.sen_sport = 0;
		matcher.sen_dport = 0;
	}
	
	er = ipsec_findroute(&matcher);
	if (er == NULL)
	{
		printk("ipsec_tunnel_do_xmit: %s: Packet [%lx->%lx] with no e-route!\n", 
		       dev->name, ntohl(iph->saddr), ntohl(iph->daddr));
#ifdef DEBUG_IPSEC_TUNNEL
		do_gettimeofday(&tv);
		if (debug_tunnel & DB_TN_REVEC)
		  printk("ipsec_tunnel_do_xmit: ts=%02d.%04d calling dev_queue_xmit\n", 
		       tv.tv_sec % 60, tv.tv_usec / 100);
#endif DEBUG_IPSEC_TUNNEL
		dev->tbusy=0;
		dev_queue_xmit(skb, prv->dev, SOPRI_NORMAL);	/* Jeremey Barrett */
		return 0;
	}

	orgdst = iph->daddr;
	
	for (i = 0; i < dev->hard_header_len; i++)
		savedheader[i] = skb->data[i];
	skb_pull(skb, dev->hard_header_len);

#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_XMIT)
	{
		printk("ipsec_tunnel_do_xmit: packet contents after pull:");
		print_ip(iph);
	}
#endif DEBUG_IPSEC_TUNNEL


	gw = (struct sockaddr_encap *)&(er->er_dst);
	target = gw->sen_ipsp_dst.s_addr;

	if ((gw != NULL) && (target == 0) &&
		(gw->sen_ipsp_sproto == 0) && (gw->sen_ipsp_spi == 0))
	{
		dev->tbusy=0;
		dev_queue_xmit(skb, prv->dev, SOPRI_NORMAL);	/* Jeremey Barrett */
		return 0;
	}

	if (gw == NULL || gw->sen_type != SENT_IPSP)
	{
		printk("ipsec_tunnel_xmit: no gw or gw data not IPSP\n");
		dev->tbusy = 0;
		dev_kfree_skb(skb, FREE_WRITE);
		return 0;
	}

	tdbp = gettdb(gw->sen_ipsp_spi, gw->sen_ipsp_dst, gw->sen_ipsp_sproto);
	if (tdbp == NULL)
	{
		printk("ipsec_tunnel_do_xmit: no tdb for spi=%x dst=%08x\n", 
		       (u_int)ntohl(gw->sen_ipsp_spi),
			(u_int)ntohl(target));
		/* ip_rt_put(rt); */
		dev->tbusy=0;
		/* stats->tx_errors++; */
		dev_kfree_skb(skb, FREE_WRITE);
		return 0;
	}

	max_headroom = max_tailroom = 0;
	
	tdbq = tdbp;			/* save */
	while (tdbp && tdbp->tdb_xform)
	{
		/* Check for tunneling */
		if (tdbp->tdb_flags & TDBF_TUNNELING)
		{
#ifdef DEBUG_IPSEC_TUNNEL
			if (debug_tunnel & DB_TN_CROUT)
				printk("ipsec_tunnel_do_xmit: calling room for IPE4\n");
#endif DEBUG_IPSEC_TUNNEL
			oerror = ipe4_room(tdbp, iphlen, pyldsz + max_headroom + max_tailroom, &max_headroom, &max_tailroom);
		}

#ifdef DEBUG_IPSEC_TUNNEL
		if (debug_tunnel & DB_TN_CROUT)
			printk("ipsec_tunnel_do_xmit: calling room for <%s>\n", 
			       tdbp->tdb_xform->xf_name);
#endif DEBUG_IPSEC_TUNNEL
		oerror = (*(tdbp->tdb_xform->xf_room))
		  (tdbp, iphlen, pyldsz + max_headroom + max_tailroom, &max_headroom, &max_tailroom);
		
		tdbp = tdbp->tdb_onext;
	}
	tdbp = tdbq;			/* restore */

	/*
	 * Okay, now see if we can stuff it in the buffer as-is.
	 */
	max_headroom += ((dev->hard_header_len+15)&~15); /* XXX was tdev */

#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_CROUT)
	{
		printk("ipsec_tunnel_do_xmit: Room left at head: %d\n", skb_headroom(skb));
		printk("ipsec_tunnel_do_xmit: Room left at tail: %d\n", skb_tailroom(skb));
		printk("ipsec_tunnel_do_xmit: Required room: %d,%d, Tunnel hlen: %d\n", 
		       max_headroom, max_tailroom, TUNL_HLEN);
	}
#endif DEBUG_IPSEC_TUNNEL

	if ((skb_headroom(skb) >= max_headroom) && 
	    (skb_tailroom(skb) >= max_tailroom) && skb->free)
	{
#ifdef DEBUG_IPSEC_TUNNEL
		if (debug_tunnel & DB_TN_CROUT)
			printk("ipsec_tunnel_do_xmit: data fits in existing skb\n");
#else /*  DEBUG_IPSEC_TUNNEL */
		;
#endif DEBUG_IPSEC_TUNNEL

	}
	else
	{
#ifdef ALLOW_SECOND_SKB_ALLOC
		struct sk_buff *new_skb;

#ifdef DEBUG_IPSEC_TUNNEL
		if (debug_tunnel & DB_TN_CROUT)
			printk("ipsec_tunnel_do_xmit: allocating new skb\n");
#endif DEBUG_IPSEC_TUNNEL
		if ( !(new_skb = dev_alloc_skb(skb->len+max_headroom+max_tailroom)) ) 
		{
			printk( KERN_INFO "%s: Out of memory, dropped packet\n",
				dev->name);
			/* ip_rt_put(rt); */
  			dev->tbusy = 0;
  			stats->tx_dropped++;
			dev_kfree_skb(skb, FREE_WRITE);
			return 0;
		}
		new_skb->free = 1;

		/*
		 * Reserve space for our header and the lower device header
		 */
		skb_reserve(new_skb, max_headroom);

		/*
		 * Copy the old packet to the new buffer.
		 * Note that new_skb->h.iph will be our (tunnel driver's) header
		 * and new_skb->ip_hdr is the IP header of the old packet.
		 */
		new_skb->ip_hdr = (struct iphdr *) skb_put(new_skb, skb->len);
		new_skb->dev = skb->dev;
		memcpy(new_skb->ip_hdr, skb->data, skb->len);
		memset(new_skb->proto_priv, 0, sizeof(skb->proto_priv));

		/* Tack on our header */
		new_skb->h.iph = (struct iphdr *)(new_skb->data);
		
		/* Free the old packet, we no longer need it */
		dev_kfree_skb(skb, FREE_WRITE);
		skb = new_skb;
#else /* ALLOW_SECOND_SKB_ALLOC */
		printk("ipsec_tunnel_do_xmit: TRIED TO ALLOCATE %d head and %d tailroom\n", 
		       max_headroom, max_tailroom);
		kfree_skb(skb, FREE_WRITE);
		dev->tbusy = 0;
		return 0;
#endif ALLOW_SECOND_SKB_ALLOC
	}

	while (tdbp && tdbp->tdb_xform)
	{
		if (tdbp->tdb_flags & TDBF_TUNNELING) {
#ifdef DEBUG_IPSEC_TUNNEL
			if (debug_tunnel & DB_TN_OXFS)
				printk("ipsec_tunnel_do_xmit: calling output for IPE4...");
#endif DEBUG_IPSEC_TUNNEL
			oerror = ipe4_output(skb, tdbp);
#ifdef DEBUG_IPSEC_TUNNEL
			if (debug_tunnel & DB_TN_OXFS)
				printk("ipsec_tunnel_do_xmit: returns %d\n", oerror);
#endif DEBUG_IPSEC_TUNNEL
		}
		
#ifdef DEBUG_IPSEC_TUNNEL
		if (debug_tunnel & DB_TN_OXFS)
			printk("ipsec_tunnel_do_xmit: calling output for <%s>...", 
			       tdbp->tdb_xform->xf_name);
#endif DEBUG_IPSEC_TUNNEL
		oerror = (*(tdbp->tdb_xform->xf_output))(skb, tdbp);
#ifdef DEBUG_IPSEC_TUNNEL
		if (debug_tunnel & DB_TN_OXFS)
			printk("ipsec_tunnel_do_xmit: returns %d\n", oerror);
#endif DEBUG_IPSEC_TUNNEL
		tdbp = tdbp->tdb_onext;
	}

#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_XMIT)
	{
		printk("ipsec_tunnel_do_xmit: packet contents after xforms:");
		print_ip(skb->h.iph);
	}
#endif DEBUG_IPSEC_TUNNEL


	if (target != orgdst)
	{
		skb->protocol		=	htons(ETH_P_IP);

		/*
		 *	Send the packet on its way!
		 *	Note that dev_queue_xmit() will eventually free the skb.
		 *	If ip_forward() made a copy, it will return 1 so we can free.
		 */

#ifdef DEBUG_IPSEC_TUNNEL
		if (debug_tunnel & DB_TN_INIT)
		{
			
			printk("ipsec_tunnel_do_xmit: skb->data= 0x%p, skb->h.iph= 0x%p, skb->ip_hdr= 0x%p\n", 
			       skb->data, skb->h.iph, skb->ip_hdr);
		
			printk("ipsec_tunnel_do_xmit: ip_forwarding of skb->dev->name %s hopefully equal to %s, %ld bytes, to %x\n", 
			       skb->dev->name, dev->name, skb->len, target);
		}
		do_gettimeofday(&tv);
		if (debug_tunnel & DB_TN_REVEC)
		  printk("ipsec_tunnel_do_xmit: ts=%02d.%04d calling ip_forward\n", 
		       tv.tv_sec % 60, tv.tv_usec / 100);
#endif DEBUG_IPSEC_TUNNEL
		
		if (ip_forward(skb, dev, IPFWD_NOTTLDEC, target))
			kfree_skb(skb, FREE_WRITE);
		
		/*
		 *	Clean up:  We're done with the route and the packet
		 */
	 
		/* ip_rt_put(rt); */
 

		/* Record statistics and return */
		/* stats->tx_packets++; */
		dev->tbusy=0;
		return 0;
	}
	else
	{
		skb_push(skb, dev->hard_header_len);
		for (i = 0; i < dev->hard_header_len; i++)
			skb->data[i] = savedheader[i];
#ifdef DEBUG_IPSEC_TUNNEL
		do_gettimeofday(&tv);
		if (debug_tunnel & DB_TN_REVEC)
		  printk("ipsec_tunnel_do_xmit: ts=%02d.%04d calling dev_queue_xmit\n", 
		       tv.tv_sec % 60, tv.tv_usec / 100);
#endif DEBUG_IPSEC_TUNNEL
/*		dev->tbusy=0; */ /* Petr Novak */
		dev_queue_xmit(skb, prv->dev, SOPRI_NORMAL);	/* Jeremey Barrett */
		return 0;
/*		ret = prv->hard_start_xmit(skb, dev); */
/*		dev->tbusy=0; */ /* Petr Novak */

/*		return ret; */
	}
}

static int ipsec_tunnel_open(struct device *dev)
{
	struct ipsecpriv *prv = dev->priv;
	
	/*
	 * Can't open until attached.
	 */

#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_INIT)
		printk("ipsec_tunnel_open: prv->dev = %s\n", prv->dev?prv->dev->name:"NONE");
#endif DEBUG_IPSEC_TUNNEL

	if (prv->dev == NULL)
		return -ENODEV;
	
	MOD_INC_USE_COUNT;
	return 0;
}

static int ipsec_tunnel_close(struct device *dev)
{
	MOD_DEC_USE_COUNT;
	return 0;
}

/*
 * Revectored calls.
 * For each of these calls, a field exists in our private structure.
 */

static int ipsec_tunnel_start_xmit(struct sk_buff *skb, struct device *dev)
{
	struct enet_statistics *stats;		/* This device's statistics */
	struct ipsecpriv *prv = dev->priv;
	int ret;
	
#ifdef DEBUG_IPSEC_TUNNEL
	struct timeval tv;
	do_gettimeofday(&tv);
	if (debug_tunnel & DB_TN_REVEC)
		printk("ipsec_tunnel_start_xmit: ts=%02d.%04d Revectored start_xmit\n", 
		       tv.tv_sec % 60, tv.tv_usec / 100);
#endif DEBUG_IPSEC_TUNNEL

	stats = (struct enet_statistics *)dev->priv;
	cli();
	if (dev->tbusy != 0) 
	{
		sti();
		stats->tx_errors++;
		printk("ipsec_tunnel_start_xmit: busy, returning\n");
		return 1;
	}
	dev->tbusy = 1;
	sti();

     	if (dev->hard_header_len == 14 &&
	    skb->data[12] == 0x08 &&
	    skb->data[13] == 0x00)
	{
#ifdef DEBUG_IPSEC_TUNNEL
		if (debug_tunnel & DB_TN_REVEC)
			printk("ipsec_tunnel_start_xmit: we have an IP packet on an ethernet\n");
#endif DEBUG_IPSEC_TUNNEL
		ret = ipsec_tunnel_do_xmit(skb, dev);
	}
	else
	{
		struct sk_buff *newskb=skb_clone(skb, GFP_ATOMIC);
#ifdef DEBUG_IPSEC_TUNNEL
		do_gettimeofday(&tv);
		if (debug_tunnel & DB_TN_REVEC)
			printk("ipsec_tunnel_start_xmit: we have a non-ethernet IP packet calling dev_queue_xmit ts=%02d.%04d\n",
			       tv.tv_sec % 60, tv.tv_usec / 100);
#endif DEBUG_IPSEC_TUNNEL
		if(newskb)
		{
			newskb->dev=prv->dev;
			newskb->arp=1;
			dev_queue_xmit(newskb, prv->dev, SOPRI_NORMAL);	/* Jeremey Barrett */
			ret = 0;	/* Jeremey Barrett */
		}
		else
		{
			ret = -1;
		}
	}
	dev_kfree_skb(skb, FREE_WRITE);
	dev->tbusy = 0;
	return ret;
}



static struct enet_statistics *ipsec_tunnel_get_stats(struct device *dev)
{
	return &(((struct ipsecpriv *)(dev->priv))->mystats);
}

static int ipsec_tunnel_hard_header(struct sk_buff *skb, struct device *dev,
	unsigned short type, void *daddr, void *saddr, unsigned len)
{
	struct ipsecpriv *prv = dev->priv;
	
#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_REVEC)
		printk("ipsec_tunnel: Revectored hard_header\n");
#endif DEBUG_IPSEC_TUNNEL
	return prv->hard_header(skb, prv->dev, type, daddr, saddr, len);
}

static int ipsec_tunnel_rebuild_header(void *eth, struct device *dev,
			unsigned long raddr, struct sk_buff *skb)
{
	struct ipsecpriv *prv = skb->dev->priv;
	struct device *tmp;
	int ret;
	
#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_REVEC)
		printk("ipsec_tunnel: Revectored rebuild_header\n");
#endif DEBUG_IPSEC_TUNNEL
	tmp = skb->dev;
	skb->dev = prv->dev;
	
	ret = prv->rebuild_header(eth, prv->dev, raddr, skb);
	skb->dev = tmp;
	return ret;
}

static int ipsec_tunnel_set_mac_address(struct device *dev, void *addr)
{
	struct ipsecpriv *prv = dev->priv;
	
#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_REVEC)
		printk("ipsec_tunnel: Revectored set_mac_address_\n");
#endif DEBUG_IPSEC_TUNNEL
	return prv->set_mac_address(prv->dev, addr);

}

static void ipsec_tunnel_cache_bind(struct hh_cache **hhp, struct device *dev,
				 unsigned short htype, __u32 daddr)
{
	struct ipsecpriv *prv = dev->priv;
	
#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_REVEC)
		printk("ipsec_tunnel: Revectored cache_bind\n");
#endif DEBUG_IPSEC_TUNNEL
	prv->header_cache_bind(hhp, dev, htype, daddr);
	return;
}

static void ipsec_tunnel_cache_update(struct hh_cache *hh, struct device *dev, unsigned char *  haddr)
{
	struct ipsecpriv *prv = dev->priv;
	
#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_REVEC)
		printk("ipsec_tunnel: Revectored cache_update\n");
#endif DEBUG_IPSEC_TUNNEL
	prv->header_cache_update(hh, prv->dev, haddr);
	return;
}

/*
 * We call the attach routine to attach another device.
 */

static int ipsec_tunnel_attach(struct device *tndev, struct ipsecpriv *prv, struct device *dev)
{
	prv->dev = dev;
	prv->hard_start_xmit = dev->hard_start_xmit;
	prv->get_stats = dev->get_stats;

	if (dev->hard_header)
	{
		prv->hard_header = dev->hard_header;
		tndev->hard_header = ipsec_tunnel_hard_header;
	}
	else
		tndev->hard_header = NULL;
	
	if (dev->rebuild_header)
	{
		prv->rebuild_header = dev->rebuild_header;
		tndev->rebuild_header = ipsec_tunnel_rebuild_header;
	}
	else
		tndev->rebuild_header = NULL;
	
	if (dev->set_mac_address)
	{
		prv->set_mac_address = dev->set_mac_address;
		tndev->set_mac_address = ipsec_tunnel_set_mac_address;
	}
	else
		tndev->set_mac_address = NULL;
	
	if (dev->header_cache_bind)
	{
		prv->header_cache_bind = dev->header_cache_bind;
		tndev->header_cache_bind = ipsec_tunnel_cache_bind;
	}
	else
		tndev->header_cache_bind = NULL;

	if (dev->header_cache_update)
	{
		prv->header_cache_update = dev->header_cache_update;
		tndev->header_cache_update = ipsec_tunnel_cache_update;
	}
	else
		tndev->header_cache_update = NULL;

	tndev->hard_header_len = dev->hard_header_len /* XXX + TUNL_HLEN */;
	tndev->mtu = dev->mtu - TUNL_HLEN;
	/* tndev->type = dev->type; should remain tunnel */
	tndev->addr_len = dev->addr_len;
	
	return 0;
}

static int ipsec_tunnel_ioctl(struct device *dev, struct ifreq *ifr, int cmd)
{
	struct ipsectunnelconf *cf = (struct ipsectunnelconf *)&ifr->ifr_data;
	struct ipsecpriv *prv = dev->priv;
	struct device *them = dev_get(cf->cf_name); /* physical device */
	
	switch (cmd)
	{
	/* attatch a virtual ipsec? device to a physical device */
	case IPSEC_SET_DEV:
		if (them == NULL)
		{
#ifdef DEBUG_IPSEC_TUNNEL
			if (debug_tunnel & DB_TN_INIT)
				printk("ipsec_tunnel_ioctl: them is null\n");
#endif DEBUG_IPSEC_TUNNEL

			return -ENODEV;
		}
		
		if (prv->dev)
			return -EBUSY;
		return ipsec_tunnel_attach(dev, dev->priv, them);

/*	case IPSEC_DEL_DEV: */

	default:
		return -EOPNOTSUPP;
	}
}



/*
 *	Called when a new tunnel device is initialized.
 *	The new tunnel device structure is passed to us.
 */
 
int
ipsec_tunnel_init(struct device *dev)
{
	int i;

	static int tun_msg=0;
	if(!tun_msg)
	{
		printk ( KERN_INFO "ipsec_tunnel_init: tunneling code 0.6\n" );
		tun_msg=1;
	}

	/* Add our tunnel functions to the device */
	dev->open		= ipsec_tunnel_open;
	dev->stop		= ipsec_tunnel_close;
	dev->hard_start_xmit	= ipsec_tunnel_start_xmit;
	dev->get_stats		= ipsec_tunnel_get_stats;

	dev->priv = kmalloc(sizeof(struct ipsecpriv), GFP_KERNEL);
	if (dev->priv == NULL)
		return -ENOMEM;
	memset(dev->priv, 0, sizeof(struct ipsecpriv));

	/* Initialize the tunnel device structure */
	for (i = 0; i < DEV_NUMBUFFS; i++)
		skb_queue_head_init(&dev->buffs[i]);

	dev->hard_header	= ipsec_tunnel_hard_header;
	dev->rebuild_header 	= ipsec_tunnel_rebuild_header;
	dev->set_multicast_list = NULL;
	dev->set_mac_address 	= ipsec_tunnel_set_mac_address;
	dev->do_ioctl		= ipsec_tunnel_ioctl;
	dev->header_cache_bind 	= ipsec_tunnel_cache_bind;
	dev->header_cache_update= ipsec_tunnel_cache_update;

	dev->type		= ARPHRD_TUNNEL;	/* initially */
	dev->hard_header_len 	= TUNL_HLEN;		/* plenty */
	dev->mtu		= 1500-TUNL_HLEN; 	/* eth_mtu */
	dev->addr_len		= 0;		/* Is this only for ARP? */
	dev->tx_queue_len	= 10;		/* Small queue */
	memset(dev->broadcast,0xFF, ETH_ALEN);

	/* New-style flags. */
	dev->flags		= IFF_NOARP /* 0 */ /* Petr Novak */;
	dev->family		= AF_INET;
	dev->pa_addr		= 0;
	dev->pa_brdaddr 	= 0;
	dev->pa_mask		= 0;
	dev->pa_alen		= 4;

	/* We're done.  Have I forgotten anything? */
	return 0;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*  Module specific interface (but it links with the rest of IPSEC  */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

static int ipsec_tunnel_probe(struct device *dev)
{
	ipsec_tunnel_init(dev); 
	return 0;
}

static struct device dev_ipsec1 = 
{
	"ipsec1\0   ", 
	0, 0, 0, 0,
 	0x0, 0,
 	0, 0, 0, NULL, ipsec_tunnel_probe 
};

static struct device dev_ipsec = 
{
	"ipsec0\0   ", 
	0, 0, 0, 0,
 	0x0, 0,
 	0, 0, 0, &dev_ipsec1, ipsec_tunnel_probe 
};

int 
ipsec_tunnel_init_module(void)
{
	/* Find a name for this unit */
	int ct= 0;
	do
		sprintf(dev_ipsec.name,"ipsec%d",ct++);
	while(dev_get(dev_ipsec.name)!=NULL && ct<100);
	
#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_INIT)
		printk("ipsec_tunnel_init_module: registering device %s\n", dev_ipsec.name);
#endif DEBUG_IPSEC_TUNNEL
	if (register_netdev(&dev_ipsec) != 0)
		return -EIO;
	return 0;
}

void 
ipsec_tunnel_cleanup_module(void)
{
#ifdef DEBUG_IPSEC_TUNNEL
	if (debug_tunnel & DB_TN_INIT)
		printk("ipsec_tunnel_cleanup_module: unloading module ipsec\n");
#endif DEBUG_IPSEC_TUNNEL
	unregister_netdev(&dev_ipsec);
	kfree_s(dev_ipsec.priv,sizeof(struct enet_statistics));
	dev_ipsec.priv=NULL;
}
