Mercurial > hg > dhcpcd
view src/if-linux.c @ 5557:e65d193a1960 draft
Linux: Support wireless IP roaming
This is achieved by checking that the interface is wireless,
IFF_UP and IFF_LOWER_UP are present, but IFF_RUNNING is missing.
This gives exactly the same support as modern NetBSD when carrier
loss is detected, but without the address verifications when the
carrier comes back as that needs to be handled in the kernel.
While IP setup is maintained, other configuration data is discarded.
Note that this should be improved in the future.
Thanks to Boris Krasnovskiy <borkra@gmail.com> for helping with this.
| author | Roy Marples <roy@marples.name> |
|---|---|
| date | Sat, 12 Dec 2020 13:12:26 +0000 |
| parents | 605f7f9a20c0 |
| children | dd1e6f7adadd |
line wrap: on
line source
/* SPDX-License-Identifier: BSD-2-Clause */ /* * Linux interface driver for dhcpcd * Copyright (c) 2006-2020 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 <asm/types.h> /* Needed for 2.4 kernels */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/param.h> #include <sys/stat.h> #include <linux/icmpv6.h> #include <linux/if_addr.h> #include <linux/if_link.h> #include <linux/if_packet.h> #include <linux/if_tun.h> #include <linux/if_vlan.h> #include <linux/filter.h> #include <linux/netlink.h> #include <linux/sockios.h> #include <linux/rtnetlink.h> #include <arpa/inet.h> #include <net/if.h> #include <netinet/in_systm.h> #include <netinet/in.h> #include <net/route.h> /* 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 <netinet/if_ether.h> #else #include <linux/if_arp.h> #endif #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <ctype.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> #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 <linux/genetlink.h> #include <linux/nl80211.h> #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" static const char *mproc = #if defined(__alpha__) "system type" #elif defined(__arm__) || defined(__aarch64__) "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; } 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 */
