Add an implementation of an IPv6 Router Solicitor as specified in

RFC6016 with regards to RDNSS and DNSSL.
This commit is contained in:
Roy Marples 2011-12-15 02:35:47 +00:00
parent 1011354f32
commit 91cd732493
15 changed files with 880 additions and 34 deletions

View File

@ -2,7 +2,7 @@
PROG= dhcpcd
SRCS= arp.c bind.c common.c control.c dhcp.c dhcpcd.c duid.c eloop.c
SRCS+= if-options.c if-pref.c ipv4ll.c net.c signals.c
SRCS+= if-options.c if-pref.c ipv4ll.c ipv6rs.c net.c signals.c
SRCS+= configure.c
CFLAGS?= -O2

View File

@ -200,6 +200,27 @@ get_monotonic(struct timeval *tp)
return gettimeofday(tp, NULL);
}
ssize_t
setvar(char ***e, const char *prefix, const char *var, const char *value)
{
size_t len = strlen(prefix) + strlen(var) + strlen(value) + 4;
**e = xmalloc(len);
snprintf(**e, len, "%s_%s=%s", prefix, var, value);
(*e)++;
return len;
}
ssize_t
setvard(char ***e, const char *prefix, const char *var, int value)
{
char buffer[32];
snprintf(buffer, sizeof(buffer), "%d", value);
return setvar(e, prefix, var, buffer);
}
time_t
uptime(void)
{

View File

@ -72,6 +72,8 @@ int set_nonblock(int);
char *get_line(FILE * __restrict);
extern int clock_monotonic;
int get_monotonic(struct timeval *);
ssize_t setvar(char ***, const char *, const char *, const char *);
ssize_t setvard(char ***, const char *, const char *, int);
time_t uptime(void);
int writepid(int, pid_t);
void *xrealloc(void *, size_t);

View File

@ -46,6 +46,7 @@
#include "dhcp.h"
#include "if-options.h"
#include "if-pref.h"
#include "ipv6rs.h"
#include "net.h"
#include "signals.h"
@ -168,6 +169,14 @@ make_env(const struct interface *iface, char ***argv)
ssize_t e, elen, l;
const struct if_options *ifo = iface->state->options;
const struct interface *ifp;
int dhcp, ra;
dhcp = 0;
ra = 0;
if (strcmp(iface->state->reason, "ROUTERADVERT") == 0)
ra = 1;
else
dhcp = 1;
/* When dumping the lease, we only want to report interface and
reason - the other interface variables are meaningless */
@ -235,7 +244,7 @@ make_env(const struct interface *iface, char ***argv)
snprintf(env[elen++], e, "old_ssid=%s", iface->ssid);
}
}
if (iface->state->old) {
if (dhcp && iface->state->old) {
e = configure_env(NULL, NULL, iface->state->old, ifo);
if (e > 0) {
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
@ -247,7 +256,7 @@ make_env(const struct interface *iface, char ***argv)
}
dumplease:
if (iface->state->new) {
if (dhcp && iface->state->new) {
e = configure_env(NULL, NULL, iface->state->new, ifo);
if (e > 0) {
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
@ -257,6 +266,13 @@ dumplease:
append_config(&env, &elen, "new",
(const char *const *)ifo->config);
}
if (ra) {
e = ipv6rs_env(NULL, NULL, iface);
if (e > 0) {
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
elen += ipv6rs_env(env + elen, "new", iface);
}
}
/* Add our base environment */
if (ifo->environ) {

12
dhcp.c
View File

@ -426,7 +426,7 @@ get_option_uint8(uint8_t *i, const struct dhcp_message *dhcp, uint8_t option)
* separated string. Returns length of string (including
* terminating zero) or zero on error. out may be NULL
* to just determine output length. */
static ssize_t
ssize_t
decode_rfc3397(char *out, ssize_t len, int pl, const uint8_t *p)
{
const uint8_t *r, *q = p;
@ -1357,16 +1357,6 @@ print_option(char *s, ssize_t len, int type, int dl, const uint8_t *data)
return bytes;
}
static void
setvar(char ***e, const char *prefix, const char *var, const char *value)
{
size_t len = strlen(prefix) + strlen(var) + strlen(value) + 4;
**e = xmalloc(len);
snprintf(**e, len, "%s_%s=%s", prefix, var, value);
(*e)++;
}
ssize_t
configure_env(char **env, const char *prefix, const struct dhcp_message *dhcp,
const struct if_options *ifo)

1
dhcp.h
View File

@ -187,6 +187,7 @@ int get_option_uint8(uint8_t *, const struct dhcp_message *, uint8_t);
!IN_LINKLOCAL(htonl((m)->yiaddr)) && \
get_option_uint8(NULL, m, DHO_MESSAGETYPE) == -1)
struct rt *get_option_routes(const struct dhcp_message *, const char *, int *);
ssize_t decode_rfc3397(char *, ssize_t, int, const uint8_t *);
ssize_t configure_env(char **, const char *, const struct dhcp_message *,
const struct if_options *);

View File

@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd December 9, 2011
.Dd December 15, 2011
.Dt DHCPCD 8 SMM
.Os
.Sh NAME
@ -99,6 +99,11 @@ changes.
.Nm
is also an implementation of the BOOTP client specified in
.Li RFC 951 .
.Pp
.Nm
is also an implementation of an IPv6 Router Solicitor as specified in
.Li RFC 6106
with regard to the RDNSS and DNSSL options.
.Ss Local Link configuration
If
.Nm
@ -571,7 +576,7 @@ running on the
.Xr fnmatch 3
.Sh STANDARDS
RFC 951, RFC 1534, RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3361, RFC 3396,
RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702, RFC 5969.
RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702, RFC 5969, RFC 6106.
.Sh AUTHORS
.An Roy Marples Aq roy@marples.name
.Sh BUGS

View File

@ -67,6 +67,7 @@ const char copyright[] = "Copyright (c) 2006-2011 Roy Marples";
#include "if-options.h"
#include "if-pref.h"
#include "ipv4ll.h"
#include "ipv6rs.h"
#include "net.h"
#include "signals.h"
@ -93,7 +94,7 @@ static char **ifv;
static int ifc;
static char *cffile;
static char *pidfile;
static int linkfd = -1;
static int linkfd = -1, ipv6rsfd = -1;
struct dhcp_op {
uint8_t value;
@ -214,7 +215,7 @@ handle_exit_timeout(_unused void *arg)
}
void
drop_config(struct interface *iface, const char *reason)
drop_dhcp(struct interface *iface, const char *reason)
{
free(iface->state->old);
iface->state->old = iface->state->new;
@ -244,7 +245,13 @@ stop_interface(struct interface *iface)
syslog(LOG_INFO, "%s: removing interface", iface->name);
if (strcmp(iface->state->reason, "RELEASE") != 0)
drop_config(iface, "STOP");
drop_dhcp(iface, "STOP");
if (iface->ras) {
ipv6rs_free(iface);
iface->ras = NULL;
iface->state->reason = "ROUTERADVERT";
run_script(iface);
}
close_sockets(iface);
delete_timeout(NULL, iface);
for (ifp = ifaces; ifp; ifp = ifp->next) {
@ -348,7 +355,7 @@ send_message(struct interface *iface, int type,
if (r == -1) {
syslog(LOG_ERR, "%s: send_raw_packet: %m", iface->name);
if (!(options & DHCPCD_TEST))
drop_config(iface, "FAIL");
drop_dhcp(iface, "FAIL");
close_sockets(iface);
delete_timeout(NULL, iface);
callback = NULL;
@ -407,7 +414,7 @@ start_expire(void *arg)
syslog(LOG_ERR, "%s: lease expired", iface->name);
delete_timeout(NULL, iface);
drop_config(iface, "EXPIRE");
drop_dhcp(iface, "EXPIRE");
unlink(iface->leasefile);
if (iface->carrier != LINK_DOWN)
start_interface(iface);
@ -504,7 +511,7 @@ handle_dhcp(struct interface *iface, struct dhcp_message **dhcpp, const struct i
/* We should restart on a NAK */
log_dhcp(LOG_WARNING, "NAK:", iface, dhcp, from);
if (!(options & DHCPCD_TEST)) {
drop_config(iface, "NAK");
drop_dhcp(iface, "NAK");
unlink(iface->leasefile);
}
close_sockets(iface);
@ -734,7 +741,7 @@ send_release(struct interface *iface)
ts.tv_sec = RELEASE_DELAY_S;
ts.tv_nsec = RELEASE_DELAY_NS;
nanosleep(&ts, NULL);
drop_config(iface, "RELEASE");
drop_dhcp(iface, "RELEASE");
}
unlink(iface->leasefile);
}
@ -903,7 +910,13 @@ handle_carrier(int action, int flags, const char *ifname)
syslog(LOG_INFO, "%s: carrier lost", iface->name);
close_sockets(iface);
delete_timeouts(iface, start_expire, NULL);
drop_config(iface, "NOCARRIER");
drop_dhcp(iface, "NOCARRIER");
if (iface->ras) {
ipv6rs_free(iface);
iface->ras = NULL;
iface->state->reason = "ROUTERADVERT";
run_script(iface);
}
}
} else if (carrier == 1 && !(~iface->flags & (IFF_UP | IFF_RUNNING))) {
if (iface->carrier != LINK_UP) {
@ -1147,6 +1160,9 @@ start_interface(void *arg)
free(iface->state->offer);
iface->state->offer = NULL;
if (ifo->options & DHCPCD_IPV6RS)
ipv6rs_start(iface);
if (iface->state->arping_index < ifo->arping_len) {
start_arping(iface);
return;
@ -1162,7 +1178,7 @@ start_interface(void *arg)
if (iface->hwlen == 0 && ifo->clientid[0] == '\0') {
syslog(LOG_WARNING, "%s: needs a clientid to configure",
iface->name);
drop_config(iface, "FAIL");
drop_dhcp(iface, "FAIL");
close_sockets(iface);
delete_timeout(NULL, iface);
return;
@ -1321,7 +1337,7 @@ handle_hwaddr(const char *ifname, unsigned char *hwaddr, size_t hwlen)
syslog(LOG_INFO,
"%s: expiring for new hardware address",
ifp->name);
drop_config(ifp, "EXPIRE");
drop_dhcp(ifp, "EXPIRE");
}
memcpy(ifp->hwaddr, hwaddr, hwlen);
ifp->hwlen = hwlen;
@ -1359,7 +1375,7 @@ handle_ifa(int type, const char *ifname,
if (type == RTM_DELADDR) {
if (ifp->state->new &&
ifp->state->new->yiaddr == addr->s_addr)
drop_config(ifp, "EXPIRE");
drop_dhcp(ifp, "EXPIRE");
return;
}
@ -1418,7 +1434,7 @@ if_reboot(struct interface *iface, int argc, char **argv)
(opt & (DHCPCD_INFORM | DHCPCD_STATIC) &&
!(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))))
{
drop_config(iface, "EXPIRE");
drop_dhcp(iface, "EXPIRE");
} else {
free(iface->state->offer);
iface->state->offer = NULL;
@ -1525,7 +1541,7 @@ handle_signal(_unused void *arg)
if (options & DHCPCD_TEST)
exit(EXIT_FAILURE);
/* As drop_config could re-arrange the order, we do it like this. */
/* As drop_dhcp could re-arrange the order, we do it like this. */
for (;;) {
/* Be sane and drop the last config first */
ifl = NULL;
@ -1966,6 +1982,24 @@ main(int argc, char **argv)
add_event(linkfd, handle_link, NULL);
}
#if 0
if (options & DHCPCD_IPV6RS && disable_rtadv() == -1) {
syslog(LOG_ERR, "ipv6rs: %m");
options &= ~DHCPCD_IPV6RS;
}
#endif
if (options & DHCPCD_IPV6RS) {
ipv6rsfd = ipv6rs_open();
if (ipv6rsfd == -1) {
syslog(LOG_ERR, "ipv6rs: %m");
options &= ~DHCPCD_IPV6RS;
} else {
add_event(ipv6rsfd, ipv6rs_handledata, NULL);
// atexit(restore_rtadv);
}
}
ifc = argc - optind;
ifv = argv + optind;

View File

@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd November 22, 2011
.Dd December 15, 2011
.Dt DHCPCD.CONF 5 SMM
.Os
.Sh NAME
@ -173,6 +173,8 @@ See
.Rs
.%T "RFC 3927"
.Re
.It Ic noipv6rs
Disable solicition of IPv6 Router Advertisements.
.It Ic nolink
Don't receive link messages about carrier status.
You should only set this for buggy interface drivers.

View File

@ -30,6 +30,7 @@
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <limits.h>
@ -81,6 +82,27 @@ struct if_state {
size_t arping_index;
};
struct ra_opt {
uint8_t type;
struct timeval expire;
char *option;
struct ra_opt *next;
};
struct ra {
struct in6_addr from;
char sfrom[INET6_ADDRSTRLEN];
struct timeval received;
uint32_t lifetime;
struct in6_addr prefix;
int prefix_len;
uint32_t prefix_vltime;
uint32_t prefix_pltime;
char sprefix[INET6_ADDRSTRLEN];
struct ra_opt *options;
struct ra *next;
};
struct interface {
char name[IF_NAMESIZE];
struct if_state *state;
@ -109,6 +131,11 @@ struct interface {
unsigned char *clientid;
unsigned char *rs;
size_t rslen;
int rsprobes;
struct ra *ras;
struct interface *next;
};
@ -138,7 +165,8 @@ void start_expire(void *);
void send_decline(struct interface *);
void open_sockets(struct interface *);
void close_sockets(struct interface *);
void drop_config(struct interface *, const char *);
void drop_dhcp(struct interface *, const char *);
void drop_interface(struct interface *, const char *);
int select_profile(struct interface *, const char *);
#endif

View File

@ -53,6 +53,7 @@
#define O_ARPING O_BASE + 1
#define O_FALLBACK O_BASE + 2
#define O_DESTINATION O_BASE + 3
#define O_NOIPV6RS O_BASE + 4
const struct option cf_options[] = {
{"background", no_argument, NULL, 'b'},
@ -103,6 +104,7 @@ const struct option cf_options[] = {
{"arping", required_argument, NULL, O_ARPING},
{"destination", required_argument, NULL, O_DESTINATION},
{"fallback", required_argument, NULL, O_FALLBACK},
{"noipv6rs", no_argument, NULL, O_NOIPV6RS},
{NULL, 0, NULL, '\0'}
};
@ -740,6 +742,9 @@ parse_option(struct if_options *ifo, int opt, const char *arg)
free(ifo->fallback);
ifo->fallback = xstrdup(arg);
break;
case O_NOIPV6RS:
ifo->options &=~ DHCPCD_IPV6RS;
break;
default:
return 0;
}
@ -783,8 +788,8 @@ read_config(const char *file,
/* Seed our default options */
ifo = xzalloc(sizeof(*ifo));
ifo->options |= DHCPCD_GATEWAY | DHCPCD_DAEMONISE;
ifo->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_LINK;
ifo->options |= DHCPCD_GATEWAY | DHCPCD_DAEMONISE | DHCPCD_LINK;
ifo->options |= DHCPCD_ARP | DHCPCD_IPV4LL | DHCPCD_IPV6RS;
ifo->timeout = DEFAULT_TIMEOUT;
ifo->reboot = DEFAULT_REBOOT;
ifo->metric = -1;

View File

@ -77,6 +77,7 @@
#define DHCPCD_XID_HWADDR (1 << 28)
#define DHCPCD_BROADCAST (1 << 29)
#define DHCPCD_DUMPLEASE (1 << 30)
#define DHCPCD_IPV6RS (1 << 31)
extern const struct option cf_options[];

View File

@ -131,7 +131,7 @@ handle_ipv4ll_failure(void *arg)
syslog(LOG_DEBUG,
"%s: IPv4LL %d second defence failed",
iface->name, DEFEND_INTERVAL);
drop_config(iface, "EXPIRE");
drop_dhcp(iface, "EXPIRE");
iface->state->conflicts = -1;
} else {
syslog(LOG_DEBUG, "%s: defended IPv4LL address",

704
ipv6rs.c Normal file
View File

@ -0,0 +1,704 @@
/*
* dhcpcd - DHCP client daemon
* Copyright (c) 2006-2011 Roy Marples <roy@marples.name>
* All rights reserved
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/param.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#ifdef __linux__
# define _LINUX_IN6_H
# include <linux/ipv6.h>
#endif
#define ELOOP_QUEUE 1
#include "bind.h"
#include "common.h"
#include "configure.h"
#include "dhcpcd.h"
#include "eloop.h"
#include "ipv6rs.h"
#define ALLROUTERS "ff02::2"
#define HOPLIMIT 255
#define ROUNDUP8(a) (1 + (((a) - 1) | 7))
#define RTR_SOLICITATION_INTERVAL 4 /* seconds */
#define MAX_RTR_SOLICITATIONS 3 /* times */
#ifndef ND_OPT_RDNSS
#define ND_OPT_RDNSS 25
struct nd_opt_rdnss { /* RDNSS option RFC 6106 */
uint8_t nd_opt_rdnss_type;
uint8_t nd_opt_rdnss_len;
uint16_t nd_opt_rdnss_reserved;
uint32_t nd_opt_rdnss_lifetime;
/* followed by list of IP prefixes */
} _packed;
#endif
#ifndef ND_OPT_DNSSL
#define ND_OPT_DNSSL 31
struct nd_opt_dnssl { /* DNSSL option RFC 6106 */
uint8_t nd_opt_dnssl_type;
uint8_t nd_opt_dnssl_len;
uint16_t nd_opt_dnssl_reserved;
uint32_t nd_opt_dnssl_lifetime;
/* followed by list of DNS servers */
} _packed;
#endif
static int sock;
static struct sockaddr_in6 allrouters, from;
static struct msghdr sndhdr;
static struct iovec sndiov[2];
static unsigned char *sndbuf;
static struct msghdr rcvhdr;
static struct iovec rcviov[2];
static unsigned char *rcvbuf;
static unsigned char ansbuf[1500];
static char ntopbuf[INET6_ADDRSTRLEN];
int
ipv6rs_open(void)
{
int on;
int len;
struct icmp6_filter filt;
memset(&allrouters, 0, sizeof(allrouters));
allrouters.sin6_family = AF_INET6;
#ifdef SIN6_LEN
allrouters.sin6_len = sizeof(allrouters);
#endif
if (inet_pton(AF_INET6, ALLROUTERS, &allrouters.sin6_addr.s6_addr) != 1)
return -1;
sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (sock == -1)
return -1;
on = 1;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
&on, sizeof(on)) == -1)
return -1;
on = 1;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
&on, sizeof(on)) == -1)
return -1;
ICMP6_FILTER_SETBLOCKALL(&filt);
ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER,
&filt, sizeof(filt)) == -1)
return -1;
len = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int));
sndbuf = xzalloc(len);
if (sndbuf == NULL)
return -1;
sndhdr.msg_namelen = sizeof(struct sockaddr_in6);
sndhdr.msg_iov = sndiov;
sndhdr.msg_iovlen = 1;
sndhdr.msg_control = sndbuf;
sndhdr.msg_controllen = len;
rcvbuf = xzalloc(len);
if (rcvbuf == NULL)
return -1;
rcvhdr.msg_name = &from;
rcvhdr.msg_namelen = sizeof(from);
rcvhdr.msg_iov = rcviov;
rcvhdr.msg_iovlen = 1;
rcvhdr.msg_control = rcvbuf;
rcvhdr.msg_controllen = len;
rcviov[0].iov_base = ansbuf;
rcviov[0].iov_len = sizeof(ansbuf);
return sock;
}
static int
ipv6rs_makeprobe(struct interface *ifp)
{
struct nd_router_solicit *rs;
struct nd_opt_hdr *nd;
ifp->rslen = sizeof(*rs) + ROUNDUP8(ifp->hwlen + 2);
ifp->rs = xzalloc(ifp->rslen);
if (ifp->rs == NULL)
return -1;
rs = (struct nd_router_solicit *)ifp->rs;
rs->nd_rs_type = ND_ROUTER_SOLICIT;
rs->nd_rs_code = 0;
rs->nd_rs_cksum = 0;
rs->nd_rs_reserved = 0;
nd = (struct nd_opt_hdr *)(ifp->rs + sizeof(*rs));
nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
nd->nd_opt_len = (ROUNDUP8(ifp->hwlen + 2)) >> 3;
memcpy(nd + 1, ifp->hwaddr, ifp->hwlen);
return 0;
}
static void
ipv6rs_sendprobe(void *arg)
{
struct interface *ifp = arg;
struct sockaddr_in6 dst;
struct cmsghdr *cm;
struct in6_pktinfo pi;
int hoplimit = HOPLIMIT;
dst = allrouters;
//dst.sin6_scope_id = ifp->linkid;
ipv6rs_makeprobe(ifp);
sndhdr.msg_name = (caddr_t)&dst;
sndhdr.msg_iov[0].iov_base = ifp->rs;
sndhdr.msg_iov[0].iov_len = ifp->rslen;
/* Set the outbound interface */
cm = CMSG_FIRSTHDR(&sndhdr);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_PKTINFO;
cm->cmsg_len = CMSG_LEN(sizeof(pi));
memset(&pi, 0, sizeof(pi));
pi.ipi6_ifindex = if_nametoindex(ifp->name);
memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
/* Hop limit */
cm = CMSG_NXTHDR(&sndhdr, cm);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_HOPLIMIT;
cm->cmsg_len = CMSG_LEN(sizeof(hoplimit));
memcpy(CMSG_DATA(cm), &hoplimit, sizeof(hoplimit));
syslog(LOG_INFO, "%s: sending IPv6 Router Solicitation", ifp->name);
if (sendmsg(sock, &sndhdr, 0) == -1)
syslog(LOG_ERR, "%s: sendmsg: %m", ifp->name);
if (++ifp->rsprobes < MAX_RTR_SOLICITATIONS)
add_timeout_sec(RTR_SOLICITATION_INTERVAL,
ipv6rs_sendprobe, ifp);
else
syslog(LOG_INFO, "%s: no IPv6 Routers available", ifp->name);
}
static void
ipv6rs_sort(struct interface *ifp)
{
struct ra *rap, *sorted, *ran, *rat;
if (ifp->ras == NULL || ifp->ras->next == NULL)
return;
/* Sort our RA's - most recent first */
sorted = ifp->ras;
ifp->ras = ifp->ras->next;
sorted->next = NULL;
for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) {
/* Are we the new head? */
if (timercmp(&rap->received, &sorted->received, <)) {
rap->next = sorted;
sorted = rap;
continue;
}
/* Do we fit in the middle? */
for (rat = sorted; rat->next; rat = rat->next) {
if (timercmp(&rap->received, &rat->next->received, <)) {
rap->next = rat->next;
rat->next = rap;
break;
}
}
/* We must be at the end */
if (!rat->next) {
rat->next = rap;
rap->next = NULL;
}
}
}
void
ipv6rs_handledata(_unused void *arg)
{
ssize_t len, l, n, olen;
struct cmsghdr *cm;
int hoplimit;
struct in6_pktinfo pkt;
struct icmp6_hdr *icp;
struct interface *ifp;
const char *sfrom;
struct nd_router_advert *radvert;
struct nd_opt_prefix_info *pi;
struct nd_opt_mtu *mtu;
struct nd_opt_rdnss *rdnss;
struct nd_opt_dnssl *dnssl;
uint32_t lifetime;
uint8_t *p, *op;
struct in6_addr addr;
char buf[INET6_ADDRSTRLEN];
const char *cbp;
struct ra *rap;
struct nd_opt_hdr *ndo;
struct ra_opt *rao, *raol;
char *opt;
struct timeval expire;
len = recvmsg(sock, &rcvhdr, 0);
if (len == -1) {
syslog(LOG_ERR, "recvmsg: %m");
return;
}
sfrom = inet_ntop(AF_INET6, &from.sin6_addr,
ntopbuf, INET6_ADDRSTRLEN);
if ((size_t)len < sizeof(struct nd_router_advert)) {
syslog(LOG_ERR, "IPv6 RA packet too short from %s", sfrom);
return;
}
pkt.ipi6_ifindex = hoplimit = 0;
for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rcvhdr);
cm;
cm = (struct cmsghdr *)CMSG_NXTHDR(&rcvhdr, cm))
{
if (cm->cmsg_level != IPPROTO_IPV6)
continue;
switch(cm->cmsg_type) {
case IPV6_PKTINFO:
if (cm->cmsg_len == CMSG_LEN(sizeof(pkt)))
memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt));
break;
case IPV6_HOPLIMIT:
if (cm->cmsg_len == CMSG_LEN(sizeof(int)))
memcpy(&hoplimit, CMSG_DATA(cm), sizeof(int));
break;
}
}
if (pkt.ipi6_ifindex == 0 || hoplimit == 0) {
syslog(LOG_ERR,
"IPv6 RA did not contain index or hop limit from %s",
sfrom);
return;
}
icp = (struct icmp6_hdr *)rcvhdr.msg_iov[0].iov_base;
if (icp->icmp6_type != ND_ROUTER_ADVERT ||
icp->icmp6_code != 0)
{
syslog(LOG_ERR, "invalid IPv6 type or code from %s", sfrom);
return;
}
if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) {
syslog(LOG_ERR, "RA recieved from non local IPv6 address %s",
sfrom);
return;
}
for (ifp = ifaces; ifp; ifp = ifp->next)
if (if_nametoindex(ifp->name) == (unsigned int)pkt.ipi6_ifindex)
break;
if (ifp == NULL) {
syslog(LOG_ERR,"received RA for unexpected interface from %s",
sfrom);
return;
}
syslog(LOG_INFO, "%s: Router Advertisement from %s", ifp->name, sfrom);
delete_timeouts(ifp, NULL);
radvert = (struct nd_router_advert *)icp;
for (rap = ifp->ras; rap; rap = rap->next) {
if (memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr,
sizeof(rap->from.s6_addr)) == 0)
break;
}
if (rap == NULL) {
rap = xmalloc(sizeof(*rap));
rap->next = ifp->ras;
rap->options = NULL;
ifp->ras = rap;
memcpy(rap->from.s6_addr, from.sin6_addr.s6_addr,
sizeof(rap->from.s6_addr));
strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom));
}
get_monotonic(&rap->received);
rap->lifetime = ntohl(icp->icmp6_dataun.icmp6_un_data32);
len -= sizeof(struct nd_router_advert);
p = ((uint8_t *)icp) + sizeof(struct nd_router_advert);
olen = 0;
lifetime = ~0U;
for (olen = 0; len > 0; p += olen, len -= olen) {
if ((size_t)len < sizeof(struct nd_opt_hdr)) {
syslog(LOG_ERR, "%s: Short option", ifp->name);
break;
}
ndo = (struct nd_opt_hdr *)p;
olen = ndo->nd_opt_len * 8 ;
if (olen == 0) {
syslog(LOG_ERR, "%s: zero length option", ifp->name);
break;
}
if (olen > len) {
syslog(LOG_ERR,
"%s: Option length exceeds message", ifp->name);
break;
}
opt = NULL;
switch (ndo->nd_opt_type) {
case ND_OPT_PREFIX_INFORMATION:
pi = (struct nd_opt_prefix_info *)ndo;
if (pi->nd_opt_pi_len != 4) {
syslog(LOG_ERR,
"%s: invalid option len for prefix",
ifp->name);
break;
}
if (pi->nd_opt_pi_prefix_len > 128) {
syslog(LOG_ERR, "%s: invalid prefix len",
ifp->name);
break;
}
if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) ||
IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix))
{
syslog(LOG_ERR,
"%s: invalid prefix in RA", ifp->name);
break;
}
opt = xstrdup(inet_ntop(AF_INET6,
pi->nd_opt_pi_prefix.s6_addr,
ntopbuf, INET6_ADDRSTRLEN));
if (opt) {
rap->prefix_len = pi->nd_opt_pi_prefix_len;
rap->prefix_vltime =
ntohl(pi->nd_opt_pi_valid_time);
rap->prefix_pltime =
ntohl(pi->nd_opt_pi_preferred_time);
}
break;
case ND_OPT_MTU:
mtu = (struct nd_opt_mtu *)p;
snprintf(buf, sizeof(buf), "%d",
ntohl(mtu->nd_opt_mtu_mtu));
opt = xstrdup(buf);
break;
case ND_OPT_RDNSS:
rdnss = (struct nd_opt_rdnss *)p;
lifetime = ntohl(rdnss->nd_opt_rdnss_lifetime);
op = (uint8_t *)ndo;
op += offsetof(struct nd_opt_rdnss,
nd_opt_rdnss_lifetime);
op += sizeof(rdnss->nd_opt_rdnss_lifetime);
l = 0;
for (n = ndo->nd_opt_len - 1; n > 1; n -= 2) {
memcpy(&addr.s6_addr, op, sizeof(addr.s6_addr));
cbp = inet_ntop(AF_INET6, &addr,
ntopbuf, INET6_ADDRSTRLEN);
if (cbp == NULL) {
syslog(LOG_ERR,
"%s: invalid RDNSS address",
ifp->name);
} else {
if (opt) {
l = strlen(opt);
opt = xrealloc(opt,
l + strlen(cbp) + 2);
opt[l] = ' ';
strcpy(opt + l + 1, cbp);
opt[l + strlen(cbp) + l + 1] =
'\0';
} else
opt = xstrdup(cbp);
}
op += sizeof(addr.s6_addr);
}
break;
case ND_OPT_DNSSL:
dnssl = (struct nd_opt_dnssl *)p;
lifetime = ntohl(dnssl->nd_opt_dnssl_lifetime);
op = p + offsetof(struct nd_opt_dnssl,
nd_opt_dnssl_lifetime);
op += sizeof(dnssl->nd_opt_dnssl_lifetime);
n = (dnssl->nd_opt_dnssl_len - 1) * 8;
l = decode_rfc3397(NULL, 0, n, op);
if (l < 1) {
syslog(LOG_ERR, "%s: invalid DNSSL option",
ifp->name);
} else {
opt = xmalloc(l);
decode_rfc3397(opt, l, n, op);
}
break;
}
if (opt == NULL)
continue;
for (raol = NULL, rao = rap->options;
rao;
raol = rao, rao = rao->next)
{
if (rao->type == ndo->nd_opt_type &&
strcmp(rao->option, opt) == 0)
break;
}
if (lifetime == 0) {
if (rao) {
if (raol)
raol->next = rao->next;
else
rap->options = rao->next;
free(rao->option);
free(rao);
}
continue;
}
if (rao == NULL) {
rao = xmalloc(sizeof(*rao));
rao->next = rap->options;
rap->options = rao;
rao->type = ndo->nd_opt_type;
rao->option = opt;
}
if (lifetime == ~0U) {
rao->expire.tv_sec = 0;
rao->expire.tv_usec = 0;
} else {
expire.tv_sec = lifetime;
expire.tv_usec = 0;
timeradd(&rap->received, &expire, &rao->expire);
}
}
ipv6rs_sort(ifp);
if (options & DHCPCD_TEST)
ifp->state->reason = "TEST";
else
ifp->state->reason = "ROUTERADVERT";
run_script(ifp);
if (options & DHCPCD_TEST)
exit(EXIT_SUCCESS);
delete_timeouts(ifp, NULL);
ipv6rs_expire(ifp);
daemonise();
}
ssize_t
ipv6rs_env(char **env, const char *prefix, const struct interface *ifp)
{
ssize_t l;
struct timeval now;
const struct ra *rap;
const struct ra_opt *rao;
int i;
char buffer[32], buffer2[32];
const char *optn;
l = 0;
get_monotonic(&now);
for (rap = ifp->ras, i = 1; rap; rap = rap->next, i++) {
if (env) {
snprintf(buffer, sizeof(buffer),
"ra%d_from", i);
setvar(&env, prefix, buffer, rap->sfrom);
}
l++;
for (rao = rap->options; rao; rao = rao->next) {
if (rao->expire.tv_sec != 0 &&
rao->expire.tv_usec != 0 &&
timercmp(&now, &rao->expire, >))
{
syslog(LOG_INFO, "%s: %s: expired option %d",
ifp->name, rap->sfrom, rao->type);
continue;
}
if (rao->option == NULL)
continue;
if (env == NULL) {
switch (rao->type) {
case ND_OPT_PREFIX_INFORMATION:
l += 4;
break;
default:
l++;
}
continue;
}
switch (rao->type) {
case ND_OPT_PREFIX_INFORMATION:
optn = "prefix";
break;
case ND_OPT_MTU:
optn = "mtu";
break;
case ND_OPT_RDNSS:
optn = "rdnss";
break;
case ND_OPT_DNSSL:
optn = "dnssl";
break;
default:
continue;
}
snprintf(buffer, sizeof(buffer), "ra%d_%s", i, optn);
setvar(&env, prefix, buffer, rao->option);
l++;
switch (rao->type) {
case ND_OPT_PREFIX_INFORMATION:
snprintf(buffer, sizeof(buffer),
"ra%d_prefix_len", i);
snprintf(buffer2, sizeof(buffer2),
"%d", rap->prefix_len);
setvar(&env, prefix, buffer, buffer2);
snprintf(buffer, sizeof(buffer),
"ra%d_prefix_vltime", i);
snprintf(buffer2, sizeof(buffer2),
"%d", rap->prefix_vltime);
setvar(&env, prefix, buffer, buffer2);
snprintf(buffer, sizeof(buffer),
"ra%d_prefix_pltime", i);
snprintf(buffer2, sizeof(buffer2),
"%d", rap->prefix_pltime);
setvar(&env, prefix, buffer, buffer2);
l += 3;
break;
}
}
}
if (env)
setvard(&env, prefix, "ra_count", i - 1);
l++;
return l;
}
static void
ipv6rs_free_opts(struct ra *rap)
{
struct ra_opt *rao, *raon;
for (rao = rap->options; rao && (raon = rao->next, 1); rao = raon) {
free(rao->option);
free(rao);
}
}
void
ipv6rs_free(struct interface *ifp)
{
struct ra *rap, *ran;
for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) {
ipv6rs_free_opts(rap);
free(rap);
}
}
void
ipv6rs_expire(void *arg)
{
struct interface *ifp;
struct ra *rap, *ran, *ral;
struct timeval now, lt, expire, next;
int expired;
uint32_t expire_secs;
ifp = arg;
get_monotonic(&now);
expired = 0;
expire_secs = ~0U;
timerclear(&next);
for (rap = ifp->ras, ral = NULL;
rap && (ran = rap->next, 1);
ral = rap, rap = ran)
{
lt.tv_sec = rap->lifetime;
lt.tv_usec = 0;
timeradd(&rap->received, &lt, &expire);
if (timercmp(&now, &expire, >)) {
expired = 1;
if (ral)
ral->next = ran;
else
ifp->ras = ran;
ipv6rs_free_opts(rap);
free(rap);
} else {
timersub(&now, &expire, &lt);
if (!timerisset(&next) || timercmp(&next, &lt, >))
next = lt;
}
}
if (timerisset(&next))
add_timeout_tv(&next, ipv6rs_expire, ifp);
if (expired) {
ifp->state->reason = "ROUTERADVERT";
run_script(ifp);
}
}
int
ipv6rs_start(struct interface *ifp)
{
delete_timeouts(ifp, NULL);
/* Always make a new probe as the underlying hardware
* address could have changed. */
free(ifp->rs);
ipv6rs_makeprobe(ifp);
if (ifp->rs == NULL)
return -1;
ifp->rsprobes = 0;
ipv6rs_sendprobe(ifp);
return 0;
}

37
ipv6rs.h Normal file
View File

@ -0,0 +1,37 @@
/*
* dhcpcd - DHCP client daemon
* Copyright (c) 2006-2010 Roy Marples <roy@marples.name>
* All rights reserved
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef IPV6RS_H
#define IPV6RS_H
int ipv6rs_open(void);
void ipv6rs_handledata(void *);
int ipv6rs_start(struct interface *);
ssize_t ipv6rs_env(char **, const char *, const struct interface *);
void ipv6rs_free(struct interface *ifp);
void ipv6rs_expire(void *arg);
#endif