diff options
Diffstat (limited to 'drivers/net/tunnel.c')
-rw-r--r-- | drivers/net/tunnel.c | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/drivers/net/tunnel.c b/drivers/net/tunnel.c new file mode 100644 index 000000000..b091fbc79 --- /dev/null +++ b/drivers/net/tunnel.c @@ -0,0 +1,311 @@ +/* tunnel.c: an IP tunnel driver + + The purpose of this driver is to provide an IP tunnel through + which you can tunnel network traffic transparently across subnets. + + This was written by looking at Nick Holloway's dummy driver + Thanks for the great code! + + -Sam Lantinga (slouken@cs.ucdavis.edu) 02/01/95 + + Minor tweaks: + Cleaned up the code a little and added some pre-1.3.0 tweaks. + dev->hard_header/hard_header_len changed to use no headers. + Comments/bracketing tweaked. + Made the tunnels use dev->name not tunnel: when error reporting. + Added tx_dropped stat + + -Alan Cox (Alan.Cox@linux.org) 21 March 95 +*/ + +#include <linux/config.h> +#ifdef CONFIG_IP_FORWARD +#ifdef MODULE +#include <linux/module.h> +#include <linux/version.h> +#endif + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <netinet/in.h> +#include <linux/ip.h> +#include <linux/string.h> +#include <asm/system.h> +#include <linux/errno.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <net/ip.h> + +#include <net/checksum.h> /* If using 1.3.0-pre net code */ + +#define ip_header_len sizeof(struct iphdr) + +static int tunnel_xmit(struct sk_buff *skb, struct device *dev); +static struct enet_statistics *tunnel_get_stats(struct device *dev); + +#ifdef MODULE +static int tunnel_open(struct device *dev) +{ + MOD_INC_USE_COUNT; + return 0; +} + +static int tunnel_close(struct device *dev) +{ + MOD_DEC_USE_COUNT; + return 0; +} + +#endif + + +int tunnel_init(struct device *dev) +{ + static int tun_msg=0; + if(!tun_msg) + { + printk ( KERN_INFO "tunnel: version v0.1a\n" ); + tun_msg=1; + } + + /* Fill in fields of the dev structure with ethernet-generic values. */ + ether_setup(dev); + + /* Custom initialize the device structure. */ + dev->hard_start_xmit = tunnel_xmit; + dev->get_stats = tunnel_get_stats; + dev->priv = kmalloc(sizeof(struct enet_statistics), GFP_KERNEL); + memset(dev->priv, 0, sizeof(struct enet_statistics)); +#ifdef MODULE + dev->open = &tunnel_open; + dev->stop = &tunnel_close; +#endif + dev->type = ARPHRD_TUNNEL; /* IP tunnel hardware type (Linux 1.1.89) */ + dev->flags |= IFF_NOARP; + dev->flags |= IFF_LOOPBACK; /* Why doesn't tunnel work without this? [ should do now - AC]*/ + dev->addr_len=0; + dev->hard_header_len=0; + dev->hard_header=NULL; + return 0; +} + +#ifdef TUNNEL_DEBUG +void print_ip(struct iphdr *ip) +{ + unsigned char *ipaddr; + + printk("IP packet:\n"); + printk("--- header len = %d\n", ip->ihl*4); + printk("--- ip version: %d\n", ip->version); + printk("--- ip protocol: %d\n", ip->protocol); + ipaddr=(unsigned char *)&ip->saddr; + printk("--- source address: %u.%u.%u.%u\n", + *ipaddr, *(ipaddr+1), *(ipaddr+2), *(ipaddr+3)); + ipaddr=(unsigned char *)&ip->daddr; + printk("--- destination address: %u.%u.%u.%u\n", + *ipaddr, *(ipaddr+1), *(ipaddr+2), *(ipaddr+3)); + printk("--- total packet len: %d\n", ntohs(ip->tot_len)); +} +#endif + +/* This function assumes it is being called from dev_queue_xmit() + and that skb is filled properly by that function. + We also presume that if we return 0, we need to free skb, but + if we return 1, we don't free anything. Right? Wrong? +*/ + +static int tunnel_xmit(struct sk_buff *skb, struct device *dev) +{ + struct enet_statistics *stats; + struct sk_buff *skb2; /* The output packet */ + int newlen; /* The length of skb2->data */ + struct iphdr *iph; /* Our new IP header */ + + /* + * Return if there is nothing to do + */ + + if (skb == NULL || dev == NULL) + return 0; + + /* + * Make sure we are not busy (check lock variable) + */ + + stats = (struct enet_statistics *)dev->priv; + cli(); + if (dev->tbusy != 0) + { + sti(); + stats->tx_errors++; + return(1); + } + dev->tbusy = 1; + sti(); + + /* + * Perform some sanity checks on the packet + */ + + if ( ! dev->pa_dstaddr ) + { + printk("%s: packet sent through tunnel to never-never land!\n", dev->name); + dev_kfree_skb(skb, FREE_WRITE); + dev->tbusy = 0; + return(1); + } + + iph=(struct iphdr *)skb->data; + if ( iph->version != 4 ) + { + /* + * Bad IP packet? Possibly an ARP packet + */ + printk("%s: Bad IP packet: ip version %d\n", dev->name, iph->version); + dev_kfree_skb(skb, FREE_WRITE); + dev->tbusy = 0; + return(0); + } + + + /* + * Check for routing loops + */ + + if ( iph->protocol == IPPROTO_IPIP && iph->saddr == dev->pa_addr ) + { + /* + * We really should do an ICMP reply here... + */ + printk("%s: Warning: IP routing loop!\n", dev->name); + dev->tbusy = 0; + dev_kfree_skb(skb, FREE_WRITE); + return(0); + } + + if ( iph->daddr == dev->pa_addr ) + { + printk("%s: Received inbound packet -- not handled.\n",dev->name); + dev_kfree_skb(skb, FREE_WRITE); + dev->tbusy = 0; + return(0); + } + +#ifdef TUNNEL_DEBUG +printk("Old IP Header....\n"); +print_ip(iph); +#endif + /* + * Everything is okay: + * See if we need to allocate memory for a new packet + */ + + newlen = (skb->len + ip_header_len); + if ( !(skb2 = alloc_skb(newlen, GFP_ATOMIC)) ) + { + printk("%s: No free memory.\n",dev->name); + dev_kfree_skb(skb, FREE_WRITE); + dev->tbusy = 0; + stats->tx_dropped++; + return(1); + } + + /* Copy the packet to a new buffer, adding a new ip header */ + skb2->free=1; + skb2->len=newlen; + iph=skb2->h.iph=(struct iphdr *)skb2->data; + memcpy(skb2->h.iph, skb->data, ip_header_len ); + memcpy(skb2->data + ip_header_len, skb->data, skb->len); + /* Free the old packet, we no longer need it */ + dev_kfree_skb(skb, FREE_WRITE); + + /* Correct the fields in the new ip header */ + ++iph->ttl; /* Note: ip_forward() decrements ttl, so compensate */ + iph->saddr = dev->pa_addr; + iph->daddr = dev->pa_dstaddr; + iph->protocol = IPPROTO_IPIP; + iph->ihl = 5; + iph->tot_len = htons(skb2->len); + iph->frag_off = 0; + + /* Here is where we compute the IP checksum */ + /* ip_fast_csum() is an inline function from net/inet/ip.h/checksum.h */ + iph->check = 0; + iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); + +#ifdef TUNNEL_DEBUG +printk("New IP Header....\n"); +print_ip(iph); +#endif + /* Now send the packet on its way */ +#ifdef TUNNEL_DEBUG + printk("tunnel: calling ip_forward()\n"); +#endif + ip_forward(skb2, dev, 0, iph->daddr, 0); + +#ifdef TUNNEL_DEBUG + printk("Packet sent through tunnel interface!\n"); +#endif + /* Record statistics */ + stats->tx_packets++; + +#ifdef TUNNEL_DEBUG + printk("tunnel: Updated usage statistics.\n"); +#endif + /* Clean up and return okay. */ + kfree_skb(skb2, FREE_WRITE); + dev->tbusy=0; + return 0; +} + +static struct enet_statistics * +tunnel_get_stats(struct device *dev) +{ + return((struct enet_statistics*) dev->priv); +} + +#ifdef MODULE +char kernel_version[] = UTS_RELEASE; + +static int tunnel_probe(struct device *dev) +{ + tunnel_init(dev); + return 0; +} + +static struct device dev_tunnel = { + "tunl0\0 ", + 0, 0, 0, 0, + 0x0, 0, + 0, 0, 0, NULL, tunnel_probe }; + +int init_module(void) +{ + /* Find a name for this unit */ + int ct= 1; + + while(dev_get(dev_tunnel.name)!=NULL && ct<100) + { + sprintf(dev_tunnel.name,"tunl%d",ct); + ct++; + } + +#ifdef TUNNEL_DEBUG + printk("tunnel: registering device %s\n", dev_tunnel.name); +#endif + if (register_netdev(&dev_tunnel) != 0) + return -EIO; + return 0; +} + +void cleanup_module(void) +{ + unregister_netdev(&dev_tunnel); + kfree_s(dev_tunnel.priv,sizeof(struct enet_statistics)); + dev_tunnel.priv=NULL; +} +#endif /* MODULE */ +#endif |