/* SPDX-License-Identifier: BSD-2-Clause */ /* * Linux interface driver for dhcpcd * Copyright (c) 2006-2021 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 /* Needed for 2.4 kernels */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* musl has its own definition of struct ethhdr, so only include * netinet/if_ether.h on systems with GLIBC. For the ARPHRD constants, * we must include linux/if_arp.h instead. */ #if defined(__GLIBC__) #include #else #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "bpf.h" #include "common.h" #include "dev.h" #include "dhcp.h" #include "if.h" #include "ipv4.h" #include "ipv4ll.h" #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" #include "privsep.h" #include "route.h" #include "sa.h" #ifdef HAVE_NL80211_H #include #include #else int if_getssid_wext(const char *ifname, uint8_t *ssid); #endif /* Support older kernels */ #ifndef IFLA_WIRELESS #define IFLA_WIRELESS (IFLA_MASTER + 1) #endif /* For some reason, glibc doesn't include newer flags from linux/if.h * However, we cannot include linux/if.h directly as it conflicts * with the glibc version. D'oh! */ #ifndef IFF_LOWER_UP #define IFF_LOWER_UP 0x10000 /* driver signals L1 up */ #endif /* Buggy CentOS and RedHat */ #ifndef SOL_NETLINK #define SOL_NETLINK 270 #endif /* * Someone should fix kernel headers for clang alignment warnings. * But this is unlikely. * https://www.spinics.net/lists/netdev/msg646934.html */ #undef NLA_ALIGNTO #undef NLA_ALIGN #undef NLA_HDRLEN #define NLA_ALIGNTO 4U #define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1)) #define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr))) #undef IFA_RTA #define IFA_RTA(r) ((struct rtattr *)(void *)(((char *)(r)) \ + NLMSG_ALIGN(sizeof(struct ifaddrmsg)))) #undef IFLA_RTA #define IFLA_RTA(r) ((struct rtattr *)(void *)(((char *)(r)) \ + NLMSG_ALIGN(sizeof(struct ifinfomsg)))) #undef NLMSG_NEXT #define NLMSG_NEXT(nlh, len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ (struct nlmsghdr *)(void *)(((char *)(nlh)) \ + NLMSG_ALIGN((nlh)->nlmsg_len))) #undef RTM_RTA #define RTM_RTA(r) (void *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))) #undef RTA_NEXT #define RTA_NEXT(rta, attrlen) ((attrlen) -= RTA_ALIGN((rta)->rta_len), \ (struct rtattr *)(void *)(((char *)(rta)) \ + RTA_ALIGN((rta)->rta_len))) struct priv { int route_fd; int generic_fd; uint32_t route_pid; }; /* We need this to send a broadcast for InfiniBand. * Our old code used sendto, but our new code writes to a raw BPF socket. * What header structure does IPoIB use? */ #if 0 /* Broadcast address for IPoIB */ static const uint8_t ipv4_bcast_addr[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff }; #endif static int if_addressexists(struct interface *, struct in_addr *); #define PROC_INET6 "/proc/net/if_inet6" #define PROC_PROMOTE "/proc/sys/net/ipv4/conf/%s/promote_secondaries" #define SYS_BRIDGE "/sys/class/net/%s/bridge/bridge_id" #define SYS_LAYER2 "/sys/class/net/%s/device/layer2" #define SYS_TUNTAP "/sys/class/net/%s/tun_flags" #if defined(__aarch64__) static const char *mproc = "AArch64"; int if_machinearch(char *str, size_t len) { return snprintf(str, len, "%s", mproc); } #else static const char *mproc = #if defined(__alpha__) "system type" #elif defined(__arm__) "Hardware" #elif defined(__avr32__) "cpu family" #elif defined(__bfin__) "BOARD Name" #elif defined(__cris__) "cpu model" #elif defined(__frv__) "System" #elif defined(__i386__) || defined(__x86_64__) "vendor_id" #elif defined(__ia64__) "vendor" #elif defined(__hppa__) "model" #elif defined(__m68k__) "MMU" #elif defined(__mips__) "system type" #elif defined(__powerpc__) || defined(__powerpc64__) "machine" #elif defined(__s390__) || defined(__s390x__) "Manufacturer" #elif defined(__sh__) "machine" #elif defined(sparc) || defined(__sparc__) "cpu" #elif defined(__vax__) "cpu" #else NULL #endif ; int if_machinearch(char *str, size_t len) { FILE *fp; char buf[256]; if (mproc == NULL) { errno = EINVAL; return -1; } fp = fopen("/proc/cpuinfo", "r"); if (fp == NULL) return -1; while (fscanf(fp, "%255s : ", buf) != EOF) { if (strncmp(buf, mproc, strlen(mproc)) == 0 && fscanf(fp, "%255s", buf) == 1) { fclose(fp); return snprintf(str, len, "%s", buf); } } fclose(fp); errno = ESRCH; return -1; } #endif static int check_proc_int(struct dhcpcd_ctx *ctx, const char *path) { char buf[64]; int error, i; if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1) return -1; i = (int)strtoi(buf, NULL, 0, INT_MIN, INT_MAX, &error); if (error != 0 && error != ENOTSUP) { errno = error; return -1; } return i; } static int check_proc_uint(struct dhcpcd_ctx *ctx, const char *path, unsigned int *u) { char buf[64]; int error; if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1) return -1; *u = (unsigned int)strtou(buf, NULL, 0, 0, UINT_MAX, &error); if (error != 0 && error != ENOTSUP) { errno = error; return error; } return 0; } static ssize_t if_writepathuint(struct dhcpcd_ctx *ctx, const char *path, unsigned int val) { char buf[64]; int len; len = snprintf(buf, sizeof(buf), "%u\n", val); if (len == -1) return -1; return dhcp_writefile(ctx, path, 0664, buf, (size_t)len); } int if_init(struct interface *ifp) { char path[sizeof(PROC_PROMOTE) + IF_NAMESIZE]; int n; /* We enable promote_secondaries so that we can do this * add 192.168.1.2/24 * add 192.168.1.3/24 * del 192.168.1.2/24 * and the subnet mask moves onto 192.168.1.3/24 * This matches the behaviour of BSD which makes coding dhcpcd * a little easier as there's just one behaviour. */ snprintf(path, sizeof(path), PROC_PROMOTE, ifp->name); n = check_proc_int(ifp->ctx, path); if (n == -1) return errno == ENOENT ? 0 : -1; if (n == 1) return 0; return if_writepathuint(ifp->ctx, path, 1) == -1 ? -1 : 0; } int if_conf(struct interface *ifp) { char path[sizeof(SYS_LAYER2) + IF_NAMESIZE]; int n; /* Some qeth setups require the use of the broadcast flag. */ snprintf(path, sizeof(path), SYS_LAYER2, ifp->name); n = check_proc_int(ifp->ctx, path); if (n == -1) return errno == ENOENT ? 0 : -1; if (n == 0) ifp->options->options |= DHCPCD_BROADCAST; return 0; } static bool if_bridge(struct dhcpcd_ctx *ctx, const char *ifname) { char path[sizeof(SYS_BRIDGE) + IF_NAMESIZE], buf[64]; snprintf(path, sizeof(path), SYS_BRIDGE, ifname); if (dhcp_readfile(ctx, path, buf, sizeof(buf)) == -1) return false; return true; } static bool if_tap(struct dhcpcd_ctx *ctx, const char *ifname) { char path[sizeof(SYS_TUNTAP) + IF_NAMESIZE]; unsigned int u; snprintf(path, sizeof(path), SYS_TUNTAP, ifname); if (check_proc_uint(ctx, path, &u) == -1) return false; return u & IFF_TAP; } bool if_ignore(struct dhcpcd_ctx *ctx, const char *ifname) { if (if_tap(ctx, ifname) || if_bridge(ctx, ifname)) return true; return false; } /* XXX work out Virtal Interface Masters */ int if_vimaster(__unused struct dhcpcd_ctx *ctx, __unused const char *ifname) { return 0; } unsigned short if_vlanid(const struct interface *ifp) { struct vlan_ioctl_args v; memset(&v, 0, sizeof(v)); strlcpy(v.device1, ifp->name, sizeof(v.device1)); v.cmd = GET_VLAN_VID_CMD; if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFVLAN, &v) != 0) return 0; /* 0 means no VLANID */ return (unsigned short)v.u.VID; } int if_linksocket(struct sockaddr_nl *nl, int protocol, int flags) { int fd; fd = xsocket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC | flags, protocol); if (fd == -1) return -1; nl->nl_family = AF_NETLINK; if (bind(fd, (struct sockaddr *)nl, sizeof(*nl)) == -1) { close(fd); return -1; } return fd; } char * if_getnetworknamespace(char *buf, size_t len) { struct stat sb_self, sb_netns; DIR *dir; struct dirent *de; char file[PATH_MAX], *bufp = NULL; if (stat("/proc/self/ns/net", &sb_self) == -1) return NULL; dir = opendir("/var/run/netns"); if (dir == NULL) return NULL; while ((de = readdir(dir)) != NULL) { snprintf(file, sizeof(file), "/var/run/netns/%s", de->d_name); if (stat(file, &sb_netns) == -1) continue; if (sb_self.st_dev != sb_netns.st_dev || sb_self.st_ino != sb_netns.st_ino) continue; strlcpy(buf, de->d_name, len); bufp = buf; break; } closedir(dir); return bufp; } int os_init(void) { char netns[PATH_MAX], *p; p = if_getnetworknamespace(netns, sizeof(netns)); if (p != NULL) loginfox("network namespace: %s", p); return 0; } int if_opensockets_os(struct dhcpcd_ctx *ctx) { struct priv *priv; struct sockaddr_nl snl; socklen_t len; #ifdef NETLINK_BROADCAST_ERROR int on = 1; #endif /* Open the link socket first so it gets pid() for the socket. * Then open our persistent route socket so we get a unique * pid that doesn't clash with a process id for after we fork. */ memset(&snl, 0, sizeof(snl)); snl.nl_groups = RTMGRP_LINK; #ifdef INET snl.nl_groups |= RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR; #endif #ifdef INET6 snl.nl_groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_NEIGH; #endif ctx->link_fd = if_linksocket(&snl, NETLINK_ROUTE, SOCK_NONBLOCK); if (ctx->link_fd == -1) return -1; #ifdef NETLINK_BROADCAST_ERROR if (setsockopt(ctx->link_fd, SOL_NETLINK, NETLINK_BROADCAST_ERROR, &on, sizeof(on)) == -1) logerr("%s: NETLINK_BROADCAST_ERROR", __func__); #endif if ((priv = calloc(1, sizeof(*priv))) == NULL) return -1; ctx->priv = priv; memset(&snl, 0, sizeof(snl)); priv->route_fd = if_linksocket(&snl, NETLINK_ROUTE, 0); if (priv->route_fd == -1) return -1; len = sizeof(snl); if (getsockname(priv->route_fd, (struct sockaddr *)&snl, &len) == -1) return -1; priv->route_pid = snl.nl_pid; memset(&snl, 0, sizeof(snl)); priv->generic_fd = if_linksocket(&snl, NETLINK_GENERIC, 0); if (priv->generic_fd == -1) return -1; return 0; } void if_closesockets_os(struct dhcpcd_ctx *ctx) { struct priv *priv; if (ctx->priv != NULL) { priv = (struct priv *)ctx->priv; close(priv->route_fd); close(priv->generic_fd); } } int if_setmac(struct interface *ifp, void *mac, uint8_t maclen) { struct ifreq ifr = { .ifr_hwaddr.sa_family = ifp->hwtype, }; if (ifp->hwlen != maclen || maclen > sizeof(ifr.ifr_hwaddr.sa_data)) { errno = EINVAL; return -1; } strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); memcpy(ifr.ifr_hwaddr.sa_data, mac, maclen); return if_ioctl(ifp->ctx, SIOCSIFHWADDR, &ifr, sizeof(ifr)); } int if_carrier(struct interface *ifp, __unused const void *ifadata) { return ifp->flags & IFF_RUNNING ? LINK_UP : LINK_DOWN; } bool if_roaming(struct interface *ifp) { #ifdef IFF_LOWER_UP if (!ifp->wireless || ifp->flags & IFF_RUNNING || (ifp->flags & (IFF_UP | IFF_LOWER_UP)) != (IFF_UP | IFF_LOWER_UP)) return false; return true; #else return false; #endif } int if_getnetlink(struct dhcpcd_ctx *ctx, struct iovec *iov, int fd, int flags, int (*cb)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *cbarg) { struct sockaddr_nl nladdr = { .nl_pid = 0 }; struct msghdr msg = { .msg_name = &nladdr, .msg_namelen = sizeof(nladdr), .msg_iov = iov, .msg_iovlen = 1, }; ssize_t len; struct nlmsghdr *nlm; int r = 0; unsigned int again; bool terminated; recv_again: len = recvmsg(fd, &msg, flags); if (len == -1 || len == 0) return (int)len; /* Check sender */ if (msg.msg_namelen != sizeof(nladdr)) { errno = EINVAL; return -1; } /* Ignore message if it is not from kernel */ if (nladdr.nl_pid != 0) return 0; again = 0; terminated = false; for (nlm = iov->iov_base; nlm && NLMSG_OK(nlm, (size_t)len); nlm = NLMSG_NEXT(nlm, len)) { again = (nlm->nlmsg_flags & NLM_F_MULTI); if (nlm->nlmsg_type == NLMSG_NOOP) continue; if (nlm->nlmsg_type == NLMSG_ERROR) { struct nlmsgerr *err; if (nlm->nlmsg_len - sizeof(*nlm) < sizeof(*err)) { errno = EBADMSG; return -1; } err = (struct nlmsgerr *)NLMSG_DATA(nlm); if (err->error != 0) { errno = -err->error; return -1; } again = 0; terminated = true; break; } if (nlm->nlmsg_type == NLMSG_DONE) { again = 0; terminated = true; break; } if (cb == NULL) continue; if (nlm->nlmsg_seq != (uint32_t)ctx->seq && fd != ctx->link_fd) logwarnx("%s: received sequence %u, expecting %d", __func__, nlm->nlmsg_seq, ctx->seq); else r = cb(ctx, cbarg, nlm); } if ((again || !terminated) && (ctx != NULL && ctx->link_fd != fd)) goto recv_again; return r; } static int if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, struct nlmsghdr *nlm) { size_t len; struct rtmsg *rtm; struct rtattr *rta; unsigned int ifindex; struct sockaddr *sa; len = nlm->nlmsg_len - sizeof(*nlm); if (len < sizeof(*rtm)) { errno = EBADMSG; return -1; } rtm = (struct rtmsg *)NLMSG_DATA(nlm); if (rtm->rtm_table != RT_TABLE_MAIN) return -1; memset(rt, 0, sizeof(*rt)); if (rtm->rtm_type == RTN_UNREACHABLE) rt->rt_flags |= RTF_REJECT; rta = RTM_RTA(rtm); len = RTM_PAYLOAD(nlm); for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { sa = NULL; switch (rta->rta_type) { case RTA_DST: sa = &rt->rt_dest; break; case RTA_GATEWAY: sa = &rt->rt_gateway; break; case RTA_PREFSRC: sa = &rt->rt_ifa; break; case RTA_OIF: ifindex = *(unsigned int *)RTA_DATA(rta); rt->rt_ifp = if_findindex(ctx->ifaces, ifindex); break; case RTA_PRIORITY: rt->rt_metric = *(unsigned int *)RTA_DATA(rta); break; case RTA_METRICS: { struct rtattr *r2; size_t l2; l2 = rta->rta_len; r2 = (struct rtattr *)RTA_DATA(rta); for (; RTA_OK(r2, l2); r2 = RTA_NEXT(r2, l2)) { switch (r2->rta_type) { case RTAX_MTU: rt->rt_mtu = *(unsigned int *)RTA_DATA(r2); break; } } break; } } if (sa != NULL) { socklen_t salen; sa->sa_family = rtm->rtm_family; salen = sa_addrlen(sa); /* sa is a union where sockaddr_in6 is the biggest. */ /* coverity[overrun-buffer-arg] */ memcpy((char *)sa + sa_addroffset(sa), RTA_DATA(rta), MIN(salen, RTA_PAYLOAD(rta))); } } /* If no RTA_DST set the unspecified address for the family. */ if (rt->rt_dest.sa_family == AF_UNSPEC) rt->rt_dest.sa_family = rtm->rtm_family; rt->rt_netmask.sa_family = rtm->rtm_family; sa_fromprefix(&rt->rt_netmask, rtm->rtm_dst_len); if (sa_is_allones(&rt->rt_netmask)) rt->rt_flags |= RTF_HOST; #if 0 if (rt->rtp_ifp == NULL && rt->src.s_addr != INADDR_ANY) { struct ipv4_addr *ap; /* For some reason the default route comes back with the * loopback interface in RTA_OIF? Lets find it by * preferred source address */ if ((ap = ipv4_findaddr(ctx, &rt->src))) rt->iface = ap->iface; } #endif if (rt->rt_ifp == NULL) { errno = ESRCH; return -1; } return 0; } static int link_route(struct dhcpcd_ctx *ctx, __unused struct interface *ifp, struct nlmsghdr *nlm) { size_t len; int cmd; struct priv *priv; struct rt rt; switch (nlm->nlmsg_type) { case RTM_NEWROUTE: cmd = RTM_ADD; break; case RTM_DELROUTE: cmd = RTM_DELETE; break; default: return 0; } len = nlm->nlmsg_len - sizeof(*nlm); if (len < sizeof(struct rtmsg)) { errno = EBADMSG; return -1; } /* Ignore messages we sent. */ #ifdef PRIVSEP if (ctx->ps_root_pid != 0 && nlm->nlmsg_pid == (uint32_t)ctx->ps_root_pid) return 0; #endif priv = (struct priv *)ctx->priv; if (nlm->nlmsg_pid == priv->route_pid) return 0; if (if_copyrt(ctx, &rt, nlm) == 0) rt_recvrt(cmd, &rt, (pid_t)nlm->nlmsg_pid); return 0; } static int link_addr(struct dhcpcd_ctx *ctx, struct interface *ifp, struct nlmsghdr *nlm) { size_t len; struct rtattr *rta; struct ifaddrmsg *ifa; struct priv *priv; #ifdef INET struct in_addr addr, net, brd; int ret; #endif #ifdef INET6 struct in6_addr addr6; int flags; #endif if (nlm->nlmsg_type != RTM_DELADDR && nlm->nlmsg_type != RTM_NEWADDR) return 0; len = nlm->nlmsg_len - sizeof(*nlm); if (len < sizeof(*ifa)) { errno = EBADMSG; return -1; } /* Ignore address deletions from ourself. * We need to process address flag changes though. */ if (nlm->nlmsg_type == RTM_DELADDR) { #ifdef PRIVSEP if (ctx->ps_root_pid != 0 && nlm->nlmsg_pid == (uint32_t)ctx->ps_root_pid) return 0; #endif priv = (struct priv*)ctx->priv; if (nlm->nlmsg_pid == priv->route_pid) return 0; } ifa = NLMSG_DATA(nlm); if ((ifp = if_findindex(ctx->ifaces, ifa->ifa_index)) == NULL) { /* We don't know about the interface the address is for * so it's not really an error */ return 1; } rta = IFA_RTA(ifa); len = NLMSG_PAYLOAD(nlm, sizeof(*ifa)); switch (ifa->ifa_family) { #ifdef INET case AF_INET: addr.s_addr = brd.s_addr = INADDR_ANY; inet_cidrtoaddr(ifa->ifa_prefixlen, &net); for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { switch (rta->rta_type) { case IFA_ADDRESS: if (ifp->flags & IFF_POINTOPOINT) { memcpy(&brd.s_addr, RTA_DATA(rta), sizeof(brd.s_addr)); } break; case IFA_BROADCAST: memcpy(&brd.s_addr, RTA_DATA(rta), sizeof(brd.s_addr)); break; case IFA_LOCAL: memcpy(&addr.s_addr, RTA_DATA(rta), sizeof(addr.s_addr)); break; } } /* Validate RTM_DELADDR really means address deleted * and anything else really means address exists. */ ret = if_addressexists(ifp, &addr); if (ret == -1) { logerr("if_addressexists: %s", inet_ntoa(addr)); break; } else if (ret == 1) { if (nlm->nlmsg_type == RTM_DELADDR) break; } else { if (nlm->nlmsg_type != RTM_DELADDR) break; } ipv4_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name, &addr, &net, &brd, ifa->ifa_flags, (pid_t)nlm->nlmsg_pid); break; #endif #ifdef INET6 case AF_INET6: memset(&addr6, 0, sizeof(addr6)); for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { switch (rta->rta_type) { case IFA_ADDRESS: memcpy(&addr6.s6_addr, RTA_DATA(rta), sizeof(addr6.s6_addr)); break; } } /* Validate RTM_DELADDR really means address deleted * and anything else really means address exists. */ flags = if_addrflags6(ifp, &addr6, NULL); if (nlm->nlmsg_type == RTM_DELADDR) { if (flags != -1) break; } else { if (flags == -1) break; } ipv6_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name, &addr6, ifa->ifa_prefixlen, ifa->ifa_flags, (pid_t)nlm->nlmsg_pid); break; #endif } return 0; } static uint8_t l2addr_len(unsigned short if_type) { switch (if_type) { case ARPHRD_ETHER: /* FALLTHROUGH */ case ARPHRD_IEEE802: /*FALLTHROUGH */ case ARPHRD_IEEE80211: return 6; case ARPHRD_IEEE1394: return 8; case ARPHRD_INFINIBAND: return 20; } /* Impossible */ return 0; } #ifdef INET6 static int link_neigh(struct dhcpcd_ctx *ctx, __unused struct interface *ifp, struct nlmsghdr *nlm) { struct ndmsg *r; struct rtattr *rta; size_t len; if (nlm->nlmsg_type != RTM_NEWNEIGH && nlm->nlmsg_type != RTM_DELNEIGH) return 0; if (nlm->nlmsg_len < sizeof(*r)) return -1; r = NLMSG_DATA(nlm); rta = RTM_RTA(r); len = RTM_PAYLOAD(nlm); if (r->ndm_family == AF_INET6) { bool unreachable; struct in6_addr addr6; unreachable = (nlm->nlmsg_type == RTM_NEWNEIGH && r->ndm_state & NUD_FAILED); memset(&addr6, 0, sizeof(addr6)); for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { switch (rta->rta_type) { case NDA_DST: memcpy(&addr6.s6_addr, RTA_DATA(rta), sizeof(addr6.s6_addr)); break; } } ipv6nd_neighbour(ctx, &addr6, !unreachable); } return 0; } #endif static int link_netlink(struct dhcpcd_ctx *ctx, void *arg, struct nlmsghdr *nlm) { struct interface *ifp = arg; int r; size_t len; struct rtattr *rta, *hwaddr; struct ifinfomsg *ifi; char ifn[IF_NAMESIZE + 1]; r = link_route(ctx, ifp, nlm); if (r != 0) return r; r = link_addr(ctx, ifp, nlm); if (r != 0) return r; #ifdef INET6 r = link_neigh(ctx, ifp, nlm); if (r != 0) return r; #endif if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK) return 0; len = nlm->nlmsg_len - sizeof(*nlm); if ((size_t)len < sizeof(*ifi)) { errno = EBADMSG; return -1; } ifi = NLMSG_DATA(nlm); if (ifi->ifi_flags & IFF_LOOPBACK) return 0; rta = (void *)((char *)ifi + NLMSG_ALIGN(sizeof(*ifi))); len = NLMSG_PAYLOAD(nlm, sizeof(*ifi)); *ifn = '\0'; hwaddr = NULL; for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { switch (rta->rta_type) { case IFLA_WIRELESS: /* Ignore wireless messages */ if (nlm->nlmsg_type == RTM_NEWLINK && ifi->ifi_change == 0) return 0; break; case IFLA_IFNAME: strlcpy(ifn, (char *)RTA_DATA(rta), sizeof(ifn)); break; case IFLA_ADDRESS: hwaddr = rta; break; } } if (nlm->nlmsg_type == RTM_DELLINK) { #ifdef PLUGIN_DEV /* If are listening to a dev manager, let that remove * the interface rather than the kernel. */ if (dev_listening(ctx) < 1) #endif dhcpcd_handleinterface(ctx, -1, ifn); return 0; } /* Virtual interfaces may not get a valid hardware address * at this point. * To trigger a valid hardware address pickup we need to pretend * that that don't exist until they have one. */ if (ifi->ifi_flags & IFF_MASTER && !hwaddr) { dhcpcd_handleinterface(ctx, -1, ifn); return 0; } /* Check for a new interface */ ifp = if_findindex(ctx->ifaces, (unsigned int)ifi->ifi_index); if (ifp == NULL) { #ifdef PLUGIN_DEV /* If are listening to a dev manager, let that announce * the interface rather than the kernel. */ if (dev_listening(ctx) < 1) #endif dhcpcd_handleinterface(ctx, 1, ifn); return 0; } /* Handle interface being renamed */ if (strcmp(ifp->name, ifn) != 0) { dhcpcd_handleinterface(ctx, -1, ifn); dhcpcd_handleinterface(ctx, 1, ifn); return 0; } /* Re-read hardware address and friends */ if (!(ifi->ifi_flags & IFF_UP)) { void *hwa = hwaddr != NULL ? RTA_DATA(hwaddr) : NULL; uint8_t hwl = l2addr_len(ifi->ifi_type); if (hwaddr != NULL && hwaddr->rta_len != RTA_LENGTH(hwl)) hwa = NULL; dhcpcd_handlehwaddr(ifp, ifi->ifi_type, hwa, hwl); } dhcpcd_handlecarrier(ifp, ifi->ifi_flags & IFF_RUNNING ? LINK_UP : LINK_DOWN, ifi->ifi_flags); return 0; } int if_handlelink(struct dhcpcd_ctx *ctx) { unsigned char buf[16 * 1024]; struct iovec iov = { .iov_base = buf, .iov_len = sizeof(buf), }; return if_getnetlink(ctx, &iov, ctx->link_fd, MSG_DONTWAIT, &link_netlink, NULL); } #ifdef PRIVSEP static bool if_netlinkpriv(int protocol, struct nlmsghdr *nlm) { if (protocol != NETLINK_ROUTE) return false; switch(nlm->nlmsg_type) { case RTM_NEWADDR: /* FALLTHROUGH */ case RTM_DELADDR: /* FALLTHROUGH */ case RTM_NEWROUTE: /* FALLTHROUGH */ case RTM_DELROUTE: /* FALLTHROUGH */ case RTM_NEWLINK: return true; default: return false; } } #endif static int if_sendnetlink(struct dhcpcd_ctx *ctx, int protocol, struct nlmsghdr *hdr, int (*cb)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *cbarg) { int s; struct sockaddr_nl snl = { .nl_family = AF_NETLINK }; struct iovec iov = { .iov_base = hdr, .iov_len = hdr->nlmsg_len }; struct msghdr msg = { .msg_name = &snl, .msg_namelen = sizeof(snl), .msg_iov = &iov, .msg_iovlen = 1 }; struct priv *priv = (struct priv *)ctx->priv; unsigned char buf[16 * 1024]; struct iovec riov = { .iov_base = buf, .iov_len = sizeof(buf), }; /* Request a reply */ hdr->nlmsg_flags |= NLM_F_ACK; hdr->nlmsg_seq = (uint32_t)++ctx->seq; if ((unsigned int)ctx->seq > UINT32_MAX) ctx->seq = 0; #ifdef PRIVSEP if (ctx->options & DHCPCD_PRIVSEP && if_netlinkpriv(protocol, hdr)) return (int)ps_root_sendnetlink(ctx, protocol, &msg); #endif switch (protocol) { case NETLINK_ROUTE: s = priv->route_fd; break; case NETLINK_GENERIC: s = priv->generic_fd; #if 0 #ifdef NETLINK_GET_STRICT_CHK if (hdr->nlmsg_type == RTM_GETADDR) { int on = 1; if (setsockopt(s, SOL_NETLINK, NETLINK_GET_STRICT_CHK, &on, sizeof(on)) == -1 && errno != ENOPROTOOPT) logerr("%s: NETLINK_GET_STRICT_CHK", __func__); } #endif #endif break; default: errno = EINVAL; return -1; } if (sendmsg(s, &msg, 0) == -1) return -1; return if_getnetlink(ctx, &riov, s, 0, cb, cbarg); } #define NLMSG_TAIL(nmsg) \ ((struct rtattr *)(((ptrdiff_t)(nmsg))+NLMSG_ALIGN((nmsg)->nlmsg_len))) static int add_attr_l(struct nlmsghdr *n, unsigned short maxlen, unsigned short type, const void *data, unsigned short alen) { unsigned short len = (unsigned short)RTA_LENGTH(alen); struct rtattr *rta; if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { errno = ENOBUFS; return -1; } rta = NLMSG_TAIL(n); rta->rta_type = type; rta->rta_len = len; if (alen) memcpy(RTA_DATA(rta), data, alen); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); return 0; } static int add_attr_8(struct nlmsghdr *n, unsigned short maxlen, unsigned short type, uint8_t data) { return add_attr_l(n, maxlen, type, &data, sizeof(data)); } static int add_attr_32(struct nlmsghdr *n, unsigned short maxlen, unsigned short type, uint32_t data) { unsigned short len = RTA_LENGTH(sizeof(data)); struct rtattr *rta; if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { errno = ENOBUFS; return -1; } rta = NLMSG_TAIL(n); rta->rta_type = type; rta->rta_len = len; memcpy(RTA_DATA(rta), &data, sizeof(data)); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; return 0; } static int rta_add_attr_32(struct rtattr *rta, unsigned short maxlen, unsigned short type, uint32_t data) { unsigned short len = RTA_LENGTH(sizeof(data)); struct rtattr *subrta; if (RTA_ALIGN(rta->rta_len) + len > maxlen) { errno = ENOBUFS; return -1; } subrta = (void *)((char*)rta + RTA_ALIGN(rta->rta_len)); subrta->rta_type = type; subrta->rta_len = len; memcpy(RTA_DATA(subrta), &data, sizeof(data)); rta->rta_len = (unsigned short)(NLMSG_ALIGN(rta->rta_len) + len); return 0; } #ifdef HAVE_NL80211_H static struct nlattr * nla_next(struct nlattr *nla, size_t *rem) { *rem -= (size_t)NLA_ALIGN(nla->nla_len); return (void *)((char *)nla + NLA_ALIGN(nla->nla_len)); } #define NLA_TYPE(nla) ((nla)->nla_type & NLA_TYPE_MASK) #define NLA_LEN(nla) (unsigned int)((nla)->nla_len - NLA_HDRLEN) #define NLA_OK(nla, rem) \ ((rem) >= sizeof(struct nlattr) && \ (nla)->nla_len >= sizeof(struct nlattr) && \ (nla)->nla_len <= rem) #define NLA_DATA(nla) (void *)((char *)(nla) + NLA_HDRLEN) #define NLA_FOR_EACH_ATTR(pos, head, len, rem) \ for (pos = head, rem = len; \ NLA_OK(pos, rem); \ pos = nla_next(pos, &(rem))) struct nlmg { struct nlmsghdr hdr; struct genlmsghdr ghdr; char buffer[64]; }; static int nla_put_32(struct nlmsghdr *n, unsigned short maxlen, unsigned short type, uint32_t data) { unsigned short len; struct nlattr *nla; len = NLA_ALIGN(NLA_HDRLEN + sizeof(data)); if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { errno = ENOBUFS; return -1; } nla = (struct nlattr *)NLMSG_TAIL(n); nla->nla_type = type; nla->nla_len = len; memcpy(NLA_DATA(nla), &data, sizeof(data)); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; return 0; } static int nla_put_string(struct nlmsghdr *n, unsigned short maxlen, unsigned short type, const char *data) { struct nlattr *nla; size_t len, sl; sl = strlen(data) + 1; len = NLA_ALIGN(NLA_HDRLEN + sl); if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { errno = ENOBUFS; return -1; } nla = (struct nlattr *)NLMSG_TAIL(n); nla->nla_type = type; nla->nla_len = (unsigned short)len; memcpy(NLA_DATA(nla), data, sl); n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + (unsigned short)len; return 0; } static int nla_parse(struct nlattr *tb[], struct nlattr *head, size_t len, int maxtype) { struct nlattr *nla; size_t rem; int type; memset(tb, 0, sizeof(*tb) * ((unsigned int)maxtype + 1)); NLA_FOR_EACH_ATTR(nla, head, len, rem) { type = NLA_TYPE(nla); if (type > maxtype) continue; tb[type] = nla; } return 0; } static int genl_parse(struct nlmsghdr *nlm, struct nlattr *tb[], int maxtype) { struct genlmsghdr *ghdr; struct nlattr *head; size_t len; ghdr = NLMSG_DATA(nlm); head = (void *)((char *)ghdr + GENL_HDRLEN); len = nlm->nlmsg_len - GENL_HDRLEN - NLMSG_HDRLEN; return nla_parse(tb, head, len, maxtype); } static int _gnl_getfamily(__unused struct dhcpcd_ctx *ctx, __unused void *arg, struct nlmsghdr *nlm) { struct nlattr *tb[CTRL_ATTR_FAMILY_ID + 1]; uint16_t family; if (genl_parse(nlm, tb, CTRL_ATTR_FAMILY_ID) == -1) return -1; if (tb[CTRL_ATTR_FAMILY_ID] == NULL) { errno = ENOENT; return -1; } memcpy(&family, NLA_DATA(tb[CTRL_ATTR_FAMILY_ID]), sizeof(family)); return (int)family; } static int gnl_getfamily(struct dhcpcd_ctx *ctx, const char *name) { struct nlmg nlm; memset(&nlm, 0, sizeof(nlm)); nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)); nlm.hdr.nlmsg_type = GENL_ID_CTRL; nlm.hdr.nlmsg_flags = NLM_F_REQUEST; nlm.ghdr.cmd = CTRL_CMD_GETFAMILY; nlm.ghdr.version = 1; if (nla_put_string(&nlm.hdr, sizeof(nlm), CTRL_ATTR_FAMILY_NAME, name) == -1) return -1; return if_sendnetlink(ctx, NETLINK_GENERIC, &nlm.hdr, &_gnl_getfamily, NULL); } static int _if_getssid_nl80211(__unused struct dhcpcd_ctx *ctx, void *arg, struct nlmsghdr *nlm) { struct interface *ifp = arg; struct nlattr *tb[NL80211_ATTR_BSS + 1]; struct nlattr *bss[NL80211_BSS_STATUS + 1]; uint32_t status; unsigned char *ie; int ie_len; if (genl_parse(nlm, tb, NL80211_ATTR_BSS) == -1) return 0; if (tb[NL80211_ATTR_BSS] == NULL) return 0; if (nla_parse(bss, NLA_DATA(tb[NL80211_ATTR_BSS]), NLA_LEN(tb[NL80211_ATTR_BSS]), NL80211_BSS_STATUS) == -1) return 0; if (bss[NL80211_BSS_BSSID] == NULL || bss[NL80211_BSS_STATUS] == NULL) return 0; memcpy(&status, NLA_DATA(bss[NL80211_BSS_STATUS]), sizeof(status)); if (status != NL80211_BSS_STATUS_ASSOCIATED) return 0; if (bss[NL80211_BSS_INFORMATION_ELEMENTS] == NULL) return 0; ie = NLA_DATA(bss[NL80211_BSS_INFORMATION_ELEMENTS]); ie_len = (int)NLA_LEN(bss[NL80211_BSS_INFORMATION_ELEMENTS]); /* ie[0] is type, ie[1] is lenth, ie[2..] is data */ while (ie_len >= 2 && ie_len >= ie[1]) { if (ie[0] == 0) { /* SSID */ if (ie[1] > IF_SSIDLEN) { errno = ENOBUFS; return -1; } ifp->ssid_len = ie[1]; memcpy(ifp->ssid, ie + 2, ifp->ssid_len); return (int)ifp->ssid_len; } ie_len -= ie[1] + 2; ie += ie[1] + 2; } return 0; } static int if_getssid_nl80211(struct interface *ifp) { int family; struct nlmg nlm; errno = 0; family = gnl_getfamily(ifp->ctx, "nl80211"); if (family == -1) return -1; /* Is this a wireless interface? */ memset(&nlm, 0, sizeof(nlm)); nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)); nlm.hdr.nlmsg_type = (unsigned short)family; nlm.hdr.nlmsg_flags = NLM_F_REQUEST; nlm.ghdr.cmd = NL80211_CMD_GET_WIPHY; nla_put_32(&nlm.hdr, sizeof(nlm), NL80211_ATTR_IFINDEX, ifp->index); if (if_sendnetlink(ifp->ctx, NETLINK_GENERIC, &nlm.hdr, NULL, NULL) == -1) return -1; /* We need to parse out the list of scan results and find the one * we are connected to. */ memset(&nlm, 0, sizeof(nlm)); nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)); nlm.hdr.nlmsg_type = (unsigned short)family; nlm.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; nlm.ghdr.cmd = NL80211_CMD_GET_SCAN; nla_put_32(&nlm.hdr, sizeof(nlm), NL80211_ATTR_IFINDEX, ifp->index); return if_sendnetlink(ifp->ctx, NETLINK_GENERIC, &nlm.hdr, &_if_getssid_nl80211, ifp); } #endif int if_getssid(struct interface *ifp) { int r; #ifdef HAVE_NL80211_H r = if_getssid_nl80211(ifp); if (r == -1) ifp->ssid_len = 0; #else r = if_getssid_wext(ifp->name, ifp->ssid); if (r != -1) ifp->ssid_len = (unsigned int)r; #endif ifp->ssid[ifp->ssid_len] = '\0'; return r; } struct nlma { struct nlmsghdr hdr; struct ifaddrmsg ifa; char buffer[64]; }; #ifdef INET struct ifiaddr { unsigned int ifa_ifindex; struct in_addr ifa_addr; bool ifa_found; }; static int _if_addressexists(__unused struct dhcpcd_ctx *ctx, void *arg, struct nlmsghdr *nlm) { struct ifiaddr *ia = arg; in_addr_t this_addr; size_t len; struct rtattr *rta; struct ifaddrmsg *ifa; ifa = NLMSG_DATA(nlm); if (ifa->ifa_index != ia->ifa_ifindex || ifa->ifa_family != AF_INET) return 0; rta = IFA_RTA(ifa); len = NLMSG_PAYLOAD(nlm, sizeof(*ifa)); for (; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { switch (rta->rta_type) { case IFA_LOCAL: memcpy(&this_addr, RTA_DATA(rta), sizeof(this_addr)); if (this_addr == ia->ifa_addr.s_addr) { ia->ifa_found = true; return 1; } break; } } return 0; } static int if_addressexists(struct interface *ifp, struct in_addr *addr) { struct ifiaddr ia = { .ifa_ifindex = ifp->index, .ifa_addr = *addr, .ifa_found = false, }; struct nlma nlm = { .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)), .hdr.nlmsg_type = RTM_GETADDR, .hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH, .ifa.ifa_family = AF_INET, .ifa.ifa_index = ifp->index, }; int error = if_sendnetlink(ifp->ctx, NETLINK_ROUTE, &nlm.hdr, &_if_addressexists, &ia); if (error == -1) return -1; return ia.ifa_found ? 1 : 0; } #endif struct nlmr { struct nlmsghdr hdr; struct rtmsg rt; char buffer[256]; }; int if_route(unsigned char cmd, const struct rt *rt) { struct nlmr nlm; bool gateway_unspec; memset(&nlm, 0, sizeof(nlm)); nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); switch (cmd) { case RTM_CHANGE: nlm.hdr.nlmsg_type = RTM_NEWROUTE; nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_REPLACE; break; case RTM_ADD: nlm.hdr.nlmsg_type = RTM_NEWROUTE; nlm.hdr.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL; break; case RTM_DELETE: nlm.hdr.nlmsg_type = RTM_DELROUTE; break; } nlm.hdr.nlmsg_flags |= NLM_F_REQUEST; nlm.rt.rtm_family = (unsigned char)rt->rt_dest.sa_family; nlm.rt.rtm_table = RT_TABLE_MAIN; gateway_unspec = sa_is_unspecified(&rt->rt_gateway); if (cmd == RTM_DELETE) { nlm.rt.rtm_scope = RT_SCOPE_NOWHERE; } else { /* Address generated routes are RTPROT_KERNEL, * otherwise RTPROT_BOOT */ #ifdef RTPROT_RA if (rt->rt_dflags & RTDF_RA) nlm.rt.rtm_protocol = RTPROT_RA; else #endif #ifdef RTPROT_DHCP if (rt->rt_dflags & RTDF_DHCP) nlm.rt.rtm_protocol = RTPROT_DHCP; else #endif if (rt->rt_dflags & RTDF_IFA_ROUTE) nlm.rt.rtm_protocol = RTPROT_KERNEL; else nlm.rt.rtm_protocol = RTPROT_BOOT; if (rt->rt_ifp->flags & IFF_LOOPBACK) nlm.rt.rtm_scope = RT_SCOPE_HOST; else if (gateway_unspec) nlm.rt.rtm_scope = RT_SCOPE_LINK; else nlm.rt.rtm_scope = RT_SCOPE_UNIVERSE; if (rt->rt_flags & RTF_REJECT) nlm.rt.rtm_type = RTN_UNREACHABLE; else nlm.rt.rtm_type = RTN_UNICAST; } #define ADDSA(type, sa) \ add_attr_l(&nlm.hdr, sizeof(nlm), (type), \ (const char *)(sa) + sa_addroffset((sa)), \ (unsigned short)sa_addrlen((sa))); nlm.rt.rtm_dst_len = (unsigned char)sa_toprefix(&rt->rt_netmask); /* rt->rt_dest and rt->gateway are unions where sockaddr_in6 * is the biggest member. However, we access them as the * generic sockaddr and coverity thinks this will overrun. */ /* coverity[overrun-buffer-arg] */ ADDSA(RTA_DST, &rt->rt_dest); if (cmd == RTM_ADD || cmd == RTM_CHANGE) { if (!gateway_unspec) { /* coverity[overrun-buffer-arg] */ ADDSA(RTA_GATEWAY, &rt->rt_gateway); } /* Cannot add tentative source addresses. * We don't know this here, so just skip INET6 ifa's.*/ if (!sa_is_unspecified(&rt->rt_ifa) && rt->rt_ifa.sa_family != AF_INET6) ADDSA(RTA_PREFSRC, &rt->rt_ifa); if (rt->rt_mtu) { char metricsbuf[32]; struct rtattr *metrics = (void *)metricsbuf; metrics->rta_type = RTA_METRICS; metrics->rta_len = RTA_LENGTH(0); rta_add_attr_32(metrics, sizeof(metricsbuf), RTAX_MTU, rt->rt_mtu); add_attr_l(&nlm.hdr, sizeof(nlm), RTA_METRICS, RTA_DATA(metrics), (unsigned short)RTA_PAYLOAD(metrics)); } #ifdef HAVE_ROUTE_PREF if (rt->rt_dflags & RTDF_RA) { uint8_t pref; switch(rt->rt_pref) { case RTPREF_LOW: pref = ICMPV6_ROUTER_PREF_LOW; break; case RTPREF_MEDIUM: pref = ICMPV6_ROUTER_PREF_MEDIUM; break; case RTPREF_HIGH: pref = ICMPV6_ROUTER_PREF_HIGH; break; default: pref = ICMPV6_ROUTER_PREF_INVALID; break; } add_attr_8(&nlm.hdr, sizeof(nlm), RTA_PREF, pref); } #endif } if (!sa_is_loopback(&rt->rt_gateway)) add_attr_32(&nlm.hdr, sizeof(nlm), RTA_OIF, rt->rt_ifp->index); if (rt->rt_metric != 0) add_attr_32(&nlm.hdr, sizeof(nlm), RTA_PRIORITY, rt->rt_metric); return if_sendnetlink(rt->rt_ifp->ctx, NETLINK_ROUTE, &nlm.hdr, NULL, NULL); } static int _if_initrt(struct dhcpcd_ctx *ctx, void *arg, struct nlmsghdr *nlm) { struct rt rt, *rtn; rb_tree_t *kroutes = arg; if (if_copyrt(ctx, &rt, nlm) != 0) return 0; if ((rtn = rt_new(rt.rt_ifp)) == NULL) { logerr(__func__); return 0; } memcpy(rtn, &rt, sizeof(*rtn)); if (rb_tree_insert_node(kroutes, rtn) != rtn) rt_free(rtn); return 0; } int if_initrt(struct dhcpcd_ctx *ctx, rb_tree_t *kroutes, int af) { struct nlmr nlm = { .hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)), .hdr.nlmsg_type = RTM_GETROUTE, .hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH, .rt.rtm_table = RT_TABLE_MAIN, .rt.rtm_family = (unsigned char)af, }; return if_sendnetlink(ctx, NETLINK_ROUTE, &nlm.hdr, &_if_initrt, kroutes); } #ifdef INET /* Linux is a special snowflake when it comes to BPF. */ const char *bpf_name = "Packet Socket"; /* Linux is a special snowflake for opening BPF. */ struct bpf * bpf_open(const struct interface *ifp, int (*filter)(const struct bpf *, const struct in_addr *), const struct in_addr *ia) { struct bpf *bpf; union sockunion { struct sockaddr sa; struct sockaddr_ll sll; struct sockaddr_storage ss; } su = { .sll = { .sll_family = PF_PACKET, .sll_protocol = htons(ETH_P_ALL), .sll_ifindex = (int)ifp->index, } }; #ifdef PACKET_AUXDATA int n; #endif bpf = calloc(1, sizeof(*bpf)); if (bpf == NULL) return NULL; bpf->bpf_ifp = ifp; /* Allocate a suitably large buffer for a single packet. */ bpf->bpf_size = ETH_DATA_LEN; bpf->bpf_buffer = malloc(bpf->bpf_size); if (bpf->bpf_buffer == NULL) goto eexit; bpf->bpf_fd = xsocket(PF_PACKET, SOCK_RAW|SOCK_CXNB,htons(ETH_P_ALL)); if (bpf->bpf_fd == -1) goto eexit; /* We cannot validate the correct interface, * so we MUST set this first. */ if (bind(bpf->bpf_fd, &su.sa, sizeof(su.sll)) == -1) goto eexit; if (filter(bpf, ia) != 0) goto eexit; /* In the ideal world, this would be set before the bind and filter. */ #ifdef PACKET_AUXDATA n = 1; if (setsockopt(bpf->bpf_fd, SOL_PACKET, PACKET_AUXDATA, &n, sizeof(n)) != 0) { if (errno != ENOPROTOOPT) goto eexit; } #endif /* * At this point we could have received packets for the wrong * interface or which don't pass the filter. * Linux should flush upon setting the filter like every other OS. * There is no way of flushing them from userland. * As such, consumers need to inspect each packet to ensure it's valid. * Or to put it another way, don't trust the Linux BPF filter. */ return bpf; eexit: if (bpf->bpf_fd != -1) close(bpf->bpf_fd); free(bpf->bpf_buffer); free(bpf); return NULL; } /* BPF requires that we read the entire buffer. * So we pass the buffer in the API so we can loop on >1 packet. */ ssize_t bpf_read(struct bpf *bpf, void *data, size_t len) { ssize_t bytes; struct iovec iov = { .iov_base = bpf->bpf_buffer, .iov_len = bpf->bpf_size, }; struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 }; #ifdef PACKET_AUXDATA union { struct cmsghdr hdr; uint8_t buf[CMSG_SPACE(sizeof(struct tpacket_auxdata))]; } cmsgbuf = { .buf = { 0 } }; struct cmsghdr *cmsg; struct tpacket_auxdata *aux; #endif #ifdef PACKET_AUXDATA msg.msg_control = cmsgbuf.buf; msg.msg_controllen = sizeof(cmsgbuf.buf); #endif bytes = recvmsg(bpf->bpf_fd, &msg, 0); if (bytes == -1) return -1; bpf->bpf_flags |= BPF_EOF; /* We only ever read one packet. */ bpf->bpf_flags &= ~BPF_PARTIALCSUM; if (bytes) { if (bpf_frame_bcast(bpf->bpf_ifp, bpf->bpf_buffer) == 0) bpf->bpf_flags |= BPF_BCAST; else bpf->bpf_flags &= ~BPF_BCAST; if ((size_t)bytes > len) bytes = (ssize_t)len; memcpy(data, bpf->bpf_buffer, (size_t)bytes); #ifdef PACKET_AUXDATA for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_PACKET && cmsg->cmsg_type == PACKET_AUXDATA) { aux = (void *)CMSG_DATA(cmsg); if (aux->tp_status & TP_STATUS_CSUMNOTREADY) bpf->bpf_flags |= BPF_PARTIALCSUM; } } #endif } return bytes; } int bpf_attach(int s, void *filter, unsigned int filter_len) { struct sock_fprog pf = { .filter = filter, .len = (unsigned short)filter_len, }; /* Install the filter. */ if (setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &pf, sizeof(pf)) == -1) return -1; #ifdef SO_LOCK_FILTER int on = 1; if (setsockopt(s, SOL_SOCKET, SO_LOCK_FILTER, &on, sizeof(on)) == -1) return -1; #endif return 0; } int if_address(unsigned char cmd, const struct ipv4_addr *ia) { struct nlma nlm; struct ifa_cacheinfo cinfo; int retval = 0; #ifdef IFA_F_NOPREFIXROUTE uint32_t flags = 0; #endif memset(&nlm, 0, sizeof(nlm)); nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); nlm.hdr.nlmsg_flags = NLM_F_REQUEST; nlm.hdr.nlmsg_type = cmd; if (cmd == RTM_NEWADDR) nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; nlm.ifa.ifa_index = ia->iface->index; nlm.ifa.ifa_family = AF_INET; nlm.ifa.ifa_prefixlen = inet_ntocidr(ia->mask); #if 0 /* This creates the aliased interface */ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL, ia->iface->alias, (unsigned short)(strlen(ia->iface->alias) + 1)); #endif add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL, &ia->addr.s_addr, sizeof(ia->addr.s_addr)); if (cmd == RTM_NEWADDR) { #ifdef IFA_F_NOPREFIXROUTE if (nlm.ifa.ifa_prefixlen < 32) flags |= IFA_F_NOPREFIXROUTE; add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags); #endif add_attr_l(&nlm.hdr, sizeof(nlm), IFA_BROADCAST, &ia->brd.s_addr, sizeof(ia->brd.s_addr)); memset(&cinfo, 0, sizeof(cinfo)); cinfo.ifa_prefered = ia->pltime; cinfo.ifa_valid = ia->vltime; add_attr_l(&nlm.hdr, sizeof(nlm), IFA_CACHEINFO, &cinfo, sizeof(cinfo)); } if (if_sendnetlink(ia->iface->ctx, NETLINK_ROUTE, &nlm.hdr, NULL, NULL) == -1) retval = -1; return retval; } int if_addrflags(__unused const struct interface *ifp, __unused const struct in_addr *addr, __unused const char *alias) { /* Linux has no support for IPv4 address flags */ return 0; } #endif #ifdef INET6 int if_address6(unsigned char cmd, const struct ipv6_addr *ia) { struct nlma nlm; struct ifa_cacheinfo cinfo; #if defined(IFA_F_MANAGETEMPADDR) || defined(IFA_F_NOPREFIXROUTE) uint32_t flags = 0; #endif memset(&nlm, 0, sizeof(nlm)); nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); nlm.hdr.nlmsg_flags = NLM_F_REQUEST; nlm.hdr.nlmsg_type = cmd; if (cmd == RTM_NEWADDR) nlm.hdr.nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; nlm.ifa.ifa_index = ia->iface->index; nlm.ifa.ifa_family = AF_INET6; /* Add as /128 if no IFA_F_NOPREFIXROUTE ? */ nlm.ifa.ifa_prefixlen = ia->prefix_len; #if 0 /* This creates the aliased interface */ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL, ia->iface->alias, (unsigned short)(strlen(ia->iface->alias) + 1)); #endif add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LOCAL, &ia->addr.s6_addr, sizeof(ia->addr.s6_addr)); if (cmd == RTM_NEWADDR) { #ifdef IPV6_MANAGETEMPADDR if (ia->flags & IPV6_AF_TEMPORARY) { /* Currently the kernel filters out these flags */ #ifdef IFA_F_NOPREFIXROUTE flags |= IFA_F_TEMPORARY; #else nlm.ifa.ifa_flags |= IFA_F_TEMPORARY; #endif } #elif IFA_F_MANAGETEMPADDR if (ia->flags & IPV6_AF_AUTOCONF && IA6_CANAUTOCONF(ia)) flags |= IFA_F_MANAGETEMPADDR; #endif #ifdef IFA_F_NOPREFIXROUTE if (!IN6_IS_ADDR_LINKLOCAL(&ia->addr)) flags |= IFA_F_NOPREFIXROUTE; #endif #if defined(IFA_F_MANAGETEMPADDR) || defined(IFA_F_NOPREFIXROUTE) add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags); #endif memset(&cinfo, 0, sizeof(cinfo)); cinfo.ifa_prefered = ia->prefix_pltime; cinfo.ifa_valid = ia->prefix_vltime; add_attr_l(&nlm.hdr, sizeof(nlm), IFA_CACHEINFO, &cinfo, sizeof(cinfo)); } return if_sendnetlink(ia->iface->ctx, NETLINK_ROUTE, &nlm.hdr, NULL, NULL); } int if_addrflags6(const struct interface *ifp, const struct in6_addr *addr, __unused const char *alias) { char buf[PS_BUFLEN], *bp = buf, *line; ssize_t buflen; char *p, ifaddress[33], address[33], name[IF_NAMESIZE + 1]; unsigned int ifindex; int prefix, scope, flags, i; buflen = dhcp_readfile(ifp->ctx, PROC_INET6, buf, sizeof(buf)); if (buflen == -1) return -1; if ((size_t)buflen == sizeof(buf)) { errno = ENOBUFS; return -1; } p = ifaddress; for (i = 0; i < (int)sizeof(addr->s6_addr); i++) { p += snprintf(p, 3, "%.2x", addr->s6_addr[i]); } *p = '\0'; while ((line = get_line(&bp, &buflen)) != NULL) { if (sscanf(line, "%32[a-f0-9] %x %x %x %x %"TOSTRING(IF_NAMESIZE)"s\n", address, &ifindex, &prefix, &scope, &flags, name) != 6 || strlen(address) != 32) { errno = EINVAL; return -1; } if (strcmp(name, ifp->name) == 0 && strcmp(ifaddress, address) == 0) return flags; } errno = ESRCH; return -1; } int if_getlifetime6(__unused struct ipv6_addr *ia) { /* God knows how to work out address lifetimes on Linux */ errno = ENOTSUP; return -1; } struct nlml { struct nlmsghdr hdr; struct ifinfomsg i; char buffer[32]; }; #ifdef HAVE_IN6_ADDR_GEN_MODE_NONE static struct rtattr * add_attr_nest(struct nlmsghdr *n, unsigned short maxlen, unsigned short type) { struct rtattr *nest; nest = NLMSG_TAIL(n); add_attr_l(n, maxlen, type, NULL, 0); return nest; } static void add_attr_nest_end(struct nlmsghdr *n, struct rtattr *nest) { nest->rta_len = (unsigned short)((char *)NLMSG_TAIL(n) - (char *)nest); } #endif static int if_disable_autolinklocal(struct dhcpcd_ctx *ctx, unsigned int ifindex) { #ifdef HAVE_IN6_ADDR_GEN_MODE_NONE struct nlml nlm; struct rtattr *afs, *afs6; memset(&nlm, 0, sizeof(nlm)); nlm.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); nlm.hdr.nlmsg_type = RTM_NEWLINK; nlm.hdr.nlmsg_flags = NLM_F_REQUEST; nlm.i.ifi_family = AF_INET6; nlm.i.ifi_index = (int)ifindex; afs = add_attr_nest(&nlm.hdr, sizeof(nlm), IFLA_AF_SPEC); afs6 = add_attr_nest(&nlm.hdr, sizeof(nlm), AF_INET6); add_attr_8(&nlm.hdr, sizeof(nlm), IFLA_INET6_ADDR_GEN_MODE, IN6_ADDR_GEN_MODE_NONE); add_attr_nest_end(&nlm.hdr, afs6); add_attr_nest_end(&nlm.hdr, afs); return if_sendnetlink(ctx, NETLINK_ROUTE, &nlm.hdr, NULL, NULL); #else UNUSED(ctx); UNUSED(ifindex); errno = ENOTSUP; return -1; #endif } static const char *p_conf = "/proc/sys/net/ipv6/conf"; static const char *p_neigh = "/proc/sys/net/ipv6/neigh"; void if_setup_inet6(const struct interface *ifp) { struct dhcpcd_ctx *ctx = ifp->ctx; int ra; char path[256]; /* The kernel cannot make stable private addresses. * However, a lot of distros ship newer kernel headers than * the kernel itself so sweep that error under the table. */ if (if_disable_autolinklocal(ctx, ifp->index) == -1 && errno != ENODEV && errno != ENOTSUP && errno != EINVAL) logdebug("%s: if_disable_autolinklocal", ifp->name); /* * If not doing autoconf, don't disable the kernel from doing it. * If we need to, we should have another option actively disable it. */ if (!(ifp->options->options & DHCPCD_IPV6RS)) return; snprintf(path, sizeof(path), "%s/%s/autoconf", p_conf, ifp->name); ra = check_proc_int(ctx, path); if (ra != 1 && ra != -1) { if (if_writepathuint(ctx, path, 0) == -1) logerr("%s: %s", __func__, path); } snprintf(path, sizeof(path), "%s/%s/accept_ra", p_conf, ifp->name); ra = check_proc_int(ctx, path); if (ra == -1) { /* The sysctl probably doesn't exist, but this isn't an * error as such so just log it and continue */ if (errno != ENOENT) logerr("%s: %s", __func__, path); } else if (ra != 0) { if (if_writepathuint(ctx, path, 0) == -1) logerr("%s: %s", __func__, path); } } int if_applyra(const struct ra *rap) { char path[256]; const char *ifname = rap->iface->name; struct dhcpcd_ctx *ctx = rap->iface->ctx; int error = 0; if (rap->hoplimit != 0) { snprintf(path, sizeof(path), "%s/%s/hop_limit", p_conf, ifname); if (if_writepathuint(ctx, path, rap->hoplimit) == -1) error = -1; } if (rap->retrans != 0) { snprintf(path, sizeof(path), "%s/%s/retrans_time_ms", p_neigh, ifname); if (if_writepathuint(ctx, path, rap->retrans) == -1) error = -1; } if (rap->reachable != 0) { snprintf(path, sizeof(path), "%s/%s/base_reachable_time_ms", p_neigh, ifname); if (if_writepathuint(ctx, path, rap->reachable) == -1) error = -1; } return error; } int ip6_forwarding(const char *ifname) { char path[256], buf[64]; int error, i; if (ifname == NULL) ifname = "all"; snprintf(path, sizeof(path), "%s/%s/forwarding", p_conf, ifname); if (readfile(path, buf, sizeof(buf)) == -1) return 0; i = (int)strtoi(buf, NULL, 0, INT_MIN, INT_MAX, &error); if (error != 0 && error != ENOTSUP) return 0; return i; } #endif /* INET6 */