diff options
| author | Roy Marples <roy@marples.name> | 2012-07-05 16:37:41 +0000 |
|---|---|---|
| committer | Roy Marples <roy@marples.name> | 2012-07-05 16:37:41 +0000 |
| commit | eebe9a1887bbf2f3bbb4fdb72e8d46ece25cdd0c (patch) | |
| tree | da2e3069767ce09c41eb2b32e6a3b7f01b164eca | |
| parent | 359e9d397807079db8d68d5e60eb4396f41d1738 (diff) | |
| download | dhcpcd-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.
| -rw-r--r-- | Makefile | 4 | ||||
| -rwxr-xr-x | configure | 37 | ||||
| -rw-r--r-- | configure.c | 16 | ||||
| -rw-r--r-- | defs.h | 2 | ||||
| -rw-r--r-- | dhcpcd.8.in | 25 | ||||
| -rw-r--r-- | dhcpcd.c | 47 | ||||
| -rw-r--r-- | dhcpcd.conf.5.in | 21 | ||||
| -rw-r--r-- | dhcpcd.h | 28 | ||||
| -rw-r--r-- | if-bsd.c | 171 | ||||
| -rw-r--r-- | if-linux.c | 83 | ||||
| -rw-r--r-- | if-options.c | 33 | ||||
| -rw-r--r-- | if-options.h | 5 | ||||
| -rw-r--r-- | ipv6.c | 438 | ||||
| -rw-r--r-- | ipv6.h | 76 | ||||
| -rw-r--r-- | ipv6ns.c | 349 | ||||
| -rw-r--r-- | ipv6ns.h | 42 | ||||
| -rw-r--r-- | ipv6rs.c | 380 | ||||
| -rw-r--r-- | ipv6rs.h | 42 | ||||
| -rw-r--r-- | net.c | 4 | ||||
| -rw-r--r-- | net.h | 22 | ||||
| -rw-r--r-- | platform-bsd.c | 43 | ||||
| -rw-r--r-- | platform-linux.c | 66 |
22 files changed, 1697 insertions, 237 deletions
@@ -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 @@ -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 <<EOF >_queue.c +#include <sys/queue.h> +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 <<EOF >>$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; } @@ -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 <version>. +dhcpcd-<version>:<os>:<machine>:<platform>. +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 @@ -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-<version>:<os>:<machine>:<platform>. +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 @@ -30,7 +30,7 @@ #include <sys/socket.h> #include <net/if.h> -#include <netinet/in.h> +//#include <netinet/in.h> #include <limits.h> @@ -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; @@ -35,8 +35,12 @@ #include <arpa/inet.h> #include <net/if.h> #include <net/if_dl.h> +#ifdef __FreeBSD__ /* Needed so that including netinet6/in6_var.h works */ +# include <net/if_var.h> +#endif #include <net/route.h> #include <netinet/in.h> +#include <netinet6/in6_var.h> #ifdef __DragonFly__ # include <netproto/802_11/ieee80211_ioctl.h> #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; @@ -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 **); @@ -0,0 +1,438 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/socket.h> + +#include <netinet/in.h> + +#include <errno.h> +#include <ifaddrs.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#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); + } +} @@ -0,0 +1,76 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2012 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef IPV6_H +#define IPV6_H + +#include <sys/queue.h> + +#include <netinet/in.h> + +#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 <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <netinet/icmp6.h> + +#include <errno.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#ifdef __linux__ +# define _LINUX_IN6_H +# include <linux/ipv6.h> +#endif + +#define ELOOP_QUEUE 1 +#include "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 <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef 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 @@ -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, <, &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"); + } +} @@ -28,10 +28,50 @@ #ifndef IPV6RS_H #define IPV6RS_H +#include <sys/queue.h> + +#include <time.h> + +#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 @@ -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]); @@ -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 <sys/utsname.h> #include <netinet/in.h> +#include <stdlib.h> #include <syslog.h> +#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 <syslog.h> #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) { |
