From mboxrd@z Thu Jan 1 00:00:00 1970 From: Lennert Buytenhek Subject: [PATCH][RFC] etherip: Ethernet-in-IPv4 tunneling Date: Wed, 12 Jan 2005 23:24:37 +0100 Message-ID: <20050112222437.GC14280@xi.wantstofly.org> Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Cc: shemminger@osdl.org, rhousley@rsasecurity.com, shollenbeck@verisign.com Return-path: To: netdev@oss.sgi.com Content-Disposition: inline Sender: netdev-bounce@oss.sgi.com Errors-to: netdev-bounce@oss.sgi.com List-Id: netdev.vger.kernel.org Hi, After struggling with various userland VPN solutions for a while (and failing to make IPSEC tunnel mode do what I want), I decided to just implement ethernet-in-IP tunneling in the kernel and let IPSEC transport mode handle the rest. There appeared to be an RFC for ethernet-in-IP already, RFC 3378, so I just implemented that. It's very simple -- slap a 16-bit header (0x3000, which is 4 bits of etherip version number and 12 bits of padding) onto the beginning of the ethernet packet, and then wrap it in an IP packet. Below is what I came up with, against the latest Fedora Core 3 kernel, which is 2.6.10-something. It survives some fairly basic testing between a number of different machines, UP and SMP. (Corresponding iproute2 patch is available from http://www.wantstofly.org/~buytenh/etherip/ ) Notes: - daddr=0 tunnel mode is meaningless for generic ethernet tunneling, so I didn't implement that. Packets are just dropped on the floor if daddr==0 at the time of sending, which is the default mode for the etherip0 device. Issues and TODO: - Implement MULTICAST(daddr) tunnel mode, seems useful to have. - Perhaps we should always present a MTU=1500 device to the user and deal with fragmentation issues ourselves. - Don't take TTL of outer packet from inner packet. - Figure out what to do with DF. - Check whether ECN bits are correctly {en,de}capsulated. - Check out iffy-looking '2 * sizeof(struct etheriphdr)' construct (same problem in ip_gre.c?) Comments? I would like to see this upstream when the remaining issues have been sorted out. cheers, Lennert diff -urN linux-2.6.10.orig/include/linux/in.h linux-2.6.10/include/linux/in.h --- linux-2.6.10.orig/include/linux/in.h 2005-01-12 21:44:31.000000000 +0100 +++ linux-2.6.10/include/linux/in.h 2005-01-12 21:44:28.000000000 +0100 @@ -39,6 +39,7 @@ IPPROTO_ESP = 50, /* Encapsulation Security Payload protocol */ IPPROTO_AH = 51, /* Authentication Header protocol */ + IPPROTO_ETHERIP = 97, /* Ethernet-in-IP tunneling (rfc 3378) */ IPPROTO_PIM = 103, /* Protocol Independent Multicast */ IPPROTO_COMP = 108, /* Compression Header protocol */ diff -urN linux-2.6.10.orig/net/ipv4/etherip.c linux-2.6.10/net/ipv4/etherip.c --- linux-2.6.10.orig/net/ipv4/etherip.c 1970-01-01 01:00:00.000000000 +0100 +++ linux-2.6.10/net/ipv4/etherip.c 2005-01-12 22:09:51.699077165 +0100 @@ -0,0 +1,758 @@ +/* + * Linux NET3: Ethernet over IP protocol decoder. + * + * Authors: Alexey Kuznetsov (kuznet@ms2.inr.ac.ru) + * + * 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 version of net/ipv4/etherip.c created by Lennert Buytenhek + by mashing net/ipv4/ip_gre.c and net/ipv4/ipip.c together. + + For comments look at net/ipv4/ip_gre.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_IPV6 +#include +#include +#include +#endif + +struct etheriphdr +{ + struct iphdr iph; + u16 version; +} __attribute__((packed)); + +#define HASH_SIZE 16 +#define HASH(addr) ((addr^(addr>>4))&0xF) + +#define ETHERIP_VERSION 0x3000 + +static int etherip_fb_tunnel_init(struct net_device *dev); +static int etherip_tunnel_init(struct net_device *dev); +static void etherip_tunnel_setup(struct net_device *dev); + +static struct net_device *etherip_fb_tunnel_dev; + +static struct ip_tunnel *tunnels_r_l[HASH_SIZE]; +static struct ip_tunnel *tunnels_r[HASH_SIZE]; +static struct ip_tunnel *tunnels_l[HASH_SIZE]; +static struct ip_tunnel *tunnels_wc[1]; + +static struct ip_tunnel **tunnels[4] = { tunnels_wc, tunnels_l, tunnels_r, tunnels_r_l }; + +static rwlock_t etherip_lock = RW_LOCK_UNLOCKED; + +static struct ip_tunnel * etherip_tunnel_lookup(u32 remote, u32 local) +{ + unsigned h0 = HASH(remote); + unsigned h1 = HASH(local); + struct ip_tunnel *t; + + for (t = tunnels_r_l[h0^h1]; t; t = t->next) { + if (local == t->parms.iph.saddr && + remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP)) + return t; + } + for (t = tunnels_r[h0]; t; t = t->next) { + if (remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP)) + return t; + } + for (t = tunnels_l[h1]; t; t = t->next) { + if (local == t->parms.iph.saddr && (t->dev->flags&IFF_UP)) + return t; + } + if ((t = tunnels_wc[0]) != NULL && (t->dev->flags&IFF_UP)) + return t; + return NULL; +} + +static struct ip_tunnel **etherip_bucket(struct ip_tunnel *t) +{ + u32 remote = t->parms.iph.daddr; + u32 local = t->parms.iph.saddr; + unsigned h = 0; + int prio = 0; + + if (remote) { + prio |= 2; + h ^= HASH(remote); + } + if (local) { + prio |= 1; + h ^= HASH(local); + } + return &tunnels[prio][h]; +} + +static void etherip_tunnel_unlink(struct ip_tunnel *t) +{ + struct ip_tunnel **tp; + + for (tp = etherip_bucket(t); *tp; tp = &(*tp)->next) { + if (t == *tp) { + write_lock_bh(ðerip_lock); + *tp = t->next; + write_unlock_bh(ðerip_lock); + break; + } + } +} + +static void etherip_tunnel_link(struct ip_tunnel *t) +{ + struct ip_tunnel **tp = etherip_bucket(t); + + t->next = *tp; + write_lock_bh(ðerip_lock); + *tp = t; + write_unlock_bh(ðerip_lock); +} + +static struct ip_tunnel * etherip_tunnel_locate(struct ip_tunnel_parm *parms, int create) +{ + u32 remote = parms->iph.daddr; + u32 local = parms->iph.saddr; + struct ip_tunnel *t, **tp, *nt; + struct net_device *dev; + unsigned h = 0; + int prio = 0; + char name[IFNAMSIZ]; + + if (remote) { + prio |= 2; + h ^= HASH(remote); + } + if (local) { + prio |= 1; + h ^= HASH(local); + } + for (tp = &tunnels[prio][h]; (t = *tp) != NULL; tp = &t->next) { + if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr) + return t; + } + if (!create) + return NULL; + + if (parms->name[0]) + strlcpy(name, parms->name, IFNAMSIZ); + else { + int i; + for (i=1; i<100; i++) { + sprintf(name, "etherip%d", i); + if (__dev_get_by_name(name) == NULL) + break; + } + if (i==100) + goto failed; + } + + dev = alloc_netdev(sizeof(*t), name, etherip_tunnel_setup); + if (!dev) + return NULL; + + nt = dev->priv; + SET_MODULE_OWNER(dev); + dev->init = etherip_tunnel_init; + nt->parms = *parms; + + if (register_netdevice(dev) < 0) { + free_netdev(dev); + goto failed; + } + + dev_hold(dev); + etherip_tunnel_link(nt); + /* Do not decrement MOD_USE_COUNT here. */ + return nt; + +failed: + return NULL; +} + +static void etherip_tunnel_uninit(struct net_device *dev) +{ + if (dev == etherip_fb_tunnel_dev) { + write_lock_bh(ðerip_lock); + tunnels_wc[0] = NULL; + write_unlock_bh(ðerip_lock); + } else + etherip_tunnel_unlink((struct ip_tunnel*)dev->priv); + dev_put(dev); +} + + +void etherip_err(struct sk_buff *skb, u32 info) +{ +#ifndef I_WISH_WORLD_WERE_PERFECT +/* It is not :-( All the routers (except for Linux) return only + 8 bytes of packet payload. It means, that precise relaying of + ICMP in the real Internet is absolutely infeasible. + */ + + struct iphdr *iph = (struct iphdr*)skb->data; + int type = skb->h.icmph->type; + int code = skb->h.icmph->code; + struct ip_tunnel *t; + + switch (type) { + default: + case ICMP_PARAMETERPROB: + return; + + case ICMP_DEST_UNREACH: + switch (code) { + case ICMP_SR_FAILED: + case ICMP_PORT_UNREACH: + /* Impossible event. */ + return; + case ICMP_FRAG_NEEDED: + /* Soft state for pmtu is maintained by IP core. */ + return; + default: + /* All others are translated to HOST_UNREACH. + rfc2003 contains "deep thoughts" about NET_UNREACH, + I believe they are just ether pollution. --ANK + */ + break; + } + break; + case ICMP_TIME_EXCEEDED: + if (code != ICMP_EXC_TTL) + return; + break; + } + + read_lock(ðerip_lock); + t = etherip_tunnel_lookup(iph->daddr, iph->saddr); + if (t == NULL || t->parms.iph.daddr == 0) + goto out; + if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED) + goto out; + + if (jiffies - t->err_time < IPTUNNEL_ERR_TIMEO) + t->err_count++; + else + t->err_count = 1; + t->err_time = jiffies; +out: + read_unlock(ðerip_lock); + return; +#endif +} + +static inline void etherip_ecn_decapsulate(struct iphdr *iph, struct sk_buff *skb) +{ + if (INET_ECN_is_ce(iph->tos)) { + if (skb->protocol == htons(ETH_P_IP)) { + IP_ECN_set_ce(skb->nh.iph); + } else if (skb->protocol == htons(ETH_P_IPV6)) { + IP6_ECN_set_ce(skb->nh.ipv6h); + } + } +} + +static inline u8 +etherip_ecn_encapsulate(u8 tos, struct iphdr *old_iph, struct sk_buff *skb) +{ + u8 inner = 0; + if (skb->protocol == htons(ETH_P_IP)) + inner = old_iph->tos; + else if (skb->protocol == htons(ETH_P_IPV6)) + inner = ipv6_get_dsfield((struct ipv6hdr *)old_iph); + return INET_ECN_encapsulate(tos, inner); +} + +int etherip_rcv(struct sk_buff *skb) +{ + struct iphdr *iph; + struct ip_tunnel *tunnel; + struct etheriphdr *ethiph; + + if (!pskb_may_pull(skb, sizeof(struct etheriphdr))) + goto out; + + ethiph = (struct etheriphdr *)skb->nh.raw; + if (ethiph->version != htons(ETHERIP_VERSION)) { + kfree_skb(skb); + return 0; + } + + iph = skb->nh.iph; + + read_lock(ðerip_lock); + if ((tunnel = etherip_tunnel_lookup(iph->saddr, iph->daddr)) != NULL) { + secpath_reset(skb); + + /* Pull etherip header. */ + skb_pull(skb, 2); + skb->protocol = eth_type_trans(skb, tunnel->dev); + + memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options)); + + tunnel->stat.rx_packets++; + tunnel->stat.rx_bytes += skb->len; + skb->dev = tunnel->dev; + dst_release(skb->dst); + skb->dst = NULL; + nf_reset(skb); + etherip_ecn_decapsulate(iph, skb); + netif_rx(skb); + read_unlock(ðerip_lock); + return 0; + } + read_unlock(ðerip_lock); + +out: + return -1; +} + +static int etherip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct ip_tunnel *tunnel = (struct ip_tunnel*)dev->priv; + struct net_device_stats *stats = &tunnel->stat; + struct iphdr *old_iph = skb->nh.iph; + struct iphdr *tiph = &tunnel->parms.iph; + u8 tos; + u16 df; + struct rtable *rt; /* Route to the other host */ + struct net_device *tdev; /* Device to other host */ + struct etheriphdr *ethiph; + struct iphdr *iph; /* Our new IP header */ + int max_headroom; /* The extra header space needed */ + int mtu; + + if (tunnel->recursion++) { + stats->collisions++; + goto tx_error; + } + + /* Need valid non-multicast daddr. */ + if (tiph->daddr == 0 || MULTICAST(tiph->daddr)) + goto tx_error; + + tos = tiph->tos; + if (tos&1) { + if (skb->protocol == htons(ETH_P_IP)) + tos = old_iph->tos; + tos &= ~1; + } + + { + struct flowi fl = { .oif = tunnel->parms.link, + .nl_u = { .ip4_u = + { .daddr = tiph->daddr, + .saddr = tiph->saddr, + .tos = RT_TOS(tos) } }, + .proto = IPPROTO_ETHERIP }; + if (ip_route_output_key(&rt, &fl)) { + stats->tx_carrier_errors++; + goto tx_error_icmp; + } + } + tdev = rt->u.dst.dev; + + if (tdev == dev) { + ip_rt_put(rt); + stats->collisions++; + goto tx_error; + } + + df = tiph->frag_off; + if (df) + mtu = dst_pmtu(&rt->u.dst) - sizeof(struct etheriphdr); + else + mtu = skb->dst ? dst_pmtu(skb->dst) : dev->mtu; + + if (skb->dst) + skb->dst->ops->update_pmtu(skb->dst, mtu); + + if (skb->protocol == htons(ETH_P_IP)) { + df |= (old_iph->frag_off&htons(IP_DF)); + + if ((old_iph->frag_off & htons(IP_DF)) && + mtu < ntohs(old_iph->tot_len)) { + icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu)); + ip_rt_put(rt); + goto tx_error; + } + } +#ifdef CONFIG_IPV6 + else if (skb->protocol == htons(ETH_P_IPV6)) { + struct rt6_info *rt6 = (struct rt6_info*)skb->dst; + + if (rt6 && mtu < dst_pmtu(skb->dst) && mtu >= IPV6_MIN_MTU) { + if (tiph->daddr || rt6->rt6i_dst.plen == 128) { + rt6->rt6i_flags |= RTF_MODIFIED; + skb->dst->metrics[RTAX_MTU-1] = mtu; + } + } + + /* @@@ Is this correct? */ + if (mtu >= IPV6_MIN_MTU && mtu < skb->len - 2 * sizeof(struct etheriphdr)) { + icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev); + ip_rt_put(rt); + goto tx_error; + } + } +#endif + + if (tunnel->err_count > 0) { + if (jiffies - tunnel->err_time < IPTUNNEL_ERR_TIMEO) { + tunnel->err_count--; + dst_link_failure(skb); + } else + tunnel->err_count = 0; + } + + max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct etheriphdr); + + if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) { + struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom); + if (!new_skb) { + ip_rt_put(rt); + stats->tx_dropped++; + dev_kfree_skb(skb); + tunnel->recursion--; + return 0; + } + if (skb->sk) + skb_set_owner_w(new_skb, skb->sk); + dev_kfree_skb(skb); + skb = new_skb; + old_iph = skb->nh.iph; + } + + skb->h.raw = skb->nh.raw; + skb->nh.raw = skb_push(skb, sizeof(struct etheriphdr)); + memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); + dst_release(skb->dst); + skb->dst = &rt->u.dst; + + /* + * Push down and install the etherip header. + */ + + ethiph = (struct etheriphdr *)skb->nh.iph; + + iph = ðiph->iph; + iph->version = 4; + iph->ihl = sizeof(struct iphdr) >> 2; + iph->frag_off = df; + iph->protocol = IPPROTO_ETHERIP; + iph->tos = etherip_ecn_encapsulate(tos, old_iph, skb); + iph->daddr = rt->rt_dst; + iph->saddr = rt->rt_src; + + ethiph->version = htons(ETHERIP_VERSION); + + if ((ethiph->iph.ttl = tiph->ttl) == 0) { + if (skb->protocol == htons(ETH_P_IP)) + ethiph->iph.ttl = old_iph->ttl; +#ifdef CONFIG_IPV6 + else if (skb->protocol == htons(ETH_P_IPV6)) + ethiph->iph.ttl = ((struct ipv6hdr*)old_iph)->hop_limit; +#endif + else + ethiph->iph.ttl = dst_metric(&rt->u.dst, RTAX_HOPLIMIT); + } + + nf_reset(skb); + + IPTUNNEL_XMIT(); + tunnel->recursion--; + return 0; + +tx_error_icmp: + dst_link_failure(skb); + +tx_error: + stats->tx_errors++; + dev_kfree_skb(skb); + tunnel->recursion--; + return 0; +} + +static int +etherip_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd) +{ + int err = 0; + struct ip_tunnel_parm p; + struct ip_tunnel *t; + + switch (cmd) { + case SIOCGETTUNNEL: + t = NULL; + if (dev == etherip_fb_tunnel_dev) { + if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) { + err = -EFAULT; + break; + } + t = etherip_tunnel_locate(&p, 0); + } + if (t == NULL) + t = (struct ip_tunnel*)dev->priv; + memcpy(&p, &t->parms, sizeof(p)); + if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p))) + err = -EFAULT; + break; + + case SIOCADDTUNNEL: + case SIOCCHGTUNNEL: + err = -EPERM; + if (!capable(CAP_NET_ADMIN)) + goto done; + + err = -EFAULT; + if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) + goto done; + + err = -EINVAL; + if (p.iph.version != 4 || p.iph.protocol != IPPROTO_ETHERIP || + p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF))) + goto done; + if (p.iph.ttl) + p.iph.frag_off |= htons(IP_DF); + + t = etherip_tunnel_locate(&p, cmd == SIOCADDTUNNEL); + + if (dev != etherip_fb_tunnel_dev && cmd == SIOCCHGTUNNEL) { + if (t != NULL) { + if (t->dev != dev) { + err = -EEXIST; + break; + } + } else { + if (!p.iph.daddr) { + err = -EINVAL; + break; + } + + t = (struct ip_tunnel*)dev->priv; + etherip_tunnel_unlink(t); + t->parms.iph.saddr = p.iph.saddr; + t->parms.iph.daddr = p.iph.daddr; + etherip_tunnel_link(t); + netdev_state_change(dev); + } + } + + if (t) { + err = 0; + if (cmd == SIOCCHGTUNNEL) { + t->parms.iph.ttl = p.iph.ttl; + t->parms.iph.tos = p.iph.tos; + t->parms.iph.frag_off = p.iph.frag_off; + } + if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p))) + err = -EFAULT; + } else + err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT); + break; + + case SIOCDELTUNNEL: + err = -EPERM; + if (!capable(CAP_NET_ADMIN)) + goto done; + + if (dev == etherip_fb_tunnel_dev) { + err = -EFAULT; + if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) + goto done; + err = -ENOENT; + if ((t = etherip_tunnel_locate(&p, 0)) == NULL) + goto done; + err = -EPERM; + if (t->dev == etherip_fb_tunnel_dev) + goto done; + dev = t->dev; + } + err = unregister_netdevice(dev); + break; + + default: + err = -EINVAL; + } + +done: + return err; +} + +static struct net_device_stats *etherip_tunnel_get_stats(struct net_device *dev) +{ + return &(((struct ip_tunnel*)dev->priv)->stat); +} + +static int etherip_tunnel_change_mtu(struct net_device *dev, int new_mtu) +{ + if (new_mtu < 68 || new_mtu > 0xFFF8 - sizeof(struct etheriphdr)) + return -EINVAL; + dev->mtu = new_mtu; + return 0; +} + +static void etherip_tunnel_setup(struct net_device *dev) +{ + SET_MODULE_OWNER(dev); + ether_setup(dev); + + dev->uninit = etherip_tunnel_uninit; + dev->destructor = free_netdev; + dev->hard_start_xmit = etherip_tunnel_xmit; + dev->get_stats = etherip_tunnel_get_stats; + dev->do_ioctl = etherip_tunnel_ioctl; + dev->change_mtu = etherip_tunnel_change_mtu; + + dev->hard_header_len = ETH_HLEN; // + sizeof(struct etheriphdr); + dev->tx_queue_len = 0; + random_ether_addr(dev->dev_addr); + + dev->iflink = 0; +} + +static int etherip_tunnel_init(struct net_device *dev) +{ + struct net_device *tdev = NULL; + struct ip_tunnel *tunnel; + struct iphdr *iph; + + tunnel = (struct ip_tunnel*)dev->priv; + iph = &tunnel->parms.iph; + + tunnel->dev = dev; + strcpy(tunnel->parms.name, dev->name); + + /* Guess output device to choose reasonable mtu and hard_header_len */ + if (iph->daddr) { + struct flowi fl = { .oif = tunnel->parms.link, + .nl_u = { .ip4_u = + { .daddr = iph->daddr, + .saddr = iph->saddr, + .tos = RT_TOS(iph->tos) } }, + .proto = IPPROTO_ETHERIP }; + struct rtable *rt; + if (!ip_route_output_key(&rt, &fl)) { + tdev = rt->u.dst.dev; + ip_rt_put(rt); + } + } + + if (!tdev && tunnel->parms.link) + tdev = __dev_get_by_index(tunnel->parms.link); + + if (tdev) { + dev->hard_header_len = tdev->hard_header_len + sizeof(struct etheriphdr); + dev->mtu = tdev->mtu - sizeof(struct etheriphdr); + } + dev->iflink = tunnel->parms.link; + + return 0; +} + +int __init etherip_fb_tunnel_init(struct net_device *dev) +{ + struct ip_tunnel *tunnel = (struct ip_tunnel*)dev->priv; + struct iphdr *iph = &tunnel->parms.iph; + + tunnel->dev = dev; + strcpy(tunnel->parms.name, dev->name); + + iph->version = 4; + iph->protocol = IPPROTO_ETHERIP; + iph->ihl = 5; + + dev_hold(dev); + tunnels_wc[0] = tunnel; + return 0; +} + + +static struct net_protocol etherip_protocol = { + .handler = etherip_rcv, + .err_handler = etherip_err, +}; + + +/* + * And now the modules code and kernel interface. + */ + +static int __init etherip_init(void) +{ + int err; + + printk(KERN_INFO "Ethernet over IPv4 tunneling driver\n"); + + if (inet_add_protocol(ðerip_protocol, IPPROTO_ETHERIP) < 0) { + printk(KERN_INFO "etherip init: can't add protocol\n"); + return -EAGAIN; + } + + etherip_fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), + "etherip0", etherip_tunnel_setup); + if (!etherip_fb_tunnel_dev) { + err = -ENOMEM; + goto err1; + } + + etherip_fb_tunnel_dev->init = etherip_fb_tunnel_init; + + if ((err = register_netdev(etherip_fb_tunnel_dev))) + goto err2; +out: + return err; +err2: + free_netdev(etherip_fb_tunnel_dev); +err1: + inet_del_protocol(ðerip_protocol, IPPROTO_ETHERIP); + goto out; +} + +void etherip_fini(void) +{ + if (inet_del_protocol(ðerip_protocol, IPPROTO_ETHERIP) < 0) + printk(KERN_INFO "etherip close: can't remove protocol\n"); + + unregister_netdev(etherip_fb_tunnel_dev); +} + +module_init(etherip_init); +module_exit(etherip_fini); +MODULE_LICENSE("GPL"); diff -urN linux-2.6.10.orig/net/ipv4/Kconfig linux-2.6.10/net/ipv4/Kconfig --- linux-2.6.10.orig/net/ipv4/Kconfig 2005-01-12 21:48:11.000000000 +0100 +++ linux-2.6.10/net/ipv4/Kconfig 2005-01-12 21:53:51.063081266 +0100 @@ -202,6 +202,15 @@ 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-in-IP tunneling" + depends on INET + help + Tunneling means encapsulating data of one protocol type within + another protocol and sending it over a channel that understands the + encapsulating protocol. This particular tunneling driver implements + the etherip Ethernet-in-IP protocol as described in RFC 3378. + config IP_MROUTE bool "IP: multicast routing" depends on IP_MULTICAST diff -urN linux-2.6.10.orig/net/ipv4/Makefile linux-2.6.10/net/ipv4/Makefile --- linux-2.6.10.orig/net/ipv4/Makefile 2004-12-24 22:34:26.000000000 +0100 +++ linux-2.6.10/net/ipv4/Makefile 2005-01-12 21:48:38.762364033 +0100 @@ -14,6 +14,7 @@ obj-$(CONFIG_IP_MROUTE) += ipmr.o obj-$(CONFIG_NET_IPIP) += ipip.o obj-$(CONFIG_NET_IPGRE) += ip_gre.o +obj-$(CONFIG_NET_ETHERIP) += etherip.o obj-$(CONFIG_SYN_COOKIES) += syncookies.o obj-$(CONFIG_INET_AH) += ah4.o obj-$(CONFIG_INET_ESP) += esp4.o