From eebe9a1887bbf2f3bbb4fdb72e8d46ece25cdd0c Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 5 Jul 2012 16:37:41 +0000 Subject: [PATCH] Improve IPv6 RA support by allowing dhcpcd to manage the address and routes 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. --- Makefile | 4 +- configure | 37 ++++ configure.c | 16 +- defs.h | 2 +- dhcpcd.8.in | 25 ++- dhcpcd.c | 47 +++-- dhcpcd.conf.5.in | 21 ++- dhcpcd.h | 28 +-- if-bsd.c | 171 ++++++++++++++++-- if-linux.c | 83 +++++++++ if-options.c | 33 +++- if-options.h | 5 + ipv6.c | 438 +++++++++++++++++++++++++++++++++++++++++++++++ ipv6.h | 76 ++++++++ ipv6ns.c | 349 +++++++++++++++++++++++++++++++++++++ ipv6ns.h | 42 +++++ ipv6rs.c | 380 ++++++++++++++++++++++++++-------------- ipv6rs.h | 42 ++++- net.c | 4 +- net.h | 22 ++- platform-bsd.c | 43 ++++- platform-linux.c | 66 ++++++- 22 files changed, 1697 insertions(+), 237 deletions(-) create mode 100644 ipv6.c create mode 100644 ipv6.h create mode 100644 ipv6ns.c create mode 100644 ipv6ns.h diff --git a/Makefile b/Makefile index 3b4dbfd6..b5a7be7b 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ 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 ipv6rs.c net.c signals.c -SRCS+= configure.c +SRCS+= configure.c if-options.c if-pref.c ipv4ll.c net.c signals.c +SRCS+= ipv6.c ipv6rs.c ipv6ns.c CFLAGS?= -O2 CSTD?= c99 diff --git a/configure b/configure index b3344970..7e3bfafe 100755 --- a/configure +++ b/configure @@ -13,6 +13,7 @@ HOST= TARGET= DEBUG= FORK= +STATIC= INCLUDEDIR= for x do @@ -27,6 +28,8 @@ for x do --fork) FORK=$var;; --disable-fork) FORK=no;; --enable-fork) FORK=yes;; + --disable-static) STATIC=no;; + --enable-static) STATIC=yes;; --prefix) prefix=$var;; --sysconfdir) SYSCONFDIR=$var;; --bindir|--sbindir) SBINDIR=$var;; @@ -194,6 +197,12 @@ fi if [ -n "$CPPLAGS" ]; then echo "CPPLAGS= $CPPLAGS" >>$CONFIG_MK fi +if [ -n "$LDFLAGS" ]; then + echo "LDFLAGS= $LDFLAGS" >>$CONFIG_MK +fi +if [ "$STATIC" = yes ]; then + echo "LDFLAGS+= -static" >>$CONFIG_MK +fi for x in $INCLUDEDIR; do echo "CPPFLAGS+= -I$x" >>$CONFIG_MK done @@ -370,6 +379,34 @@ if [ "$STRLCPY" = no ]; then echo "#include \"compat/strlcpy.h\"" >>$CONFIG_H fi +if [ -z "$TAILQ_FOREACH_SAFE" ]; then + printf "Testing for TAILQ_FOREACH_SAFE ... " + cat <_queue.c +#include +int main(void) { +#ifndef TAILQ_FOREACH_SAFE +#error TAILQ_FOREACH_SAFE +#endif + return 0; +} +EOF + if $XCC _queue.c -o _queue 2>/dev/null; then + TAILQ_FOREACH_SAFE=yes + else + TAILQ_FOREACH_SAFE=no + fi + echo "$TAILQ_FOREACH_SAFE" + rm -f _queue.c _queue +fi +if [ "$TAILQ_FOREACH_SAFE" = no ]; then + cat <>$CONFIG_H +#define TAILQ_FOREACH_SAFE(var, head, field, next) \ + for ((var) = TAILQ_FIRST((head)); \ + (var) && ((next) = TAILQ_NEXT((var), field), 1); \ + (var) = (next)) +EOF +fi + if [ -z "$SERVICECMD" ]; then printf "Checking for OpenRC ... " if [ -x /sbin/rc-service ]; then diff --git a/configure.c b/configure.c index a2d262f5..15747fdd 100644 --- a/configure.c +++ b/configure.c @@ -52,16 +52,6 @@ #define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin" -/* Some systems have route metrics */ -#ifndef HAVE_ROUTE_METRIC -# ifdef __linux__ -# define HAVE_ROUTE_METRIC 1 -# endif -# ifndef HAVE_ROUTE_METRIC -# define HAVE_ROUTE_METRIC 0 -# endif -#endif - static struct rt *routes; static int @@ -173,7 +163,7 @@ make_env(const struct interface *iface, const char *reason, char ***argv) dhcp = ra = 0; if (strcmp(reason, "TEST") == 0) { - if (iface->ras) + if (ipv6rs_has_ra(iface)) ra = 1; else dhcp = 1; @@ -226,7 +216,7 @@ make_env(const struct interface *iface, const char *reason, char ***argv) e--; } *--p = '\0'; - if ((dhcp && iface->state->new) || (ra && iface->ras)) { + if ((dhcp && iface->state->new) || (ra && ipv6rs_has_ra(iface))) { env[8] = strdup("if_up=true"); env[9] = strdup("if_down=false"); } else { @@ -333,7 +323,7 @@ send_interface(int fd, const struct interface *iface) int retval = 0; if (send_interface1(fd, iface, iface->state->reason) == -1) retval = -1; - if (iface->ras) { + if (ipv6rs_has_ra(iface)) { if (send_interface1(fd, iface, "ROUTERADVERT") == -1) retval = -1; } diff --git a/defs.h b/defs.h index b4f23809..10de82d3 100644 --- a/defs.h +++ b/defs.h @@ -28,7 +28,7 @@ #define CONFIG_H #define PACKAGE "dhcpcd" -#define VERSION "5.5.6" +#define VERSION "5.6.0" #ifndef CONFIG # define CONFIG SYSCONFDIR "/" PACKAGE ".conf" diff --git a/dhcpcd.8.in b/dhcpcd.8.in index fc3c04c6..d41f6245 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 March 19, 2012 +.Dd June 7, 2012 .Dt DHCPCD 8 .Os .Sh NAME @@ -102,8 +102,18 @@ is also an implementation of the BOOTP client specified in .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. +.Li RFC 4861 +and +.Li RFC 6106 . +.Nm +can optionally handle address and route management itself, +and will do so by default if Router Solicitation is disabled in the kernel. +If +.Nm +is managing routes, +.Nm +sends Neighbor Solicitions to each advertising router periodically and will +expire the ones that do not respond. .Ss Local Link configuration If .Nm @@ -274,8 +284,12 @@ Override the .Ar vendorclassid field sent. The default is -dhcpcd . +dhcpcd-:::. +For example +.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386 If not set then none is sent. +Some badly configured DHCP servers reject unknown vendorclassids. +To work around it, try and impersonate Windows by using the MSFT vendorclassid. .It Fl k , Fl Fl release This causes an existing .Nm @@ -578,7 +592,8 @@ running on the .Xr resolvconf 8 .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 6106. +RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702, RFC 4861, RFC 5969, +RFC 6106. .Sh AUTHORS .An Roy Marples Aq roy@marples.name .Sh BUGS diff --git a/dhcpcd.c b/dhcpcd.c index b6595fea..4b318cac 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -67,6 +67,8 @@ const char copyright[] = "Copyright (c) 2006-2012 Roy Marples"; #include "if-options.h" #include "if-pref.h" #include "ipv4ll.h" +#include "ipv6.h" +#include "ipv6ns.h" #include "ipv6rs.h" #include "net.h" #include "platform.h" @@ -80,7 +82,6 @@ const char copyright[] = "Copyright (c) 2006-2012 Roy Marples"; #define RELEASE_DELAY_S 0 #define RELEASE_DELAY_NS 10000000 -unsigned long long options = 0; int pidfd = -1; struct interface *ifaces = NULL; int ifac = 0; @@ -95,7 +96,7 @@ static char **ifv; static int ifc; static char *cffile; static char *pidfile; -static int linkfd = -1, ipv6rsfd = -1; +static int linkfd = -1, ipv6rsfd = -1, ipv6nsfd = -1; struct dhcp_op { uint8_t value; @@ -254,15 +255,8 @@ stop_interface(struct interface *iface) struct interface *ifp, *ifl = NULL; syslog(LOG_INFO, "%s: removing interface", iface->name); - if (iface->ras) { - ipv6rs_free(iface); - iface->ras = NULL; - run_script_reason(iface, "ROUTERADVERT"); - } - if (strcmp(iface->state->reason, "RELEASE") != 0) - drop_dhcp(iface, "STOP"); - close_sockets(iface); - delete_timeout(NULL, iface); + + // Remove the interface from our list for (ifp = ifaces; ifp; ifp = ifp->next) { if (ifp == iface) break; @@ -272,6 +266,12 @@ stop_interface(struct interface *iface) ifl->next = ifp->next; else ifaces = ifp->next; + + ipv6rs_drop(iface); + if (strcmp(iface->state->reason, "RELEASE") != 0) + drop_dhcp(iface, "STOP"); + close_sockets(iface); + delete_timeout(NULL, iface); free_interface(ifp); if (!(options & (DHCPCD_MASTER | DHCPCD_TEST))) exit(EXIT_FAILURE); @@ -922,11 +922,7 @@ 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); - if (iface->ras) { - ipv6rs_free(iface); - iface->ras = NULL; - run_script_reason(iface, "ROUTERADVERT"); - } + ipv6rs_drop(iface); drop_dhcp(iface, "NOCARRIER"); } } else if (carrier == 1 && !(~iface->flags & IFF_UP)) { @@ -1616,7 +1612,7 @@ handle_args(struct fd_list *fd, int argc, char **argv) if (argc == 1) { for (ifp = ifaces; ifp; ifp = ifp->next) { len++; - if (ifp->ras) + if (ipv6rs_has_ra(ifp)) len++; } len = write(fd->fd, &len, sizeof(len)); @@ -1631,7 +1627,7 @@ handle_args(struct fd_list *fd, int argc, char **argv) for (ifp = ifaces; ifp; ifp = ifp->next) if (strcmp(argv[opt], ifp->name) == 0) { len++; - if (ifp->ras) + if (ipv6rs_has_ra(ifp)) len++; } } @@ -1914,7 +1910,7 @@ main(int argc, char **argv) syslog(LOG_INFO, "sending signal %d to pid %d", sig, pid); if (pid == 0 || kill(pid, sig) != 0) { - if (sig != SIGALRM) + if (sig != SIGALRM && errno != EPERM) syslog(LOG_ERR, ""PACKAGE" not running"); if (pid != 0 && errno != ESRCH) { syslog(LOG_ERR, "kill: %m"); @@ -2006,6 +2002,10 @@ main(int argc, char **argv) if (options & DHCPCD_IPV6RS && !check_ipv6(NULL)) options &= ~DHCPCD_IPV6RS; + if (options & DHCPCD_IPV6RS && ipv6_open() == -1) { + options &= ~DHCPCD_IPV6RS; + syslog(LOG_ERR, "ipv6_open: %m"); + } if (options & DHCPCD_IPV6RS) { ipv6rsfd = ipv6rs_open(); if (ipv6rsfd == -1) { @@ -2015,6 +2015,15 @@ main(int argc, char **argv) add_event(ipv6rsfd, ipv6rs_handledata, NULL); // atexit(restore_rtadv); } + if (options & DHCPCD_IPV6RA_OWN || + options & DHCPCD_IPV6RA_OWN_DEFAULT) + { + ipv6nsfd = ipv6ns_open(); + if (ipv6nsfd == -1) + syslog(LOG_ERR, "ipv6nd: %m"); + else + add_event(ipv6nsfd, ipv6ns_handledata, NULL); + } } ifc = argc - optind; diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index 9f0ef27b..a1decb74 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 March 19, 2012 +.Dd May 21, 2012 .Dt DHCPCD.CONF 5 SMM .Os .Sh NAME @@ -154,6 +154,18 @@ RDNSS option. Set this option so to make .Nm dhcpcd always fork on an RA. +.It ic ipv6ra_own +Disables kernel IPv6 Router Advertisment processing so dhcpcd can manage +addresses and routes. +.It ic ipv6ra_own_default +Each time dhcpcd receives an IPv6 Router Adveristment, dhcpcd will manage +the default route only. +This allows dhcpcd to prefer an interface for outbound traffic based on metric +and/or user selection rather than the kernel. +.It ic ipv6rs +Enables IPv6 Router Advertisment solicitation. +This is on by default, but is documented here in the case where it is disabled +globally but needs to be enabled for one interface. .It Ic leasetime Ar seconds Request a leasetime of .Ar seconds . @@ -289,8 +301,13 @@ Set the vendor option 03 with an IP address as a string. Set un-encapsulated vendor option to hello world. .D1 vendor ,"hello world" .It Ic vendorclassid Ar string -Change the default vendorclassid sent from dhcpcd-version. +The default is +dhcpcd-:::. +For example +.D1 dhcpcd-5.5.6:NetBSD-6.99.5:i386:i386 If not set then none is sent. +Some badly configured DHCP servers reject unknown vendorclassids. +To work around it, try and impersonate Windows by using the MSFT vendorclassid. .It Ic waitip Wait for an address to be assigned before forking to the background. .It Ic xidhwaddr diff --git a/dhcpcd.h b/dhcpcd.h index 642aed06..fbee3988 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -30,7 +30,7 @@ #include #include -#include +//#include #include @@ -82,30 +82,6 @@ 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]; - unsigned char *data; - ssize_t data_len; - 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; - int expired; - struct ra *next; -}; - struct interface { char name[IF_NAMESIZE]; struct if_state *state; @@ -138,13 +114,11 @@ struct interface { unsigned char *rs; size_t rslen; int rsprobes; - struct ra *ras; struct interface *next; }; extern int pidfd; -extern unsigned long long options; extern int ifac; extern char **ifav; extern int ifdc; diff --git a/if-bsd.c b/if-bsd.c index c3ffcace..c4765385 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -35,8 +35,12 @@ #include #include #include +#ifdef __FreeBSD__ /* Needed so that including netinet6/in6_var.h works */ +# include +#endif #include #include +#include #ifdef __DragonFly__ # include #elif __APPLE__ @@ -59,6 +63,7 @@ #include "configure.h" #include "dhcp.h" #include "if-options.h" +#include "ipv6.h" #include "net.h" #ifndef RT_ROUNDUP @@ -154,7 +159,6 @@ if_address(const struct interface *iface, const struct in_addr *address, const struct in_addr *netmask, const struct in_addr *broadcast, int action) { - int retval; struct ifaliasreq ifa; union { struct sockaddr *sa; @@ -178,23 +182,16 @@ if_address(const struct interface *iface, const struct in_addr *address, } #undef ADDADDR - if (action < 0) - retval = ioctl(socket_afnet, SIOCDIFADDR, &ifa); - else - retval = ioctl(socket_afnet, SIOCAIFADDR, &ifa); - return retval; + return ioctl(socket_afnet, + action < 0 ? SIOCDIFADDR : SIOCAIFADDR, &ifa); } -/* ARGSUSED4 */ int if_route(const struct rt *rt, int action) { union sockunion { struct sockaddr sa; struct sockaddr_in sin; -#ifdef INET6 - struct sockaddr_in6 sin6; -#endif struct sockaddr_dl sdl; struct sockaddr_storage ss; } su; @@ -207,17 +204,17 @@ if_route(const struct rt *rt, int action) size_t l; int retval = 0; -#define ADDSU(_su) { \ - l = RT_ROUNDUP(_su.sa.sa_len); \ - memcpy(bp, &(_su), l); \ +#define ADDSU { \ + l = RT_ROUNDUP(su.sa.sa_len); \ + memcpy(bp, &su, l); \ bp += l; \ } -#define ADDADDR(_a) { \ - memset (&su, 0, sizeof(su)); \ +#define ADDADDR(addr) { \ + memset(&su, 0, sizeof(su)); \ su.sin.sin_family = AF_INET; \ su.sin.sin_len = sizeof(su.sin); \ - memcpy (&su.sin.sin_addr, _a, sizeof(su.sin.sin_addr)); \ - ADDSU(su); \ + (&su.sin)->sin_addr = *addr; \ + ADDSU; \ } memset(&rtm, 0, sizeof(rtm)); @@ -255,16 +252,154 @@ if_route(const struct rt *rt, int action) memset(&su, 0, sizeof(su)); su.sdl.sdl_len = sizeof(struct sockaddr_dl); link_addr(rt->iface->name, &su.sdl); - ADDSU(su); + ADDSU; } else ADDADDR(&rt->gate); if (rtm.hdr.rtm_addrs & RTA_NETMASK) ADDADDR(&rt->net); + /* IFP here if we need it */ + if (rtm.hdr.rtm_addrs & RTA_IFA) ADDADDR(&rt->iface->addr); +#undef ADDADDR +#undef ADDSU + + rtm.hdr.rtm_msglen = l = bp - (char *)&rtm; + if (write(r_fd, &rtm, l) == -1) + retval = -1; + return retval; +} + +int +if_address6(const struct interface *ifp, const struct ipv6_addr *a, int action) +{ + struct in6_aliasreq ifa; + struct in6_addr mask; + + memset(&ifa, 0, sizeof(ifa)); + strlcpy(ifa.ifra_name, ifp->name, sizeof(ifa.ifra_name)); + +#define ADDADDR(v, addr) { \ + (v)->sin6_family = AF_INET6; \ + (v)->sin6_len = sizeof(*v); \ + (v)->sin6_addr = *addr; \ + } + + ADDADDR(&ifa.ifra_addr, &a->addr); + ipv6_mask(&mask, a->prefix_len); + ADDADDR(&ifa.ifra_prefixmask, &mask); + ifa.ifra_lifetime.ia6t_vltime = a->prefix_vltime; + ifa.ifra_lifetime.ia6t_pltime = a->prefix_pltime; +#undef ADDADDR + + return ioctl(socket_afnet6, + action < 0 ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6, &ifa); +} + +int +if_route6(const struct rt6 *rt, int action) +{ + union sockunion { + struct sockaddr sa; + struct sockaddr_in6 sin; + struct sockaddr_dl sdl; + struct sockaddr_storage ss; + } su; + struct rtm + { + struct rt_msghdr hdr; + char buffer[sizeof(su) * 4]; + } rtm; + char *bp = rtm.buffer; + size_t l; + int retval = 0; + +/* KAME based systems want to store the scope inside the sin6_addr + * for link local addreses */ +#ifdef __KAME__ +#define SCOPE { \ + if (IN6_IS_ADDR_LINKLOCAL(&su.sin.sin6_addr)) { \ + *(uint16_t *)(void *)&su.sin.sin6_addr.s6_addr[2] = \ + htons(su.sin.sin6_scope_id); \ + su.sin.sin6_scope_id = 0; \ + } \ + } +#else +#define SCOPE +#endif + +#define ADDSU { \ + l = RT_ROUNDUP(su.sa.sa_len); \ + memcpy(bp, &su, l); \ + bp += l; \ + } +#define ADDADDRS(addr, scope) { \ + memset(&su, 0, sizeof(su)); \ + su.sin.sin6_family = AF_INET6; \ + su.sin.sin6_len = sizeof(su.sin); \ + (&su.sin)->sin6_addr = *addr; \ + su.sin.sin6_scope_id = scope; \ + SCOPE; \ + ADDSU; \ + } +#define ADDADDR(addr) ADDADDRS(addr, 0) + + memset(&rtm, 0, sizeof(rtm)); + rtm.hdr.rtm_version = RTM_VERSION; + rtm.hdr.rtm_seq = 1; + if (action == 0) + rtm.hdr.rtm_type = RTM_CHANGE; + else if (action > 0) + rtm.hdr.rtm_type = RTM_ADD; + else + rtm.hdr.rtm_type = RTM_DELETE; + + rtm.hdr.rtm_flags = RTF_UP; + /* None interface subnet routes are static. */ + if (IN6_IS_ADDR_UNSPECIFIED(&rt->dest) && + IN6_IS_ADDR_UNSPECIFIED(&rt->net)) + rtm.hdr.rtm_flags |= RTF_GATEWAY; + else + rtm.hdr.rtm_flags |= RTF_CLONING; + + rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK; +// if (action >= 0) +// rtm.hdr.rtm_addrs |= RTA_IFA; + + ADDADDR(&rt->dest); + if (rtm.hdr.rtm_flags & (RTF_HOST | RTF_CLONING)) { + /* Make us a link layer socket for the host gateway */ + memset(&su, 0, sizeof(su)); + su.sdl.sdl_len = sizeof(struct sockaddr_dl); + link_addr(rt->iface->name, &su.sdl); + ADDSU; + } else + ADDADDRS(&rt->gate, rt->iface->index); + + if (rtm.hdr.rtm_addrs & RTA_NETMASK) { + if (rtm.hdr.rtm_flags & RTF_GATEWAY) { + memset(&su, 0, sizeof(su)); + su.sin.sin6_family = AF_INET6; + ADDSU; + } else + ADDADDR(&rt->net); + } + + /* IFP here if we need it */ + /* IFA here if we need it */ + +#undef ADDADDR +#undef ADDSU +#undef SCOPE + + if (action >= 0 && rt->mtu) { + rtm.hdr.rtm_inits |= RTV_MTU; + rtm.hdr.rtm_rmx.rmx_mtu = rt->mtu; + } + rtm.hdr.rtm_msglen = l = bp - (char *)&rtm; if (write(r_fd, &rtm, l) == -1) retval = -1; diff --git a/if-linux.c b/if-linux.c index 08acaef6..46b726cf 100644 --- a/if-linux.c +++ b/if-linux.c @@ -59,6 +59,7 @@ #include "common.h" #include "configure.h" #include "dhcp.h" +#include "ipv6.h" #include "net.h" static int sock_fd; @@ -583,3 +584,85 @@ if_route(const struct rt *rt, int action) free(nlm); return retval; } + +int +if_address6(const struct interface *ifp, const struct ipv6_addr *ap, int action) +{ + struct nlma *nlm; + int retval = 0; + + nlm = xzalloc(sizeof(*nlm)); + nlm->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + nlm->hdr.nlmsg_flags = NLM_F_REQUEST; + if (action >= 0) { + nlm->hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; + nlm->hdr.nlmsg_type = RTM_NEWADDR; + } else + nlm->hdr.nlmsg_type = RTM_DELADDR; + nlm->ifa.ifa_index = ifp->index; + nlm->ifa.ifa_family = AF_INET6; + nlm->ifa.ifa_prefixlen = ap->prefix_len; + /* This creates the aliased interface */ + add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LABEL, + ifp->name, strlen(ifp->name) + 1); + add_attr_l(&nlm->hdr, sizeof(*nlm), IFA_LOCAL, + &ap->addr.s6_addr, sizeof(ap->addr.s6_addr)); + + if (send_netlink(&nlm->hdr) == -1) + retval = -1; + free(nlm); + return retval; +} + +int +if_route6(const struct rt6 *rt, int action) +{ + struct nlmr *nlm; + int retval = 0; + + nlm = xzalloc(sizeof(*nlm)); + nlm->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + nlm->hdr.nlmsg_type = RTM_NEWROUTE; + nlm->hdr.nlmsg_flags = NLM_F_REQUEST; + if (action == 0) + nlm->hdr.nlmsg_flags |= NLM_F_REPLACE; + else if (action == 1) + nlm->hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + else + nlm->hdr.nlmsg_type = RTM_DELROUTE; + nlm->rt.rtm_family = AF_INET6; + nlm->rt.rtm_table = RT_TABLE_MAIN; + + if (action == -1 || action == -2) + nlm->rt.rtm_scope = RT_SCOPE_NOWHERE; + else { + nlm->hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + /* None interface subnet routes are static. */ + if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) { + nlm->rt.rtm_protocol = RTPROT_KERNEL; + nlm->rt.rtm_scope = RT_SCOPE_LINK; + } else + nlm->rt.rtm_protocol = RTPROT_BOOT; + nlm->rt.rtm_type = RTN_UNICAST; + } + + nlm->rt.rtm_dst_len = ipv6_prefixlen(&rt->net); + add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_DST, + &rt->dest.s6_addr, sizeof(rt->dest.s6_addr)); + + /* If destination == gateway then don't add the gateway */ + if (!IN6_IS_ADDR_UNSPECIFIED(&rt->gate) && + !IN6_ARE_ADDR_EQUAL(&rt->dest, &rt->gate)) + add_attr_l(&nlm->hdr, sizeof(*nlm), RTA_GATEWAY, + &rt->gate.s6_addr, sizeof(rt->gate.s6_addr)); + + add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_OIF, rt->iface->index); + add_attr_32(&nlm->hdr, sizeof(*nlm), RTA_PRIORITY, rt->metric); + + if (send_netlink(&nlm->hdr) == -1) + retval = -1; + free(nlm); + return retval; + errno = ENOTSUP; + return -1; +} diff --git a/if-options.c b/if-options.c index 864dc85a..36e99658 100644 --- a/if-options.c +++ b/if-options.c @@ -47,14 +47,19 @@ #include "net.h" #include "platform.h" +unsigned long long options = 0; + /* These options only make sense in the config file, so don't use any valid short options for them */ -#define O_BASE MAX('z', 'Z') + 1 -#define O_ARPING O_BASE + 1 -#define O_FALLBACK O_BASE + 2 -#define O_DESTINATION O_BASE + 3 -#define O_NOIPV6RS O_BASE + 4 -#define O_IPV6_RA_FORK O_BASE + 5 +#define O_BASE MAX('z', 'Z') + 1 +#define O_ARPING O_BASE + 1 +#define O_FALLBACK O_BASE + 2 +#define O_DESTINATION O_BASE + 3 +#define O_IPV6RS O_BASE + 4 +#define O_NOIPV6RS O_BASE + 5 +#define O_IPV6RA_FORK O_BASE + 6 +#define O_IPV6RA_OWN O_BASE + 7 +#define O_IPV6RA_OWN_D O_BASE + 8 const struct option cf_options[] = { {"background", no_argument, NULL, 'b'}, @@ -105,8 +110,11 @@ const struct option cf_options[] = { {"arping", required_argument, NULL, O_ARPING}, {"destination", required_argument, NULL, O_DESTINATION}, {"fallback", required_argument, NULL, O_FALLBACK}, + {"ipv6rs", no_argument, NULL, O_IPV6RS}, {"noipv6rs", no_argument, NULL, O_NOIPV6RS}, - {"ipv6ra_fork", no_argument, NULL, O_IPV6_RA_FORK}, + {"ipv6ra_fork", no_argument, NULL, O_IPV6RA_FORK}, + {"ipv6ra_own", no_argument, NULL, O_IPV6RA_OWN}, + {"ipv6ra_own_default", no_argument, NULL, O_IPV6RA_OWN_D}, {NULL, 0, NULL, '\0'} }; @@ -744,12 +752,21 @@ parse_option(struct if_options *ifo, int opt, const char *arg) free(ifo->fallback); ifo->fallback = xstrdup(arg); break; + case O_IPV6RS: + ifo->options |= DHCPCD_IPV6RS; + break; case O_NOIPV6RS: ifo->options &= ~DHCPCD_IPV6RS; break; - case O_IPV6_RA_FORK: + case O_IPV6RA_FORK: ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; break; + case O_IPV6RA_OWN: + ifo->options |= DHCPCD_IPV6RA_OWN; + break; + case O_IPV6RA_OWN_D: + ifo->options |= DHCPCD_IPV6RA_OWN_DEFAULT; + break; default: return 0; } diff --git a/if-options.h b/if-options.h index 685de6e0..3e083d87 100644 --- a/if-options.h +++ b/if-options.h @@ -79,6 +79,9 @@ #define DHCPCD_DUMPLEASE (1ULL << 30) #define DHCPCD_IPV6RS (1ULL << 31) #define DHCPCD_IPV6RA_REQRDNSS (1ULL << 32) +#define DHCPCD_IPV6RA_OWN (1ULL << 33) +#define DHCPCD_IPV6RA_OWN_DEFAULT (1ULL << 34) +#define DHCPCD_IPV4 (1ULL << 35) extern const struct option cf_options[]; @@ -117,6 +120,8 @@ struct if_options { char *fallback; }; +extern unsigned long long options; + struct if_options *read_config(const char *, const char *, const char *, const char *); int add_options(struct if_options *, int, char **); diff --git a/ipv6.c b/ipv6.c new file mode 100644 index 00000000..e88035e6 --- /dev/null +++ b/ipv6.c @@ -0,0 +1,438 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 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 "common.h" +#include "configure.h" +#include "dhcpcd.h" +#include "ipv6.h" +#include "ipv6rs.h" + +/* Hackery at it's finest. */ +#ifndef s6_addr32 +# define s6_addr32 __u6_addr.__u6_addr32 +#endif + +int socket_afnet6; +static struct rt6head *routes; + +#ifdef DEBUG_MEMORY +static void +ipv6_cleanup() +{ + + free(routes); +} +#endif + +int +ipv6_open(void) +{ + socket_afnet6 = socket(AF_INET6, SOCK_DGRAM, 0); + if (socket_afnet6 == -1) + return -1; + set_cloexec(socket_afnet6); + routes = xmalloc(sizeof(*routes)); + TAILQ_INIT(routes); +#ifdef DEBUG_MEMORY + atexit(ipv6_cleanup); +#endif + return socket_afnet6; +} + +struct in6_addr * +ipv6_linklocal(const char *ifname) +{ + struct ifaddrs *ifaddrs, *ifa; + struct sockaddr_in6 *sa6; + struct in6_addr *in6; + + if (getifaddrs(&ifaddrs) == -1) + return NULL; + + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || + ifa->ifa_addr->sa_family != AF_INET6) + continue; + if (strcmp(ifa->ifa_name, ifname)) + continue; + sa6 = (struct sockaddr_in6 *)(void *)ifa->ifa_addr; + if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) + break; + } + + if (ifa) { + in6 = xmalloc(sizeof(*in6)); + memcpy(in6, &sa6->sin6_addr, sizeof(*in6)); + } else + in6 = NULL; + + freeifaddrs(ifaddrs); + return in6; +} + +int +ipv6_makeaddr(struct in6_addr *addr, const char *ifname, + const struct in6_addr *prefix, int prefix_len) +{ + struct in6_addr *lla; + + if (prefix_len > 64) { + errno = EINVAL; + return -1; + } + + lla = ipv6_linklocal(ifname); + if (lla == NULL) { + errno = ENOENT; + return -1; + } + + memcpy(addr, prefix, sizeof(*prefix)); + addr->s6_addr32[2] = lla->s6_addr32[2]; + addr->s6_addr32[3] = lla->s6_addr32[3]; + free(lla); + return 0; +} + +int +ipv6_mask(struct in6_addr *mask, int len) +{ + static const unsigned char masks[NBBY] = + { 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff }; + int bytes, bits, i; + + if (len < 0 || len > 128) { + errno = EINVAL; + return -1; + } + + memset(mask, 0, sizeof(*mask)); + bytes = len / NBBY; + bits = len % NBBY; + for (i = 0; i < bytes; i++) + mask->s6_addr[i] = 0xff; + if (bits) + mask->s6_addr[bytes] = masks[bits - 1]; + return 0; +} + +int +ipv6_prefixlen(const struct in6_addr *mask) +{ + int x = 0, y; + const unsigned char *lim, *p; + + lim = (const unsigned char *)mask + sizeof(*mask); + for (p = (const unsigned char *)mask; p < lim; x++, p++) { + if (*p != 0xff) + break; + } + y = 0; + if (p < lim) { + for (y = 0; y < NBBY; y++) { + if ((*p & (0x80 >> y)) == 0) + break; + } + } + + /* + * when the limit pointer is given, do a stricter check on the + * remaining bits. + */ + if (p < lim) { + if (y != 0 && (*p & (0x00ff >> y)) != 0) + return -1; + for (p = p + 1; p < lim; p++) + if (*p != 0) + return -1; + } + + return x * NBBY + y; +} + +static struct rt6 * +find_route6(struct rt6head *rts, const struct rt6 *r) +{ + struct rt6 *rt; + + TAILQ_FOREACH(rt, rts, next) { + if (IN6_ARE_ADDR_EQUAL(&rt->dest, &r->dest) && +#if HAVE_ROUTE_METRIC + rt->iface->metric == r->iface->metric && +#endif + IN6_ARE_ADDR_EQUAL(&rt->net, &r->net)) + return rt; + } + return NULL; +} + +static void +desc_route(const char *cmd, const struct rt6 *rt) +{ + char destbuf[INET6_ADDRSTRLEN]; + char gatebuf[INET6_ADDRSTRLEN]; + const char *ifname = rt->iface->name, *dest, *gate; + + dest = inet_ntop(AF_INET6, &rt->dest.s6_addr, + destbuf, INET6_ADDRSTRLEN); + gate = inet_ntop(AF_INET6, &rt->gate.s6_addr, + gatebuf, INET6_ADDRSTRLEN); + if (IN6_ARE_ADDR_EQUAL(&rt->gate, &in6addr_any)) + syslog(LOG_DEBUG, "%s: %s route to %s/%d", ifname, cmd, + dest, ipv6_prefixlen(&rt->net)); + else if (IN6_ARE_ADDR_EQUAL(&rt->dest, &in6addr_any) && + IN6_ARE_ADDR_EQUAL(&rt->net, &in6addr_any)) + syslog(LOG_DEBUG, "%s: %s default route via %s", ifname, cmd, + gate); + else + syslog(LOG_DEBUG, "%s: %s route to %s/%d via %s", ifname, cmd, + dest, ipv6_prefixlen(&rt->net), gate); +} + +static int +n_route(struct rt6 *rt) +{ + + /* Don't set default routes if not asked to */ + if (IN6_IS_ADDR_UNSPECIFIED(&rt->dest) && + IN6_IS_ADDR_UNSPECIFIED(&rt->net) && + !(rt->iface->state->options->options & DHCPCD_GATEWAY)) + return -1; + + /* Delete the route first as it could exist prior to dhcpcd running + * and we need to ensure it leaves via our preffered interface */ + del_route6(rt); + desc_route("adding", rt); + if (!add_route6(rt)) + return 0; + + syslog(LOG_ERR, "%s: add_route: %m", rt->iface->name); + return -1; +} + +static int +c_route(struct rt6 *ort, struct rt6 *nrt) +{ + + /* Don't set default routes if not asked to */ + if (IN6_IS_ADDR_UNSPECIFIED(&nrt->dest) && + IN6_IS_ADDR_UNSPECIFIED(&nrt->net) && + !(nrt->iface->state->options->options & DHCPCD_GATEWAY)) + return -1; + + desc_route("changing", nrt); + /* We delete and add the route so that we can change metric. + * This also has the nice side effect of flushing ARP entries so + * we don't have to do that manually. */ + del_route6(ort); + if (!add_route6(nrt)) + return 0; + syslog(LOG_ERR, "%s: add_route: %m", nrt->iface->name); + return -1; +} + +static int +d_route(struct rt6 *rt) +{ + int retval; + + desc_route("deleting", rt); + retval = del_route6(rt); + if (retval != 0 && errno != ENOENT && errno != ESRCH) + syslog(LOG_ERR,"%s: del_route: %m", rt->iface->name); + return retval; +} + +static struct rt6 * +make_route(struct ra *rap) +{ + struct rt6 *r; + + r = xzalloc(sizeof(*r)); + r->ra = rap; + r->iface = rap->iface; + r->metric = rap->iface->metric; + r->mtu = rap->mtu; + return r; +} + +static struct rt6 * +make_prefix(struct ra *rap, struct ipv6_addr *addr) +{ + struct rt6 *r; + + if (addr == NULL || addr->prefix_len > 128) + return NULL; + + r = make_route(rap); + r->dest = addr->prefix; + ipv6_mask(&r->net, addr->prefix_len); + r->gate = in6addr_any; + return r; +} + +static struct rt6 * +make_router(struct ra *rap) +{ + struct rt6 *r; + + r = make_route(rap); + r->dest = in6addr_any; + r->net = in6addr_any; + r->gate = rap->from; + return r; +} + +int +ipv6_remove_subnet(struct ra *rap, struct ipv6_addr *addr) +{ + struct rt6 *rt; + int r; + + /* We need to delete the subnet route to have our metric or + * prefer the interface. */ + r = 0; + rt = make_prefix(rap, addr); + if (rt) { + rt->iface = rap->iface; +#ifdef __linux__ + rt->metric = 256; +#else + rt->metric = 0; +#endif + if (!find_route6(routes, rt)) + r = del_route6(rt); + free(rt); + } + return r; +} + +#define RT_IS_DEFAULT(rtp) \ + (IN6_ARE_ADDR_EQUAL(&((rtp)->dest), &in6addr_any) && \ + IN6_ARE_ADDR_EQUAL(&((rtp)->net), &in6addr_any)) + +void +ipv6_build_routes(void) +{ + struct rt6head dnr, *nrs; + struct rt6 *rt, *rtn, *or; + struct ra *rap, *ran; + struct ipv6_addr *addr; + int have_default; + + if (!(options & (DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT))) + return; + + TAILQ_INIT(&dnr); + TAILQ_FOREACH(rap, &ipv6_routers, next) { + if (rap->expired) + continue; + if (options & DHCPCD_IPV6RA_OWN) + TAILQ_FOREACH(addr, &rap->addrs, next) { + rt = make_prefix(rap, addr); + if (rt) + TAILQ_INSERT_TAIL(&dnr, rt, next); + } + rt = make_router(rap); + if (rt) + TAILQ_INSERT_TAIL(&dnr, rt, next); + } + + nrs = xmalloc(sizeof(*nrs)); + TAILQ_INIT(nrs); + have_default = 0; + TAILQ_FOREACH_SAFE(rt, &dnr, next, rtn) { + /* Is this route already in our table? */ + if (find_route6(nrs, rt) != NULL) + continue; + //rt->src.s_addr = ifp->addr.s_addr; + /* Do we already manage it? */ + if ((or = find_route6(routes, rt))) { + if (or->iface != rt->iface || + // or->src.s_addr != ifp->addr.s_addr || + !IN6_ARE_ADDR_EQUAL(&rt->gate, &or->gate) || + rt->metric != or->metric) + { + if (c_route(or, rt) != 0) + continue; + } + TAILQ_REMOVE(routes, or, next); + free(or); + } else { + if (n_route(rt) != 0) + continue; + } + if (RT_IS_DEFAULT(rt)) + have_default = 1; + TAILQ_REMOVE(&dnr, rt, next); + TAILQ_INSERT_TAIL(nrs, rt, next); + } + + /* Free any routes we failed to add/change */ + while ((rt = TAILQ_FIRST(&dnr))) { + TAILQ_REMOVE(&dnr, rt, next); + free(rt); + } + + /* Remove old routes we used to manage + * If we own the default route, but not RA management itself + * then we need to preserve the last best default route we had */ + TAILQ_FOREACH_SAFE(rt, routes, next, rtn) { + if (find_route6(nrs, rt) == NULL) { + if (!have_default && + (options & DHCPCD_IPV6RA_OWN_DEFAULT) && + !(options & DHCPCD_IPV6RA_OWN) && + RT_IS_DEFAULT(rt)) + have_default = 1; + /* no need to add it back to our routing table + * as we delete an exiting route when we add + * a new one */ + else + d_route(rt); + } + free(rt); + } + free(routes); + routes = nrs; + + /* Now drop expired routers */ + TAILQ_FOREACH_SAFE(rap, &ipv6_routers, next, ran) { + if (rap->expired) + ipv6rs_drop_ra(rap); + } +} diff --git a/ipv6.h b/ipv6.h new file mode 100644 index 00000000..8f6ef27f --- /dev/null +++ b/ipv6.h @@ -0,0 +1,76 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 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 IPV6_H +#define IPV6_H + +#include + +#include + +#include "ipv6rs.h" + +#define ALLROUTERS "ff02::2" +#define HOPLIMIT 255 + +#define ROUNDUP8(a) (1 + (((a) - 1) | 7)) + +struct ipv6_addr { + TAILQ_ENTRY(ipv6_addr) next; + struct in6_addr prefix; + int prefix_len; + uint32_t prefix_vltime; + uint32_t prefix_pltime; + struct in6_addr addr; + int new; + char saddr[INET6_ADDRSTRLEN]; +}; + +struct rt6 { + TAILQ_ENTRY(rt6) next; + struct in6_addr dest; + struct in6_addr net; + struct in6_addr gate; + const struct interface *iface; + struct ra *ra; + int metric; + unsigned int mtu; +}; +TAILQ_HEAD(rt6head, rt6); + +extern int socket_afnet6; + +int ipv6_open(void); +struct in6_addr *ipv6_linklocal(const char *); +int ipv6_makeaddr(struct in6_addr *, const char *, const struct in6_addr *, int); +int ipv6_mask(struct in6_addr *, int); +int ipv6_prefixlen(const struct in6_addr *); +int ipv6_remove_subnet(struct ra *, struct ipv6_addr *); +void ipv6_build_routes(void); +void ipv6_drop(struct interface *); + +#endif diff --git a/ipv6ns.c b/ipv6ns.c new file mode 100644 index 00000000..3e483863 --- /dev/null +++ b/ipv6ns.c @@ -0,0 +1,349 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 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 +#include +#include + +#ifdef __linux__ +# define _LINUX_IN6_H +# include +#endif + +#define ELOOP_QUEUE 1 +#include "common.h" +#include "configure.h" +#include "dhcpcd.h" +#include "eloop.h" +#include "ipv6.h" +#include "ipv6ns.h" + +#define MIN_RANDOM_FACTOR (500 * 1000) /* milliseconds in usecs */ +#define MAX_RANDOM_FACTOR (1500 * 1000) /* milliseconds in usecs */ + +/* Debugging Neighbor Solicitations is a lot of spam, so disable it */ +//#define DEBUG_NS + +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]; + +#if DEBUG_MEMORY +static void +ipv6ns_cleanup(void) +{ + + free(sndbuf); + free(rcvbuf); +} +#endif + +int +ipv6ns_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_NEIGHBOR_ADVERT, &filt); + if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, + &filt, sizeof(filt)) == -1) + return -1; + + set_cloexec(sock); +#if DEBUG_MEMORY + atexit(ipv6ns_cleanup); +#endif + + 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 +ipv6ns_makeprobe(struct ra *rap) +{ + struct nd_neighbor_solicit *ns; + struct nd_opt_hdr *nd; + + free(rap->ns); + rap->nslen = sizeof(*ns) + ROUNDUP8(rap->iface->hwlen + 2); + rap->ns = xzalloc(rap->nslen); + if (rap->ns == NULL) + return -1; + ns = (struct nd_neighbor_solicit *)(void *)rap->ns; + ns->nd_ns_type = ND_NEIGHBOR_SOLICIT; + ns->nd_ns_cksum = 0; + ns->nd_ns_code = 0; + ns->nd_ns_reserved = 0; + ns->nd_ns_target = rap->from; + nd = (struct nd_opt_hdr *)(rap->ns + sizeof(*ns)); + nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR; + nd->nd_opt_len = (ROUNDUP8(rap->iface->hwlen + 2)) >> 3; + memcpy(nd + 1, rap->iface->hwaddr, rap->iface->hwlen); + return 0; +} + +void +ipv6ns_sendprobe(void *arg) +{ + struct ra *rap = arg; + struct sockaddr_in6 dst; + struct cmsghdr *cm; + struct in6_pktinfo pi; + int hoplimit = HOPLIMIT; + struct timeval tv, rtv; + + if (!rap->nsprobes) { + if (ipv6ns_makeprobe(rap) == -1) + return; + } + + dst = allrouters; + //dst.sin6_scope_id = ifp->linkid; + + sndhdr.msg_name = (caddr_t)&dst; + sndhdr.msg_iov[0].iov_base = rap->ns; + sndhdr.msg_iov[0].iov_len = rap->nslen; + + /* 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 = rap->iface->index; + 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)); + +#ifdef DEBUG_NS + syslog(LOG_INFO, "%s: sending IPv6 NS for %s", + rap->iface->name, rap->sfrom); +#endif + if (sendmsg(sock, &sndhdr, 0) == -1) + syslog(LOG_ERR, "%s: sendmsg: %m", rap->iface->name); + + tv.tv_sec = RETRANS_TIMER; + tv.tv_usec = MIN_RANDOM_FACTOR; + rtv.tv_sec = 0; + rtv.tv_usec = arc4random() % (MAX_RANDOM_FACTOR - MIN_RANDOM_FACTOR); + timeradd(&tv, &rtv, &tv); + add_timeout_tv(&tv, ipv6ns_sendprobe, rap); +} + +void +ipv6ns_unreachable(void *arg) +{ + struct ra *rap = arg; + + /* We could add an unreachable flag and persist the information, + * but that is more effort than it's probably worth. */ + syslog(LOG_WARNING, "%s: %s is unreachable, expiring it", + rap->iface->name, rap->sfrom); + rap->expired = 1; + ipv6_build_routes(); + run_script_reason(rap->iface, "ROUTERADVERT"); /* XXX not RA */ +} + +/* ARGSUSED */ +void +ipv6ns_handledata(_unused void *arg) +{ + ssize_t len; + struct cmsghdr *cm; + int hoplimit; + struct in6_pktinfo pkt; + struct icmp6_hdr *icp; + struct interface *ifp; + const char *sfrom; + struct nd_neighbor_advert *nd_na; + struct ra *rap; + int is_router, is_solicited; + + 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_neighbor_advert)) { + syslog(LOG_ERR, "IPv6 NA 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 != 255) { + syslog(LOG_ERR, + "IPv6 NA 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_NEIGHBOR_ADVERT || + icp->icmp6_code != 0) + { + syslog(LOG_ERR, "invalid IPv6 type or code from %s", sfrom); + return; + } + + for (ifp = ifaces; ifp; ifp = ifp->next) + if (ifp->index == (unsigned int)pkt.ipi6_ifindex) + break; + if (ifp == NULL) { +#ifdef DEBUG_NS + syslog(LOG_DEBUG, "NA for unexpected interface from %s", sfrom); +#endif + return; + } + + nd_na = (struct nd_neighbor_advert *)icp; + is_router = nd_na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER; + is_solicited = nd_na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED; + + if (IN6_IS_ADDR_MULTICAST(&nd_na->nd_na_target)) { + syslog(LOG_ERR, "%s: NA for multicast address from %s", + ifp->name, sfrom); + return; + } + + TAILQ_FOREACH(rap, &ipv6_routers, next) { + if (memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr, + sizeof(rap->from.s6_addr)) == 0) + break; + } + if (rap == NULL) { +#ifdef DEBUG_NS + syslog(LOG_DEBUG, "%s: unexpected NA from %s", + ifp->name, sfrom); +#endif + return; + } + +#ifdef DEBUG_NS + syslog(LOG_DEBUG, "%s: %sNA from %s", + ifp->name, is_solicited ? "solicited " : "", sfrom); +#endif + + /* Node is no longer a router, so remove it from consideration */ + if (!is_router && !rap->expired) { + syslog(LOG_INFO, "%s: %s is no longer a router", + ifp->name, sfrom); + rap->expired = 1; + ipv6_build_routes(); + run_script_reason(ifp, "ROUTERADVERT"); + return; + } + + if (is_solicited) { + rap->nsprobes = 1; + add_timeout_sec(REACHABLE_TIME, ipv6ns_unreachable, rap); + add_timeout_sec(DELAY_FIRST_PROBE_TIME, ipv6ns_sendprobe, rap); + } +} diff --git a/ipv6ns.h b/ipv6ns.h new file mode 100644 index 00000000..05636c9e --- /dev/null +++ b/ipv6ns.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef IPV6NS_H +#define IPV6NS_H + +#include "dhcpcd.h" +#include "ipv6rs.h" + +#define REACHABLE_TIME 30 /* seconds */ +#define RETRANS_TIMER 1 /* second */ +#define DELAY_FIRST_PROBE_TIME 5 /* seconds */ + +int ipv6ns_open(void); +void ipv6ns_unreachable(void *); +void ipv6ns_sendprobe(void *); +void ipv6ns_handledata(void *); +#endif 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 #include #include #include @@ -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, <, &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, <); if (!timerisset(&next) || timercmp(&next, <, >)) 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, <); @@ -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"); + } +} diff --git a/ipv6rs.h b/ipv6rs.h index 481aed34..4af65450 100644 --- a/ipv6rs.h +++ b/ipv6rs.h @@ -28,10 +28,50 @@ #ifndef IPV6RS_H #define IPV6RS_H +#include + +#include + +#include "dhcpcd.h" +#include "ipv6.h" + +struct ra_opt { + TAILQ_ENTRY(ra_opt) next; + uint8_t type; + struct timeval expire; + char *option; +}; + +struct ra { + TAILQ_ENTRY(ra) next; + struct interface *iface; + struct in6_addr from; + char sfrom[INET6_ADDRSTRLEN]; + unsigned char *data; + ssize_t data_len; + struct timeval received; + unsigned char flags; + uint32_t lifetime; + uint32_t mtu; + TAILQ_HEAD(, ipv6_addr) addrs; + TAILQ_HEAD(, ra_opt) options; + + unsigned char *ns; + size_t nslen; + int nsprobes; + + int expired; +}; + +extern TAILQ_HEAD(rahead, ra) ipv6_routers; + 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_drop_ra(struct ra *); +ssize_t ipv6rs_free(struct interface *); void ipv6rs_expire(void *arg); +int ipv6rs_has_ra(const struct interface *); +void ipv6rs_drop(struct interface *); #endif diff --git a/net.c b/net.c index 86a95e33..ae373c16 100644 --- a/net.c +++ b/net.c @@ -70,7 +70,7 @@ #include "net.h" #include "signals.h" -static char hwaddr_buffer[(HWADDR_LEN * 3) + 1]; +static char hwaddr_buffer[(HWADDR_LEN * 3) + 1 + 1024]; int socket_afnet = -1; @@ -133,7 +133,7 @@ hwaddr_ntoa(const unsigned char *hwaddr, size_t hwlen) char *p = hwaddr_buffer; size_t i; - for (i = 0; i < hwlen && i < HWADDR_LEN; i++) { + for (i = 0; i < hwlen; i++) { if (i > 0) *p ++= ':'; p += snprintf(p, 3, "%.2x", hwaddr[i]); diff --git a/net.h b/net.h index 5fbab78b..0d1475a0 100644 --- a/net.h +++ b/net.h @@ -41,6 +41,17 @@ #include "config.h" #include "dhcp.h" #include "dhcpcd.h" +#include "ipv6.h" + +/* Some systems have route metrics */ +#ifndef HAVE_ROUTE_METRIC +# ifdef __linux__ +# define HAVE_ROUTE_METRIC 1 +# endif +# ifndef HAVE_ROUTE_METRIC +# define HAVE_ROUTE_METRIC 0 +# endif +#endif #ifndef DUID_LEN # define DUID_LEN 128 + 2 @@ -59,7 +70,6 @@ # define ARPHRD_INFINIBAND 32 #endif - /* Work out if we have a private address or not * 10/8 * 172.16/12 @@ -130,6 +140,16 @@ int if_route(const struct rt *rt, int); #define del_src_route(rt) if_route(rt, -2); void free_routes(struct rt *); +int if_address6(const struct interface *, const struct ipv6_addr *, int); +#define add_address6(ifp, a) if_address6(ifp, a, 1) +#define del_address6(ifp, a) if_address6(ifp, a, -1) + +int if_route6(const struct rt6 *rt, int); +#define add_route6(rt) if_route6(rt, 1) +#define change_route6(rt) if_route6(rt, 0) +#define del_route6(rt) if_route6(rt, -1) +#define del_src_route6(rt) if_route6(rt, -2); + int open_udp_socket(struct interface *); extern const size_t udp_dhcp_len; ssize_t make_udp_packet(uint8_t **, const uint8_t *, size_t, diff --git a/platform-bsd.c b/platform-bsd.c index afa43845..fc656130 100644 --- a/platform-bsd.c +++ b/platform-bsd.c @@ -31,8 +31,10 @@ #include #include +#include #include +#include "if-options.h" #include "platform.h" #ifndef SYS_NMLN /* OSX */ @@ -53,37 +55,62 @@ hardware_platform(void) return march; } +#define get_inet6_sysctl(code) inet6_sysctl(code, 0, 0) +#define set_inet6_sysctl(code, val) inet6_sysctl(code, val, 1) static int -inet6_sysctl(int code) +inet6_sysctl(int code, int val, int action) { int mib[] = { CTL_NET, PF_INET6, IPPROTO_IPV6, 0 }; - int val; size_t size; mib[3] = code; size = sizeof(val); + if (action) { + if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), + NULL, 0, &val, size) == -1) + return -1; + return 0; + } if (sysctl(mib, sizeof(mib)/sizeof(mib[0]), &val, &size, NULL, 0) == -1) return -1; return val; } +static void +restore_kernel_ra(void) +{ + + syslog(LOG_INFO, "restoring Kernel IPv6 RA support"); + if (set_inet6_sysctl(IPV6CTL_ACCEPT_RTADV, 1) == -1) + syslog(LOG_ERR, "IPV6CTL_ACCEPT_RTADV: %m"); +} + int check_ipv6(const char *ifname) { + int val; - /* BSD doesn't support these values per iface, so just reutrn 1 */ + /* BSD doesn't support these values per iface, so just return 1 */ if (ifname) return 1; - if (inet6_sysctl(IPV6CTL_ACCEPT_RTADV) != 1) { - syslog(LOG_WARNING, - "Kernel is not configured to accept IPv6 RAs"); - return 0; + val = get_inet6_sysctl(IPV6CTL_ACCEPT_RTADV); + if (val == 0) + options |= DHCPCD_IPV6RA_OWN; + else if (options & DHCPCD_IPV6RA_OWN) { + syslog(LOG_INFO, "disabling Kernel IPv6 RA support"); + if (set_inet6_sysctl(IPV6CTL_ACCEPT_RTADV, 0) == -1) { + syslog(LOG_ERR, "IPV6CTL_ACCEPT_RTADV: %m"); + return 0; + } + atexit(restore_kernel_ra); } - if (inet6_sysctl(IPV6CTL_FORWARDING) != 0) { +return 1; + if (get_inet6_sysctl(IPV6CTL_FORWARDING) != 0) { syslog(LOG_WARNING, "Kernel is configured as a router, not a host"); return 0; } + return 1; } diff --git a/platform-linux.c b/platform-linux.c index 119ec501..64059175 100644 --- a/platform-linux.c +++ b/platform-linux.c @@ -32,6 +32,7 @@ #include #include "common.h" +#include "if-options.h" #include "platform.h" static const char *mproc = @@ -72,6 +73,9 @@ static const char *mproc = #endif ; +char **restore; +ssize_t nrestore; + char * hardware_platform(void) { @@ -121,23 +125,73 @@ check_proc_int(const char *path) return atoi(buf); } +static ssize_t +write_path(const char *path, const char *val) +{ + FILE *fp; + ssize_t r; + + fp = fopen(path, "w"); + if (fp == NULL) + return -1; + r = fprintf(fp, "%s\n", val); + fclose(fp); + return r; +} + static const char *prefix = "/proc/sys/net/ipv6/conf"; +static void +restore_kernel_ra(void) +{ + char path[256]; + + for (nrestore--; nrestore >= 0; nrestore--) { + syslog(LOG_INFO, "%s: restoring Kernel IPv6 RA support", + restore[nrestore]); + snprintf(path, sizeof(path), "%s/%s/accept_ra", + prefix, restore[nrestore]); + if (write_path(path, "1") == -1) + syslog(LOG_ERR, "write_path: %s: %m", path); +#ifdef DEBUG_MEMORY + free(restore[nrestore]); +#endif + } +#ifdef DEBUG_MEMORY + free(restore); +#endif +} + int check_ipv6(const char *ifname) { - int r; + int r, ex; char path[256]; - if (ifname == NULL) + if (ifname == NULL) { ifname = "all"; + ex = 1; + } else + ex = 0; snprintf(path, sizeof(path), "%s/%s/accept_ra", prefix, ifname); r = check_proc_int(path); - if (r != 1 && r != 2) { - syslog(LOG_WARNING, - "%s: not configured to accept IPv6 RAs", ifname); - return 0; + if (r == 0) + options |= DHCPCD_IPV6RA_OWN; + else if (options & DHCPCD_IPV6RA_OWN) { + syslog(LOG_INFO, "disabling Kernel IPv6 RA support"); + if (write_path(path, "0") == -1) { + syslog(LOG_ERR, "write_path: %s: %m", path); + return 0; + } + restore = realloc(restore, (nrestore + 1) * sizeof(char *)); + if (restore == NULL) { + syslog(LOG_ERR, "realloc: %m"); + exit(EXIT_FAILURE); + } + restore[nrestore++] = xstrdup(ifname); + if (ex) + atexit(restore_kernel_ra); } if (r != 2) { -- 2.47.2