[IPV6]: ROUTE: Add experimental support for Route Information Option in RA (RFC4191).
authorYOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Tue, 21 Mar 2006 01:06:24 +0000 (17:06 -0800)
committerDavid S. Miller <davem@davemloft.net>
Tue, 21 Mar 2006 01:06:24 +0000 (17:06 -0800)
Signed-off-by: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/ipv6_route.h
include/net/ip6_route.h
include/net/ndisc.h
net/ipv6/Kconfig
net/ipv6/ndisc.c
net/ipv6/route.c

index f4b085c916088800d6d48d1b85e4d7dae1ab1569..b323ff5779672c77b6adfba1c1bdc87f4981f85c 100644 (file)
@@ -23,6 +23,8 @@
 #define RTF_NONEXTHOP  0x00200000      /* route with no nexthop        */
 #define RTF_EXPIRES    0x00400000
 
+#define RTF_ROUTEINFO  0x00800000      /* route information - RA       */
+
 #define RTF_CACHE      0x01000000      /* cache entry                  */
 #define RTF_FLOW       0x02000000      /* flow significant route       */
 #define RTF_POLICY     0x04000000      /* policy route                 */
index 50161322b8282ca1ad4b717fbd9362b697edec23..a398ae5e30f991c427638af44216de453492474f 100644 (file)
@@ -7,6 +7,23 @@
 #define IP6_RT_PRIO_KERN       512
 #define IP6_RT_FLOW_MASK       0x00ff
 
+struct route_info {
+       __u8                    type;
+       __u8                    length;
+       __u8                    prefix_len;
+#if defined(__BIG_ENDIAN_BITFIELD)
+       __u8                    reserved_h:3,
+                               route_pref:2,
+                               reserved_l:3;
+#elif defined(__LITTLE_ENDIAN_BITFIELD)
+       __u8                    reserved_l:3,
+                               route_pref:2,
+                               reserved_h:3;
+#endif
+       __u32                   lifetime;
+       __u8                    prefix[0];      /* 0,8 or 16 */
+};
+
 #ifdef __KERNEL__
 
 #include <net/flow.h>
@@ -92,6 +109,10 @@ extern struct rt6_info *    rt6_add_dflt_router(struct in6_addr *gwaddr,
 
 extern void                    rt6_purge_dflt_routers(void);
 
+extern int                     rt6_route_rcv(struct net_device *dev,
+                                             u8 *opt, int len,
+                                             struct in6_addr *gwaddr);
+
 extern void                    rt6_redirect(struct in6_addr *dest,
                                             struct in6_addr *saddr,
                                             struct neighbour *neigh,
index bbac87eeb422603a91377918f0e4b3bd9acc4689..91fa271a00640d5a0147c20f10dadef8aa536965 100644 (file)
@@ -22,6 +22,8 @@ enum {
        ND_OPT_PREFIX_INFO = 3,         /* RFC2461 */
        ND_OPT_REDIRECT_HDR = 4,        /* RFC2461 */
        ND_OPT_MTU = 5,                 /* RFC2461 */
+       __ND_OPT_ARRAY_MAX,
+       ND_OPT_ROUTE_INFO = 24,         /* RFC4191 */
        __ND_OPT_MAX
 };
 
index c456ead8a4a3093fe9928712aa4f1775d3f5f93e..e6f83b6a2b76beec7b1a82d03fdc3fdb604c4e9f 100644 (file)
@@ -49,6 +49,14 @@ config IPV6_ROUTER_PREF
 
          If unsure, say N.
 
+config IPV6_ROUTE_INFO
+       bool "IPv6: Route Information (RFC 4191) support (EXPERIMENTAL)"
+       depends on IPV6_ROUTER_PREF && EXPERIMENTAL
+       ---help---
+         This is experimental support of Route Information.
+
+         If unsure, say N.
+
 config INET6_AH
        tristate "IPv6: AH transformation"
        depends on IPV6
index f4462ee33024f4793b844554e33871883d99f8ed..1f6256909674307727408937e83d5c2280d81d4d 100644 (file)
@@ -156,7 +156,11 @@ struct neigh_table nd_tbl = {
 
 /* ND options */
 struct ndisc_options {
-       struct nd_opt_hdr *nd_opt_array[__ND_OPT_MAX];
+       struct nd_opt_hdr *nd_opt_array[__ND_OPT_ARRAY_MAX];
+#ifdef CONFIG_IPV6_ROUTE_INFO
+       struct nd_opt_hdr *nd_opts_ri;
+       struct nd_opt_hdr *nd_opts_ri_end;
+#endif
 };
 
 #define nd_opts_src_lladdr     nd_opt_array[ND_OPT_SOURCE_LL_ADDR]
@@ -255,6 +259,13 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
                        if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0)
                                ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;
                        break;
+#ifdef CONFIG_IPV6_ROUTE_INFO
+               case ND_OPT_ROUTE_INFO:
+                       ndopts->nd_opts_ri_end = nd_opt;
+                       if (!ndopts->nd_opts_ri)
+                               ndopts->nd_opts_ri = nd_opt;
+                       break;
+#endif
                default:
                        /*
                         * Unknown options must be silently ignored,
@@ -1202,6 +1213,18 @@ skip_defrtr:
                             NEIGH_UPDATE_F_ISROUTER);
        }
 
+#ifdef CONFIG_IPV6_ROUTE_INFO
+       if (ndopts.nd_opts_ri) {
+               struct nd_opt_hdr *p;
+               for (p = ndopts.nd_opts_ri;
+                    p;
+                    p = ndisc_next_option(p, ndopts.nd_opts_ri_end)) {
+                       rt6_route_rcv(skb->dev, (u8*)p, (p->nd_opt_len) << 3,
+                                     &skb->nh.ipv6h->saddr);
+               }
+       }
+#endif
+
        if (in6_dev->cnf.accept_ra_pinfo && ndopts.nd_opts_pi) {
                struct nd_opt_hdr *p;
                for (p = ndopts.nd_opts_pi;
index c797b9bbb7d1aedb32d97453a680f6d55a08a1ce..0f30ee3d94eacd3010fbe40ebd442412675f2de3 100644 (file)
@@ -98,6 +98,14 @@ static int           ip6_pkt_discard_out(struct sk_buff *skb);
 static void            ip6_link_failure(struct sk_buff *skb);
 static void            ip6_rt_update_pmtu(struct dst_entry *dst, u32 mtu);
 
+#ifdef CONFIG_IPV6_ROUTE_INFO
+static struct rt6_info *rt6_add_route_info(struct in6_addr *prefix, int prefixlen,
+                                          struct in6_addr *gwaddr, int ifindex,
+                                          unsigned pref);
+static struct rt6_info *rt6_get_route_info(struct in6_addr *prefix, int prefixlen,
+                                          struct in6_addr *gwaddr, int ifindex);
+#endif
+
 static struct dst_ops ip6_dst_ops = {
        .family                 =       AF_INET6,
        .protocol               =       __constant_htons(ETH_P_IPV6),
@@ -346,6 +354,84 @@ static struct rt6_info *rt6_select(struct rt6_info **head, int oif,
        return (match ? match : &ip6_null_entry);
 }
 
+#ifdef CONFIG_IPV6_ROUTE_INFO
+int rt6_route_rcv(struct net_device *dev, u8 *opt, int len,
+                 struct in6_addr *gwaddr)
+{
+       struct route_info *rinfo = (struct route_info *) opt;
+       struct in6_addr prefix_buf, *prefix;
+       unsigned int pref;
+       u32 lifetime;
+       struct rt6_info *rt;
+
+       if (len < sizeof(struct route_info)) {
+               return -EINVAL;
+       }
+
+       /* Sanity check for prefix_len and length */
+       if (rinfo->length > 3) {
+               return -EINVAL;
+       } else if (rinfo->prefix_len > 128) {
+               return -EINVAL;
+       } else if (rinfo->prefix_len > 64) {
+               if (rinfo->length < 2) {
+                       return -EINVAL;
+               }
+       } else if (rinfo->prefix_len > 0) {
+               if (rinfo->length < 1) {
+                       return -EINVAL;
+               }
+       }
+
+       pref = rinfo->route_pref;
+       if (pref == ICMPV6_ROUTER_PREF_INVALID)
+               pref = ICMPV6_ROUTER_PREF_MEDIUM;
+
+       lifetime = htonl(rinfo->lifetime);
+       if (lifetime == 0xffffffff) {
+               /* infinity */
+       } else if (lifetime > 0x7fffffff/HZ) {
+               /* Avoid arithmetic overflow */
+               lifetime = 0x7fffffff/HZ - 1;
+       }
+
+       if (rinfo->length == 3)
+               prefix = (struct in6_addr *)rinfo->prefix;
+       else {
+               /* this function is safe */
+               ipv6_addr_prefix(&prefix_buf,
+                                (struct in6_addr *)rinfo->prefix,
+                                rinfo->prefix_len);
+               prefix = &prefix_buf;
+       }
+
+       rt = rt6_get_route_info(prefix, rinfo->prefix_len, gwaddr, dev->ifindex);
+
+       if (rt && !lifetime) {
+               ip6_del_rt(rt, NULL, NULL, NULL);
+               rt = NULL;
+       }
+
+       if (!rt && lifetime)
+               rt = rt6_add_route_info(prefix, rinfo->prefix_len, gwaddr, dev->ifindex,
+                                       pref);
+       else if (rt)
+               rt->rt6i_flags = RTF_ROUTEINFO |
+                                (rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
+
+       if (rt) {
+               if (lifetime == 0xffffffff) {
+                       rt->rt6i_flags &= ~RTF_EXPIRES;
+               } else {
+                       rt->rt6i_expires = jiffies + HZ * lifetime;
+                       rt->rt6i_flags |= RTF_EXPIRES;
+               }
+               dst_release(&rt->u.dst);
+       }
+       return 0;
+}
+#endif
+
 struct rt6_info *rt6_lookup(struct in6_addr *daddr, struct in6_addr *saddr,
                            int oif, int strict)
 {
@@ -1277,6 +1363,54 @@ static struct rt6_info * ip6_rt_copy(struct rt6_info *ort)
        return rt;
 }
 
+#ifdef CONFIG_IPV6_ROUTE_INFO
+static struct rt6_info *rt6_get_route_info(struct in6_addr *prefix, int prefixlen,
+                                          struct in6_addr *gwaddr, int ifindex)
+{
+       struct fib6_node *fn;
+       struct rt6_info *rt = NULL;
+
+       write_lock_bh(&rt6_lock);
+       fn = fib6_locate(&ip6_routing_table, prefix ,prefixlen, NULL, 0);
+       if (!fn)
+               goto out;
+
+       for (rt = fn->leaf; rt; rt = rt->u.next) {
+               if (rt->rt6i_dev->ifindex != ifindex)
+                       continue;
+               if ((rt->rt6i_flags & (RTF_ROUTEINFO|RTF_GATEWAY)) != (RTF_ROUTEINFO|RTF_GATEWAY))
+                       continue;
+               if (!ipv6_addr_equal(&rt->rt6i_gateway, gwaddr))
+                       continue;
+               dst_hold(&rt->u.dst);
+               break;
+       }
+out:
+       write_unlock_bh(&rt6_lock);
+       return rt;
+}
+
+static struct rt6_info *rt6_add_route_info(struct in6_addr *prefix, int prefixlen,
+                                          struct in6_addr *gwaddr, int ifindex,
+                                          unsigned pref)
+{
+       struct in6_rtmsg rtmsg;
+
+       memset(&rtmsg, 0, sizeof(rtmsg));
+       rtmsg.rtmsg_type = RTMSG_NEWROUTE;
+       ipv6_addr_copy(&rtmsg.rtmsg_dst, prefix);
+       rtmsg.rtmsg_dst_len = prefixlen;
+       ipv6_addr_copy(&rtmsg.rtmsg_gateway, gwaddr);
+       rtmsg.rtmsg_metric = 1024;
+       rtmsg.rtmsg_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_ROUTEINFO | RTF_UP | RTF_PREF(pref);
+       rtmsg.rtmsg_ifindex = ifindex;
+
+       ip6_route_add(&rtmsg, NULL, NULL, NULL);
+
+       return rt6_get_route_info(prefix, prefixlen, gwaddr, ifindex);
+}
+#endif
+
 struct rt6_info *rt6_get_dflt_router(struct in6_addr *addr, struct net_device *dev)
 {      
        struct rt6_info *rt;