netns xfrm: ipcomp support
[safe/jmp/linux-2.6] / net / ipv4 / ip_gre.c
index fd192d6..7631b20 100644 (file)
@@ -27,6 +27,7 @@
 #include <linux/inetdevice.h>
 #include <linux/igmp.h>
 #include <linux/netfilter_ipv4.h>
 #include <linux/inetdevice.h>
 #include <linux/igmp.h>
 #include <linux/netfilter_ipv4.h>
+#include <linux/etherdevice.h>
 #include <linux/if_ether.h>
 
 #include <net/sock.h>
 #include <linux/if_ether.h>
 
 #include <net/sock.h>
@@ -41,6 +42,7 @@
 #include <net/xfrm.h>
 #include <net/net_namespace.h>
 #include <net/netns/generic.h>
 #include <net/xfrm.h>
 #include <net/net_namespace.h>
 #include <net/netns/generic.h>
+#include <net/rtnetlink.h>
 
 #ifdef CONFIG_IPV6
 #include <net/ipv6.h>
 
 #ifdef CONFIG_IPV6
 #include <net/ipv6.h>
    solution, but it supposes maintaing new variable in ALL
    skb, even if no tunneling is used.
 
    solution, but it supposes maintaing new variable in ALL
    skb, even if no tunneling is used.
 
-   Current solution: t->recursion lock breaks dead loops. It looks
-   like dev->tbusy flag, but I preferred new variable, because
-   the semantics is different. One day, when hard_start_xmit
-   will be multithreaded we will have to use skb->encapsulation.
+   Current solution: HARD_TX_LOCK lock breaks dead loops.
 
 
 
 
 
 
    Alexey Kuznetsov.
  */
 
    Alexey Kuznetsov.
  */
 
+static struct rtnl_link_ops ipgre_link_ops __read_mostly;
 static int ipgre_tunnel_init(struct net_device *dev);
 static void ipgre_tunnel_setup(struct net_device *dev);
 static int ipgre_tunnel_init(struct net_device *dev);
 static void ipgre_tunnel_setup(struct net_device *dev);
+static int ipgre_tunnel_bind_dev(struct net_device *dev);
 
 /* Fallback tunnel: no source, no destination, no key, no options */
 
 
 /* Fallback tunnel: no source, no destination, no key, no options */
 
-static int ipgre_fb_tunnel_init(struct net_device *dev);
-
 #define HASH_SIZE  16
 
 #define HASH_SIZE  16
 
-static int ipgre_net_id;
+static int ipgre_net_id __read_mostly;
 struct ipgre_net {
        struct ip_tunnel *tunnels[4][HASH_SIZE];
 
 struct ipgre_net {
        struct ip_tunnel *tunnels[4][HASH_SIZE];
 
@@ -157,46 +156,135 @@ struct ipgre_net {
 #define tunnels_r      tunnels[2]
 #define tunnels_l      tunnels[1]
 #define tunnels_wc     tunnels[0]
 #define tunnels_r      tunnels[2]
 #define tunnels_l      tunnels[1]
 #define tunnels_wc     tunnels[0]
+/*
+ * Locking : hash tables are protected by RCU and a spinlock
+ */
+static DEFINE_SPINLOCK(ipgre_lock);
 
 
-static DEFINE_RWLOCK(ipgre_lock);
+#define for_each_ip_tunnel_rcu(start) \
+       for (t = rcu_dereference(start); t; t = rcu_dereference(t->next))
 
 /* Given src, dst and key, find appropriate for input tunnel. */
 
 
 /* Given src, dst and key, find appropriate for input tunnel. */
 
-static struct ip_tunnel * ipgre_tunnel_lookup(struct net *net,
-               __be32 remote, __be32 local, __be32 key)
+static struct ip_tunnel * ipgre_tunnel_lookup(struct net_device *dev,
+                                             __be32 remote, __be32 local,
+                                             __be32 key, __be16 gre_proto)
 {
 {
+       struct net *net = dev_net(dev);
+       int link = dev->ifindex;
        unsigned h0 = HASH(remote);
        unsigned h1 = HASH(key);
        unsigned h0 = HASH(remote);
        unsigned h1 = HASH(key);
-       struct ip_tunnel *t;
+       struct ip_tunnel *t, *cand = NULL;
        struct ipgre_net *ign = net_generic(net, ipgre_net_id);
        struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       int dev_type = (gre_proto == htons(ETH_P_TEB)) ?
+                      ARPHRD_ETHER : ARPHRD_IPGRE;
+       int score, cand_score = 4;
+
+       for_each_ip_tunnel_rcu(ign->tunnels_r_l[h0 ^ h1]) {
+               if (local != t->parms.iph.saddr ||
+                   remote != t->parms.iph.daddr ||
+                   key != t->parms.i_key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
 
 
-       for (t = ign->tunnels_r_l[h0^h1]; t; t = t->next) {
-               if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr) {
-                       if (t->parms.i_key == key && (t->dev->flags&IFF_UP))
-                               return t;
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
                }
        }
                }
        }
-       for (t = ign->tunnels_r[h0^h1]; t; t = t->next) {
-               if (remote == t->parms.iph.daddr) {
-                       if (t->parms.i_key == key && (t->dev->flags&IFF_UP))
-                               return t;
+
+       for_each_ip_tunnel_rcu(ign->tunnels_r[h0 ^ h1]) {
+               if (remote != t->parms.iph.daddr ||
+                   key != t->parms.i_key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
                }
        }
                }
        }
-       for (t = ign->tunnels_l[h1]; t; t = t->next) {
-               if (local == t->parms.iph.saddr ||
-                    (local == t->parms.iph.daddr &&
-                     ipv4_is_multicast(local))) {
-                       if (t->parms.i_key == key && (t->dev->flags&IFF_UP))
-                               return t;
+
+       for_each_ip_tunnel_rcu(ign->tunnels_l[h1]) {
+               if ((local != t->parms.iph.saddr &&
+                    (local != t->parms.iph.daddr ||
+                     !ipv4_is_multicast(local))) ||
+                   key != t->parms.i_key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
+                       return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
                }
        }
                }
        }
-       for (t = ign->tunnels_wc[h1]; t; t = t->next) {
-               if (t->parms.i_key == key && (t->dev->flags&IFF_UP))
+
+       for_each_ip_tunnel_rcu(ign->tunnels_wc[h1]) {
+               if (t->parms.i_key != key ||
+                   !(t->dev->flags & IFF_UP))
+                       continue;
+
+               if (t->dev->type != ARPHRD_IPGRE &&
+                   t->dev->type != dev_type)
+                       continue;
+
+               score = 0;
+               if (t->parms.link != link)
+                       score |= 1;
+               if (t->dev->type != dev_type)
+                       score |= 2;
+               if (score == 0)
                        return t;
                        return t;
+
+               if (score < cand_score) {
+                       cand = t;
+                       cand_score = score;
+               }
        }
 
        }
 
-       if (ign->fb_tunnel_dev->flags&IFF_UP)
-               return netdev_priv(ign->fb_tunnel_dev);
+       if (cand != NULL)
+               return cand;
+
+       dev = ign->fb_tunnel_dev;
+       if (dev->flags & IFF_UP)
+               return netdev_priv(dev);
+
        return NULL;
 }
 
        return NULL;
 }
 
@@ -229,10 +317,10 @@ static void ipgre_tunnel_link(struct ipgre_net *ign, struct ip_tunnel *t)
 {
        struct ip_tunnel **tp = ipgre_bucket(ign, t);
 
 {
        struct ip_tunnel **tp = ipgre_bucket(ign, t);
 
+       spin_lock_bh(&ipgre_lock);
        t->next = *tp;
        t->next = *tp;
-       write_lock_bh(&ipgre_lock);
-       *tp = t;
-       write_unlock_bh(&ipgre_lock);
+       rcu_assign_pointer(*tp, t);
+       spin_unlock_bh(&ipgre_lock);
 }
 
 static void ipgre_tunnel_unlink(struct ipgre_net *ign, struct ip_tunnel *t)
 }
 
 static void ipgre_tunnel_unlink(struct ipgre_net *ign, struct ip_tunnel *t)
@@ -241,33 +329,47 @@ static void ipgre_tunnel_unlink(struct ipgre_net *ign, struct ip_tunnel *t)
 
        for (tp = ipgre_bucket(ign, t); *tp; tp = &(*tp)->next) {
                if (t == *tp) {
 
        for (tp = ipgre_bucket(ign, t); *tp; tp = &(*tp)->next) {
                if (t == *tp) {
-                       write_lock_bh(&ipgre_lock);
+                       spin_lock_bh(&ipgre_lock);
                        *tp = t->next;
                        *tp = t->next;
-                       write_unlock_bh(&ipgre_lock);
+                       spin_unlock_bh(&ipgre_lock);
                        break;
                }
        }
 }
 
                        break;
                }
        }
 }
 
-static struct ip_tunnel * ipgre_tunnel_locate(struct net *net,
-               struct ip_tunnel_parm *parms, int create)
+static struct ip_tunnel *ipgre_tunnel_find(struct net *net,
+                                          struct ip_tunnel_parm *parms,
+                                          int type)
 {
        __be32 remote = parms->iph.daddr;
        __be32 local = parms->iph.saddr;
        __be32 key = parms->i_key;
 {
        __be32 remote = parms->iph.daddr;
        __be32 local = parms->iph.saddr;
        __be32 key = parms->i_key;
-       struct ip_tunnel *t, **tp, *nt;
+       int link = parms->link;
+       struct ip_tunnel *t, **tp;
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+
+       for (tp = __ipgre_bucket(ign, parms); (t = *tp) != NULL; tp = &t->next)
+               if (local == t->parms.iph.saddr &&
+                   remote == t->parms.iph.daddr &&
+                   key == t->parms.i_key &&
+                   link == t->parms.link &&
+                   type == t->dev->type)
+                       break;
+
+       return t;
+}
+
+static struct ip_tunnel * ipgre_tunnel_locate(struct net *net,
+               struct ip_tunnel_parm *parms, int create)
+{
+       struct ip_tunnel *t, *nt;
        struct net_device *dev;
        char name[IFNAMSIZ];
        struct ipgre_net *ign = net_generic(net, ipgre_net_id);
 
        struct net_device *dev;
        char name[IFNAMSIZ];
        struct ipgre_net *ign = net_generic(net, ipgre_net_id);
 
-       for (tp = __ipgre_bucket(ign, parms); (t = *tp) != NULL; tp = &t->next) {
-               if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr) {
-                       if (key == t->parms.i_key)
-                               return t;
-               }
-       }
-       if (!create)
-               return NULL;
+       t = ipgre_tunnel_find(net, parms, ARPHRD_IPGRE);
+       if (t || !create)
+               return t;
 
        if (parms->name[0])
                strlcpy(name, parms->name, IFNAMSIZ);
 
        if (parms->name[0])
                strlcpy(name, parms->name, IFNAMSIZ);
@@ -285,9 +387,11 @@ static struct ip_tunnel * ipgre_tunnel_locate(struct net *net,
                        goto failed_free;
        }
 
                        goto failed_free;
        }
 
-       dev->init = ipgre_tunnel_init;
        nt = netdev_priv(dev);
        nt->parms = *parms;
        nt = netdev_priv(dev);
        nt->parms = *parms;
+       dev->rtnl_link_ops = &ipgre_link_ops;
+
+       dev->mtu = ipgre_tunnel_bind_dev(dev);
 
        if (register_netdevice(dev) < 0)
                goto failed_free;
 
        if (register_netdevice(dev) < 0)
                goto failed_free;
@@ -327,7 +431,7 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
    by themself???
  */
 
    by themself???
  */
 
-       struct iphdr *iph = (struct iphdr*)skb->data;
+       struct iphdr *iph = (struct iphdr *)skb->data;
        __be16       *p = (__be16*)(skb->data+(iph->ihl<<2));
        int grehlen = (iph->ihl<<2) + 4;
        const int type = icmp_hdr(skb)->type;
        __be16       *p = (__be16*)(skb->data+(iph->ihl<<2));
        int grehlen = (iph->ihl<<2) + 4;
        const int type = icmp_hdr(skb)->type;
@@ -378,10 +482,11 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
                break;
        }
 
                break;
        }
 
-       read_lock(&ipgre_lock);
-       t = ipgre_tunnel_lookup(dev_net(skb->dev), iph->daddr, iph->saddr,
-                       (flags&GRE_KEY) ?
-                       *(((__be32*)p) + (grehlen>>2) - 1) : 0);
+       rcu_read_lock();
+       t = ipgre_tunnel_lookup(skb->dev, iph->daddr, iph->saddr,
+                               flags & GRE_KEY ?
+                               *(((__be32 *)p) + (grehlen / 4) - 1) : 0,
+                               p[1]);
        if (t == NULL || t->parms.iph.daddr == 0 ||
            ipv4_is_multicast(t->parms.iph.daddr))
                goto out;
        if (t == NULL || t->parms.iph.daddr == 0 ||
            ipv4_is_multicast(t->parms.iph.daddr))
                goto out;
@@ -389,13 +494,13 @@ static void ipgre_err(struct sk_buff *skb, u32 info)
        if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
                goto out;
 
        if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
                goto out;
 
-       if (jiffies - t->err_time < IPTUNNEL_ERR_TIMEO)
+       if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO))
                t->err_count++;
        else
                t->err_count = 1;
        t->err_time = jiffies;
 out:
                t->err_count++;
        else
                t->err_count = 1;
        t->err_time = jiffies;
 out:
-       read_unlock(&ipgre_lock);
+       rcu_read_unlock();
        return;
 }
 
        return;
 }
 
@@ -431,6 +536,8 @@ static int ipgre_rcv(struct sk_buff *skb)
        u32    seqno = 0;
        struct ip_tunnel *tunnel;
        int    offset = 4;
        u32    seqno = 0;
        struct ip_tunnel *tunnel;
        int    offset = 4;
+       __be16 gre_proto;
+       unsigned int len;
 
        if (!pskb_may_pull(skb, 16))
                goto drop_nolock;
 
        if (!pskb_may_pull(skb, 16))
                goto drop_nolock;
@@ -470,20 +577,22 @@ static int ipgre_rcv(struct sk_buff *skb)
                }
        }
 
                }
        }
 
-       read_lock(&ipgre_lock);
-       if ((tunnel = ipgre_tunnel_lookup(dev_net(skb->dev),
-                                       iph->saddr, iph->daddr, key)) != NULL) {
+       gre_proto = *(__be16 *)(h + 2);
+
+       rcu_read_lock();
+       if ((tunnel = ipgre_tunnel_lookup(skb->dev,
+                                         iph->saddr, iph->daddr, key,
+                                         gre_proto))) {
                struct net_device_stats *stats = &tunnel->dev->stats;
 
                secpath_reset(skb);
 
                struct net_device_stats *stats = &tunnel->dev->stats;
 
                secpath_reset(skb);
 
-               skb->protocol = *(__be16*)(h + 2);
+               skb->protocol = gre_proto;
                /* WCCP version 1 and 2 protocol decoding.
                 * - Change protocol to IP
                 * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
                 */
                /* WCCP version 1 and 2 protocol decoding.
                 * - Change protocol to IP
                 * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header
                 */
-               if (flags == 0 &&
-                   skb->protocol == htons(ETH_P_WCCP)) {
+               if (flags == 0 && gre_proto == htons(ETH_P_WCCP)) {
                        skb->protocol = htons(ETH_P_IP);
                        if ((*(h + offset) & 0xF0) != 0x40)
                                offset += 4;
                        skb->protocol = htons(ETH_P_IP);
                        if ((*(h + offset) & 0xF0) != 0x40)
                                offset += 4;
@@ -491,13 +600,12 @@ static int ipgre_rcv(struct sk_buff *skb)
 
                skb->mac_header = skb->network_header;
                __pskb_pull(skb, offset);
 
                skb->mac_header = skb->network_header;
                __pskb_pull(skb, offset);
-               skb_reset_network_header(skb);
                skb_postpull_rcsum(skb, skb_transport_header(skb), offset);
                skb->pkt_type = PACKET_HOST;
 #ifdef CONFIG_NET_IPGRE_BROADCAST
                if (ipv4_is_multicast(iph->daddr)) {
                        /* Looped back packet, drop it! */
                skb_postpull_rcsum(skb, skb_transport_header(skb), offset);
                skb->pkt_type = PACKET_HOST;
 #ifdef CONFIG_NET_IPGRE_BROADCAST
                if (ipv4_is_multicast(iph->daddr)) {
                        /* Looped back packet, drop it! */
-                       if (skb->rtable->fl.iif == 0)
+                       if (skb_rtable(skb)->fl.iif == 0)
                                goto drop;
                        stats->multicast++;
                        skb->pkt_type = PACKET_BROADCAST;
                                goto drop;
                        stats->multicast++;
                        skb->pkt_type = PACKET_BROADCAST;
@@ -519,30 +627,49 @@ static int ipgre_rcv(struct sk_buff *skb)
                        }
                        tunnel->i_seqno = seqno + 1;
                }
                        }
                        tunnel->i_seqno = seqno + 1;
                }
+
+               len = skb->len;
+
+               /* Warning: All skb pointers will be invalidated! */
+               if (tunnel->dev->type == ARPHRD_ETHER) {
+                       if (!pskb_may_pull(skb, ETH_HLEN)) {
+                               stats->rx_length_errors++;
+                               stats->rx_errors++;
+                               goto drop;
+                       }
+
+                       iph = ip_hdr(skb);
+                       skb->protocol = eth_type_trans(skb, tunnel->dev);
+                       skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
+               }
+
                stats->rx_packets++;
                stats->rx_packets++;
-               stats->rx_bytes += skb->len;
+               stats->rx_bytes += len;
                skb->dev = tunnel->dev;
                skb->dev = tunnel->dev;
-               dst_release(skb->dst);
-               skb->dst = NULL;
+               skb_dst_drop(skb);
                nf_reset(skb);
                nf_reset(skb);
+
+               skb_reset_network_header(skb);
                ipgre_ecn_decapsulate(iph, skb);
                ipgre_ecn_decapsulate(iph, skb);
+
                netif_rx(skb);
                netif_rx(skb);
-               read_unlock(&ipgre_lock);
+               rcu_read_unlock();
                return(0);
        }
        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
 
 drop:
                return(0);
        }
        icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
 
 drop:
-       read_unlock(&ipgre_lock);
+       rcu_read_unlock();
 drop_nolock:
        kfree_skb(skb);
        return(0);
 }
 
 drop_nolock:
        kfree_skb(skb);
        return(0);
 }
 
-static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
+static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
 {
        struct ip_tunnel *tunnel = netdev_priv(dev);
 {
        struct ip_tunnel *tunnel = netdev_priv(dev);
-       struct net_device_stats *stats = &tunnel->dev->stats;
+       struct net_device_stats *stats = &dev->stats;
+       struct netdev_queue *txq = netdev_get_tx_queue(dev, 0);
        struct iphdr  *old_iph = ip_hdr(skb);
        struct iphdr  *tiph;
        u8     tos;
        struct iphdr  *old_iph = ip_hdr(skb);
        struct iphdr  *tiph;
        u8     tos;
@@ -555,14 +682,12 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
        __be32 dst;
        int    mtu;
 
        __be32 dst;
        int    mtu;
 
-       if (tunnel->recursion++) {
-               stats->collisions++;
-               goto tx_error;
-       }
+       if (dev->type == ARPHRD_ETHER)
+               IPCB(skb)->flags = 0;
 
 
-       if (dev->header_ops) {
+       if (dev->header_ops && dev->type == ARPHRD_IPGRE) {
                gre_hlen = 0;
                gre_hlen = 0;
-               tiph = (struct iphdr*)skb->data;
+               tiph = (struct iphdr *)skb->data;
        } else {
                gre_hlen = tunnel->hlen;
                tiph = &tunnel->parms.iph;
        } else {
                gre_hlen = tunnel->hlen;
                tiph = &tunnel->parms.iph;
@@ -571,13 +696,13 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
        if ((dst = tiph->daddr) == 0) {
                /* NBMA tunnel */
 
        if ((dst = tiph->daddr) == 0) {
                /* NBMA tunnel */
 
-               if (skb->dst == NULL) {
+               if (skb_dst(skb) == NULL) {
                        stats->tx_fifo_errors++;
                        goto tx_error;
                }
 
                if (skb->protocol == htons(ETH_P_IP)) {
                        stats->tx_fifo_errors++;
                        goto tx_error;
                }
 
                if (skb->protocol == htons(ETH_P_IP)) {
-                       rt = skb->rtable;
+                       rt = skb_rtable(skb);
                        if ((dst = rt->rt_gateway) == 0)
                                goto tx_error_icmp;
                }
                        if ((dst = rt->rt_gateway) == 0)
                                goto tx_error_icmp;
                }
@@ -585,12 +710,12 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
                else if (skb->protocol == htons(ETH_P_IPV6)) {
                        struct in6_addr *addr6;
                        int addr_type;
                else if (skb->protocol == htons(ETH_P_IPV6)) {
                        struct in6_addr *addr6;
                        int addr_type;
-                       struct neighbour *neigh = skb->dst->neighbour;
+                       struct neighbour *neigh = skb_dst(skb)->neighbour;
 
                        if (neigh == NULL)
                                goto tx_error;
 
 
                        if (neigh == NULL)
                                goto tx_error;
 
-                       addr6 = (struct in6_addr*)&neigh->primary_key;
+                       addr6 = (struct in6_addr *)&neigh->primary_key;
                        addr_type = ipv6_addr_type(addr6);
 
                        if (addr_type == IPV6_ADDR_ANY) {
                        addr_type = ipv6_addr_type(addr6);
 
                        if (addr_type == IPV6_ADDR_ANY) {
@@ -609,10 +734,10 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
        }
 
        tos = tiph->tos;
        }
 
        tos = tiph->tos;
-       if (tos&1) {
+       if (tos == 1) {
+               tos = 0;
                if (skb->protocol == htons(ETH_P_IP))
                        tos = old_iph->tos;
                if (skb->protocol == htons(ETH_P_IP))
                        tos = old_iph->tos;
-               tos &= ~1;
        }
 
        {
        }
 
        {
@@ -639,10 +764,10 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
        if (df)
                mtu = dst_mtu(&rt->u.dst) - dev->hard_header_len - tunnel->hlen;
        else
        if (df)
                mtu = dst_mtu(&rt->u.dst) - dev->hard_header_len - tunnel->hlen;
        else
-               mtu = skb->dst ? dst_mtu(skb->dst) : dev->mtu;
+               mtu = skb_dst(skb) ? dst_mtu(skb_dst(skb)) : dev->mtu;
 
 
-       if (skb->dst)
-               skb->dst->ops->update_pmtu(skb->dst, mtu);
+       if (skb_dst(skb))
+               skb_dst(skb)->ops->update_pmtu(skb_dst(skb), mtu);
 
        if (skb->protocol == htons(ETH_P_IP)) {
                df |= (old_iph->frag_off&htons(IP_DF));
 
        if (skb->protocol == htons(ETH_P_IP)) {
                df |= (old_iph->frag_off&htons(IP_DF));
@@ -656,14 +781,14 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
        }
 #ifdef CONFIG_IPV6
        else if (skb->protocol == htons(ETH_P_IPV6)) {
        }
 #ifdef CONFIG_IPV6
        else if (skb->protocol == htons(ETH_P_IPV6)) {
-               struct rt6_info *rt6 = (struct rt6_info*)skb->dst;
+               struct rt6_info *rt6 = (struct rt6_info *)skb_dst(skb);
 
 
-               if (rt6 && mtu < dst_mtu(skb->dst) && mtu >= IPV6_MIN_MTU) {
+               if (rt6 && mtu < dst_mtu(skb_dst(skb)) && mtu >= IPV6_MIN_MTU) {
                        if ((tunnel->parms.iph.daddr &&
                             !ipv4_is_multicast(tunnel->parms.iph.daddr)) ||
                            rt6->rt6i_dst.plen == 128) {
                                rt6->rt6i_flags |= RTF_MODIFIED;
                        if ((tunnel->parms.iph.daddr &&
                             !ipv4_is_multicast(tunnel->parms.iph.daddr)) ||
                            rt6->rt6i_dst.plen == 128) {
                                rt6->rt6i_flags |= RTF_MODIFIED;
-                               skb->dst->metrics[RTAX_MTU-1] = mtu;
+                               skb_dst(skb)->metrics[RTAX_MTU-1] = mtu;
                        }
                }
 
                        }
                }
 
@@ -676,7 +801,8 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
 #endif
 
        if (tunnel->err_count > 0) {
 #endif
 
        if (tunnel->err_count > 0) {
-               if (jiffies - tunnel->err_time < IPTUNNEL_ERR_TIMEO) {
+               if (time_before(jiffies,
+                               tunnel->err_time + IPTUNNEL_ERR_TIMEO)) {
                        tunnel->err_count--;
 
                        dst_link_failure(skb);
                        tunnel->err_count--;
 
                        dst_link_failure(skb);
@@ -691,10 +817,9 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
                struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
                if (!new_skb) {
                        ip_rt_put(rt);
                struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
                if (!new_skb) {
                        ip_rt_put(rt);
-                       stats->tx_dropped++;
+                       txq->tx_dropped++;
                        dev_kfree_skb(skb);
                        dev_kfree_skb(skb);
-                       tunnel->recursion--;
-                       return 0;
+                       return NETDEV_TX_OK;
                }
                if (skb->sk)
                        skb_set_owner_w(new_skb, skb->sk);
                }
                if (skb->sk)
                        skb_set_owner_w(new_skb, skb->sk);
@@ -703,14 +828,14 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
                old_iph = ip_hdr(skb);
        }
 
                old_iph = ip_hdr(skb);
        }
 
-       skb->transport_header = skb->network_header;
+       skb_reset_transport_header(skb);
        skb_push(skb, gre_hlen);
        skb_reset_network_header(skb);
        memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
        IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED |
                              IPSKB_REROUTED);
        skb_push(skb, gre_hlen);
        skb_reset_network_header(skb);
        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;
+       skb_dst_drop(skb);
+       skb_dst_set(skb, &rt->u.dst);
 
        /*
         *      Push down and install the IPIP header.
 
        /*
         *      Push down and install the IPIP header.
@@ -730,14 +855,15 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
                        iph->ttl = old_iph->ttl;
 #ifdef CONFIG_IPV6
                else if (skb->protocol == htons(ETH_P_IPV6))
                        iph->ttl = old_iph->ttl;
 #ifdef CONFIG_IPV6
                else if (skb->protocol == htons(ETH_P_IPV6))
-                       iph->ttl = ((struct ipv6hdr*)old_iph)->hop_limit;
+                       iph->ttl = ((struct ipv6hdr *)old_iph)->hop_limit;
 #endif
                else
                        iph->ttl = dst_metric(&rt->u.dst, RTAX_HOPLIMIT);
        }
 
 #endif
                else
                        iph->ttl = dst_metric(&rt->u.dst, RTAX_HOPLIMIT);
        }
 
-       ((__be16*)(iph+1))[0] = tunnel->parms.o_flags;
-       ((__be16*)(iph+1))[1] = skb->protocol;
+       ((__be16 *)(iph + 1))[0] = tunnel->parms.o_flags;
+       ((__be16 *)(iph + 1))[1] = (dev->type == ARPHRD_ETHER) ?
+                                  htons(ETH_P_TEB) : skb->protocol;
 
        if (tunnel->parms.o_flags&(GRE_KEY|GRE_CSUM|GRE_SEQ)) {
                __be32 *ptr = (__be32*)(((u8*)iph) + tunnel->hlen - 4);
 
        if (tunnel->parms.o_flags&(GRE_KEY|GRE_CSUM|GRE_SEQ)) {
                __be32 *ptr = (__be32*)(((u8*)iph) + tunnel->hlen - 4);
@@ -760,8 +886,7 @@ static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
        nf_reset(skb);
 
        IPTUNNEL_XMIT();
        nf_reset(skb);
 
        IPTUNNEL_XMIT();
-       tunnel->recursion--;
-       return 0;
+       return NETDEV_TX_OK;
 
 tx_error_icmp:
        dst_link_failure(skb);
 
 tx_error_icmp:
        dst_link_failure(skb);
@@ -769,11 +894,10 @@ tx_error_icmp:
 tx_error:
        stats->tx_errors++;
        dev_kfree_skb(skb);
 tx_error:
        stats->tx_errors++;
        dev_kfree_skb(skb);
-       tunnel->recursion--;
-       return 0;
+       return NETDEV_TX_OK;
 }
 
 }
 
-static void ipgre_tunnel_bind_dev(struct net_device *dev)
+static int ipgre_tunnel_bind_dev(struct net_device *dev)
 {
        struct net_device *tdev = NULL;
        struct ip_tunnel *tunnel;
 {
        struct net_device *tdev = NULL;
        struct ip_tunnel *tunnel;
@@ -799,7 +923,9 @@ static void ipgre_tunnel_bind_dev(struct net_device *dev)
                        tdev = rt->u.dst.dev;
                        ip_rt_put(rt);
                }
                        tdev = rt->u.dst.dev;
                        ip_rt_put(rt);
                }
-               dev->flags |= IFF_POINTOPOINT;
+
+               if (dev->type != ARPHRD_ETHER)
+                       dev->flags |= IFF_POINTOPOINT;
        }
 
        if (!tdev && tunnel->parms.link)
        }
 
        if (!tdev && tunnel->parms.link)
@@ -821,9 +947,14 @@ static void ipgre_tunnel_bind_dev(struct net_device *dev)
                        addend += 4;
        }
        dev->needed_headroom = addend + hlen;
                        addend += 4;
        }
        dev->needed_headroom = addend + hlen;
-       dev->mtu = mtu - dev->hard_header_len - addend;
+       mtu -= dev->hard_header_len + addend;
+
+       if (mtu < 68)
+               mtu = 68;
+
        tunnel->hlen = addend;
 
        tunnel->hlen = addend;
 
+       return mtu;
 }
 
 static int
 }
 
 static int
@@ -884,7 +1015,7 @@ ipgre_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
                                        break;
                                }
                        } else {
                                        break;
                                }
                        } else {
-                               unsigned nflags=0;
+                               unsigned nflags = 0;
 
                                t = netdev_priv(dev);
 
 
                                t = netdev_priv(dev);
 
@@ -917,7 +1048,7 @@ ipgre_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
                                t->parms.iph.frag_off = p.iph.frag_off;
                                if (t->parms.link != p.link) {
                                        t->parms.link = p.link;
                                t->parms.iph.frag_off = p.iph.frag_off;
                                if (t->parms.link != p.link) {
                                        t->parms.link = p.link;
-                                       ipgre_tunnel_bind_dev(dev);
+                                       dev->mtu = ipgre_tunnel_bind_dev(dev);
                                        netdev_state_change(dev);
                                }
                        }
                                        netdev_state_change(dev);
                                }
                        }
@@ -1026,7 +1157,7 @@ static int ipgre_header(struct sk_buff *skb, struct net_device *dev,
 
 static int ipgre_header_parse(const struct sk_buff *skb, unsigned char *haddr)
 {
 
 static int ipgre_header_parse(const struct sk_buff *skb, unsigned char *haddr)
 {
-       struct iphdr *iph = (struct iphdr*) skb_mac_header(skb);
+       struct iphdr *iph = (struct iphdr *) skb_mac_header(skb);
        memcpy(haddr, &iph->saddr, 4);
        return 4;
 }
        memcpy(haddr, &iph->saddr, 4);
        return 4;
 }
@@ -1064,6 +1195,7 @@ static int ipgre_open(struct net_device *dev)
 static int ipgre_close(struct net_device *dev)
 {
        struct ip_tunnel *t = netdev_priv(dev);
 static int ipgre_close(struct net_device *dev)
 {
        struct ip_tunnel *t = netdev_priv(dev);
+
        if (ipv4_is_multicast(t->parms.iph.daddr) && t->mlink) {
                struct in_device *in_dev;
                in_dev = inetdev_by_index(dev_net(dev), t->mlink);
        if (ipv4_is_multicast(t->parms.iph.daddr) && t->mlink) {
                struct in_device *in_dev;
                in_dev = inetdev_by_index(dev_net(dev), t->mlink);
@@ -1077,13 +1209,22 @@ static int ipgre_close(struct net_device *dev)
 
 #endif
 
 
 #endif
 
+static const struct net_device_ops ipgre_netdev_ops = {
+       .ndo_init               = ipgre_tunnel_init,
+       .ndo_uninit             = ipgre_tunnel_uninit,
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+       .ndo_open               = ipgre_open,
+       .ndo_stop               = ipgre_close,
+#endif
+       .ndo_start_xmit         = ipgre_tunnel_xmit,
+       .ndo_do_ioctl           = ipgre_tunnel_ioctl,
+       .ndo_change_mtu         = ipgre_tunnel_change_mtu,
+};
+
 static void ipgre_tunnel_setup(struct net_device *dev)
 {
 static void ipgre_tunnel_setup(struct net_device *dev)
 {
-       dev->uninit             = ipgre_tunnel_uninit;
+       dev->netdev_ops         = &ipgre_netdev_ops;
        dev->destructor         = free_netdev;
        dev->destructor         = free_netdev;
-       dev->hard_start_xmit    = ipgre_tunnel_xmit;
-       dev->do_ioctl           = ipgre_tunnel_ioctl;
-       dev->change_mtu         = ipgre_tunnel_change_mtu;
 
        dev->type               = ARPHRD_IPGRE;
        dev->needed_headroom    = LL_MAX_HEADER + sizeof(struct iphdr) + 4;
 
        dev->type               = ARPHRD_IPGRE;
        dev->needed_headroom    = LL_MAX_HEADER + sizeof(struct iphdr) + 4;
@@ -1092,6 +1233,7 @@ static void ipgre_tunnel_setup(struct net_device *dev)
        dev->iflink             = 0;
        dev->addr_len           = 4;
        dev->features           |= NETIF_F_NETNS_LOCAL;
        dev->iflink             = 0;
        dev->addr_len           = 4;
        dev->features           |= NETIF_F_NETNS_LOCAL;
+       dev->priv_flags         &= ~IFF_XMIT_DST_RELEASE;
 }
 
 static int ipgre_tunnel_init(struct net_device *dev)
 }
 
 static int ipgre_tunnel_init(struct net_device *dev)
@@ -1108,8 +1250,6 @@ static int ipgre_tunnel_init(struct net_device *dev)
        memcpy(dev->dev_addr, &tunnel->parms.iph.saddr, 4);
        memcpy(dev->broadcast, &tunnel->parms.iph.daddr, 4);
 
        memcpy(dev->dev_addr, &tunnel->parms.iph.saddr, 4);
        memcpy(dev->broadcast, &tunnel->parms.iph.daddr, 4);
 
-       ipgre_tunnel_bind_dev(dev);
-
        if (iph->daddr) {
 #ifdef CONFIG_NET_IPGRE_BROADCAST
                if (ipv4_is_multicast(iph->daddr)) {
        if (iph->daddr) {
 #ifdef CONFIG_NET_IPGRE_BROADCAST
                if (ipv4_is_multicast(iph->daddr)) {
@@ -1117,8 +1257,6 @@ static int ipgre_tunnel_init(struct net_device *dev)
                                return -EINVAL;
                        dev->flags = IFF_BROADCAST;
                        dev->header_ops = &ipgre_header_ops;
                                return -EINVAL;
                        dev->flags = IFF_BROADCAST;
                        dev->header_ops = &ipgre_header_ops;
-                       dev->open = ipgre_open;
-                       dev->stop = ipgre_close;
                }
 #endif
        } else
                }
 #endif
        } else
@@ -1127,7 +1265,7 @@ static int ipgre_tunnel_init(struct net_device *dev)
        return 0;
 }
 
        return 0;
 }
 
-static int ipgre_fb_tunnel_init(struct net_device *dev)
+static void ipgre_fb_tunnel_init(struct net_device *dev)
 {
        struct ip_tunnel *tunnel = netdev_priv(dev);
        struct iphdr *iph = &tunnel->parms.iph;
 {
        struct ip_tunnel *tunnel = netdev_priv(dev);
        struct iphdr *iph = &tunnel->parms.iph;
@@ -1143,43 +1281,36 @@ static int ipgre_fb_tunnel_init(struct net_device *dev)
 
        dev_hold(dev);
        ign->tunnels_wc[0]      = tunnel;
 
        dev_hold(dev);
        ign->tunnels_wc[0]      = tunnel;
-       return 0;
 }
 
 
 }
 
 
-static struct net_protocol ipgre_protocol = {
+static const struct net_protocol ipgre_protocol = {
        .handler        =       ipgre_rcv,
        .err_handler    =       ipgre_err,
        .netns_ok       =       1,
 };
 
        .handler        =       ipgre_rcv,
        .err_handler    =       ipgre_err,
        .netns_ok       =       1,
 };
 
-static void ipgre_destroy_tunnels(struct ipgre_net *ign)
+static void ipgre_destroy_tunnels(struct ipgre_net *ign, struct list_head *head)
 {
        int prio;
 
        for (prio = 0; prio < 4; prio++) {
                int h;
                for (h = 0; h < HASH_SIZE; h++) {
 {
        int prio;
 
        for (prio = 0; prio < 4; prio++) {
                int h;
                for (h = 0; h < HASH_SIZE; h++) {
-                       struct ip_tunnel *t;
-                       while ((t = ign->tunnels[prio][h]) != NULL)
-                               unregister_netdevice(t->dev);
+                       struct ip_tunnel *t = ign->tunnels[prio][h];
+
+                       while (t != NULL) {
+                               unregister_netdevice_queue(t->dev, head);
+                               t = t->next;
+                       }
                }
        }
 }
 
                }
        }
 }
 
-static int ipgre_init_net(struct net *net)
+static int __net_init ipgre_init_net(struct net *net)
 {
 {
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
        int err;
        int err;
-       struct ipgre_net *ign;
-
-       err = -ENOMEM;
-       ign = kzalloc(sizeof(struct ipgre_net), GFP_KERNEL);
-       if (ign == NULL)
-               goto err_alloc;
-
-       err = net_assign_generic(net, ipgre_net_id, ign);
-       if (err < 0)
-               goto err_assign;
 
        ign->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), "gre0",
                                           ipgre_tunnel_setup);
 
        ign->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), "gre0",
                                           ipgre_tunnel_setup);
@@ -1187,10 +1318,11 @@ static int ipgre_init_net(struct net *net)
                err = -ENOMEM;
                goto err_alloc_dev;
        }
                err = -ENOMEM;
                goto err_alloc_dev;
        }
-
-       ign->fb_tunnel_dev->init = ipgre_fb_tunnel_init;
        dev_net_set(ign->fb_tunnel_dev, net);
 
        dev_net_set(ign->fb_tunnel_dev, net);
 
+       ipgre_fb_tunnel_init(ign->fb_tunnel_dev);
+       ign->fb_tunnel_dev->rtnl_link_ops = &ipgre_link_ops;
+
        if ((err = register_netdev(ign->fb_tunnel_dev)))
                goto err_reg_dev;
 
        if ((err = register_netdev(ign->fb_tunnel_dev)))
                goto err_reg_dev;
 
@@ -1199,27 +1331,328 @@ static int ipgre_init_net(struct net *net)
 err_reg_dev:
        free_netdev(ign->fb_tunnel_dev);
 err_alloc_dev:
 err_reg_dev:
        free_netdev(ign->fb_tunnel_dev);
 err_alloc_dev:
-       /* nothing */
-err_assign:
-       kfree(ign);
-err_alloc:
        return err;
 }
 
        return err;
 }
 
-static void ipgre_exit_net(struct net *net)
+static void __net_exit ipgre_exit_net(struct net *net)
 {
        struct ipgre_net *ign;
 {
        struct ipgre_net *ign;
+       LIST_HEAD(list);
 
        ign = net_generic(net, ipgre_net_id);
        rtnl_lock();
 
        ign = net_generic(net, ipgre_net_id);
        rtnl_lock();
-       ipgre_destroy_tunnels(ign);
+       ipgre_destroy_tunnels(ign, &list);
+       unregister_netdevice_many(&list);
        rtnl_unlock();
        rtnl_unlock();
-       kfree(ign);
 }
 
 static struct pernet_operations ipgre_net_ops = {
        .init = ipgre_init_net,
        .exit = ipgre_exit_net,
 }
 
 static struct pernet_operations ipgre_net_ops = {
        .init = ipgre_init_net,
        .exit = ipgre_exit_net,
+       .id   = &ipgre_net_id,
+       .size = sizeof(struct ipgre_net),
+};
+
+static int ipgre_tunnel_validate(struct nlattr *tb[], struct nlattr *data[])
+{
+       __be16 flags;
+
+       if (!data)
+               return 0;
+
+       flags = 0;
+       if (data[IFLA_GRE_IFLAGS])
+               flags |= nla_get_be16(data[IFLA_GRE_IFLAGS]);
+       if (data[IFLA_GRE_OFLAGS])
+               flags |= nla_get_be16(data[IFLA_GRE_OFLAGS]);
+       if (flags & (GRE_VERSION|GRE_ROUTING))
+               return -EINVAL;
+
+       return 0;
+}
+
+static int ipgre_tap_validate(struct nlattr *tb[], struct nlattr *data[])
+{
+       __be32 daddr;
+
+       if (tb[IFLA_ADDRESS]) {
+               if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN)
+                       return -EINVAL;
+               if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS])))
+                       return -EADDRNOTAVAIL;
+       }
+
+       if (!data)
+               goto out;
+
+       if (data[IFLA_GRE_REMOTE]) {
+               memcpy(&daddr, nla_data(data[IFLA_GRE_REMOTE]), 4);
+               if (!daddr)
+                       return -EINVAL;
+       }
+
+out:
+       return ipgre_tunnel_validate(tb, data);
+}
+
+static void ipgre_netlink_parms(struct nlattr *data[],
+                               struct ip_tunnel_parm *parms)
+{
+       memset(parms, 0, sizeof(*parms));
+
+       parms->iph.protocol = IPPROTO_GRE;
+
+       if (!data)
+               return;
+
+       if (data[IFLA_GRE_LINK])
+               parms->link = nla_get_u32(data[IFLA_GRE_LINK]);
+
+       if (data[IFLA_GRE_IFLAGS])
+               parms->i_flags = nla_get_be16(data[IFLA_GRE_IFLAGS]);
+
+       if (data[IFLA_GRE_OFLAGS])
+               parms->o_flags = nla_get_be16(data[IFLA_GRE_OFLAGS]);
+
+       if (data[IFLA_GRE_IKEY])
+               parms->i_key = nla_get_be32(data[IFLA_GRE_IKEY]);
+
+       if (data[IFLA_GRE_OKEY])
+               parms->o_key = nla_get_be32(data[IFLA_GRE_OKEY]);
+
+       if (data[IFLA_GRE_LOCAL])
+               parms->iph.saddr = nla_get_be32(data[IFLA_GRE_LOCAL]);
+
+       if (data[IFLA_GRE_REMOTE])
+               parms->iph.daddr = nla_get_be32(data[IFLA_GRE_REMOTE]);
+
+       if (data[IFLA_GRE_TTL])
+               parms->iph.ttl = nla_get_u8(data[IFLA_GRE_TTL]);
+
+       if (data[IFLA_GRE_TOS])
+               parms->iph.tos = nla_get_u8(data[IFLA_GRE_TOS]);
+
+       if (!data[IFLA_GRE_PMTUDISC] || nla_get_u8(data[IFLA_GRE_PMTUDISC]))
+               parms->iph.frag_off = htons(IP_DF);
+}
+
+static int ipgre_tap_init(struct net_device *dev)
+{
+       struct ip_tunnel *tunnel;
+
+       tunnel = netdev_priv(dev);
+
+       tunnel->dev = dev;
+       strcpy(tunnel->parms.name, dev->name);
+
+       ipgre_tunnel_bind_dev(dev);
+
+       return 0;
+}
+
+static const struct net_device_ops ipgre_tap_netdev_ops = {
+       .ndo_init               = ipgre_tap_init,
+       .ndo_uninit             = ipgre_tunnel_uninit,
+       .ndo_start_xmit         = ipgre_tunnel_xmit,
+       .ndo_set_mac_address    = eth_mac_addr,
+       .ndo_validate_addr      = eth_validate_addr,
+       .ndo_change_mtu         = ipgre_tunnel_change_mtu,
+};
+
+static void ipgre_tap_setup(struct net_device *dev)
+{
+
+       ether_setup(dev);
+
+       dev->netdev_ops         = &ipgre_tap_netdev_ops;
+       dev->destructor         = free_netdev;
+
+       dev->iflink             = 0;
+       dev->features           |= NETIF_F_NETNS_LOCAL;
+}
+
+static int ipgre_newlink(struct net *src_net, struct net_device *dev, struct nlattr *tb[],
+                        struct nlattr *data[])
+{
+       struct ip_tunnel *nt;
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       int mtu;
+       int err;
+
+       nt = netdev_priv(dev);
+       ipgre_netlink_parms(data, &nt->parms);
+
+       if (ipgre_tunnel_find(net, &nt->parms, dev->type))
+               return -EEXIST;
+
+       if (dev->type == ARPHRD_ETHER && !tb[IFLA_ADDRESS])
+               random_ether_addr(dev->dev_addr);
+
+       mtu = ipgre_tunnel_bind_dev(dev);
+       if (!tb[IFLA_MTU])
+               dev->mtu = mtu;
+
+       err = register_netdevice(dev);
+       if (err)
+               goto out;
+
+       dev_hold(dev);
+       ipgre_tunnel_link(ign, nt);
+
+out:
+       return err;
+}
+
+static int ipgre_changelink(struct net_device *dev, struct nlattr *tb[],
+                           struct nlattr *data[])
+{
+       struct ip_tunnel *t, *nt;
+       struct net *net = dev_net(dev);
+       struct ipgre_net *ign = net_generic(net, ipgre_net_id);
+       struct ip_tunnel_parm p;
+       int mtu;
+
+       if (dev == ign->fb_tunnel_dev)
+               return -EINVAL;
+
+       nt = netdev_priv(dev);
+       ipgre_netlink_parms(data, &p);
+
+       t = ipgre_tunnel_locate(net, &p, 0);
+
+       if (t) {
+               if (t->dev != dev)
+                       return -EEXIST;
+       } else {
+               t = nt;
+
+               if (dev->type != ARPHRD_ETHER) {
+                       unsigned nflags = 0;
+
+                       if (ipv4_is_multicast(p.iph.daddr))
+                               nflags = IFF_BROADCAST;
+                       else if (p.iph.daddr)
+                               nflags = IFF_POINTOPOINT;
+
+                       if ((dev->flags ^ nflags) &
+                           (IFF_POINTOPOINT | IFF_BROADCAST))
+                               return -EINVAL;
+               }
+
+               ipgre_tunnel_unlink(ign, t);
+               t->parms.iph.saddr = p.iph.saddr;
+               t->parms.iph.daddr = p.iph.daddr;
+               t->parms.i_key = p.i_key;
+               if (dev->type != ARPHRD_ETHER) {
+                       memcpy(dev->dev_addr, &p.iph.saddr, 4);
+                       memcpy(dev->broadcast, &p.iph.daddr, 4);
+               }
+               ipgre_tunnel_link(ign, t);
+               netdev_state_change(dev);
+       }
+
+       t->parms.o_key = p.o_key;
+       t->parms.iph.ttl = p.iph.ttl;
+       t->parms.iph.tos = p.iph.tos;
+       t->parms.iph.frag_off = p.iph.frag_off;
+
+       if (t->parms.link != p.link) {
+               t->parms.link = p.link;
+               mtu = ipgre_tunnel_bind_dev(dev);
+               if (!tb[IFLA_MTU])
+                       dev->mtu = mtu;
+               netdev_state_change(dev);
+       }
+
+       return 0;
+}
+
+static size_t ipgre_get_size(const struct net_device *dev)
+{
+       return
+               /* IFLA_GRE_LINK */
+               nla_total_size(4) +
+               /* IFLA_GRE_IFLAGS */
+               nla_total_size(2) +
+               /* IFLA_GRE_OFLAGS */
+               nla_total_size(2) +
+               /* IFLA_GRE_IKEY */
+               nla_total_size(4) +
+               /* IFLA_GRE_OKEY */
+               nla_total_size(4) +
+               /* IFLA_GRE_LOCAL */
+               nla_total_size(4) +
+               /* IFLA_GRE_REMOTE */
+               nla_total_size(4) +
+               /* IFLA_GRE_TTL */
+               nla_total_size(1) +
+               /* IFLA_GRE_TOS */
+               nla_total_size(1) +
+               /* IFLA_GRE_PMTUDISC */
+               nla_total_size(1) +
+               0;
+}
+
+static int ipgre_fill_info(struct sk_buff *skb, const struct net_device *dev)
+{
+       struct ip_tunnel *t = netdev_priv(dev);
+       struct ip_tunnel_parm *p = &t->parms;
+
+       NLA_PUT_U32(skb, IFLA_GRE_LINK, p->link);
+       NLA_PUT_BE16(skb, IFLA_GRE_IFLAGS, p->i_flags);
+       NLA_PUT_BE16(skb, IFLA_GRE_OFLAGS, p->o_flags);
+       NLA_PUT_BE32(skb, IFLA_GRE_IKEY, p->i_key);
+       NLA_PUT_BE32(skb, IFLA_GRE_OKEY, p->o_key);
+       NLA_PUT_BE32(skb, IFLA_GRE_LOCAL, p->iph.saddr);
+       NLA_PUT_BE32(skb, IFLA_GRE_REMOTE, p->iph.daddr);
+       NLA_PUT_U8(skb, IFLA_GRE_TTL, p->iph.ttl);
+       NLA_PUT_U8(skb, IFLA_GRE_TOS, p->iph.tos);
+       NLA_PUT_U8(skb, IFLA_GRE_PMTUDISC, !!(p->iph.frag_off & htons(IP_DF)));
+
+       return 0;
+
+nla_put_failure:
+       return -EMSGSIZE;
+}
+
+static const struct nla_policy ipgre_policy[IFLA_GRE_MAX + 1] = {
+       [IFLA_GRE_LINK]         = { .type = NLA_U32 },
+       [IFLA_GRE_IFLAGS]       = { .type = NLA_U16 },
+       [IFLA_GRE_OFLAGS]       = { .type = NLA_U16 },
+       [IFLA_GRE_IKEY]         = { .type = NLA_U32 },
+       [IFLA_GRE_OKEY]         = { .type = NLA_U32 },
+       [IFLA_GRE_LOCAL]        = { .len = FIELD_SIZEOF(struct iphdr, saddr) },
+       [IFLA_GRE_REMOTE]       = { .len = FIELD_SIZEOF(struct iphdr, daddr) },
+       [IFLA_GRE_TTL]          = { .type = NLA_U8 },
+       [IFLA_GRE_TOS]          = { .type = NLA_U8 },
+       [IFLA_GRE_PMTUDISC]     = { .type = NLA_U8 },
+};
+
+static struct rtnl_link_ops ipgre_link_ops __read_mostly = {
+       .kind           = "gre",
+       .maxtype        = IFLA_GRE_MAX,
+       .policy         = ipgre_policy,
+       .priv_size      = sizeof(struct ip_tunnel),
+       .setup          = ipgre_tunnel_setup,
+       .validate       = ipgre_tunnel_validate,
+       .newlink        = ipgre_newlink,
+       .changelink     = ipgre_changelink,
+       .get_size       = ipgre_get_size,
+       .fill_info      = ipgre_fill_info,
+};
+
+static struct rtnl_link_ops ipgre_tap_ops __read_mostly = {
+       .kind           = "gretap",
+       .maxtype        = IFLA_GRE_MAX,
+       .policy         = ipgre_policy,
+       .priv_size      = sizeof(struct ip_tunnel),
+       .setup          = ipgre_tap_setup,
+       .validate       = ipgre_tap_validate,
+       .newlink        = ipgre_newlink,
+       .changelink     = ipgre_changelink,
+       .get_size       = ipgre_get_size,
+       .fill_info      = ipgre_fill_info,
 };
 
 /*
 };
 
 /*
@@ -1237,21 +1670,41 @@ static int __init ipgre_init(void)
                return -EAGAIN;
        }
 
                return -EAGAIN;
        }
 
-       err = register_pernet_gen_device(&ipgre_net_id, &ipgre_net_ops);
+       err = register_pernet_device(&ipgre_net_ops);
+       if (err < 0)
+               goto gen_device_failed;
+
+       err = rtnl_link_register(&ipgre_link_ops);
+       if (err < 0)
+               goto rtnl_link_failed;
+
+       err = rtnl_link_register(&ipgre_tap_ops);
        if (err < 0)
        if (err < 0)
-               inet_del_protocol(&ipgre_protocol, IPPROTO_GRE);
+               goto tap_ops_failed;
 
 
+out:
        return err;
        return err;
+
+tap_ops_failed:
+       rtnl_link_unregister(&ipgre_link_ops);
+rtnl_link_failed:
+       unregister_pernet_device(&ipgre_net_ops);
+gen_device_failed:
+       inet_del_protocol(&ipgre_protocol, IPPROTO_GRE);
+       goto out;
 }
 
 static void __exit ipgre_fini(void)
 {
 }
 
 static void __exit ipgre_fini(void)
 {
+       rtnl_link_unregister(&ipgre_tap_ops);
+       rtnl_link_unregister(&ipgre_link_ops);
+       unregister_pernet_device(&ipgre_net_ops);
        if (inet_del_protocol(&ipgre_protocol, IPPROTO_GRE) < 0)
                printk(KERN_INFO "ipgre close: can't remove protocol\n");
        if (inet_del_protocol(&ipgre_protocol, IPPROTO_GRE) < 0)
                printk(KERN_INFO "ipgre close: can't remove protocol\n");
-
-       unregister_pernet_gen_device(ipgre_net_id, &ipgre_net_ops);
 }
 
 module_init(ipgre_init);
 module_exit(ipgre_fini);
 MODULE_LICENSE("GPL");
 }
 
 module_init(ipgre_init);
 module_exit(ipgre_fini);
 MODULE_LICENSE("GPL");
+MODULE_ALIAS_RTNL_LINK("gre");
+MODULE_ALIAS_RTNL_LINK("gretap");