Subject: [PATCH] EtherIP tunnel driver (RFC 3378)

From: Joerg Roedel <[email-masked]>
Date: Mon, 11 Sep 2006 22:41:29 +0200

This driver implements the tunneling of Ethernet packets over IPv4
networks for Linux. It uses the protocol defined in RFC 3378.  The
protocol is also implemented by various BSD derivates and the Linux
driver is interoperable with them (tested with NetBSD). The driver is
also tested on different hardware platforms (currently i386 and 32bit
HP-PA)

Signed-off-by: Joerg Roedel <[email-masked]>
diff -uprN -X linux-2.6.17.13/Documentation/dontdiff linux-2.6.17.13-vanilla/include/linux/in.h linux-2.6.17.13/include/linux/in.h
--- linux-2.6.17.13-vanilla/include/linux/in.h	2006-09-09 05:23:25.000000000 +0200
+++ linux-2.6.17.13/include/linux/in.h	2006-09-11 21:06:43.000000000 +0200
@@ -40,6 +40,7 @@ enum {
 
   IPPROTO_ESP = 50,            /* Encapsulation Security Payload protocol */
   IPPROTO_AH = 51,             /* Authentication Header protocol       */
+  IPPROTO_ETHERIP = 97,		/* Ethernet over IPv4 protocol */
   IPPROTO_PIM    = 103,		/* Protocol Independent Multicast	*/
 
   IPPROTO_COMP   = 108,                /* Compression Header protocol */
diff -uprN -X linux-2.6.17.13/Documentation/dontdiff linux-2.6.17.13-vanilla/net/ipv4/etherip.c linux-2.6.17.13/net/ipv4/etherip.c
--- linux-2.6.17.13-vanilla/net/ipv4/etherip.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.17.13/net/ipv4/etherip.c	2006-09-11 21:59:40.000000000 +0200
@@ -0,0 +1,546 @@
+/*
+ * etherip.c: Ethernet over IPv4 tunnel driver (according to RFC3378)
+ *
+ * This driver could be used to tunnel Ethernet packets through IPv4
+ * networks. This is especially usefull together with the bridging
+ * code in Linux.
+ *
+ * This code was written with an eye on the IPIP driver in linux from
+ * Sam Lantinga. Thanks for the great work.
+ *
+ *      This program is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU General Public License
+ *      version 2 (no later version) as published by the
+ *      Free Software Foundation.
+ *
+ */
+
+#include <linux/capability.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <linux/netfilter_ipv4.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <net/ipip.h>
+#include <net/xfrm.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Joerg Roedel <[email-masked]>");
+MODULE_DESCRIPTION("Ethernet over IPv4 tunnel driver");
+
+/* 
+ * These 2 defines are taken from ipip.c - if it's good enough for them
+ * it's good enough for me.
+ */
+#define HASH_SIZE        16
+#define HASH(addr)       ((addr^(addr>>4))&0xF)
+
+#define ETHERIP_HEADER   ((u16)0x0300)
+#define ETHERIP_HLEN     2
+
+#define BANNER1 "etherip: Ethernet over IPv4 tunneling driver\n"
+#define BANNER2 "etherip: (C) 2006 by Joerg Roedel <[email-masked]>\n"
+
+struct etherip_tunnel {
+	struct list_head list;
+	struct net_device *dev;
+	struct net_device_stats stats;
+	struct ip_tunnel_parm parms;
+	unsigned int recursion;
+};
+
+static struct net_device *etherip_tunnel_dev;
+static struct list_head tunnels[HASH_SIZE];
+
+static DEFINE_RWLOCK(etherip_lock);
+
+static void etherip_tunnel_setup(struct net_device *dev);
+
+/* add a tunnel to the hash */
+static void etherip_tunnel_add(struct etherip_tunnel *tun)
+{
+	unsigned h = HASH(tun->parms.iph.daddr);
+	list_add_tail(&tun->list, &tunnels[h]);
+}
+
+/* delete a tunnel from the hash*/
+static void etherip_tunnel_del(struct etherip_tunnel *tun)
+{
+	list_del(&tun->list);
+}
+
+/* find a tunnel in the hash by parameters from userspace */
+static struct etherip_tunnel* etherip_tunnel_find(struct ip_tunnel_parm *p)
+{
+	struct list_head *ptr;
+	struct etherip_tunnel *ret;
+	unsigned h = HASH(p->iph.daddr);
+
+	list_for_each(ptr, &tunnels[h]) {
+		ret = list_entry(ptr, struct etherip_tunnel, list);
+		if (ret->parms.iph.daddr == p->iph.daddr) {
+			return ret;
+		}
+	}
+
+	return NULL;
+}
+
+/* find a tunnel by its destination address */
+static struct etherip_tunnel* etherip_tunnel_locate(u32 remote)
+{
+	struct list_head *ptr;
+	struct etherip_tunnel *ret;
+	unsigned h = HASH(remote);
+
+	list_for_each(ptr, &tunnels[h]) {
+		ret = list_entry(ptr, struct etherip_tunnel, list);
+		if (ret->parms.iph.daddr == remote) {
+			return ret;
+		}
+	}
+
+	return NULL;
+}
+
+/* netdevice start function */
+static int etherip_tunnel_open(struct net_device *dev)
+{
+	netif_start_queue(dev);
+	return 0;
+}
+
+/* netdevice stop function */
+static int etherip_tunnel_stop(struct net_device *dev)
+{
+	netif_stop_queue(dev);
+	return 0;
+}
+
+/* netdevice hard_start_xmit function
+ * it gets an Ethernet packet in skb and encapsulates it in another IP
+ * packet */
+static int etherip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct etherip_tunnel *tunnel = netdev_priv(dev);
+	struct rtable *rt;
+	struct iphdr *iph;
+	struct flowi fl;
+	struct net_device *tdev;
+	int max_headroom;
+	struct net_device_stats *stats = &tunnel->stats;
+
+	if (tunnel->recursion++) {
+		tunnel->stats.collisions++;
+		goto tx_error;
+	}
+
+	fl.oif = fl.iif      = 0;
+	fl.proto             = IPPROTO_ETHERIP;
+	fl.nl_u.ip4_u.daddr  = tunnel->parms.iph.daddr;
+	fl.nl_u.ip4_u.saddr  = tunnel->parms.iph.saddr;
+	fl.nl_u.ip4_u.fwmark = 0;
+	fl.nl_u.ip4_u.tos    = 0;
+
+	if (ip_route_output_key(&rt, &fl)) {
+		tunnel->stats.tx_carrier_errors++;
+		goto tx_error_icmp;
+	}
+
+	tdev = rt->u.dst.dev;
+	if (tdev == dev) {
+		ip_rt_put(rt);
+		tunnel->stats.collisions++;
+		goto tx_error;
+	}
+
+	max_headroom = (LL_RESERVED_SPACE(tdev)+sizeof(struct iphdr)
+			+ ETHERIP_HLEN);
+
+	if (skb_headroom(skb) < max_headroom || skb_cloned(skb)
+			|| skb_shared(skb)) {
+		struct sk_buff *n_skb = skb_realloc_headroom(skb,max_headroom);
+		if (!n_skb) {
+			ip_rt_put(rt);
+			dev_kfree_skb(skb);
+			tunnel->stats.tx_dropped++;
+			return 0;
+		}
+		if (skb->sk)
+			skb_set_owner_w(n_skb, skb->sk);
+		dev_kfree_skb(skb);
+		skb = n_skb;
+	}
+
+	skb->h.raw = skb->nh.raw;
+	skb->nh.raw = skb_push(skb, sizeof(struct iphdr)+ETHERIP_HLEN);
+	memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
+	IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED |
+			IPSKB_REROUTED);                         
+	dst_release(skb->dst);
+	skb->dst = &rt->u.dst;
+
+	/* Build the IP header for the outgoing packet
+	 *
+	 * Note: This driver never sets the DF flag on outgoing packets
+	 *       to ensure that the tunnel provides the full Ethernet MTU.
+	 *       This behavior guarantees that protocols can be
+	 *       encapsulated within the Ethernet packet which do not
+	 *       know the concept of a path MTU
+	 */
+	iph = skb->nh.iph;
+	iph->version = 4;
+	iph->ihl = sizeof(struct iphdr)>>2;
+	iph->frag_off = 0;
+	iph->protocol = IPPROTO_ETHERIP;
+	iph->tos = 0;
+	iph->daddr = rt->rt_dst;
+	iph->saddr = rt->rt_src;
+	iph->ttl = tunnel->parms.iph.ttl;
+	if (iph->ttl == 0)
+		iph->ttl = dst_metric(&rt->u.dst, RTAX_HOPLIMIT);
+
+	/* add the 16bit etherip header after the ip header */
+	*((u16*)(skb->nh.raw + sizeof(struct iphdr))) = ntohs(ETHERIP_HEADER);
+	nf_reset(skb);
+	IPTUNNEL_XMIT();
+	tunnel->dev->trans_start = jiffies;
+	tunnel->recursion--;
+
+	return 0;
+
+tx_error_icmp:
+	dst_link_failure(skb);
+
+tx_error:
+	tunnel->stats.tx_errors++;
+	dev_kfree_skb(skb);
+	tunnel->recursion--;
+	return 0;
+}
+
+/* get statistics callback */
+static struct net_device_stats *etherip_tunnel_stats(struct net_device *dev)
+{
+	struct etherip_tunnel *ethip = netdev_priv(dev);
+	return &ethip->stats;
+}
+
+/* checks parameters the driver gets from userspace */
+static int etherip_param_check(struct ip_tunnel_parm *p)
+{
+	if ((p->iph.version != 4)
+		|| (p->iph.protocol != IPPROTO_ETHERIP)
+		|| (p->iph.ihl != 5)
+		|| (p->iph.daddr == INADDR_ANY)
+		|| MULTICAST(p->iph.daddr))
+		return -EINVAL;
+
+	return 0;
+}
+
+/* central ioctl function for all netdevices this driver manages
+ * it allows to create, delete, modify a tunnel and fetch tunnel
+ * information */
+static int etherip_tunnel_ioctl(struct net_device *dev, struct ifreq *ifr,
+		int cmd)
+{
+	int err = 0;
+	struct ip_tunnel_parm p;
+	struct net_device *new_dev;
+	char *dev_name;
+	struct etherip_tunnel *t;
+
+
+	switch (cmd) {
+	case SIOCGETTUNNEL:
+		err = -EINVAL;
+		if (dev == etherip_tunnel_dev)
+			goto out;
+		t = netdev_priv(dev);
+		if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms,
+				sizeof(t->parms)))
+			err = -EFAULT;
+		err = 0;
+		break;
+	case SIOCADDTUNNEL:
+		err = -EINVAL;
+		if (dev != etherip_tunnel_dev)
+			goto out;
+
+	case SIOCCHGTUNNEL:
+		err = -EPERM;
+		if (!capable(CAP_NET_ADMIN))
+			goto out;
+
+		err = -EFAULT;
+		if (copy_from_user(&p, ifr->ifr_ifru.ifru_data,
+					sizeof(p)))
+			goto out;
+
+		if ((err = etherip_param_check(&p)) < 0)
+			goto out;
+
+		t = etherip_tunnel_find(&p);
+
+		err = -EEXIST;
+		if ((t != NULL) && (t->dev != dev))
+			goto out;
+
+		if (cmd == SIOCADDTUNNEL) {
+
+			p.name[IFNAMSIZ-1] = 0;
+			dev_name = p.name;
+			if (dev_name[0] == 0)
+				dev_name = "ethip%d";
+
+			err = -ENOMEM;
+			new_dev = alloc_netdev(
+					sizeof(struct etherip_tunnel),
+					dev_name,
+					etherip_tunnel_setup);
+
+			if (new_dev == NULL)
+				goto out;
+				
+			if (strchr(new_dev->name, '%')) {
+				err = dev_alloc_name( new_dev, new_dev->name);
+				if (err < 0)
+					goto add_err;
+			}
+			
+			t = netdev_priv(new_dev);
+			t->dev = new_dev;
+			strncpy(p.name, new_dev->name, IFNAMSIZ);
+			memcpy(&(t->parms), &p, sizeof(p));
+				
+			err = register_netdevice(new_dev);
+			if (err < 0)
+				goto add_err;
+
+			err = -EFAULT;
+			if (copy_to_user(ifr->ifr_ifru.ifru_data, &p,
+						sizeof(p)))
+				goto add_err;
+			
+			write_lock(&etherip_lock);
+			etherip_tunnel_add(t);
+			write_unlock(&etherip_lock);
+
+		} else {
+			err = -EINVAL;
+			if ((t = netdev_priv(dev)) == NULL)
+				goto out;
+			if (dev == etherip_tunnel_dev)
+				goto out;
+			write_lock(&etherip_lock);
+			memcpy(&(t->parms), &p, sizeof(p));
+			write_unlock(&etherip_lock);
+		}
+
+		err = 0;
+		break;
+add_err:
+		free_netdev(new_dev);
+		goto out;
+
+	case SIOCDELTUNNEL:
+		err = -EPERM;
+		if (!capable(CAP_NET_ADMIN))
+			goto out;
+
+		err = -EINVAL;
+		if (dev == etherip_tunnel_dev)
+			goto out;
+
+		t = netdev_priv(dev);
+			
+		write_lock(&etherip_lock);
+		etherip_tunnel_del(t);
+		write_unlock(&etherip_lock);
+
+		unregister_netdevice(t->dev);
+		err = 0;
+
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+out:
+	return err;
+}
+
+/* device init function - called via register_netdevice
+ * The tunnel is registered as an Ethernet device. This allows
+ * the tunnel to be added to a bridge */
+static void etherip_tunnel_setup(struct net_device *dev)
+{
+	SET_MODULE_OWNER(dev);
+	dev->open = etherip_tunnel_open;
+	dev->hard_start_xmit = etherip_tunnel_xmit;
+	dev->stop = etherip_tunnel_stop;
+	dev->get_stats = etherip_tunnel_stats;
+	dev->do_ioctl = etherip_tunnel_ioctl;
+	dev->destructor = free_netdev;
+
+	ether_setup(dev);
+	dev->tx_queue_len = 0;
+	random_ether_addr(dev->dev_addr);
+}
+
+/* receive function for EtherIP packets
+ * Does some basic checks on the MAC addresses and
+ * interface modes */
+static int etherip_rcv(struct sk_buff *skb)
+{
+	struct iphdr *iph;
+	struct ethhdr *ehdr;
+	struct etherip_tunnel *tunnel;
+	struct net_device *dev;
+
+	iph = skb->nh.iph;
+
+	read_lock(&etherip_lock);
+	tunnel = etherip_tunnel_locate(iph->saddr);
+	if (tunnel == NULL)
+		goto drop;
+
+	dev = tunnel->dev;
+	secpath_reset(skb);
+	memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
+	skb_pull(skb, (skb->nh.raw - skb->data)
+			+ sizeof(struct iphdr) + ETHERIP_HLEN);
+	ehdr = (struct ethhdr*)skb->data;
+	skb->dev = dev;
+	skb->pkt_type = PACKET_HOST;
+	skb->protocol = eth_type_trans(skb, tunnel->dev);
+	skb->ip_summed = CHECKSUM_UNNECESSARY;
+	dst_release(skb->dst);
+	skb->dst = NULL;
+
+	/* do some checks */
+	if (skb->pkt_type == PACKET_HOST || skb->pkt_type == PACKET_BROADCAST)
+		goto accept;
+
+	if (skb->pkt_type == PACKET_MULTICAST &&
+			(dev->mc_count > 0 || dev->flags & IFF_ALLMULTI))
+		goto accept;
+	
+	if (skb->pkt_type == PACKET_OTHERHOST && dev->flags & IFF_PROMISC)
+		goto accept;
+
+	goto drop;
+
+accept:
+	tunnel->dev->last_rx = jiffies;
+	tunnel->stats.rx_packets++;
+	tunnel->stats.rx_bytes += skb->len;
+	nf_reset(skb);
+	netif_rx(skb);
+	read_unlock(&etherip_lock);
+	return 0;
+
+drop:
+	read_unlock(&etherip_lock);
+	kfree_skb(skb);
+	return 0;
+}
+
+static struct net_protocol etherip_protocol = {
+	.handler      = etherip_rcv,
+	.err_handler  = 0,
+	.no_policy    = 1,
+};
+
+/* module init function
+ * initializes the EtherIP protocol (97) and registers the initial
+ * device */
+static int __init etherip_init(void)
+{
+	int err, i;
+	struct etherip_tunnel *p;
+
+	printk(KERN_INFO BANNER1);
+	printk(KERN_INFO BANNER2);
+
+	for (i=0;i<HASH_SIZE;++i)
+		INIT_LIST_HEAD(&tunnels[i]);
+
+	if (inet_add_protocol(&etherip_protocol, IPPROTO_ETHERIP)) {
+		printk(KERN_ERR "etherip: can't add protocol\n");
+		return -EAGAIN;
+	}
+
+	etherip_tunnel_dev = alloc_netdev(sizeof(struct etherip_tunnel),
+			"ethip0",
+			etherip_tunnel_setup);
+	
+	if (!etherip_tunnel_dev) {
+		err = -ENOMEM;
+		goto err2;
+	}
+
+	p = netdev_priv(etherip_tunnel_dev);
+	p->dev = etherip_tunnel_dev;
+
+	if ((err = register_netdev(etherip_tunnel_dev)))
+		goto err1;
+
+out:
+	return err;
+err1:
+	free_netdev(etherip_tunnel_dev);
+err2:
+	inet_del_protocol(&etherip_protocol, IPPROTO_ETHERIP);
+	goto out;
+}
+
+/* destroy all tunnels */
+static void __exit etherip_destroy_tunnels(void)
+{
+	int i;
+	struct list_head *ptr;
+	struct etherip_tunnel *tun;
+	
+	for (i=0;i<HASH_SIZE;++i) {
+		/*
+		ptr = tunnels[i].next;
+		while (ptr != &(tunnels[i])) {
+			ret = list_entry(ptr, struct etherip_tunnel, list);
+			ptr = ptr->next;
+			unregister_netdevice(ret->dev);
+		}*/
+		list_for_each(ptr, &tunnels[i]) {
+			tun = list_entry(ptr, struct etherip_tunnel, list);
+			ptr = ptr->prev;
+			etherip_tunnel_del(tun);
+			unregister_netdevice(tun->dev);
+		}
+	}
+}
+
+/* module cleanup function */
+static void __exit etherip_exit(void)
+{
+	rtnl_lock();
+	etherip_destroy_tunnels();
+	unregister_netdevice(etherip_tunnel_dev);
+	rtnl_unlock();
+	if (inet_del_protocol(&etherip_protocol, IPPROTO_ETHERIP))
+		printk(KERN_ERR "etherip: can't remove protocol\n");
+}
+
+module_init(etherip_init);
+module_exit(etherip_exit);
diff -uprN -X linux-2.6.17.13/Documentation/dontdiff linux-2.6.17.13-vanilla/net/ipv4/Kconfig linux-2.6.17.13/net/ipv4/Kconfig
--- linux-2.6.17.13-vanilla/net/ipv4/Kconfig	2006-09-09 05:23:25.000000000 +0200
+++ linux-2.6.17.13/net/ipv4/Kconfig	2006-09-11 21:39:55.000000000 +0200
@@ -272,6 +272,20 @@ config NET_IPGRE_BROADCAST
 	  Network), but can be distributed all over the Internet. If you want
 	  to do that, say Y here and to "IP multicast routing" below.
 
+config NET_ETHERIP
+	tristate "IP: Ethernet over IP tunneling (experimental)"
+	help
+	 Tunneling means encapsulating data of one protocol type within
+	 another protocol and sending it over a channel that understands the
+	 encapsulating protocol. This tunnel driver implements the
+	 encapsulation of Ethernet frames into IP packets using the EtherIP
+	 protocol defined in RFC 3378. This is especially usefull together with
+	 the bridging code to build distributed OSI Layer 2 networks.
+
+	 To configure tunnels an extra tool is required. You can download
+	 it from http://zlug.fh-zwickau.de/~joro/projects/ under the
+	 EtherIP section. If unsure, say N.
+
 config IP_MROUTE
 	bool "IP: multicast routing"
 	depends on IP_MULTICAST
diff -uprN -X linux-2.6.17.13/Documentation/dontdiff linux-2.6.17.13-vanilla/net/ipv4/Makefile linux-2.6.17.13/net/ipv4/Makefile
--- linux-2.6.17.13-vanilla/net/ipv4/Makefile	2006-09-09 05:23:25.000000000 +0200
+++ linux-2.6.17.13/net/ipv4/Makefile	2006-09-11 21:06:43.000000000 +0200
@@ -17,6 +17,7 @@ obj-$(CONFIG_PROC_FS) += proc.o
 obj-$(CONFIG_IP_MULTIPLE_TABLES) += fib_rules.o
 obj-$(CONFIG_IP_MROUTE) += ipmr.o
 obj-$(CONFIG_NET_IPIP) += ipip.o
+obj-$(CONFIG_NET_ETHERIP) += etherip.o
 obj-$(CONFIG_NET_IPGRE) += ip_gre.o
 obj-$(CONFIG_SYN_COOKIES) += syncookies.o
 obj-$(CONFIG_INET_AH) += ah4.o