/* * Solaris interface driver for dhcpcd * Copyright (c) 2016 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 /* private interface we can hook into to get * a better getifaddrs(3). */ #include #include #include #include #include #include #include #include #include #include "config.h" #include "common.h" #include "dhcp.h" #include "if.h" #include "if-options.h" #include "ipv4.h" #include "ipv6.h" #include "ipv6nd.h" #ifndef ARP_MOD_NAME # define ARP_MOD_NAME "arp" #endif #ifndef RT_ROUNDUP #define RT_ROUNDUP(a) \ ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) #define RT_ADVANCE(x, n) (x += RT_ROUNDUP(salen(n))) #endif #define COPYOUT(sin, sa) do { \ if ((sa) && ((sa)->sa_family == AF_INET)) \ (sin) = ((const struct sockaddr_in *)(const void *) \ (sa))->sin_addr; \ } while (0) #define COPYOUT6(sin, sa) do { \ if ((sa) && ((sa)->sa_family == AF_INET6)) \ (sin) = ((const struct sockaddr_in6 *)(const void *) \ (sa))->sin6_addr; \ } while (0) #ifndef CLLADDR # define CLLADDR(s) (const void *)((s)->sdl_data + (s)->sdl_nlen) #endif #ifdef INET /* Instead of using DLPI directly, we use libdlpi which is * Solaris sepcific. */ struct dl_if { TAILQ_ENTRY(dl_if) next; struct interface *iface; int fd; dlpi_handle_t dh; uint8_t broadcast[DLPI_PHYSADDR_MAX]; }; TAILQ_HEAD(dl_if_head, dl_if); #endif struct priv { #ifdef INET struct dl_if_head dl_ifs; #endif #ifdef INET6 int pf_inet6_fd; #endif }; int if_init(__unused struct interface *ifp) { return 0; } int if_conf(__unused struct interface *ifp) { return 0; } int if_opensockets_os(struct dhcpcd_ctx *ctx) { struct priv *priv; if ((priv = malloc(sizeof(*priv))) == NULL) return -1; ctx->priv = priv; #ifdef INET TAILQ_INIT(&priv->dl_ifs); #endif #ifdef INET6 priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); /* Don't return an error so we at least work on kernels witout INET6 * even though we expect INET6 support. * We will fail noisily elsewhere anyway. */ #endif ctx->link_fd = socket(PF_ROUTE, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); #ifdef INET if (ctx->link_fd == -1) free(ctx->priv); #endif return ctx->link_fd == -1 ? -1 : 0; } void if_closesockets_os(struct dhcpcd_ctx *ctx) { #ifdef INET6 struct priv *priv; priv = (struct priv *)ctx->priv; if (priv->pf_inet6_fd != -1) close(priv->pf_inet6_fd); #endif /* each interface should have closed itself */ free(ctx->priv); } int if_getssid(struct interface *ifp) { UNUSED(ifp); errno = ENOTSUP; return -1; } int if_vimaster(__unused const struct dhcpcd_ctx *ctx, __unused const char *ifname) { return 0; } int if_machinearch(__unused char *str, __unused size_t len) { /* There is no extra data really. * isainfo -v does return amd64, but also i386. */ return 0; } struct linkwalk { struct ifaddrs *lw_ifa; int lw_error; }; static boolean_t if_newaddr(const char *ifname, void *arg) { struct linkwalk *lw = arg; struct ifaddrs *ifa; dlpi_handle_t dh; dlpi_info_t dlinfo; uint8_t pa[DLPI_PHYSADDR_MAX]; size_t pa_len; struct sockaddr_dl *sdl; ifa = NULL; if (dlpi_open(ifname, &dh, 0) != DLPI_SUCCESS) goto failed1; if (dlpi_info(dh, &dlinfo, 0) != DLPI_SUCCESS) goto failed; /* For some reason, dlpi_info won't return the * physical address, it's all zero's. * So cal dlpi_get_physaddr. */ pa_len = DLPI_PHYSADDR_MAX; if (dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR, pa, &pa_len) != DLPI_SUCCESS) goto failed; if ((ifa = calloc(1, sizeof(*ifa))) == NULL) goto failed; if ((ifa->ifa_name = strdup(ifname)) == NULL) goto failed; if ((sdl = calloc(1, sizeof(*sdl))) == NULL) goto failed; ifa->ifa_addr = (struct sockaddr *)sdl; sdl->sdl_index = if_nametoindex(ifname); sdl->sdl_family = AF_LINK; switch (dlinfo.di_mactype) { case DL_ETHER: sdl->sdl_type = IFT_ETHER; break; case DL_IB: sdl->sdl_type = IFT_IB; break; default: sdl->sdl_type = IFT_OTHER; break; } sdl->sdl_alen = pa_len; memcpy(sdl->sdl_data, pa, pa_len); ifa->ifa_next = lw->lw_ifa; lw->lw_ifa = ifa; dlpi_close(dh); return (B_FALSE); failed: dlpi_close(dh); if (ifa != NULL) { free(ifa->ifa_name); free(ifa->ifa_addr); free(ifa); } failed1: lw->lw_error = errno; return (B_TRUE); } /* Creates an empty sockaddr_dl for lo0. */ static struct ifaddrs * if_ifa_lo0(void) { struct ifaddrs *ifa; struct sockaddr_dl *sdl; if ((ifa = calloc(1, sizeof(*ifa))) == NULL) return NULL; if ((sdl = calloc(1, sizeof(*sdl))) == NULL) { free(ifa); return NULL; } if ((ifa->ifa_name = strdup("lo0")) == NULL) { free(ifa); free(sdl); return NULL; } ifa->ifa_addr = (struct sockaddr *)sdl; ifa->ifa_flags = IFF_LOOPBACK; sdl->sdl_family = AF_LINK; sdl->sdl_index = if_nametoindex("lo0"); return ifa; } /* getifaddrs(3) does not support AF_LINK, strips aliases and won't * report addresses that are not UP. * As such it's just totally useless, so we need to roll our own. */ int if_getifaddrs(struct ifaddrs **ifap) { struct linkwalk lw; struct ifaddrs *ifa; /* Private libc function which we should not have to call * to get non UP addresses. */ if (getallifaddrs(AF_UNSPEC, &lw.lw_ifa, 0) == -1) return -1; /* Start with some AF_LINK addresses. */ lw.lw_error = 0; dlpi_walk(if_newaddr, &lw, 0); if (lw.lw_error != 0) { freeifaddrs(lw.lw_ifa); errno = lw.lw_error; return -1; } /* lo0 doesn't appear in dlpi_walk, so fudge it. */ if ((ifa = if_ifa_lo0()) == NULL) { freeifaddrs(lw.lw_ifa); return -1; } ifa->ifa_next = lw.lw_ifa; *ifap = ifa; return 0; } static int salen(const struct sockaddr *sa) { switch (sa->sa_family) { case AF_LINK: return sizeof(struct sockaddr_dl); case AF_INET: return sizeof(struct sockaddr_in); case AF_INET6: return sizeof(struct sockaddr_in6); default: return sizeof(struct sockaddr); } } static void if_linkaddr(struct sockaddr_dl *sdl, const struct interface *ifp) { memset(sdl, 0, sizeof(*sdl)); sdl->sdl_family = AF_LINK; sdl->sdl_nlen = sdl->sdl_alen = sdl->sdl_slen = 0; sdl->sdl_index = (unsigned short)ifp->index; } static int get_addrs(int type, const void *data, const struct sockaddr **sa) { const char *cp; int i; cp = data; for (i = 0; i < RTAX_MAX; i++) { if (type & (1 << i)) { sa[i] = (const struct sockaddr *)cp; RT_ADVANCE(cp, sa[i]); } else sa[i] = NULL; } return 0; } static struct interface * if_findsdl(struct dhcpcd_ctx *ctx, const struct sockaddr_dl *sdl) { if (sdl->sdl_index) return if_findindex(ctx->ifaces, sdl->sdl_index); if (sdl->sdl_nlen) { char ifname[IF_NAMESIZE]; memcpy(ifname, sdl->sdl_data, sdl->sdl_nlen); ifname[sdl->sdl_nlen] = '\0'; return if_find(ctx->ifaces, ifname); } if (sdl->sdl_alen) { struct interface *ifp; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (ifp->hwlen == sdl->sdl_alen && memcmp(ifp->hwaddr, sdl->sdl_data, sdl->sdl_alen) == 0) return ifp; } } errno = ENOENT; return NULL; } static struct interface * if_findsa(struct dhcpcd_ctx *ctx, const struct sockaddr *sa) { if (sa == NULL) { errno = EINVAL; return NULL; } switch (sa->sa_family) { case AF_LINK: { const struct sockaddr_dl *sdl; sdl = (const void *)sa; return if_findsdl(ctx, sdl); } #ifdef INET case AF_INET: { const struct sockaddr_in *sin; struct ipv4_addr *ia; sin = (const void *)sa; if ((ia = ipv4_findmaskaddr(ctx, &sin->sin_addr))) return ia->iface; break; } #endif #ifdef INET6 case AF_INET6: { const struct sockaddr_in6 *sin; struct ipv6_addr *ia; sin = (const void *)sa; if ((ia = ipv6_findmaskaddr(ctx, &sin->sin6_addr))) return ia->iface; break; } #endif default: errno = EAFNOSUPPORT; return NULL; } errno = ENOENT; return NULL; } #ifdef INET static void if_finishrt(struct rt *rt) { /* Solaris has a subnet route with the gateway * of the owning address. * dhcpcd has a blank gateway here to indicate a * subnet route. */ if (rt->gate.s_addr != ntohl(INADDR_ANY) && ipv4_iffindaddr(UNCONST(rt->iface), &rt->gate, NULL)) rt->gate.s_addr = ntohl(INADDR_ANY); /* Solaris likes to set route MTU to match * interface MTU when adding routes. * This confuses dhcpcd as it expects MTU to be 0 * when no explicit MTU has been set. */ if (rt->mtu == (unsigned int)if_getmtu(rt->iface)) rt->mtu = 0; } static int if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, const struct rt_msghdr *rtm) { const struct sockaddr *sa, *rti_info[RTAX_MAX]; sa = (const void *)(rtm + 1); if (sa->sa_family != AF_INET) return -1; if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) return -1; get_addrs(rtm->rtm_addrs, sa, rti_info); memset(rt, 0, sizeof(*rt)); rt->flags = (unsigned int)rtm->rtm_flags; COPYOUT(rt->dest, rti_info[RTAX_DST]); if (rtm->rtm_addrs & RTA_NETMASK) COPYOUT(rt->mask, rti_info[RTAX_NETMASK]); else rt->mask.s_addr = INADDR_BROADCAST; COPYOUT(rt->gate, rti_info[RTAX_GATEWAY]); COPYOUT(rt->src, rti_info[RTAX_SRC]); rt->mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu; if (rtm->rtm_index) rt->iface = if_findindex(ctx->ifaces, rtm->rtm_index); else if (rtm->rtm_addrs & RTA_IFP) rt->iface = if_findsa(ctx, rti_info[RTAX_IFP]); else if (rtm->rtm_addrs & RTA_GATEWAY) rt->iface = if_findsa(ctx, rti_info[RTAX_GATEWAY]); /* If we don't have an interface and it's a host route, it maybe * to a local ip via the loopback interface. */ if (rt->iface == NULL && !(~rtm->rtm_flags & (RTF_HOST | RTF_GATEWAY))) { struct ipv4_addr *ia; if ((ia = ipv4_findaddr(ctx, &rt->dest))) rt->iface = ia->iface; } if (rt->iface == NULL) { errno = ESRCH; return -1; } if_finishrt(rt); return 0; } #endif #ifdef INET6 static void if_finishrt6(struct rt6 *rt) { /* Solaris has a subnet route with the gateway * of the owning address. * dhcpcd has a blank gateway here to indicate a * subnet route. */ if (!IN6_IS_ADDR_UNSPECIFIED(&rt->gate) && ipv6_iffindaddr(UNCONST(rt->iface), &rt->gate, 0)) rt->gate = in6addr_any; /* Solarais likes to set route MTU to match * interface MTU when adding routes. * This confuses dhcpcd as it expects MTU to be 0 * when no explicit MTU has been set. */ if (rt->mtu == (unsigned int)if_getmtu(rt->iface)) rt->mtu = 0; } static int if_copyrt6(struct dhcpcd_ctx *ctx, struct rt6 *rt, const struct rt_msghdr *rtm) { const struct sockaddr *sa, *rti_info[RTAX_MAX]; sa = (const void *)(rtm + 1); if (sa->sa_family != AF_INET6) return -1; if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) return -1; get_addrs(rtm->rtm_addrs, sa, rti_info); memset(rt, 0, sizeof(*rt)); rt->flags = (unsigned int)rtm->rtm_flags; COPYOUT6(rt->dest, rti_info[RTAX_DST]); if (rtm->rtm_addrs & RTA_NETMASK) COPYOUT6(rt->mask, rti_info[RTAX_NETMASK]); else ipv6_mask(&rt->mask, 128); COPYOUT6(rt->gate, rti_info[RTAX_GATEWAY]); rt->mtu = (unsigned int)rtm->rtm_rmx.rmx_mtu; if (rtm->rtm_index) rt->iface = if_findindex(ctx->ifaces, rtm->rtm_index); else if (rtm->rtm_addrs & RTA_IFP) rt->iface = if_findsa(ctx, rti_info[RTAX_IFP]); else if (rtm->rtm_addrs & RTA_GATEWAY) rt->iface = if_findsa(ctx, rti_info[RTAX_GATEWAY]); /* If we don't have an interface and it's a host route, it maybe * to a local ip via the loopback interface. */ if (rt->iface == NULL && !(~rtm->rtm_flags & (RTF_HOST | RTF_GATEWAY))) { struct ipv6_addr *ia; if ((ia = ipv6_findaddr(ctx, &rt->dest, 0))) rt->iface = ia->iface; } if (rt->iface == NULL) { errno = ESRCH; return -1; } if_finishrt6(rt); return 0; } #endif static void if_rtm(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) { const struct sockaddr *sa; /* Ignore messages generated by us */ if (rtm->rtm_pid == getpid()) { ctx->options &= ~DHCPCD_RTM_PPID; return; } /* Ignore messages sent by the parent after forking */ if ((ctx->options & (DHCPCD_RTM_PPID | DHCPCD_DAEMONISED)) == (DHCPCD_RTM_PPID | DHCPCD_DAEMONISED) && rtm->rtm_pid == ctx->ppid) { /* If this is the last successful message sent, * clear the check flag as it's possible another * process could re-use the same pid and also * manipulate therouting table. */ if (rtm->rtm_seq == ctx->pseq) ctx->options &= ~DHCPCD_RTM_PPID; return; } sa = (const void *)(rtm + 1); switch (sa->sa_family) { #ifdef INET case AF_INET: { struct rt rt; if (if_copyrt(ctx, &rt, rtm) == 0) ipv4_handlert(ctx, rtm->rtm_type, &rt, 0); break; } #endif #ifdef INET6 case AF_INET6: { struct rt6 rt6; if (~rtm->rtm_addrs & (RTA_DST | RTA_GATEWAY)) break; /* * BSD announces host routes. * But does this work on Solaris? * As such, we should be notified of reachability by its * existance with a hardware address. */ if (rtm->rtm_flags & (RTF_HOST)) { const struct sockaddr *rti_info[RTAX_MAX]; struct in6_addr dst6; struct sockaddr_dl sdl; get_addrs(rtm->rtm_addrs, sa, rti_info); COPYOUT6(dst6, rti_info[RTAX_DST]); if (rti_info[RTAX_GATEWAY]->sa_family == AF_LINK) memcpy(&sdl, rti_info[RTAX_GATEWAY], sizeof(sdl)); else sdl.sdl_alen = 0; ipv6nd_neighbour(ctx, &dst6, rtm->rtm_type != RTM_DELETE && sdl.sdl_alen ? IPV6ND_REACHABLE : 0); break; } if (if_copyrt6(ctx, &rt6, rtm) == 0) ipv6_handlert(ctx, rtm->rtm_type, &rt6); break; } #endif } } static void if_ifa(struct dhcpcd_ctx *ctx, const struct ifa_msghdr *ifam) { struct interface *ifp; const struct sockaddr *sa, *rti_info[RTAX_MAX]; /* XXX We have no way of knowing who generated these * messages wich truely sucks because we want to * avoid listening to our own delete messages. */ if ((ifp = if_findindex(ctx->ifaces, ifam->ifam_index)) == NULL) return; sa = (const void *)(ifam + 1); get_addrs(ifam->ifam_addrs, sa, rti_info); if (rti_info[RTAX_IFA] == NULL) return; switch (rti_info[RTAX_IFA]->sa_family) { case AF_LINK: { struct sockaddr_dl sdl; if (ifam->ifam_type != RTM_CHGADDR && ifam->ifam_type != RTM_NEWADDR) break; memcpy(&sdl, rti_info[RTAX_IFA], sizeof(sdl)); dhcpcd_handlehwaddr(ctx, ifp->name, CLLADDR(&sdl),sdl.sdl_alen); break; } #ifdef INET case AF_INET: { struct in_addr addr, mask, bcast; COPYOUT(addr, rti_info[RTAX_IFA]); COPYOUT(mask, rti_info[RTAX_NETMASK]); COPYOUT(bcast, rti_info[RTAX_BRD]); ipv4_handleifa(ctx, ifam->ifam_type == RTM_CHGADDR ? RTM_NEWADDR : ifam->ifam_type, NULL, ifp->name, &addr, &mask, &bcast); break; } #endif #ifdef INET6 case AF_INET6: { struct in6_addr addr6, mask6; const struct sockaddr_in6 *sin6; sin6 = (const void *)rti_info[RTAX_IFA]; addr6 = sin6->sin6_addr; sin6 = (const void *)rti_info[RTAX_NETMASK]; mask6 = sin6->sin6_addr; ipv6_handleifa(ctx, ifam->ifam_type == RTM_CHGADDR ? RTM_NEWADDR : ifam->ifam_type, NULL, ifp->name, &addr6, ipv6_prefixlen(&mask6)); break; } #endif } } static void if_ifinfo(struct dhcpcd_ctx *ctx, const struct if_msghdr *ifm) { struct interface *ifp; int state; if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL) return; if (ifm->ifm_flags & IFF_OFFLINE || !(ifm->ifm_flags & IFF_UP)) state = LINK_DOWN; else state = LINK_UP; dhcpcd_handlecarrier(ctx, state, (unsigned int)ifm->ifm_flags, ifp->name); } static void if_dispatch(struct dhcpcd_ctx *ctx, const struct rt_msghdr *rtm) { if (rtm->rtm_version != RTM_VERSION) return; switch(rtm->rtm_type) { case RTM_IFINFO: if_ifinfo(ctx, (const void *)rtm); break; case RTM_ADD: /* FALLTHROUGH */ case RTM_CHANGE: /* FALLTHROUGH */ case RTM_DELETE: if_rtm(ctx, (const void *)rtm); break; case RTM_CHGADDR: /* FALLTHROUGH */ case RTM_DELADDR: /* FALLTHROUGH */ case RTM_NEWADDR: if_ifa(ctx, (const void *)rtm); break; } } int if_handlelink(struct dhcpcd_ctx *ctx) { struct msghdr msg; ssize_t len; memset(&msg, 0, sizeof(msg)); msg.msg_iov = ctx->iov; msg.msg_iovlen = 1; if ((len = recvmsg_realloc(ctx->link_fd, &msg, 0)) == -1) return -1; if (len != 0) if_dispatch(ctx, ctx->iov[0].iov_base); return 0; } static void if_octetstr(char *buf, const Octet_t *o, ssize_t len) { int i; char *p; p = buf; for (i = 0; i < o->o_length; i++) { if ((p + 1) - buf < len) *p++ = o->o_bytes[i]; else break; } *p = '\0'; } static int if_parsert(struct dhcpcd_ctx *ctx, unsigned int level, unsigned int name, int (*walkrt)(struct dhcpcd_ctx *, char *, size_t)) { int s, retval, code, flags; uintptr_t buf[512 / sizeof(uintptr_t)]; struct strbuf ctlbuf, databuf; struct T_optmgmt_req *tor = (struct T_optmgmt_req *)buf; struct T_optmgmt_ack *toa = (struct T_optmgmt_ack *)buf; struct T_error_ack *tea = (struct T_error_ack *)buf; struct opthdr *req; if ((s = open("/dev/arp", O_RDWR)) == -1) return -1; /* Assume we are erroring. */ retval = -1; tor->PRIM_type = T_SVR4_OPTMGMT_REQ; tor->OPT_offset = sizeof (struct T_optmgmt_req); tor->OPT_length = sizeof (struct opthdr); tor->MGMT_flags = T_CURRENT; req = (struct opthdr *)&tor[1]; req->level = EXPER_IP_AND_ALL_IRES; req->name = 0; req->len = 1; ctlbuf.buf = (char *)buf; ctlbuf.len = tor->OPT_length + tor->OPT_offset; if (putmsg(s, &ctlbuf, NULL, 0) == 1) goto out; req = (struct opthdr *)&toa[1]; ctlbuf.maxlen = sizeof(buf); /* Create a reasonable buffer to start with */ databuf.maxlen = BUFSIZ * 2; if ((databuf.buf = malloc(databuf.maxlen)) == NULL) goto out; for (;;) { flags = 0; if ((code = getmsg(s, &ctlbuf, 0, &flags)) == -1) break; if (code == 0 && toa->PRIM_type == T_OPTMGMT_ACK && toa->MGMT_flags == T_SUCCESS && (size_t)ctlbuf.len >= sizeof(struct T_optmgmt_ack)) { /* End of messages, so return success! */ retval = 0; break; } if (tea->PRIM_type == T_ERROR_ACK) { errno = tea->TLI_error == TSYSERR ? tea->UNIX_error : EPROTO; break; } if (code != MOREDATA || toa->PRIM_type != T_OPTMGMT_ACK || toa->MGMT_flags != T_SUCCESS) { errno = ENOMSG; break; } /* Try to ensure out buffer is big enough * for future messages as well. */ if ((size_t)databuf.maxlen < req->len) { size_t newlen; free(databuf.buf); newlen = roundup(req->len, BUFSIZ); if ((databuf.buf = malloc(newlen)) == NULL) break; databuf.maxlen = newlen; } flags = 0; if (getmsg(s, NULL, &databuf, &flags) == -1) break; /* We always have to get the data before moving onto * the next item, so don't move this test higher up * to avoid the buffer allocation and getmsg calls. */ if (req->level == level && req->name == name) { if (walkrt(ctx, databuf.buf, req->len) == -1) break; } } free(databuf.buf); out: close(s); return retval; } static int if_addaddr(int fd, const char *ifname, struct sockaddr_storage *addr, struct sockaddr_storage *mask) { struct lifreq lifr; memset(&lifr, 0, sizeof(lifr)); strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); /* First assign the netmask. */ lifr.lifr_addr = *mask; if (ioctl(fd, SIOCSLIFNETMASK, &lifr) == -1) return -1; /* Then assign the address. */ lifr.lifr_addr = *addr; if (ioctl(fd, SIOCSLIFADDR, &lifr) == -1) return -1; /* Now bring it up. */ if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) return -1; if (!(lifr.lifr_flags & IFF_UP)) { lifr.lifr_flags |= IFF_UP; if (ioctl(fd, SIOCSLIFFLAGS, &lifr) == -1) return -1; } return 0; } static int if_plumblif(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname) { struct lifreq lifr; int s; memset(&lifr, 0, sizeof(lifr)); strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); lifr.lifr_addr.ss_family = af; if (af == AF_INET) s = ctx->pf_inet_fd; else { struct priv *priv; priv = (struct priv *)ctx->priv; s = priv->pf_inet6_fd; } return ioctl(s, cmd == RTM_NEWADDR ? SIOCLIFADDIF : SIOCLIFREMOVEIF, &lifr) == -1 && errno != EEXIST ? -1 : 0; } static int if_plumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname) { dlpi_handle_t dh; int fd, af_fd, mux_fd, retval; struct lifreq lifr; const char *udp_dev; memset(&lifr, 0, sizeof(lifr)); switch (af) { case AF_INET: lifr.lifr_flags = IFF_IPV4; af_fd = ctx->pf_inet_fd; udp_dev = UDP_DEV_NAME; break; case AF_INET6: { struct priv *priv; /* We will take care of setting the link local address. */ lifr.lifr_flags = IFF_IPV6 | IFF_NOLINKLOCAL; priv = (struct priv *)ctx->priv; af_fd = priv->pf_inet6_fd; udp_dev = UDP6_DEV_NAME; break; } default: errno = EPROTONOSUPPORT; return -1; } if (dlpi_open(ifname, &dh, DLPI_NOATTACH) != DLPI_SUCCESS) { errno = EINVAL; return -1; } fd = dlpi_fd(dh); retval = -1; mux_fd = -1; if (ioctl(fd, I_PUSH, IP_MOD_NAME) == -1) goto out; strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1) goto out; /* Get full flags. */ if (ioctl(af_fd, SIOCGLIFFLAGS, &lifr) == -1) goto out; /* Open UDP as a multiplexor to PLINK the interface stream. * UDP is used because STREAMS will not let you PLINK a driver * under itself and IP is generally at the bottom of the stream. */ if ((mux_fd = open(udp_dev, O_RDWR)) == -1) goto out; /* POP off all undesired modules. */ while (ioctl(mux_fd, I_POP, 0) != -1) ; if (errno != EINVAL) goto out; if (lifr.lifr_flags & IFF_IPV4 && !(lifr.lifr_flags & IFF_NOARP)) { if (ioctl(mux_fd, I_PUSH, ARP_MOD_NAME) == -1) goto out; } /* PLINK the interface stream so it persists. */ if (ioctl(mux_fd, I_PLINK, fd) == -1) goto out; retval = 0; out: dlpi_close(dh); if (mux_fd != -1) close(mux_fd); return retval; } static int if_unplumbif(const struct dhcpcd_ctx *ctx, int af, const char *ifname) { struct sockaddr_storage addr, mask; /* For the time being, don't unplumb the interface, just * set the address to zero. */ memset(&addr, 0, sizeof(addr)); addr.ss_family = af; memset(&mask, 0, sizeof(mask)); mask.ss_family = af; return if_addaddr(ctx->pf_inet_fd, ifname , &addr, &mask); } static int if_plumb(int cmd, const struct dhcpcd_ctx *ctx, int af, const char *ifname) { struct if_spec spec; if (if_nametospec(ifname, &spec) == -1) return -1; if (spec.lun != -1) return if_plumblif(cmd, ctx, af, ifname); if (cmd == RTM_NEWADDR) return if_plumbif(ctx, af, ifname); else return if_unplumbif(ctx, af, ifname); } static int if_rtmsg(unsigned char cmd, const struct interface *ifp, int addrs, int flags, const struct sockaddr *dst, const struct sockaddr *mask, const struct sockaddr *gate, const struct sockaddr *src, uint32_t mtu) { struct rtm { struct rt_msghdr hdr; char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX]; } rtm; char *bp = rtm.buffer; size_t l; /* WARNING: Solaris will not allow you to delete RTF_KERNEL routes. * This includes subnet/prefix routes. */ if ((cmd == RTM_ADD || cmd == RTM_DELETE || cmd == RTM_CHANGE) && ifp->ctx->options & DHCPCD_DAEMONISE && !(ifp->ctx->options & DHCPCD_DAEMONISED)) ifp->ctx->options |= DHCPCD_RTM_PPID; #define ADDSA(sa) { \ l = RT_ROUNDUP(salen((sa))); \ memcpy(bp, (sa), l); \ bp += l; \ } memset(&rtm, 0, sizeof(rtm)); rtm.hdr.rtm_version = RTM_VERSION; rtm.hdr.rtm_type = cmd; rtm.hdr.rtm_seq = ++ifp->ctx->seq; rtm.hdr.rtm_flags = flags; rtm.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | addrs; if (cmd == RTM_ADD || cmd == RTM_CHANGE) { rtm.hdr.rtm_flags |= RTF_UP; if (!(rtm.hdr.rtm_flags & RTF_REJECT) || !(rtm.hdr.rtm_flags & RTF_GATEWAY)) rtm.hdr.rtm_addrs |= RTA_IFP; if (mtu != 0) { rtm.hdr.rtm_inits |= RTV_MTU; rtm.hdr.rtm_rmx.rmx_mtu = mtu; } } ADDSA(dst); ADDSA(gate); if (rtm.hdr.rtm_addrs & RTA_NETMASK) ADDSA(mask); if (rtm.hdr.rtm_addrs & RTA_IFP) { struct sockaddr_dl sdl; if_linkaddr(&sdl, ifp); ADDSA((struct sockaddr *)&sdl); } /* This no workie :/ */ #if 0 /* route(1M) says RTA_IFA is accepted but ignored * it's unclear how RTA_SRC is different. */ if (rtm.hdr.rtm_addrs & RTA_IFA) { rtm.hdr.rtm_addrs &= ~RTA_IFA; rtm.hdr.rtm_addrs |= RTA_SRC; } if (rtm.hdr.rtm_addrs & RTA_SRC) ADDSA(src); #endif #undef ADDSA rtm.hdr.rtm_msglen = (unsigned short)(bp - (char *)&rtm); if (write(ifp->ctx->link_fd, &rtm, rtm.hdr.rtm_msglen) == -1) return -1; ifp->ctx->sseq = ifp->ctx->seq; return 0; } static int if_addrflags0(int fd, const char *ifname) { struct lifreq lifr; memset(&lifr, 0, sizeof(lifr)); strlcpy(lifr.lifr_name, ifname, sizeof(lifr.lifr_name)); if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) return -1; return lifr.lifr_flags; } #ifdef INET const char *if_pfname = "DLPI"; static struct dl_if * if_findraw(struct interface *ifp, int fd) { struct dl_if *di; struct priv *priv; priv = (struct priv *)ifp->ctx->priv; TAILQ_FOREACH(di, &priv->dl_ifs, next) { if (di->fd == fd) return di; } errno = ENXIO; return NULL; } void if_closeraw(struct interface *ifp, int fd) { struct dl_if *di; if ((di = if_findraw(ifp, fd)) != NULL) { struct priv *priv; priv = (struct priv *)ifp->ctx->priv; TAILQ_REMOVE(&priv->dl_ifs, di, next); dlpi_close(di->dh); free(di); } } int if_openraw(struct interface *ifp, uint16_t protocol) { dlpi_handle_t dh; struct priv *priv; struct dl_if *di; dlpi_info_t dlinfo; struct packetfilt pf; ushort_t *pfp; struct strioctl sioc; if (dlpi_open(ifp->name, &dh, 0) != DLPI_SUCCESS) return -1; /* We need to register pfmod, which is similar to BPF * so the kernel can filter out the packets we don't need. */ if (dlpi_info(dh, &dlinfo, 0) != DLPI_SUCCESS || dlpi_bind(dh, protocol, NULL) != DLPI_SUCCESS || (di = malloc(sizeof(*di))) == NULL) goto failed1; di->iface = ifp; di->dh = dh; di->fd = dlpi_fd(dh); memcpy(di->broadcast, dlinfo.di_bcastaddr, dlinfo.di_bcastaddrlen); priv = (struct priv *)ifp->ctx->priv; pf.Pf_Priority = 0; pfp = pf.Pf_Filter; /* pfmod operates on 16 bits, so divide offsets by 2. * When working on a 8 bits, mask off the bits not teested. */ switch (protocol) { case ETHERTYPE_IP: /* Filter fragments. */ *pfp++ = ENF_PUSHWORD + (offsetof(struct ip, ip_off) / 2); *pfp++ = ENF_PUSHLIT | ENF_AND; *pfp++ = htons(0x1fff | IP_MF); *pfp++ = ENF_PUSHZERO | ENF_CAND; /* Filter UDP. */ *pfp++ = ENF_PUSHWORD + (offsetof(struct ip, ip_p) / 2); *pfp++ = ENF_PUSHFF00 | ENF_AND; *pfp++ = ENF_PUSHLIT | ENF_CAND; *pfp++ = htons(IPPROTO_UDP); /* Filter BOOTPC. */ *pfp++ = ENF_PUSHWORD + ((sizeof(struct ip) + offsetof(struct udphdr, uh_dport)) / 2); *pfp++ = ENF_PUSHLIT | ENF_CAND; *pfp++ = htons(BOOTPC); break; case ETHERTYPE_ARP: /* We are only interested in IP. */ *pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_hrd) / 2); *pfp++ = ENF_PUSHLIT | ENF_CAND; *pfp++ = htons(ARPHRD_ETHER); /* Must be REQUEST or REPLY. */ *pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_op) / 2); *pfp++ = ENF_PUSHLIT | ENF_CNAND; *pfp++ = htons(ARPOP_REQUEST); *pfp++ = ENF_PUSHWORD + (offsetof(struct arphdr, ar_op) / 2); *pfp++ = ENF_PUSHLIT | ENF_CAND; *pfp++ = htons(ARPOP_REPLY); break; default: errno = EPROTOTYPE; goto failed; } pf.Pf_FilterLen = pfp - pf.Pf_Filter; sioc.ic_cmd = PFIOCSETF; sioc.ic_timout = INFTIM; sioc.ic_len = sizeof(pf); sioc.ic_dp = (void *)&pf; /* Install the filter and then flush the stream. */ if (ioctl(di->fd, I_PUSH, "pfmod") == -1 || ioctl(di->fd, I_STR, &sioc) == -1 || ioctl(di->fd, I_FLUSH, FLUSHR) == -1) goto failed; TAILQ_INSERT_TAIL(&priv->dl_ifs, di, next); return di->fd; failed: free(di); failed1: dlpi_close(dh); return -1; } ssize_t if_sendraw(const struct interface *cifp, int fd, __unused uint16_t protocol, const void *data, size_t len) { struct dl_if *di; int r; struct interface *ifp = UNCONST(cifp); if ((di = if_findraw(ifp, fd)) == NULL) return -1; r = dlpi_send(di->dh, di->broadcast, ifp->hwlen, data, len, NULL); return r == DLPI_SUCCESS ? (ssize_t)len : -1; } ssize_t if_readraw(struct interface *ifp, int fd, void *data, size_t len, int *flags) { struct dl_if *di; int r; size_t mlen; if ((di = if_findraw(ifp, fd)) == NULL) return -1; *flags = RAW_EOF; /* We only ever read one packet. */ mlen = len; *flags = RAW_EOF; /* We only ever read one packet. */ r = dlpi_recv(di->dh, NULL, NULL, data, &mlen, -1, NULL); return r == DLPI_SUCCESS ? (ssize_t)mlen : -1; } int if_address(unsigned char cmd, const struct ipv4_addr *ia) { struct sockaddr_storage ss_addr, ss_mask; struct sockaddr_in *sin_addr, *sin_mask; /* Either remove the alias or ensure it exists. */ if (if_plumb(cmd, ia->iface->ctx, AF_INET, ia->alias) == -1) return -1; if (cmd == RTM_DELADDR) return 0; if (cmd != RTM_NEWADDR) { errno = EINVAL; return -1; } sin_addr = (struct sockaddr_in *)&ss_addr; sin_addr->sin_family = AF_INET; sin_addr->sin_addr = ia->addr; sin_mask = (struct sockaddr_in *)&ss_mask; sin_mask->sin_family = AF_INET; sin_mask->sin_addr = ia->mask; return if_addaddr(ia->iface->ctx->pf_inet_fd, ia->alias, &ss_addr, &ss_mask); } int if_addrflags(const struct ipv4_addr *ia) { int flags, aflags; aflags = if_addrflags0(ia->iface->ctx->pf_inet_fd, ia->alias); if (aflags == -1) return -1; flags = 0; if (aflags & IFF_DUPLICATE) flags |= IN_IFF_DUPLICATED; return flags; } int if_route(unsigned char cmd, const struct rt *rt) { struct sockaddr_in dst = { .sin_family = AF_INET, .sin_addr = rt->dest }; struct sockaddr_in mask = { .sin_family = AF_INET, .sin_addr = rt->mask }; struct sockaddr_in gate = { .sin_family = AF_INET, .sin_addr = rt->gate }; struct sockaddr_in src = { .sin_family = AF_INET, .sin_addr = rt->src }; struct sockaddr_in *g; int addrs, flags; addrs = 0; flags = 0; if (cmd == RTM_ADD || cmd == RTM_CHANGE) { addrs |= RTA_GATEWAY | RTA_IFP; /* Subnet routes are cloning or connected if supported. * All other routes are static. */ if (rt->gate.s_addr != ntohl(INADDR_ANY)) flags |= RTF_STATIC; if (rt->src.s_addr != ntohl(INADDR_ANY)) addrs |= RTA_IFA; } if (rt->mask.s_addr == htonl(INADDR_BROADCAST) && rt->gate.s_addr == htonl(INADDR_ANY)) { flags |= RTF_HOST; } else if (rt->gate.s_addr == htonl(INADDR_LOOPBACK) && rt->mask.s_addr == htonl(INADDR_BROADCAST)) { flags |= RTF_HOST | RTF_GATEWAY; /* Going via lo0 so remove the interface flags */ addrs &= ~(RTA_IFA | RTA_IFP); } else { addrs |= RTA_NETMASK; if (flags & RTF_STATIC) flags |= RTF_GATEWAY; if (rt->mask.s_addr == htonl(INADDR_BROADCAST)) flags |= RTF_HOST; } if ((flags & RTF_HOST && rt->gate.s_addr == htonl(INADDR_ANY)) || !(flags & RTF_STATIC)) g = &src; else g = &gate; return if_rtmsg(cmd, rt->iface, addrs, flags, (struct sockaddr *)&dst, (struct sockaddr *)&mask, (struct sockaddr *)g, (struct sockaddr *)&src, rt->mtu); } static int if_walkrt(struct dhcpcd_ctx *ctx, char *data, size_t len) { mib2_ipRouteEntry_t *re, *e; struct rt rt; char ifname[IF_NAMESIZE]; if (len % sizeof(*re) != 0) { errno = EINVAL; return -1; } re = (mib2_ipRouteEntry_t *)data; e = (mib2_ipRouteEntry_t *)(data + len); do { /* Skip route types we don't want. */ switch (re->ipRouteInfo.re_ire_type) { case IRE_IF_CLONE: case IRE_BROADCAST: case IRE_MULTICAST: case IRE_NOROUTE: case IRE_LOCAL: continue; default: break; } memset(&rt, 0, sizeof(rt)); rt.dest.s_addr = re->ipRouteDest; rt.mask.s_addr = re->ipRouteMask; rt.gate.s_addr = re->ipRouteNextHop; rt.flags = re->ipRouteInfo.re_flags; rt.src.s_addr = re->ipRouteInfo.re_src_addr; rt.mtu = re->ipRouteInfo.re_max_frag; if_octetstr(ifname, &re->ipRouteIfIndex, sizeof(ifname)); rt.iface = if_find(ctx->ifaces, ifname); if (rt.iface != NULL) { if_finishrt(&rt); ipv4_handlert(ctx, RTM_ADD, &rt, 1); } else { char destbuf[INET6_ADDRSTRLEN]; char gatebuf[INET6_ADDRSTRLEN]; const char *dest, *gate; dest = inet_ntop(AF_INET, &rt.dest, destbuf, INET6_ADDRSTRLEN); gate = inet_ntop(AF_INET, &rt.gate, gatebuf, INET6_ADDRSTRLEN); logger(ctx, LOG_ERR, "no iface (%s) for route to %s via %s", ifname, dest, gate); } } while (++re < e); return 0; } int if_initrt(struct dhcpcd_ctx *ctx) { ipv4_freerts(ctx->ipv4_kroutes); return if_parsert(ctx, MIB2_IP,MIB2_IP_ROUTE, if_walkrt); } #endif #ifdef INET6 int if_address6(unsigned char cmd, const struct ipv6_addr *ia) { struct sockaddr_storage ss_addr, ss_mask; struct sockaddr_in6 *sin6_addr, *sin6_mask; struct priv *priv; int r; /* Either remove the alias or ensure it exists. */ if (if_plumb(cmd, ia->iface->ctx, AF_INET6, ia->alias) == -1) return -1; if (cmd == RTM_DELADDR) return 0; if (cmd != RTM_NEWADDR) { errno = EINVAL; return -1; } priv = (struct priv *)ia->iface->ctx->priv; sin6_addr = (struct sockaddr_in6 *)&ss_addr; sin6_addr->sin6_family = AF_INET6; sin6_addr->sin6_addr = ia->addr; sin6_mask = (struct sockaddr_in6 *)&ss_mask; sin6_mask->sin6_family = AF_INET6; ipv6_mask(&sin6_mask->sin6_addr, ia->prefix_len); r = if_addaddr(priv->pf_inet6_fd, ia->alias, &ss_addr, &ss_mask); if (r == -1 && errno == EEXIST) return 0; return r; } int if_addrflags6(const struct ipv6_addr *ia) { struct priv *priv; int aflags, flags; priv = (struct priv *)ia->iface->ctx->priv; aflags = if_addrflags0(priv->pf_inet6_fd, ia->alias); flags = 0; if (aflags & IFF_DUPLICATE) flags |= IN6_IFF_DUPLICATED; return flags; } int if_getlifetime6(struct ipv6_addr *addr) { UNUSED(addr); errno = ENOTSUP; return -1; } int if_route6(unsigned char cmd, const struct rt6 *rt) { struct sockaddr_in6 dst = { .sin6_family = AF_INET6, .sin6_addr = rt->dest }; struct sockaddr_in6 mask = { .sin6_family = AF_INET6, .sin6_addr = rt->mask }; struct sockaddr_in6 gate = { .sin6_family = AF_INET6, .sin6_addr = rt->gate }; struct sockaddr_in6 src = { .sin6_family = AF_INET6, .sin6_addr = rt->src }; struct sockaddr_in6 *g; int addrs, flags; addrs = RTA_NETMASK; flags = 0; if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) { /* XXX FIXME: How to tell kernel route is subnet? */ } else flags |= RTF_GATEWAY | RTF_STATIC; if (IN6_IS_ADDR_UNSPECIFIED(&rt->gate)) g = &src; else g = &gate; return if_rtmsg(cmd, rt->iface, addrs, flags, (struct sockaddr *)&dst, (struct sockaddr *)&mask, (struct sockaddr *)g, (struct sockaddr *)&src, rt->mtu); } static int if_walkrt6(struct dhcpcd_ctx *ctx, char *data, size_t len) { mib2_ipv6RouteEntry_t *re, *e; struct rt6 rt; char ifname[IF_NAMESIZE]; if (len % sizeof(*re) != 0) { errno = EINVAL; return -1; } re = (mib2_ipv6RouteEntry_t *)data; e = (mib2_ipv6RouteEntry_t *)(data + len); do { /* Skip route types we don't want. */ switch (re->ipv6RouteInfo.re_ire_type) { case IRE_IF_CLONE: case IRE_BROADCAST: case IRE_MULTICAST: case IRE_NOROUTE: case IRE_LOCAL: continue; default: break; } memset(&rt, 0, sizeof(rt)); rt.dest = re->ipv6RouteDest; ipv6_mask(&rt.mask, re->ipv6RoutePfxLength); rt.gate = re->ipv6RouteNextHop; rt.mtu = re->ipv6RouteInfo.re_max_frag; if_octetstr(ifname, &re->ipv6RouteIfIndex, sizeof(ifname)); rt.iface = if_find(ctx->ifaces, ifname); if (rt.iface != NULL) { if_finishrt6(&rt); ipv6_handlert(ctx, RTM_ADD, &rt); } else { char destbuf[INET6_ADDRSTRLEN]; char gatebuf[INET6_ADDRSTRLEN]; const char *dest, *gate; dest = inet_ntop(AF_INET6, &rt.dest, destbuf, INET6_ADDRSTRLEN); gate = inet_ntop(AF_INET6, &rt.gate, gatebuf, INET6_ADDRSTRLEN); logger(ctx, LOG_ERR, "no iface (%s) for route to %s via %s", ifname, dest, gate); } } while (++re < e); return 0; } int if_initrt6(struct dhcpcd_ctx *ctx) { ipv6_freerts(&ctx->ipv6->kroutes); return if_parsert(ctx, MIB2_IP6, MIB2_IP6_ROUTE, if_walkrt6); } int if_checkipv6(__unused struct dhcpcd_ctx *ctx, __unused const struct interface *ifp, int __unused own) { return 0; } #endif