From: Roy Marples Date: Thu, 15 Dec 2011 02:35:47 +0000 (+0000) Subject: Add an implementation of an IPv6 Router Solicitor as specified in X-Git-Tag: v5.5.0~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=91cd732493213c9550060f6cb7664daf62259a68;p=thirdparty%2Fdhcpcd.git Add an implementation of an IPv6 Router Solicitor as specified in RFC6016 with regards to RDNSS and DNSSL. --- diff --git a/Makefile b/Makefile index ea6431f3..3b4dbfd6 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/common.c b/common.c index 06420558..95dc1405 100644 --- a/common.c +++ b/common.c @@ -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) { diff --git a/common.h b/common.h index 90cd475a..877656b4 100644 --- a/common.h +++ b/common.h @@ -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); diff --git a/configure.c b/configure.c index f7e72026..db5b0ec2 100644 --- a/configure.c +++ b/configure.c @@ -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) { diff --git a/dhcp.c b/dhcp.c index fa8b02d8..a88cb20c 100644 --- a/dhcp.c +++ b/dhcp.c @@ -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) diff --git a/dhcp.h b/dhcp.h index 8e14f456..1c2b81af 100644 --- a/dhcp.h +++ b/dhcp.h @@ -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 *); diff --git a/dhcpcd.8.in b/dhcpcd.8.in index 47944883..b68d5dcd 100644 --- a/dhcpcd.8.in +++ b/dhcpcd.8.in @@ -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 diff --git a/dhcpcd.c b/dhcpcd.c index d9edab93..75fd3c5b 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -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; diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index 0c6ec693..aa94b8a6 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -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. diff --git a/dhcpcd.h b/dhcpcd.h index 1f6ae059..0ca4b68b 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -30,6 +30,7 @@ #include #include +#include #include @@ -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 diff --git a/if-options.c b/if-options.c index 8211e789..fbf8485c 100644 --- a/if-options.c +++ b/if-options.c @@ -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; diff --git a/if-options.h b/if-options.h index 9fcb64bd..85459d16 100644 --- a/if-options.h +++ b/if-options.h @@ -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[]; diff --git a/ipv4ll.c b/ipv4ll.c index 7f4369ce..dfe692af 100644 --- a/ipv4ll.c +++ b/ipv4ll.c @@ -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", diff --git a/ipv6rs.c b/ipv6rs.c new file mode 100644 index 00000000..931c24cd --- /dev/null +++ b/ipv6rs.c @@ -0,0 +1,704 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2011 Roy Marples + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __linux__ +# define _LINUX_IN6_H +# include +#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, <, &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, <); + if (!timerisset(&next) || timercmp(&next, <, >)) + 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; +} diff --git a/ipv6rs.h b/ipv6rs.h new file mode 100644 index 00000000..0856acbf --- /dev/null +++ b/ipv6rs.h @@ -0,0 +1,37 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2010 Roy Marples + * 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