summaryrefslogtreecommitdiffstats
path: root/ipv6rs.c
diff options
context:
space:
mode:
authorRoy Marples <roy@marples.name>2012-07-05 16:37:41 +0000
committerRoy Marples <roy@marples.name>2012-07-05 16:37:41 +0000
commiteebe9a1887bbf2f3bbb4fdb72e8d46ece25cdd0c (patch)
treeda2e3069767ce09c41eb2b32e6a3b7f01b164eca /ipv6rs.c
parent359e9d397807079db8d68d5e60eb4396f41d1738 (diff)
downloaddhcpcd-eebe9a1887bbf2f3bbb4fdb72e8d46ece25cdd0c.tar.xz
Improve IPv6 RA support by allowing dhcpcd to manage the address and routesdhcpcd-5.6.0
instead of the kernel. dhcpcd will only do this if RA is disabled in the kernel or dhcpcd has been instructed to do this via dhcpcd.conf(5) ipv6ra_own and ipv6ra_own_default directives. Send and process IPv6 Neighbor Solicitions and Adverts to prove router reachability. If a router cannot be reached in this way then it is expired. When debugging, all ND messages are displayed which will create a lot of log spam. To ease packaging, ./configure now accepts LDFLAGS and --enable-static.
Diffstat (limited to 'ipv6rs.c')
-rw-r--r--ipv6rs.c380
1 files changed, 256 insertions, 124 deletions
diff --git a/ipv6rs.c b/ipv6rs.c
index 31587264..242ff7ca 100644
--- a/ipv6rs.c
+++ b/ipv6rs.c
@@ -25,6 +25,7 @@
* SUCH DAMAGE.
*/
+#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <net/if.h>
@@ -49,13 +50,10 @@
#include "configure.h"
#include "dhcpcd.h"
#include "eloop.h"
+#include "ipv6.h"
+#include "ipv6ns.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 */
@@ -81,6 +79,28 @@ struct nd_opt_dnssl { /* DNSSL option RFC 6106 */
} _packed;
#endif
+/* Minimal IPv6 MTU */
+#ifndef IPV6_MMTU
+#define IPV6_MMTU 1280
+#endif
+
+#ifndef ND_RA_FLAG_RTPREF_HIGH
+#define ND_RA_FLAG_RTPREF_MASK 0x18
+#define ND_RA_FLAG_RTPREF_HIGH 0x08
+#define ND_RA_FLAG_RTPREF_MEDIUM 0x00
+#define ND_RA_FLAG_RTPREF_LOW 0x18
+#define ND_RA_FLAG_RTPREF_RSV 0x10
+#endif
+
+/* RTPREF_MEDIUM has to be 0! */
+#define RTPREF_HIGH 1
+#define RTPREF_MEDIUM 0
+#define RTPREF_LOW (-1)
+#define RTPREF_RESERVED (-2)
+#define RTPREF_INVALID (-3) /* internal */
+
+struct rahead ipv6_routers = TAILQ_HEAD_INITIALIZER(ipv6_routers);
+
static int sock;
static struct sockaddr_in6 allrouters, from;
static struct msghdr sndhdr;
@@ -135,6 +155,7 @@ ipv6rs_open(void)
&filt, sizeof(filt)) == -1)
return -1;
+ set_cloexec(sock);
#if DEBUG_MEMORY
atexit(ipv6rs_cleanup);
#endif
@@ -173,7 +194,7 @@ ipv6rs_makeprobe(struct interface *ifp)
ifp->rs = xzalloc(ifp->rslen);
if (ifp->rs == NULL)
return -1;
- rs = (struct nd_router_solicit *)ifp->rs;
+ rs = (struct nd_router_solicit *)(void *)ifp->rs;
rs->nd_rs_type = ND_ROUTER_SOLICIT;
rs->nd_rs_code = 0;
rs->nd_rs_cksum = 0;
@@ -230,40 +251,101 @@ ipv6rs_sendprobe(void *arg)
}
static void
-ipv6rs_sort(struct interface *ifp)
+ipv6rs_free_opts(struct ra *rap)
{
- struct ra *rap, *sorted, *ran, *rat;
+ struct ra_opt *rao;
- if (ifp->ras == NULL || ifp->ras->next == NULL)
- return;
+ while ((rao = TAILQ_FIRST(&rap->options))) {
+ TAILQ_REMOVE(&rap->options, rao, next);
+ free(rao->option);
+ free(rao);
+ }
+}
- /* 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;
+static void
+ipv6rs_drop_addrs(struct ra *rap)
+{
+ struct ipv6_addr *ap;
+
+ while ((ap = TAILQ_FIRST(&rap->addrs))) {
+ TAILQ_REMOVE(&rap->addrs, ap, next);
+ if ((options & DHCPCD_IPV6RA_OWN)) {
+ syslog(LOG_INFO, "%s: deleting address %s",
+ rap->iface->name, ap->saddr);
+ if (del_address6(rap->iface, ap) == -1)
+ syslog(LOG_ERR, "del_address6 %m");
}
- /* 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;
- }
+ free(ap);
+ }
+}
+
+void ipv6rs_drop_ra(struct ra *rap)
+{
+
+ delete_timeout(NULL, rap->iface);
+ delete_timeout(NULL, rap);
+ TAILQ_REMOVE(&ipv6_routers, rap, next);
+ ipv6rs_drop_addrs(rap);
+ ipv6rs_free_opts(rap);
+ free(rap->data);
+ free(rap->ns);
+ free(rap);
+}
+
+ssize_t
+ipv6rs_free(struct interface *ifp)
+{
+ struct ra *rap, *ran;
+ ssize_t n;
+
+ free(ifp->rs);
+ ifp->rs = NULL;
+ n = 0;
+ TAILQ_FOREACH_SAFE(rap, &ipv6_routers, next, ran) {
+ if (rap->iface == ifp) {
+ ipv6rs_drop_ra(rap);
+ n++;
}
- /* We must be at the end */
- if (!rat->next) {
- rat->next = rap;
- rap->next = NULL;
+ }
+ return n;
+}
+
+static int
+rtpref(struct ra *rap)
+{
+ switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) {
+ case ND_RA_FLAG_RTPREF_HIGH:
+ return (RTPREF_HIGH);
+ case ND_RA_FLAG_RTPREF_MEDIUM:
+ case ND_RA_FLAG_RTPREF_RSV:
+ return (RTPREF_MEDIUM);
+ case ND_RA_FLAG_RTPREF_LOW:
+ return (RTPREF_LOW);
+ default:
+ syslog(LOG_ERR, "rtpref: impossible RA flag %x", rap->flags);
+ return (RTPREF_INVALID);
+ }
+ /* NOTREACHED */
+}
+
+static void
+add_router(struct ra *router)
+{
+ struct ra *rap;
+
+ TAILQ_FOREACH(rap, &ipv6_routers, next) {
+ if (router->iface->metric < rap->iface->metric ||
+ (router->iface->metric == rap->iface->metric &&
+ rtpref(router) > rtpref(rap)))
+ {
+ TAILQ_INSERT_BEFORE(rap, router, next);
+ return;
}
}
+ TAILQ_INSERT_HEAD(&ipv6_routers, router, next);
}
+/* ARGSUSED */
void
ipv6rs_handledata(_unused void *arg)
{
@@ -279,17 +361,18 @@ ipv6rs_handledata(_unused void *arg)
struct nd_opt_mtu *mtu;
struct nd_opt_rdnss *rdnss;
struct nd_opt_dnssl *dnssl;
- uint32_t lifetime;
+ uint32_t lifetime, mtuv;
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;
+ struct ra_opt *rao;
+ struct ipv6_addr *ap;
char *opt;
struct timeval expire;
- int has_dns;
+ int has_dns, new_rap;
len = recvmsg(sock, &rcvhdr, 0);
if (len == -1) {
@@ -338,8 +421,7 @@ ipv6rs_handledata(_unused void *arg)
}
if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) {
- syslog(LOG_ERR, "RA recieved from non local IPv6 address %s",
- sfrom);
+ syslog(LOG_ERR, "RA from non local address %s", sfrom);
return;
}
@@ -347,11 +429,10 @@ ipv6rs_handledata(_unused void *arg)
if (ifp->index == (unsigned int)pkt.ipi6_ifindex)
break;
if (ifp == NULL) {
- syslog(LOG_ERR,"received RA for unexpected interface from %s",
- sfrom);
+ syslog(LOG_ERR, "RA for unexpected interface from %s", sfrom);
return;
}
- for (rap = ifp->ras; rap; rap = rap->next) {
+ TAILQ_FOREACH(rap, &ipv6_routers, next) {
if (memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr,
sizeof(rap->from.s6_addr)) == 0)
break;
@@ -372,15 +453,16 @@ ipv6rs_handledata(_unused void *arg)
}
if (rap == NULL) {
- rap = xmalloc(sizeof(*rap));
- rap->next = ifp->ras;
- rap->options = NULL;
- ifp->ras = rap;
+ rap = xzalloc(sizeof(*rap));
+ rap->iface = ifp;
memcpy(rap->from.s6_addr, from.sin6_addr.s6_addr,
sizeof(rap->from.s6_addr));
strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom));
- rap->data_len = 0;
- }
+ TAILQ_INIT(&rap->addrs);
+ TAILQ_INIT(&rap->options);
+ new_rap = 1;
+ } else
+ new_rap = 0;
if (rap->data_len == 0) {
rap->data = xmalloc(len);
memcpy(rap->data, icp, len);
@@ -389,6 +471,7 @@ ipv6rs_handledata(_unused void *arg)
get_monotonic(&rap->received);
nd_ra = (struct nd_router_advert *)icp;
+ rap->flags = nd_ra->nd_ra_flags_reserved;
rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime);
rap->expired = 0;
@@ -417,7 +500,7 @@ ipv6rs_handledata(_unused void *arg)
opt = NULL;
switch (ndo->nd_opt_type) {
case ND_OPT_PREFIX_INFORMATION:
- pi = (struct nd_opt_prefix_info *)ndo;
+ pi = (struct nd_opt_prefix_info *)(void *)ndo;
if (pi->nd_opt_pi_len != 4) {
syslog(LOG_ERR,
"%s: invalid option len for prefix",
@@ -436,22 +519,53 @@ ipv6rs_handledata(_unused void *arg)
"%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);
- }
+ TAILQ_FOREACH(ap, &rap->addrs, next)
+ if (ap->prefix_len ==pi->nd_opt_pi_prefix_len &&
+ memcmp(ap->prefix.s6_addr,
+ pi->nd_opt_pi_prefix.s6_addr,
+ sizeof(ap->prefix.s6_addr)) == 0)
+ break;
+ if (ap == NULL) {
+ ap = xmalloc(sizeof(*ap));
+ ap->new = 1;
+ ap->prefix_len = pi->nd_opt_pi_prefix_len;
+ memcpy(ap->prefix.s6_addr,
+ pi->nd_opt_pi_prefix.s6_addr,
+ sizeof(ap->prefix.s6_addr));
+ ipv6_makeaddr(&ap->addr, ifp->name,
+ &ap->prefix, pi->nd_opt_pi_prefix_len);
+ cbp = inet_ntop(AF_INET6, ap->addr.s6_addr,
+ ntopbuf, INET6_ADDRSTRLEN);
+ if (cbp)
+ memcpy(ap->saddr, cbp,
+ sizeof(ap->saddr));
+ else
+ ap->saddr[0] = '\0';
+ TAILQ_INSERT_TAIL(&rap->addrs, ap, next);
+ } else if (ap->prefix_vltime !=
+ ntohl(pi->nd_opt_pi_valid_time) ||
+ ap->prefix_pltime !=
+ ntohl(pi->nd_opt_pi_preferred_time))
+ ap->new = 1;
+ else
+ ap->new = 0;
+ ap->prefix_vltime =
+ ntohl(pi->nd_opt_pi_valid_time);
+ ap->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));
+ mtu = (struct nd_opt_mtu *)(void *)p;
+ mtuv = ntohl(mtu->nd_opt_mtu_mtu);
+ if (mtuv < IPV6_MMTU) {
+ syslog(LOG_ERR, "%s: invalid MTU %d",
+ ifp->name, mtuv);
+ break;
+ }
+ if (rap->mtu == 0 || mtuv < rap->mtu)
+ rap->mtu = mtuv;
+ snprintf(buf, sizeof(buf), "%d", mtuv);
opt = xstrdup(buf);
break;
@@ -507,20 +621,14 @@ ipv6rs_handledata(_unused void *arg)
if (opt == NULL)
continue;
- for (raol = NULL, rao = rap->options;
- rao;
- raol = rao, rao = rao->next)
- {
+ TAILQ_FOREACH(rao, &rap->options, 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;
+ TAILQ_REMOVE(&rap->options, rao, next);
free(rao->option);
free(rao);
}
@@ -529,10 +637,9 @@ ipv6rs_handledata(_unused void *arg)
if (rao == NULL) {
rao = xmalloc(sizeof(*rao));
- rao->next = rap->options;
- rap->options = rao;
rao->type = ndo->nd_opt_type;
rao->option = opt;
+ TAILQ_INSERT_TAIL(&rap->options, rao, next);
} else
free(opt);
if (lifetime == ~0U)
@@ -544,7 +651,25 @@ ipv6rs_handledata(_unused void *arg)
}
}
- ipv6rs_sort(ifp);
+ if (new_rap)
+ add_router(rap);
+ if (options & DHCPCD_IPV6RA_OWN) {
+ TAILQ_FOREACH(ap, &rap->addrs, next) {
+ syslog(ap->new ? LOG_INFO : LOG_DEBUG,
+ "%s: adding address %s",
+ ifp->name, ap->saddr);
+ if (add_address6(ifp, ap) == -1)
+ syslog(LOG_ERR, "add_address6 %m");
+ else if (ipv6_remove_subnet(rap, ap) == -1)
+ syslog(LOG_ERR, "ipv6_remove_subnet %m");
+ else
+ syslog(ap->new ? LOG_INFO : LOG_DEBUG,
+ "%s: vltime %d seconds, pltime %d seconds",
+ ifp->name, ap->prefix_vltime,
+ ap->prefix_pltime);
+ }
+ }
+ ipv6_build_routes();
run_script_reason(ifp, options & DHCPCD_TEST ? "TEST" : "ROUTERADVERT");
if (options & DHCPCD_TEST)
exit(EXIT_SUCCESS);
@@ -556,6 +681,7 @@ ipv6rs_handledata(_unused void *arg)
if (has_dns)
delete_q_timeout(0, handle_exit_timeout, NULL);
delete_timeout(NULL, ifp);
+ delete_timeout(NULL, rap); /* reachable timer */
ipv6rs_expire(ifp);
if (has_dns)
daemonise();
@@ -563,6 +689,27 @@ ipv6rs_handledata(_unused void *arg)
syslog(LOG_WARNING,
"%s: did not fork due to an absent RDNSS option in the RA",
ifp->name);
+
+ /* If we're owning the RA then we need to try and ensure the
+ * router is actually reachable */
+ if (options & DHCPCD_IPV6RA_OWN ||
+ options & DHCPCD_IPV6RA_OWN_DEFAULT)
+ {
+ rap->nsprobes = 0;
+ add_timeout_sec(REACHABLE_TIME, ipv6ns_unreachable, rap);
+ add_timeout_sec(DELAY_FIRST_PROBE_TIME, ipv6ns_sendprobe, rap);
+ }
+}
+
+int
+ipv6rs_has_ra(const struct interface *ifp)
+{
+ const struct ra *rap;
+
+ TAILQ_FOREACH(rap, &ipv6_routers, next)
+ if (rap->iface == ifp)
+ return 1;
+ return 0;
}
ssize_t
@@ -573,12 +720,16 @@ ipv6rs_env(char **env, const char *prefix, const struct interface *ifp)
const struct ra *rap;
const struct ra_opt *rao;
int i;
- char buffer[32], buffer2[32];
+ char buffer[32];
const char *optn;
-
+
+ i = 1;
l = 0;
get_monotonic(&now);
- for (rap = ifp->ras, i = 1; rap; rap = rap->next, i++) {
+ TAILQ_FOREACH(rap, &ipv6_routers, next) {
+ i++;
+ if (rap->iface != ifp)
+ continue;
if (env) {
snprintf(buffer, sizeof(buffer),
"ra%d_from", i);
@@ -586,7 +737,7 @@ ipv6rs_env(char **env, const char *prefix, const struct interface *ifp)
}
l++;
- for (rao = rap->options; rao; rao = rao->next) {
+ TAILQ_FOREACH(rao, &rap->options, next) {
if (rao->option == NULL)
continue;
if (env == NULL) {
@@ -618,6 +769,7 @@ ipv6rs_env(char **env, const char *prefix, const struct interface *ifp)
snprintf(buffer, sizeof(buffer), "ra%d_%s", i, optn);
setvar(&env, prefix, buffer, rao->option);
l++;
+#if 0
switch (rao->type) {
case ND_OPT_PREFIX_INFORMATION:
snprintf(buffer, sizeof(buffer),
@@ -640,7 +792,7 @@ ipv6rs_env(char **env, const char *prefix, const struct interface *ifp)
l += 3;
break;
}
-
+#endif
}
}
@@ -650,38 +802,12 @@ ipv6rs_env(char **env, const char *prefix, const struct interface *ifp)
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;
-
- free(ifp->rs);
- ifp->rs = NULL;
- for (rap = ifp->ras; rap && (ran = rap->next, 1); rap = ran) {
- ipv6rs_free_opts(rap);
- free(rap->data);
- free(rap);
- }
- ifp->ras = NULL;
-}
-
void
ipv6rs_expire(void *arg)
{
struct interface *ifp;
- struct ra *rap, *ran, *ral;
- struct ra_opt *rao, *raol, *raon;
+ struct ra *rap, *ran;
+ struct ra_opt *rao, *raon;
struct timeval now, lt, expire, next;
int expired;
@@ -690,10 +816,9 @@ ipv6rs_expire(void *arg)
expired = 0;
timerclear(&next);
- for (rap = ifp->ras, ral = NULL;
- rap && (ran = rap->next, 1);
- ral = rap, rap = ran)
- {
+ TAILQ_FOREACH_SAFE(rap, &ipv6_routers, next, ran) {
+ if (rap->iface != ifp)
+ continue;
lt.tv_sec = rap->lifetime;
lt.tv_usec = 0;
timeradd(&rap->received, &lt, &expire);
@@ -701,33 +826,23 @@ ipv6rs_expire(void *arg)
syslog(LOG_INFO, "%s: %s: expired Router Advertisement",
ifp->name, rap->sfrom);
rap->expired = expired = 1;
- if (ral)
- ral->next = ran;
- else
- ifp->ras = ran;
- ipv6rs_free_opts(rap);
- free(rap);
continue;
}
timersub(&expire, &now, &lt);
if (!timerisset(&next) || timercmp(&next, &lt, >))
next = lt;
-
- for (rao = rap->options, raol = NULL;
- rao && (raon = rao->next);
- raol = rao, rao = raon)
- {
+
+ TAILQ_FOREACH_SAFE(rao, &rap->options, next, raon) {
if (!timerisset(&rao->expire))
continue;
if (timercmp(&now, &rao->expire, >)) {
syslog(LOG_INFO,
"%s: %s: expired option %d",
ifp->name, rap->sfrom, rao->type);
- rap->expired = expired = 1;
- if (raol)
- raol = raon;
- else
- rap->options = raon;
+ TAILQ_REMOVE(&rap->options, rao, next);
+ expired = 1;
+ free(rao->option);
+ free(rao);
continue;
}
timersub(&rao->expire, &now, &lt);
@@ -738,8 +853,10 @@ ipv6rs_expire(void *arg)
if (timerisset(&next))
add_timeout_tv(&next, ipv6rs_expire, ifp);
- if (expired)
+ if (expired) {
+ ipv6_build_routes();
run_script_reason(ifp, "ROUTERADVERT");
+ }
}
int
@@ -758,3 +875,18 @@ ipv6rs_start(struct interface *ifp)
ipv6rs_sendprobe(ifp);
return 0;
}
+
+void
+ipv6rs_drop(struct interface *ifp)
+{
+ struct ra *rap;
+ int expired = 0;
+
+ TAILQ_FOREACH(rap, &ipv6_routers, next)
+ if (rap->iface == ifp)
+ rap->expired = expired = 1;
+ if (expired) {
+ ipv6_build_routes();
+ run_script_reason(ifp, "ROUTERADVERT");
+ }
+}