]> bbs.cooldavid.org Git - net-next-2.6.git/blobdiff - net/ipv6/ndisc.c
[IPv6]: Export userland ND options through netlink (RDNSS support)
[net-next-2.6.git] / net / ipv6 / ndisc.c
index d4acd283111bab6cd32bc7a3653d28f89d64f669..6cc33dc83d1cb354767de626024fd6cd982573c3 100644 (file)
 /*
  *     Changes:
  *
+ *     Pierre Ynard                    :       export userland ND options
+ *                                             through netlink (RDNSS support)
  *     Lars Fenneberg                  :       fixed MTU setting on receipt
  *                                             of an RA.
- *
  *     Janos Farkas                    :       kmalloc failure checks
  *     Alexey Kuznetsov                :       state machine reworked
  *                                             and moved to net/core.
@@ -78,6 +79,9 @@
 #include <net/addrconf.h>
 #include <net/icmp.h>
 
+#include <net/netlink.h>
+#include <linux/rtnetlink.h>
+
 #include <net/flow.h>
 #include <net/ip6_checksum.h>
 #include <linux/proc_fs.h>
@@ -161,6 +165,8 @@ struct ndisc_options {
        struct nd_opt_hdr *nd_opts_ri;
        struct nd_opt_hdr *nd_opts_ri_end;
 #endif
+       struct nd_opt_hdr *nd_useropts;
+       struct nd_opt_hdr *nd_useropts_end;
 };
 
 #define nd_opts_src_lladdr     nd_opt_array[ND_OPT_SOURCE_LL_ADDR]
@@ -225,6 +231,22 @@ static struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur,
        return (cur <= end && cur->nd_opt_type == type ? cur : NULL);
 }
 
+static inline int ndisc_is_useropt(struct nd_opt_hdr *opt)
+{
+       return (opt->nd_opt_type == ND_OPT_RDNSS);
+}
+
+static struct nd_opt_hdr *ndisc_next_useropt(struct nd_opt_hdr *cur,
+                                            struct nd_opt_hdr *end)
+{
+       if (!cur || !end || cur >= end)
+               return NULL;
+       do {
+               cur = ((void *)cur) + (cur->nd_opt_len << 3);
+       } while(cur < end && !ndisc_is_useropt(cur));
+       return (cur <= end && ndisc_is_useropt(cur) ? cur : NULL);
+}
+
 static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
                                                 struct ndisc_options *ndopts)
 {
@@ -267,14 +289,21 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
                        break;
 #endif
                default:
-                       /*
-                        * Unknown options must be silently ignored,
-                        * to accommodate future extension to the protocol.
-                        */
-                       ND_PRINTK2(KERN_NOTICE
-                                  "%s(): ignored unsupported option; type=%d, len=%d\n",
-                                  __FUNCTION__,
-                                  nd_opt->nd_opt_type, nd_opt->nd_opt_len);
+                       if (ndisc_is_useropt(nd_opt)) {
+                               ndopts->nd_useropts_end = nd_opt;
+                               if (!ndopts->nd_useropts)
+                                       ndopts->nd_useropts = nd_opt;
+                       } else {
+                               /*
+                                * Unknown options must be silently ignored,
+                                * to accommodate future extension to the
+                                * protocol.
+                                */
+                               ND_PRINTK2(KERN_NOTICE
+                                          "%s(): ignored unsupported option; type=%d, len=%d\n",
+                                          __FUNCTION__,
+                                          nd_opt->nd_opt_type, nd_opt->nd_opt_len);
+                       }
                }
                opt_len -= l;
                nd_opt = ((void *)nd_opt) + l;
@@ -984,6 +1013,53 @@ out:
        in6_dev_put(idev);
 }
 
+static void ndisc_ra_useropt(struct sk_buff *ra, struct nd_opt_hdr *opt)
+{
+       struct icmp6hdr *icmp6h = (struct icmp6hdr *)skb_transport_header(ra);
+       struct sk_buff *skb;
+       struct nlmsghdr *nlh;
+       struct nduseroptmsg *ndmsg;
+       int err;
+       int base_size = NLMSG_ALIGN(sizeof(struct nduseroptmsg)
+                                   + (opt->nd_opt_len << 3));
+       size_t msg_size = base_size + nla_total_size(sizeof(struct in6_addr));
+
+       skb = nlmsg_new(msg_size, GFP_ATOMIC);
+       if (skb == NULL) {
+               err = -ENOBUFS;
+               goto errout;
+       }
+
+       nlh = nlmsg_put(skb, 0, 0, RTM_NEWNDUSEROPT, base_size, 0);
+       if (nlh == NULL) {
+               goto nla_put_failure;
+       }
+
+       ndmsg = nlmsg_data(nlh);
+       ndmsg->nduseropt_family = AF_INET6;
+       ndmsg->nduseropt_icmp_type = icmp6h->icmp6_type;
+       ndmsg->nduseropt_icmp_code = icmp6h->icmp6_code;
+       ndmsg->nduseropt_opts_len = opt->nd_opt_len << 3;
+
+       memcpy(ndmsg + 1, opt, opt->nd_opt_len << 3);
+
+       NLA_PUT(skb, NDUSEROPT_SRCADDR, sizeof(struct in6_addr),
+               &ipv6_hdr(ra)->saddr);
+       nlmsg_end(skb, nlh);
+
+       err = rtnl_notify(skb, 0, RTNLGRP_ND_USEROPT, NULL, GFP_ATOMIC);
+       if (err < 0)
+               goto errout;
+
+       return;
+
+nla_put_failure:
+       nlmsg_free(skb);
+       err = -EMSGSIZE;
+errout:
+       rtnl_set_sk_err(RTNLGRP_ND_USEROPT, err);
+}
+
 static void ndisc_router_discovery(struct sk_buff *skb)
 {
        struct ra_msg *ra_msg = (struct ra_msg *)skb_transport_header(skb);
@@ -1216,6 +1292,15 @@ skip_defrtr:
                }
        }
 
+       if (ndopts.nd_useropts) {
+               struct nd_opt_hdr *opt;
+               for (opt = ndopts.nd_useropts;
+                    opt;
+                    opt = ndisc_next_useropt(opt, ndopts.nd_useropts_end)) {
+                               ndisc_ra_useropt(skb, opt);
+               }
+       }
+
        if (ndopts.nd_opts_tgt_lladdr || ndopts.nd_opts_rh) {
                ND_PRINTK2(KERN_WARNING
                           "ICMPv6 RA: invalid RA options");