ipv6 sit: 6rd (IPv6 Rapid Deployment) Support.
[pandora-kernel.git] / net / ipv6 / sit.c
index fcb5396..6955654 100644 (file)
@@ -15,7 +15,6 @@
  * Roger Venning <r.venning@telstra.com>:      6to4 support
  * Nate Thompson <nate@thebog.net>:            6to4 support
  * Fred Templin <fred.l.templin@boeing.com>:   isatap support
- * Sascha Hlusiak <mail@saschahlusiak.de>:     stateless autoconf for isatap
  */
 
 #include <linux/module.h>
@@ -162,6 +161,21 @@ static void ipip6_tunnel_link(struct sit_net *sitn, struct ip_tunnel *t)
        write_unlock_bh(&ipip6_lock);
 }
 
+static void ipip6_tunnel_clone_6rd(struct ip_tunnel *t, struct sit_net *sitn)
+{
+#ifdef CONFIG_IPV6_SIT_6RD
+       if (t->dev == sitn->fb_tunnel_dev) {
+               ipv6_addr_set(&t->ip6rd.prefix, htonl(0x20020000), 0, 0, 0);
+               t->ip6rd.relay_prefix = 0;
+               t->ip6rd.prefixlen = 16;
+               t->ip6rd.relay_prefixlen = 0;
+       } else {
+               struct ip_tunnel *t0 = netdev_priv(sitn->fb_tunnel_dev);
+               memcpy(&t->ip6rd, &t0->ip6rd, sizeof(t->ip6rd));
+       }
+#endif
+}
+
 static struct ip_tunnel * ipip6_tunnel_locate(struct net *net,
                struct ip_tunnel_parm *parms, int create)
 {
@@ -214,6 +228,8 @@ static struct ip_tunnel * ipip6_tunnel_locate(struct net *net,
 
        dev_hold(dev);
 
+       ipip6_tunnel_clone_6rd(t, sitn);
+
        ipip6_tunnel_link(sitn, nt);
        return nt;
 
@@ -223,44 +239,6 @@ failed:
        return NULL;
 }
 
-static void ipip6_tunnel_rs_timer(unsigned long data)
-{
-       struct ip_tunnel_prl_entry *p = (struct ip_tunnel_prl_entry *) data;
-       struct inet6_dev *ifp;
-       struct inet6_ifaddr *addr;
-
-       spin_lock(&p->lock);
-       ifp = __in6_dev_get(p->tunnel->dev);
-
-       read_lock_bh(&ifp->lock);
-       for (addr = ifp->addr_list; addr; addr = addr->if_next) {
-               struct in6_addr rtr;
-
-               if (!(ipv6_addr_type(&addr->addr) & IPV6_ADDR_LINKLOCAL))
-                       continue;
-
-               /* Send RS to guessed linklocal address of router
-                *
-                * Better: send to ff02::2 encapsuled in unicast directly
-                * to router-v4 instead of guessing the v6 address.
-                *
-                * Cisco/Windows seem to not set the u/l bit correctly,
-                * so we won't guess right.
-                */
-               ipv6_addr_set(&rtr,  htonl(0xFE800000), 0, 0, 0);
-               if (!__ipv6_isatap_ifid(rtr.s6_addr + 8,
-                                       p->addr)) {
-                       ndisc_send_rs(p->tunnel->dev, &addr->addr, &rtr);
-               }
-       }
-       read_unlock_bh(&ifp->lock);
-
-       mod_timer(&p->rs_timer, jiffies + HZ * p->rs_delay);
-       spin_unlock(&p->lock);
-
-       return;
-}
-
 static struct ip_tunnel_prl_entry *
 __ipip6_tunnel_locate_prl(struct ip_tunnel *t, __be32 addr)
 {
@@ -313,13 +291,12 @@ static int ipip6_tunnel_get_prl(struct ip_tunnel *t,
 
        c = 0;
        for (prl = t->prl; prl; prl = prl->next) {
-               if (c > cmax)
+               if (c >= cmax)
                        break;
                if (kprl.addr != htonl(INADDR_ANY) && prl->addr != kprl.addr)
                        continue;
                kp[c].addr = prl->addr;
                kp[c].flags = prl->flags;
-               kp[c].rs_delay = prl->rs_delay;
                c++;
                if (kprl.addr != htonl(INADDR_ANY))
                        break;
@@ -369,23 +346,11 @@ ipip6_tunnel_add_prl(struct ip_tunnel *t, struct ip_tunnel_prl *a, int chg)
        }
 
        p->next = t->prl;
-       p->tunnel = t;
        t->prl = p;
        t->prl_count++;
-
-       spin_lock_init(&p->lock);
-       setup_timer(&p->rs_timer, ipip6_tunnel_rs_timer, (unsigned long) p);
 update:
        p->addr = a->addr;
        p->flags = a->flags;
-       p->rs_delay = a->rs_delay;
-       if (p->rs_delay == 0)
-               p->rs_delay = IPTUNNEL_RS_DEFAULT_DELAY;
-       spin_lock(&p->lock);
-       del_timer(&p->rs_timer);
-       if (p->flags & PRL_DEFAULT)
-               mod_timer(&p->rs_timer, jiffies + 1);
-       spin_unlock(&p->lock);
 out:
        write_unlock(&ipip6_lock);
        return err;
@@ -404,9 +369,6 @@ ipip6_tunnel_del_prl(struct ip_tunnel *t, struct ip_tunnel_prl *a)
                        if ((*p)->addr == a->addr) {
                                x = *p;
                                *p = x->next;
-                               spin_lock(&x->lock);
-                               del_timer(&x->rs_timer);
-                               spin_unlock(&x->lock);
                                kfree(x);
                                t->prl_count--;
                                goto out;
@@ -417,9 +379,6 @@ ipip6_tunnel_del_prl(struct ip_tunnel *t, struct ip_tunnel_prl *a)
                while (t->prl) {
                        x = t->prl;
                        t->prl = t->prl->next;
-                       spin_lock(&x->lock);
-                       del_timer(&x->rs_timer);
-                       spin_unlock(&x->lock);
                        kfree(x);
                        t->prl_count--;
                }
@@ -590,17 +549,41 @@ out:
        return 0;
 }
 
-/* Returns the embedded IPv4 address if the IPv6 address
-   comes from 6to4 (RFC 3056) addr space */
-
-static inline __be32 try_6to4(struct in6_addr *v6dst)
+/*
+ * Returns the embedded IPv4 address if the IPv6 address
+ * comes from 6rd / 6to4 (RFC 3056) addr space.
+ */
+static inline
+__be32 try_6rd(struct in6_addr *v6dst, struct ip_tunnel *tunnel)
 {
        __be32 dst = 0;
 
+#ifdef CONFIG_IPV6_SIT_6RD
+       if (ipv6_prefix_equal(v6dst, &tunnel->ip6rd.prefix,
+                             tunnel->ip6rd.prefixlen)) {
+               unsigned pbw0, pbi0;
+               int pbi1;
+               u32 d;
+
+               pbw0 = tunnel->ip6rd.prefixlen >> 5;
+               pbi0 = tunnel->ip6rd.prefixlen & 0x1f;
+
+               d = (ntohl(tunnel->ip6rd.prefix.s6_addr32[pbw0]) << pbi0) >>
+                   tunnel->ip6rd.relay_prefixlen;
+
+               pbi1 = pbi0 - tunnel->ip6rd.relay_prefixlen;
+               if (pbi1 > 0)
+                       d |= ntohl(tunnel->ip6rd.prefix.s6_addr32[pbw0 + 1]) >>
+                            (32 - pbi1);
+
+               dst = tunnel->ip6rd.relay_prefix | htonl(d);
+       }
+#else
        if (v6dst->s6_addr16[0] == htons(0x2002)) {
                /* 6to4 v6 addr has 16 bits prefix, 32 v4addr, 16 SLA, ... */
                memcpy(&dst, &v6dst->s6_addr16[1], 4);
        }
+#endif
        return dst;
 }
 
@@ -613,7 +596,8 @@ static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb,
                                     struct net_device *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  *tiph = &tunnel->parms.iph;
        struct ipv6hdr *iph6 = ipv6_hdr(skb);
        u8     tos = tunnel->parms.iph.tos;
@@ -653,7 +637,7 @@ static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb,
        }
 
        if (!dst)
-               dst = try_6to4(&iph6->daddr);
+               dst = try_6rd(&iph6->daddr, tunnel);
 
        if (!dst) {
                struct neighbour *neigh = NULL;
@@ -746,7 +730,7 @@ static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb,
                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);
                        return NETDEV_TX_OK;
                }
@@ -843,9 +827,15 @@ ipip6_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
        struct ip_tunnel *t;
        struct net *net = dev_net(dev);
        struct sit_net *sitn = net_generic(net, sit_net_id);
+#ifdef CONFIG_IPV6_SIT_6RD
+       struct ip_tunnel_6rd ip6rd;
+#endif
 
        switch (cmd) {
        case SIOCGETTUNNEL:
+#ifdef CONFIG_IPV6_SIT_6RD
+       case SIOCGET6RD:
+#endif
                t = NULL;
                if (dev == sitn->fb_tunnel_dev) {
                        if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) {
@@ -856,9 +846,25 @@ ipip6_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
                }
                if (t == NULL)
                        t = netdev_priv(dev);
-               memcpy(&p, &t->parms, sizeof(p));
-               if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
-                       err = -EFAULT;
+
+               err = -EFAULT;
+               if (cmd == SIOCGETTUNNEL) {
+                       memcpy(&p, &t->parms, sizeof(p));
+                       if (copy_to_user(ifr->ifr_ifru.ifru_data, &p,
+                                        sizeof(p)))
+                               goto done;
+#ifdef CONFIG_IPV6_SIT_6RD
+               } else {
+                       ipv6_addr_copy(&ip6rd.prefix, &t->ip6rd.prefix);
+                       ip6rd.relay_prefix = t->ip6rd.relay_prefix;
+                       ip6rd.prefixlen = t->ip6rd.prefixlen;
+                       ip6rd.relay_prefixlen = t->ip6rd.relay_prefixlen;
+                       if (copy_to_user(ifr->ifr_ifru.ifru_data, &ip6rd,
+                                        sizeof(ip6rd)))
+                               goto done;
+#endif
+               }
+               err = 0;
                break;
 
        case SIOCADDTUNNEL:
@@ -979,6 +985,51 @@ ipip6_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
                netdev_state_change(dev);
                break;
 
+#ifdef CONFIG_IPV6_SIT_6RD
+       case SIOCADD6RD:
+       case SIOCCHG6RD:
+       case SIOCDEL6RD:
+               err = -EPERM;
+               if (!capable(CAP_NET_ADMIN))
+                       goto done;
+
+               err = -EFAULT;
+               if (copy_from_user(&ip6rd, ifr->ifr_ifru.ifru_data,
+                                  sizeof(ip6rd)))
+                       goto done;
+
+               t = netdev_priv(dev);
+
+               if (cmd != SIOCDEL6RD) {
+                       struct in6_addr prefix;
+                       __be32 relay_prefix;
+
+                       err = -EINVAL;
+                       if (ip6rd.relay_prefixlen > 32 ||
+                           ip6rd.prefixlen + (32 - ip6rd.relay_prefixlen) > 64)
+                               goto done;
+
+                       ipv6_addr_prefix(&prefix, &ip6rd.prefix,
+                                        ip6rd.prefixlen);
+                       if (!ipv6_addr_equal(&prefix, &ip6rd.prefix))
+                               goto done;
+                       relay_prefix = ip6rd.relay_prefix &
+                                      htonl(0xffffffffUL <<
+                                            (32 - ip6rd.relay_prefixlen));
+                       if (relay_prefix != ip6rd.relay_prefix)
+                               goto done;
+
+                       ipv6_addr_copy(&t->ip6rd.prefix, &prefix);
+                       t->ip6rd.relay_prefix = relay_prefix;
+                       t->ip6rd.prefixlen = ip6rd.prefixlen;
+                       t->ip6rd.relay_prefixlen = ip6rd.relay_prefixlen;
+               } else
+                       ipip6_tunnel_clone_6rd(t, sitn);
+
+               err = 0;
+               break;
+#endif
+
        default:
                err = -EINVAL;
        }