Commit 31910575 authored by Pierre Ynard's avatar Pierre Ynard Committed by David S. Miller

[IPv6]: Export userland ND options through netlink (RDNSS support)

As discussed before, this patch provides userland with a way to access
relevant options in Router Advertisements, after they are processed
and validated by the kernel. Extra options are processed in a generic
way; this patch only exports RDNSS options described in RFC5006, but
support to control which options are exported could be easily added.

A new rtnetlink message type is defined, to transport Neighbor
Discovery options, along with optional context information. At the
moment only the address of the router sending an RDNSS option is
included, but additional attributes may be later defined, if needed by
new use cases.
Signed-off-by: default avatarPierre Ynard <linkfanel@yahoo.fr>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 092e9d93
...@@ -97,6 +97,9 @@ enum { ...@@ -97,6 +97,9 @@ enum {
RTM_SETNEIGHTBL, RTM_SETNEIGHTBL,
#define RTM_SETNEIGHTBL RTM_SETNEIGHTBL #define RTM_SETNEIGHTBL RTM_SETNEIGHTBL
RTM_NEWNDUSEROPT = 68,
#define RTM_NEWNDUSEROPT RTM_NEWNDUSEROPT
__RTM_MAX, __RTM_MAX,
#define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) #define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1)
}; };
...@@ -479,6 +482,30 @@ enum ...@@ -479,6 +482,30 @@ enum
#define TCA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcmsg)))) #define TCA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct tcmsg))))
#define TCA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcmsg)) #define TCA_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct tcmsg))
/********************************************************************
* Neighbor Discovery userland options
****/
struct nduseroptmsg
{
unsigned char nduseropt_family;
unsigned char nduseropt_pad1;
unsigned short nduseropt_opts_len; /* Total length of options */
__u8 nduseropt_icmp_type;
__u8 nduseropt_icmp_code;
unsigned short nduseropt_pad2;
/* Followed by one or more ND options */
};
enum
{
NDUSEROPT_UNSPEC,
NDUSEROPT_SRCADDR,
__NDUSEROPT_MAX
};
#define NDUSEROPT_MAX (__NDUSEROPT_MAX - 1)
#ifndef __KERNEL__ #ifndef __KERNEL__
/* RTnetlink multicast groups - backwards compatibility for userspace */ /* RTnetlink multicast groups - backwards compatibility for userspace */
#define RTMGRP_LINK 1 #define RTMGRP_LINK 1
...@@ -542,6 +569,8 @@ enum rtnetlink_groups { ...@@ -542,6 +569,8 @@ enum rtnetlink_groups {
#define RTNLGRP_IPV6_PREFIX RTNLGRP_IPV6_PREFIX #define RTNLGRP_IPV6_PREFIX RTNLGRP_IPV6_PREFIX
RTNLGRP_IPV6_RULE, RTNLGRP_IPV6_RULE,
#define RTNLGRP_IPV6_RULE RTNLGRP_IPV6_RULE #define RTNLGRP_IPV6_RULE RTNLGRP_IPV6_RULE
RTNLGRP_ND_USEROPT,
#define RTNLGRP_ND_USEROPT RTNLGRP_ND_USEROPT
__RTNLGRP_MAX __RTNLGRP_MAX
}; };
#define RTNLGRP_MAX (__RTNLGRP_MAX - 1) #define RTNLGRP_MAX (__RTNLGRP_MAX - 1)
......
...@@ -24,6 +24,7 @@ enum { ...@@ -24,6 +24,7 @@ enum {
ND_OPT_MTU = 5, /* RFC2461 */ ND_OPT_MTU = 5, /* RFC2461 */
__ND_OPT_ARRAY_MAX, __ND_OPT_ARRAY_MAX,
ND_OPT_ROUTE_INFO = 24, /* RFC4191 */ ND_OPT_ROUTE_INFO = 24, /* RFC4191 */
ND_OPT_RDNSS = 25, /* RFC5006 */
__ND_OPT_MAX __ND_OPT_MAX
}; };
......
...@@ -15,9 +15,10 @@ ...@@ -15,9 +15,10 @@
/* /*
* Changes: * Changes:
* *
* Pierre Ynard : export userland ND options
* through netlink (RDNSS support)
* Lars Fenneberg : fixed MTU setting on receipt * Lars Fenneberg : fixed MTU setting on receipt
* of an RA. * of an RA.
*
* Janos Farkas : kmalloc failure checks * Janos Farkas : kmalloc failure checks
* Alexey Kuznetsov : state machine reworked * Alexey Kuznetsov : state machine reworked
* and moved to net/core. * and moved to net/core.
...@@ -78,6 +79,9 @@ ...@@ -78,6 +79,9 @@
#include <net/addrconf.h> #include <net/addrconf.h>
#include <net/icmp.h> #include <net/icmp.h>
#include <net/netlink.h>
#include <linux/rtnetlink.h>
#include <net/flow.h> #include <net/flow.h>
#include <net/ip6_checksum.h> #include <net/ip6_checksum.h>
#include <linux/proc_fs.h> #include <linux/proc_fs.h>
...@@ -161,6 +165,8 @@ struct ndisc_options { ...@@ -161,6 +165,8 @@ struct ndisc_options {
struct nd_opt_hdr *nd_opts_ri; struct nd_opt_hdr *nd_opts_ri;
struct nd_opt_hdr *nd_opts_ri_end; struct nd_opt_hdr *nd_opts_ri_end;
#endif #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] #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, ...@@ -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); 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, static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
struct ndisc_options *ndopts) struct ndisc_options *ndopts)
{ {
...@@ -267,14 +289,21 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, ...@@ -267,14 +289,21 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
break; break;
#endif #endif
default: default:
/* if (ndisc_is_useropt(nd_opt)) {
* Unknown options must be silently ignored, ndopts->nd_useropts_end = nd_opt;
* to accommodate future extension to the protocol. if (!ndopts->nd_useropts)
*/ ndopts->nd_useropts = nd_opt;
ND_PRINTK2(KERN_NOTICE } else {
"%s(): ignored unsupported option; type=%d, len=%d\n", /*
__FUNCTION__, * Unknown options must be silently ignored,
nd_opt->nd_opt_type, nd_opt->nd_opt_len); * 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; opt_len -= l;
nd_opt = ((void *)nd_opt) + l; nd_opt = ((void *)nd_opt) + l;
...@@ -984,6 +1013,53 @@ static void ndisc_recv_rs(struct sk_buff *skb) ...@@ -984,6 +1013,53 @@ static void ndisc_recv_rs(struct sk_buff *skb)
in6_dev_put(idev); 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) static void ndisc_router_discovery(struct sk_buff *skb)
{ {
struct ra_msg *ra_msg = (struct ra_msg *)skb_transport_header(skb); struct ra_msg *ra_msg = (struct ra_msg *)skb_transport_header(skb);
...@@ -1216,6 +1292,15 @@ static void ndisc_router_discovery(struct sk_buff *skb) ...@@ -1216,6 +1292,15 @@ static void ndisc_router_discovery(struct sk_buff *skb)
} }
} }
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) { if (ndopts.nd_opts_tgt_lladdr || ndopts.nd_opts_rh) {
ND_PRINTK2(KERN_WARNING ND_PRINTK2(KERN_WARNING
"ICMPv6 RA: invalid RA options"); "ICMPv6 RA: invalid RA options");
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment