Index: contrib/dhcpcd/LICENSE =================================================================== --- contrib/dhcpcd/LICENSE +++ contrib/dhcpcd/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2006-2019 Roy Marples +Copyright (c) 2006-2021 Roy Marples All rights reserved. Redistribution and use in source and binary forms, with or without Index: contrib/dhcpcd/README.md =================================================================== --- contrib/dhcpcd/README.md +++ contrib/dhcpcd/README.md @@ -22,14 +22,9 @@ See [BUILDING.md](BUILDING.md) for how to build dhcpcd. -If you wish to file a support ticket or help out with development, please -[visit the Development Area](https://dev.marples.name/project/profile/101/) -or join the mailing list below. - ## Configuration -You should read the -[dhcpcd.conf man page](http://roy.marples.name/man/html5/dhcpcd.conf.html) +You should read the dhcpcd.conf man page and put your options into `/etc/dhcpcd.conf`. The default configuration file should work for most people just fine. Here it is, in case you lose it. @@ -78,7 +73,8 @@ slaac private ``` -The [dhcpcd man page](/man/html8/dhcpcd.html) has a lot of the same options and more, which only apply to calling dhcpcd from the command line. +The dhcpcd man page has a lot of the same options and more, +which only apply to calling dhcpcd from the command line. ## Compatibility @@ -89,13 +85,12 @@ dhcpcd-7 defaults the database directory to `/var/db/dhcpcd` instead of `/var/db` and now stores dhcpcd.duid and dhcpcd.secret in there instead of in /etc. -The Makefile `_confinstall` target will attempt to move the files correctly from -the old locations to the new locations. -Of course this won't work if dhcpcd-7 is packaged up, so packagers will need to -install similar logic into their dhcpcd package. + +dhcpcd-9 defaults the run directory to `/var/run/dhcpcd` instead of +`/var/run` and the prefix of dhcpcd has been removed from the files. ## ChangeLog We no longer supply a ChangeLog. However, you're more than welcome to read the -[commit log](http://roy.marples.name/git/dhcpcd.git/log/) and +[commit log](https://roy.marples.name/git/dhcpcd/log) and [archived release announcements](http://roy.marples.name/archives/dhcpcd-discuss/). Index: contrib/dhcpcd/compat/_strtoi.h =================================================================== --- contrib/dhcpcd/compat/_strtoi.h +++ contrib/dhcpcd/compat/_strtoi.h @@ -35,6 +35,9 @@ * NetBSD: src/common/lib/libc/stdlib/_strtoul.h,v 1.7 2013/05/17 12:55:56 joerg Exp */ +#ifndef _STRTOI_H +#define _STRTOI_H + /* * function template for strtoi and strtou * @@ -91,3 +94,4 @@ return im; } +#endif Index: contrib/dhcpcd/compat/pidfile.c =================================================================== --- contrib/dhcpcd/compat/pidfile.c +++ contrib/dhcpcd/compat/pidfile.c @@ -86,7 +86,9 @@ else if (ftruncate(pidfile_fd, 0) == -1) error = errno; else { - (void) unlink(pidfile_path); +#ifndef HAVE_PLEDGE /* Avoid a pledge violating segfault. */ + (void)unlink(pidfile_path); +#endif error = 0; } @@ -208,7 +210,7 @@ goto return_pid; #ifndef O_CLOEXEC if ((opts = fcntl(fd, F_GETFD)) == -1 || - fctnl(fd, F_SETFL, opts | FD_CLOEXEC) == -1) + fcntl(fd, F_SETFL, opts | FD_CLOEXEC) == -1) { int error = errno; Index: contrib/dhcpcd/compat/strtoi.c =================================================================== --- contrib/dhcpcd/compat/strtoi.c +++ contrib/dhcpcd/compat/strtoi.c @@ -1,4 +1,4 @@ -/* $NetBSD: strtoi.c,v 1.2 2015/05/01 14:17:56 christos Exp $ */ +/* $NetBSD: strtoi.c,v 1.3 2019/11/28 12:33:23 roy Exp $ */ /*- * Copyright (c) 2005 The DragonFly Project. All rights reserved. @@ -30,7 +30,7 @@ * NetBSD: src/common/lib/libc/stdlib/strtoul.c,v 1.3 2008/08/20 19:58:34 oster Exp */ -#if HAVE_NBTOOL_CONFIG_H +#if defined(HAVE_NBTOOL_CONFIG_H) && HAVE_NBTOOL_CONFIG_H #include "nbtool_config.h" #endif Index: contrib/dhcpcd/compat/strtou.c =================================================================== --- contrib/dhcpcd/compat/strtou.c +++ contrib/dhcpcd/compat/strtou.c @@ -1,4 +1,4 @@ -/* $NetBSD: strtou.c,v 1.2 2015/05/01 14:17:56 christos Exp $ */ +/* $NetBSD: strtou.c,v 1.3 2019/11/28 12:33:23 roy Exp $ */ /*- * Copyright (c) 2005 The DragonFly Project. All rights reserved. @@ -30,7 +30,7 @@ * NetBSD: src/common/lib/libc/stdlib/strtoul.c,v 1.3 2008/08/20 19:58:34 oster Exp */ -#if HAVE_NBTOOL_CONFIG_H +#if defined(HAVE_NBTOOL_CONFIG_H) && HAVE_NBTOOL_CONFIG_H #include "nbtool_config.h" #endif Index: contrib/dhcpcd/hooks/01-test =================================================================== --- contrib/dhcpcd/hooks/01-test +++ contrib/dhcpcd/hooks/01-test @@ -1,9 +1,37 @@ # Echo the interface flags, reason and message options if [ "$reason" = "TEST" ]; then - set | grep \ - "^\(interface\|pid\|reason\|protocol\|profile\|skip_hooks\)=" | sort - set | grep "^if\(carrier\|flags\|mtu\|wireless\|ssid\)=" | sort - set | grep "^\(new_\|old_\|nd[0-9]*_\)" | sort + # General variables at the top + set | while read line; do + case "$line" in + interface=*|pid=*|reason=*|protocol=*|profile=*|skip_hooks=*) + echo "$line";; + esac + done + # Interface flags + set | while read line; do + case "$line" in + ifcarrier=*|ifflags=*|ifmetric=*|ifmtu=*|ifwireless=*|ifssid=*) + echo "$line";; + esac + done + # Old lease + set | while read line; do + case "$line" in + old_*) echo "$line";; + esac + done + # New lease + set | while read line; do + case "$line" in + new_*) echo "$line";; + esac + done + # Router Advertisements + set | while read line; do + case "$line" in + nd[0-9]*_*) echo "$line";; + esac + done exit 0 fi Index: contrib/dhcpcd/hooks/02-dump =================================================================== --- contrib/dhcpcd/hooks/02-dump +++ /dev/null @@ -1,8 +0,0 @@ -# Just echo our DHCP options we have - -case "$reason" in -DUMP|DUMP6) - set | sed -ne 's/^new_//p' | sort - exit 0 - ;; -esac Index: contrib/dhcpcd/hooks/15-timezone =================================================================== --- contrib/dhcpcd/hooks/15-timezone +++ contrib/dhcpcd/hooks/15-timezone @@ -42,6 +42,6 @@ ;; esac -if $if_up; then +if $if_configured && $if_up; then set_zoneinfo fi Index: contrib/dhcpcd/hooks/20-resolv.conf =================================================================== --- contrib/dhcpcd/hooks/20-resolv.conf +++ contrib/dhcpcd/hooks/20-resolv.conf @@ -7,9 +7,15 @@ # or dnsmasq. This is important as the libc resolver isn't that powerful. resolv_conf_dir="$state_dir/resolv.conf" +nocarrier_roaming_dir="$state_dir/roaming" NL=" " : ${resolvconf:=resolvconf} +if type "$resolvconf" >/dev/null 2>&1; then + have_resolvconf=true +else + have_resolvconf=false +fi build_resolv_conf() { @@ -42,7 +48,7 @@ # Build the nameserver list srvs=$(cd "$resolv_conf_dir"; \ key_get_value "nameserver " ${interfaces}) - for x in $(uniqify ${srvs}); do + for x in $(uniqify $srvs); do servers="${servers}nameserver $x$NL" done fi @@ -152,6 +158,7 @@ fi fi if [ -n "$new_domain_search" ]; then + new_domain_search=$(uniqify $new_domain_search) if valid_domainname_list $new_domain_search; then conf="${conf}search $new_domain_search$NL" elif ! $warn; then @@ -159,10 +166,11 @@ "$new_domain_search" fi fi + new_domain_name_servers=$(uniqify $new_domain_name_servers) for x in ${new_domain_name_servers}; do conf="${conf}nameserver $x$NL" done - if type "$resolvconf" >/dev/null 2>&1; then + if $have_resolvconf; then [ -n "$ifmetric" ] && export IF_METRIC="$ifmetric" printf %s "$conf" | "$resolvconf" -a "$ifname" return $? @@ -178,7 +186,7 @@ remove_resolv_conf() { - if type "$resolvconf" >/dev/null 2>&1; then + if $have_resolvconf; then "$resolvconf" -d "$ifname" -f else if [ -e "$resolv_conf_dir/$ifname" ]; then @@ -196,8 +204,21 @@ ;; esac -if $if_up || [ "$reason" = ROUTERADVERT ]; then - add_resolv_conf -elif $if_down; then - remove_resolv_conf +if $if_configured; then + if $have_resolvconf && [ "$reason" = NOCARRIER_ROAMING ]; then + # avoid calling resolvconf -c on CARRIER unless we roam + mkdir -p "$nocarrier_roaming_dir" + echo " " >"$nocarrier_roaming_dir/$interface" + "$resolvconf" -C "$interface.*" + elif $have_resolvconf && [ "$reason" = CARRIER ]; then + # Not all resolvconf implementations support -c + if [ -e "$nocarrier_roaming_dir/$interface" ]; then + rm -f "$nocarrier_roaming_dir/$interface" + "$resolvconf" -c "$interface.*" + fi + elif $if_up || [ "$reason" = ROUTERADVERT ]; then + add_resolv_conf + elif $if_down; then + remove_resolv_conf + fi fi Index: contrib/dhcpcd/hooks/30-hostname =================================================================== --- contrib/dhcpcd/hooks/30-hostname +++ contrib/dhcpcd/hooks/30-hostname @@ -17,16 +17,16 @@ # If we used to set the hostname, but relinquish control of it, we should # reset to the default value. -: ${hostname_default=localhost} +: ${hostname_default=} # Some systems don't have hostname(1) _hostname() { if [ -z "${1+x}" ]; then - if type hostname >/dev/null 2>&1; then - hostname - elif [ -r /proc/sys/kernel/hostname ]; then + if [ -r /proc/sys/kernel/hostname ]; then read name /dev/null 2>/dev/null; then + hostname elif sysctl kern.hostname >/dev/null 2>&1; then sysctl -n kern.hostname elif sysctl kernel.hostname >/dev/null 2>&1; then @@ -37,48 +37,39 @@ return $? fi - # Always prefer hostname(1) if we have it - if type hostname >/dev/null 2>&1; then - hostname "$1" - elif [ -w /proc/sys/kernel/hostname ]; then + if [ -w /proc/sys/kernel/hostname ]; then echo "$1" >/proc/sys/kernel/hostname + elif [ -n "$1" ] && type hostname >/dev/null 2>&1; then + hostname "$1" elif sysctl kern.hostname >/dev/null 2>&1; then - sysctl -w "kern.hostname=$1" + sysctl -w "kern.hostname=$1" >/dev/null elif sysctl kernel.hostname >/dev/null 2>&1; then - sysctl -w "kernel.hostname=$1" + sysctl -w "kernel.hostname=$1" >/dev/null else - # We know this will fail, but it will now fail - # with an error to stdout + # May fail to set a blank hostname hostname "$1" fi } -set_hostname_vars() +is_default_hostname() { - hfqdn=false - hshort=false - case "$hostname_fqdn" in - [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;; - ""|[Ss][Ee][Rr][Vv][Ee][Rr]) ;; - *) hshort=true;; + case "$1" in + ""|"$hostname_default"|localhost|localhost.localdomain) + return 0;; esac + return 1 } need_hostname() { # Always load the hostname variable for future use hostname="$(_hostname)" - case "$hostname" in - ""|"(none)"|localhost|localhost.localdomain|"$hostname_default") - return 0;; - esac + is_default_hostname "$hostname" && return 0 case "$force_hostname" in [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) return 0;; esac - set_hostname_vars - if [ -n "$old_fqdn" ]; then if ${hfqdn} || ! ${hshort}; then [ "$hostname" = "$old_fqdn" ] @@ -119,9 +110,15 @@ set_hostname() { - need_hostname || return + hfqdn=false + hshort=false + case "$hostname_fqdn" in + [Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]|1) hfqdn=true;; + ""|[Ss][Ee][Rr][Vv][Ee][Rr]) ;; + *) hshort=true;; + esac - set_hostname_vars + need_hostname || return if [ -n "$new_fqdn" ]; then if ${hfqdn} || ! ${hshort}; then @@ -143,7 +140,7 @@ else try_hostname "$new_host_name" fi - elif [ -n "${hostname_default+x}" ]; then + elif ! is_default_hostname "$hostname"; then try_hostname "$hostname_default" fi } @@ -156,6 +153,6 @@ ;; esac -if $if_up; then +if $if_configured && $if_up && [ "$reason" != ROUTERADVERT ]; then set_hostname fi Index: contrib/dhcpcd/hooks/50-ntp.conf =================================================================== --- contrib/dhcpcd/hooks/50-ntp.conf +++ contrib/dhcpcd/hooks/50-ntp.conf @@ -41,7 +41,7 @@ esac fi -# Debian has a seperate file for DHCP config to avoid stamping on +# Debian has a separate file for DHCP config to avoid stamping on # the master. if [ "$ntp_service" = ntpd ] && type invoke-rc.d >/dev/null 2>&1; then [ -e /var/lib/ntp ] || mkdir /var/lib/ntp @@ -135,8 +135,10 @@ ;; esac -if $if_up; then - add_ntp_conf -elif $if_down; then - remove_ntp_conf +if $if_configured; then + if $if_up; then + add_ntp_conf + elif $if_down; then + remove_ntp_conf + fi fi Index: contrib/dhcpcd/hooks/50-ypbind =================================================================== --- contrib/dhcpcd/hooks/50-ypbind +++ contrib/dhcpcd/hooks/50-ypbind @@ -4,9 +4,8 @@ : ${ypbind_restart_cmd:=service_command ypbind restart} : ${ypbind_stop_cmd:=service_condcommand ypbind stop} ypbind_dir="$state_dir/ypbind" -: ${ypdomain_dir:=@YPDOMAIN_DIR@} -: ${ypdomain_suffix:=@YPDOMAIN_SUFFIX@} - +: ${ypdomain_dir:=} +: ${ypdomain_suffix:=} best_domain() { @@ -68,7 +67,9 @@ fi } -if [ "$reason" = PREINIT ]; then +if ! $if_configured; then + ; +elif [ "$reason" = PREINIT ]; then rm -f "$ypbind_dir/$interface".* elif $if_up || $if_down; then if [ -n "$new_nis_domain" ]; then Index: contrib/dhcpcd/hooks/dhcpcd-run-hooks =================================================================== --- contrib/dhcpcd/hooks/dhcpcd-run-hooks +++ contrib/dhcpcd/hooks/dhcpcd-run-hooks @@ -8,7 +8,7 @@ signature="$signature_base $from $ifname" signature_base_end="# End of dhcpcd" signature_end="$signature_base_end $from $ifname" -state_dir=@RUNDIR@/dhcpcd +state_dir=/var/run/dhcpcd/hook-state _detected_init=false : ${if_up:=false} @@ -211,13 +211,13 @@ } # With the advent of alternative init systems, it's possible to have -# more than one installed. So we need to try and guess what one we're -# using unless overriden by configure. +# more than one installed. So we need to try to guess what one we're +# using unless overridden by configure. detect_init() { - _service_exists="@SERVICEEXISTS@" - _service_cmd="@SERVICECMD@" - _service_status="@SERVICESTATUS@" + _service_exists="" + _service_cmd="" + _service_status="" [ -n "$_service_cmd" ] && return 0 @@ -229,7 +229,7 @@ # Detect the running init system. # As systemd and OpenRC can be installed on top of legacy init # systems we try to detect them first. - status="@STATUSARG@" + status="onestatus" : ${status:=status} if [ -x /bin/systemctl ] && [ -S /run/systemd/private ]; then _service_exists="/bin/systemctl --quiet is-enabled \$1.service" @@ -334,9 +334,9 @@ # Thus, the user can create their dhcpcd.enter/exit-hook script to configure # /etc/resolv.conf how they want and stop the system scripts ever updating it. for hook in \ - @SYSCONFDIR@/dhcpcd.enter-hook \ - @HOOKDIR@/* \ - @SYSCONFDIR@/dhcpcd.exit-hook + /etc/dhcpcd.enter-hook \ + /libexec/dhcpcd-hooks/* \ + /etc/dhcpcd.exit-hook do for skip in $skip_hooks; do case "$hook" in Index: contrib/dhcpcd/hooks/dhcpcd-run-hooks.8 =================================================================== --- contrib/dhcpcd/hooks/dhcpcd-run-hooks.8 +++ contrib/dhcpcd/hooks/dhcpcd-run-hooks.8 @@ -1,4 +1,4 @@ -.\" Copyright (c) 2006-2018 Roy Marples +.\" Copyright (c) 2006-2021 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd February 20, 2018 +.Dd December 27, 2020 .Dt DHCPCD-RUN-HOOKS 8 .Os .Sh NAME @@ -34,11 +34,11 @@ .Xr dhcpcd 8 to run any system and user defined hook scripts. System hook scripts are found in -.Pa @HOOKDIR@ +.Pa /libexec/dhcpcd-hooks and the user defined hooks are -.Pa @SYSCONFDIR@/dhcpcd.enter-hook . +.Pa /etc/dhcpcd.enter-hook . and -.Pa @SYSCONFDIR@/dhcpcd.exit-hook . +.Pa /etc/dhcpcd.exit-hook . The default install supplies hook scripts for configuring .Pa /etc/resolv.conf and the hostname. @@ -50,7 +50,7 @@ The hooks scripts are loaded into the current shell rather than executed in their own process. This allows each hook script, such as -.Pa @SYSCONFDIR@/dhcpcd.enter-hook +.Pa /etc/dhcpcd.enter-hook to customise environment variables or provide alternative functions to hooks further down the chain. As such, using the shell builtins @@ -69,6 +69,7 @@ is run on and .Ev $reason is to the reason why +q .Nm was invoked. DHCP information to be configured is held in variables starting with the word @@ -91,8 +92,11 @@ .It Dv NOCARRIER dhcpcd lost the carrier. The cable may have been unplugged or association to the wireless point lost. +.It Dv NOCARRIER_ROAMING +dhcpcd lost the carrier but the interface configuration is persisted. +The OS has to support wireless roaming or IP Persistance for this to happen. .It Dv INFORM | Dv INFORM6 -dhcpcd informed a DHCP server about it's address and obtained other +dhcpcd informed a DHCP server about its address and obtained other configuration details. .It Dv BOUND | Dv BOUND6 dhcpcd obtained a new lease from a DHCP server. @@ -133,8 +137,6 @@ This normally happens when dhcpcd does not support the raw interface, which means it cannot work as a DHCP or ZeroConf client. Static configuration and DHCP INFORM is still allowed. -.It Dv DUMP -dhcpcd has been asked to dump the last lease for the interface. .It Dv TEST dhcpcd received an OFFER from a DHCP server but will not configure the interface. @@ -144,9 +146,7 @@ .Sh ENVIRONMENT .Nm dhcpcd will clear the environment variables aside from -.Ev $PATH -and -.Ev $RC_SVCNAME . +.Ev $PATH . The following variables will then be set, along with any protocol supplied ones. .Bl -tag -width xnew_delegated_dhcp6_prefix @@ -192,12 +192,14 @@ .Ev interface is up, otherwise .Dv false . +This is more than IFF_UP and may not be equal. .It Ev $if_down .Dv true if the .Ev interface is down, otherwise .Dv false . +This is more than IFF_UP and may not be equal. .It Ev $af_waiting Address family waiting for, as defined in .Xr dhcpcd.conf 5 . @@ -211,11 +213,11 @@ When .Nm runs, it loads -.Pa @SYSCONFDIR@/dhcpcd.enter-hook +.Pa /etc/dhcpcd.enter-hook and any scripts found in -.Pa @HOOKDIR@ +.Pa /libexec/dhcpcd-hooks in a lexical order and then finally -.Pa @SYSCONFDIR@/dhcpcd.exit-hook +.Pa /etc/dhcpcd.exit-hook .Sh SEE ALSO .Xr dhcpcd 8 .Sh AUTHORS Index: contrib/dhcpcd/src/arp.h =================================================================== --- contrib/dhcpcd/src/arp.h +++ contrib/dhcpcd/src/arp.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -41,12 +41,14 @@ #define RATE_LIMIT_INTERVAL 60 #define DEFEND_INTERVAL 10 +#include "bpf.h" #include "dhcpcd.h" #include "if.h" #ifdef IN_IFF_DUPLICATED /* NetBSD gained RFC 5227 support in the kernel. - * This means dhcpcd doesn't need ARP except for ARPing support. */ + * This means dhcpcd doesn't need ARP except for ARPing support + * and ARP announcing an address. */ #if defined(__NetBSD_Version__) && __NetBSD_Version__ >= 799003900 #define KERNEL_RFC5227 #endif @@ -54,32 +56,34 @@ struct arp_msg { uint16_t op; - unsigned char sha[HWADDR_LEN]; + uint8_t sha[HWADDR_LEN]; struct in_addr sip; - unsigned char tha[HWADDR_LEN]; + uint8_t tha[HWADDR_LEN]; struct in_addr tip; + /* Frame header and sender to diagnose failures */ + uint8_t fsha[HWADDR_LEN]; + uint8_t ftha[HWADDR_LEN]; }; struct arp_state { TAILQ_ENTRY(arp_state) next; struct interface *iface; + struct in_addr addr; + struct bpf *bpf; + + int probes; + int claims; + struct timespec defend; void (*found_cb)(struct arp_state *, const struct arp_msg *); void (*not_found_cb)(struct arp_state *); void (*announced_cb)(struct arp_state *); void (*defend_failed_cb)(struct arp_state *); void (*free_cb)(struct arp_state *); - - struct in_addr addr; - int probes; - int claims; - struct timespec defend; }; TAILQ_HEAD(arp_statehead, arp_state); struct iarp_state { - int bpf_fd; - unsigned int bpf_flags; struct arp_statehead arp_states; }; @@ -89,12 +93,12 @@ ((const struct iarp_state *)(ifp)->if_data[IF_DATA_ARP]) #ifdef ARP +void arp_packet(struct interface *, uint8_t *, size_t, unsigned int); struct arp_state *arp_new(struct interface *, const struct in_addr *); void arp_probe(struct arp_state *); -void arp_announce(struct arp_state *); -void arp_announceaddr(struct dhcpcd_ctx *, const struct in_addr *); -void arp_ifannounceaddr(struct interface *, const struct in_addr *); -void arp_cancel(struct arp_state *); +struct arp_state *arp_announceaddr(struct dhcpcd_ctx *, const struct in_addr *); +struct arp_state *arp_ifannounceaddr(struct interface *, const struct in_addr *); +struct arp_state * arp_find(struct interface *, const struct in_addr *); void arp_free(struct arp_state *); void arp_freeaddr(struct interface *, const struct in_addr *); void arp_drop(struct interface *); Index: contrib/dhcpcd/src/arp.c =================================================================== --- contrib/dhcpcd/src/arp.c +++ contrib/dhcpcd/src/arp.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - ARP handler - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ #include #include -#define ELOOP_QUEUE 5 +#define ELOOP_QUEUE ELOOP_ARP #include "config.h" #include "arp.h" #include "bpf.h" @@ -53,10 +53,12 @@ #include "if-options.h" #include "ipv4ll.h" #include "logerr.h" +#include "privsep.h" #if defined(ARP) -#define ARP_LEN \ - (sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN)) +#define ARP_LEN \ + (FRAMEHDRLEN_MAX + \ + sizeof(struct arphdr) + (2 * sizeof(uint32_t)) + (2 * HWADDR_LEN)) /* ARP debugging can be quite noisy. Enable this for more noise! */ //#define ARP_DEBUG @@ -65,16 +67,17 @@ __CTASSERT(sizeof(struct arphdr) == 8); static ssize_t -arp_request(const struct interface *ifp, - const struct in_addr *sip, const struct in_addr *tip) +arp_request(const struct arp_state *astate, + const struct in_addr *sip) { + const struct interface *ifp = astate->iface; + const struct in_addr *tip = &astate->addr; uint8_t arp_buffer[ARP_LEN]; struct arphdr ar; size_t len; uint8_t *p; - const struct iarp_state *state; - ar.ar_hrd = htons(ifp->family); + ar.ar_hrd = htons(ifp->hwtype); ar.ar_pro = htons(ETHERTYPE_IP); ar.ar_hln = ifp->hwlen; ar.ar_pln = sizeof(tip->s_addr); @@ -103,8 +106,13 @@ ZERO(ifp->hwlen); APPEND(&tip->s_addr, sizeof(tip->s_addr)); - state = ARP_CSTATE(ifp); - return bpf_send(ifp, state->bpf_fd, ETHERTYPE_ARP, arp_buffer, len); +#ifdef PRIVSEP + if (ifp->ctx->options & DHCPCD_PRIVSEP) + return ps_bpf_sendarp(ifp, tip, arp_buffer, len); +#endif + /* Note that well formed ethernet will add extra padding + * to ensure that the packet is at least 60 bytes (64 including FCS). */ + return bpf_send(astate->bpf, ETHERTYPE_ARP, arp_buffer, len); eexit: errno = ENOBUFS; @@ -115,7 +123,8 @@ arp_report_conflicted(const struct arp_state *astate, const struct arp_msg *amsg) { - char buf[HWADDR_LEN * 3]; + char abuf[HWADDR_LEN * 3]; + char fbuf[HWADDR_LEN * 3]; if (amsg == NULL) { logerrx("%s: DAD detected %s", @@ -123,9 +132,16 @@ return; } - logerrx("%s: hardware address %s claims %s", - astate->iface->name, - hwaddr_ntoa(amsg->sha, astate->iface->hwlen, buf, sizeof(buf)), + hwaddr_ntoa(amsg->sha, astate->iface->hwlen, abuf, sizeof(abuf)); + if (bpf_frame_header_len(astate->iface) == 0) { + logwarnx("%s: %s claims %s", + astate->iface->name, abuf, inet_ntoa(astate->addr)); + return; + } + + logwarnx("%s: %s(%s) claims %s", + astate->iface->name, abuf, + hwaddr_ntoa(amsg->fsha, astate->iface->hwlen, fbuf, sizeof(fbuf)), inet_ntoa(astate->addr)); } @@ -135,7 +151,7 @@ struct interface *ifp; struct ipv4_addr *ia; #ifndef KERNEL_RFC5227 - struct timespec now, defend; + struct timespec now; #endif arp_report_conflicted(astate, amsg); @@ -158,13 +174,12 @@ * messages. * If another conflict happens within DEFEND_INTERVAL * then we must drop our address and negotiate a new one. */ - defend.tv_sec = astate->defend.tv_sec + DEFEND_INTERVAL; - defend.tv_nsec = astate->defend.tv_nsec; clock_gettime(CLOCK_MONOTONIC, &now); - if (timespeccmp(&defend, &now, >)) + if (timespecisset(&astate->defend) && + eloop_timespec_diff(&now, &astate->defend, NULL) < DEFEND_INTERVAL) logwarnx("%s: %d second defence failed for %s", ifp->name, DEFEND_INTERVAL, inet_ntoa(astate->addr)); - else if (arp_request(ifp, &astate->addr, &astate->addr) == -1) + else if (arp_request(astate, &astate->addr) == -1) logerr(__func__); else { logdebugx("%s: defended address %s", @@ -182,8 +197,8 @@ arp_validate(const struct interface *ifp, struct arphdr *arp) { - /* Families must match */ - if (arp->ar_hrd != htons(ifp->family)) + /* Address type must match */ + if (arp->ar_hrd != htons(ifp->hwtype)) return false; /* Protocol must be IP. */ @@ -206,10 +221,11 @@ return true; } - -static void -arp_packet(struct interface *ifp, uint8_t *data, size_t len) +void +arp_packet(struct interface *ifp, uint8_t *data, size_t len, + unsigned int bpf_flags) { + size_t fl = bpf_frame_header_len(ifp), falen; const struct interface *ifn; struct arphdr ar; struct arp_msg arm; @@ -217,6 +233,21 @@ struct arp_state *astate, *astaten; uint8_t *hw_s, *hw_t; + /* Copy the frame header source and destination out */ + memset(&arm, 0, sizeof(arm)); + if (fl != 0) { + hw_s = bpf_frame_header_src(ifp, data, &falen); + if (hw_s != NULL && falen <= sizeof(arm.fsha)) + memcpy(arm.fsha, hw_s, falen); + hw_t = bpf_frame_header_dst(ifp, data, &falen); + if (hw_t != NULL && falen <= sizeof(arm.ftha)) + memcpy(arm.ftha, hw_t, falen); + + /* Skip past the frame header */ + data += fl; + len -= fl; + } + /* We must have a full ARP header */ if (len < sizeof(ar)) return; @@ -256,96 +287,45 @@ /* Match the ARP probe to our states. * Ignore Unicast Poll, RFC1122. */ state = ARP_CSTATE(ifp); + if (state == NULL) + return; TAILQ_FOREACH_SAFE(astate, &state->arp_states, next, astaten) { if (IN_ARE_ADDR_EQUAL(&arm.sip, &astate->addr) || (IN_IS_ADDR_UNSPECIFIED(&arm.sip) && IN_ARE_ADDR_EQUAL(&arm.tip, &astate->addr) && - state->bpf_flags & BPF_BCAST)) + bpf_flags & BPF_BCAST)) arp_found(astate, &arm); } } -static void -arp_close(struct interface *ifp) -{ - struct iarp_state *state; - - if ((state = ARP_STATE(ifp)) == NULL || state->bpf_fd == -1) - return; - - eloop_event_delete(ifp->ctx->eloop, state->bpf_fd); - bpf_close(ifp, state->bpf_fd); - state->bpf_fd = -1; - state->bpf_flags |= BPF_EOF; -} - -static void -arp_tryfree(struct interface *ifp) -{ - struct iarp_state *state = ARP_STATE(ifp); - - /* If there are no more ARP states, close the socket. */ - if (TAILQ_FIRST(&state->arp_states) == NULL) { - arp_close(ifp); - if (state->bpf_flags & BPF_READING) - state->bpf_flags |= BPF_EOF; - else { - free(state); - ifp->if_data[IF_DATA_ARP] = NULL; - } - } else { - if (bpf_arp(ifp, state->bpf_fd) == -1) - logerr(__func__); - } -} - static void arp_read(void *arg) { - struct interface *ifp = arg; - struct iarp_state *state; + struct arp_state *astate = arg; + struct bpf *bpf = astate->bpf; + struct interface *ifp = astate->iface; uint8_t buf[ARP_LEN]; ssize_t bytes; + struct in_addr addr = astate->addr; /* Some RAW mechanisms are generic file descriptors, not sockets. * This means we have no kernel call to just get one packet, * so we have to process the entire buffer. */ - state = ARP_STATE(ifp); - state->bpf_flags &= ~BPF_EOF; - state->bpf_flags |= BPF_READING; - while (!(state->bpf_flags & BPF_EOF)) { - bytes = bpf_read(ifp, state->bpf_fd, buf, sizeof(buf), - &state->bpf_flags); + bpf->bpf_flags &= ~BPF_EOF; + while (!(bpf->bpf_flags & BPF_EOF)) { + bytes = bpf_read(bpf, buf, sizeof(buf)); if (bytes == -1) { logerr("%s: %s", __func__, ifp->name); - arp_close(ifp); - break; + arp_free(astate); + return; } - arp_packet(ifp, buf, (size_t)bytes); + arp_packet(ifp, buf, (size_t)bytes, bpf->bpf_flags); /* Check we still have a state after processing. */ - if ((state = ARP_STATE(ifp)) == NULL) + if ((astate = arp_find(ifp, &addr)) == NULL) + break; + if ((bpf = astate->bpf) == NULL) break; } - if (state != NULL) { - state->bpf_flags &= ~BPF_READING; - /* Try and free the state if nothing left to do. */ - arp_tryfree(ifp); - } -} - -static int -arp_open(struct interface *ifp) -{ - struct iarp_state *state; - - state = ARP_STATE(ifp); - if (state->bpf_fd == -1) { - state->bpf_fd = bpf_open(ifp, bpf_arp); - if (state->bpf_fd == -1) - return -1; - eloop_event_add(ifp->ctx->eloop, state->bpf_fd, arp_read, ifp); - } - return state->bpf_fd; } static void @@ -362,24 +342,22 @@ { struct arp_state *astate = arg; struct interface *ifp = astate->iface; - struct timespec tv; + unsigned int delay; if (++astate->probes < PROBE_NUM) { - tv.tv_sec = PROBE_MIN; - tv.tv_nsec = (suseconds_t)arc4random_uniform( - (PROBE_MAX - PROBE_MIN) * NSEC_PER_SEC); - timespecnorm(&tv); - eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probe1, astate); + delay = (PROBE_MIN * MSEC_PER_SEC) + + (arc4random_uniform( + (PROBE_MAX - PROBE_MIN) * MSEC_PER_SEC)); + eloop_timeout_add_msec(ifp->ctx->eloop, delay, arp_probe1, astate); } else { - tv.tv_sec = ANNOUNCE_WAIT; - tv.tv_nsec = 0; - eloop_timeout_add_tv(ifp->ctx->eloop, &tv, arp_probed, astate); + delay = ANNOUNCE_WAIT * MSEC_PER_SEC; + eloop_timeout_add_msec(ifp->ctx->eloop, delay, arp_probed, astate); } logdebugx("%s: ARP probing %s (%d of %d), next in %0.1f seconds", ifp->name, inet_ntoa(astate->addr), astate->probes ? astate->probes : PROBE_NUM, PROBE_NUM, - timespec_to_double(&tv)); - if (arp_request(ifp, NULL, &astate->addr) == -1) + (float)delay / MSEC_PER_SEC); + if (arp_request(astate, NULL) == -1) logerr(__func__); } @@ -387,15 +365,6 @@ arp_probe(struct arp_state *astate) { - if (arp_open(astate->iface) == -1) { - logerr(__func__); - return; - } else { - const struct iarp_state *state = ARP_CSTATE(astate->iface); - - if (bpf_arp(astate->iface, state->bpf_fd) == -1) - logerr(__func__); - } astate->probes = 0; logdebugx("%s: probing for %s", astate->iface->name, inet_ntoa(astate->addr)); @@ -403,7 +372,7 @@ } #endif /* ARP */ -static struct arp_state * +struct arp_state * arp_find(struct interface *ifp, const struct in_addr *addr) { struct iarp_state *state; @@ -459,7 +428,7 @@ goto skip_request; #endif - if (arp_request(ifp, &astate->addr, &astate->addr) == -1) + if (arp_request(astate, &astate->addr) == -1) logerr(__func__); #ifndef __linux__ @@ -474,7 +443,7 @@ astate); } -void +static void arp_announce(struct arp_state *astate) { struct iarp_state *state; @@ -482,11 +451,6 @@ struct arp_state *a2; int r; - if (arp_open(astate->iface) == -1) { - logerr(__func__); - return; - } - /* Cancel any other ARP announcements for this address. */ TAILQ_FOREACH(ifp, astate->iface->ctx->ifaces, next) { state = ARP_STATE(ifp); @@ -502,11 +466,13 @@ a2); if (r == -1) logerr(__func__); - else if (r != 0) + else if (r != 0) { logdebugx("%s: ARP announcement " "of %s cancelled", a2->iface->name, inet_ntoa(a2->addr)); + arp_announced(a2); + } } } @@ -514,38 +480,39 @@ arp_announce1(astate); } -void +struct arp_state * arp_ifannounceaddr(struct interface *ifp, const struct in_addr *ia) { struct arp_state *astate; - if (ifp->flags & IFF_NOARP) - return; + if (ifp->flags & IFF_NOARP || !(ifp->options->options & DHCPCD_ARP)) + return NULL; astate = arp_find(ifp, ia); if (astate == NULL) { astate = arp_new(ifp, ia); if (astate == NULL) - return; + return NULL; astate->announced_cb = arp_free; } arp_announce(astate); + return astate; } -void +struct arp_state * arp_announceaddr(struct dhcpcd_ctx *ctx, const struct in_addr *ia) { struct interface *ifp, *iff = NULL; struct ipv4_addr *iap; TAILQ_FOREACH(ifp, ctx->ifaces, next) { - if (!ifp->active || ifp->carrier <= LINK_DOWN) + if (!ifp->active || !if_is_link_up(ifp)) continue; iap = ipv4_iffindaddr(ifp, ia, NULL); if (iap == NULL) continue; #ifdef IN_IFF_NOTUSEABLE - if (!(iap->addr_flags & IN_IFF_NOTUSEABLE)) + if (iap->addr_flags & IN_IFF_NOTUSEABLE) continue; #endif if (iff != NULL && iff->metric < ifp->metric) @@ -553,9 +520,9 @@ iff = ifp; } if (iff == NULL) - return; + return NULL; - arp_ifannounceaddr(iff, ia); + return arp_ifannounceaddr(iff, ia); } struct arp_state * @@ -571,11 +538,9 @@ logerr(__func__); return NULL; } - state->bpf_fd = -1; - state->bpf_flags = 0; TAILQ_INIT(&state->arp_states); } else { - if (addr && (astate = arp_find(ifp, addr))) + if ((astate = arp_find(ifp, addr)) != NULL) return astate; } @@ -584,41 +549,68 @@ return NULL; } astate->iface = ifp; - if (addr) - astate->addr = *addr; - state = ARP_STATE(ifp); - TAILQ_INSERT_TAIL(&state->arp_states, astate, next); - - if (bpf_arp(ifp, state->bpf_fd) == -1) - logerr(__func__); /* try and continue */ + astate->addr = *addr; - return astate; -} +#ifdef PRIVSEP + if (IN_PRIVSEP(ifp->ctx)) { + if (ps_bpf_openarp(ifp, addr) == -1) { + logerr(__func__); + free(astate); + return NULL; + } + } else +#endif + { + astate->bpf = bpf_open(ifp, bpf_arp, addr); + if (astate->bpf == NULL) { + logerr(__func__); + free(astate); + return NULL; + } + eloop_event_add(ifp->ctx->eloop, astate->bpf->bpf_fd, + arp_read, astate); + } -void -arp_cancel(struct arp_state *astate) -{ - eloop_timeout_delete(astate->iface->ctx->eloop, NULL, astate); + state = ARP_STATE(ifp); + TAILQ_INSERT_TAIL(&state->arp_states, astate, next); + return astate; } void arp_free(struct arp_state *astate) { struct interface *ifp; + struct dhcpcd_ctx *ctx; struct iarp_state *state; if (astate == NULL) return; ifp = astate->iface; - eloop_timeout_delete(ifp->ctx->eloop, NULL, astate); + ctx = ifp->ctx; + eloop_timeout_delete(ctx->eloop, NULL, astate); + state = ARP_STATE(ifp); TAILQ_REMOVE(&state->arp_states, astate, next); if (astate->free_cb) astate->free_cb(astate); + +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx) && ps_bpf_closearp(ifp, &astate->addr) == -1) + logerr(__func__); +#endif + if (astate->bpf != NULL) { + eloop_event_delete(ctx->eloop, astate->bpf->bpf_fd); + bpf_close(astate->bpf); + } + free(astate); - arp_tryfree(ifp); + + if (TAILQ_FIRST(&state->arp_states) == NULL) { + free(state); + ifp->if_data[IF_DATA_ARP] = NULL; + } } void @@ -639,6 +631,4 @@ while ((state = ARP_STATE(ifp)) != NULL && (astate = TAILQ_FIRST(&state->arp_states)) != NULL) arp_free(astate); - - /* No need to close because the last free will close */ } Index: contrib/dhcpcd/src/auth.h =================================================================== --- contrib/dhcpcd/src/auth.h +++ contrib/dhcpcd/src/auth.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -90,7 +90,11 @@ const void *, size_t, int, int, const void *, size_t); -ssize_t dhcp_auth_encode(struct auth *, const struct token *, +struct dhcpcd_ctx; +ssize_t dhcp_auth_encode(struct dhcpcd_ctx *, struct auth *, + const struct token *, void *, size_t, int, int, void *, size_t); + +int auth_get_rdm_monotonic(uint64_t *rdm); #endif Index: contrib/dhcpcd/src/auth.c =================================================================== --- contrib/dhcpcd/src/auth.c +++ contrib/dhcpcd/src/auth.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -27,6 +27,8 @@ */ #include +#include + #include #include #include @@ -42,6 +44,7 @@ #include "dhcp.h" #include "dhcp6.h" #include "dhcpcd.h" +#include "privsep-root.h" #ifdef HAVE_HMAC_H #include @@ -408,11 +411,11 @@ return t; } -static uint64_t -get_next_rdm_monotonic_counter(struct auth *auth) +int +auth_get_rdm_monotonic(uint64_t *rdm) { FILE *fp; - uint64_t rdm; + int err; #ifdef LOCK_EX int flocked; #endif @@ -420,41 +423,43 @@ fp = fopen(RDM_MONOFILE, "r+"); if (fp == NULL) { if (errno != ENOENT) - return ++auth->last_replay; /* report error? */ + return -1; fp = fopen(RDM_MONOFILE, "w"); if (fp == NULL) - return ++auth->last_replay; /* report error? */ + return -1; + if (chmod(RDM_MONOFILE, 0400) == -1) { + fclose(fp); + unlink(RDM_MONOFILE); + return -1; + } #ifdef LOCK_EX flocked = flock(fileno(fp), LOCK_EX); #endif - rdm = 0; + *rdm = 0; } else { #ifdef LOCK_EX flocked = flock(fileno(fp), LOCK_EX); #endif - if (fscanf(fp, "0x%016" PRIu64, &rdm) != 1) - rdm = 0; /* truncated? report error? */ + if (fscanf(fp, "0x%016" PRIu64, rdm) != 1) { + fclose(fp); + return -1; + } } - rdm++; + (*rdm)++; if (fseek(fp, 0, SEEK_SET) == -1 || ftruncate(fileno(fp), 0) == -1 || - fprintf(fp, "0x%016" PRIu64 "\n", rdm) != 19 || + fprintf(fp, "0x%016" PRIu64 "\n", *rdm) != 19 || fflush(fp) == EOF) - { - if (!auth->last_replay_set) { - auth->last_replay = rdm; - auth->last_replay_set = 1; - } else - rdm = ++auth->last_replay; - /* report error? */ - } + err = -1; + else + err = 0; #ifdef LOCK_EX if (flocked == 0) flock(fileno(fp), LOCK_UN); #endif fclose(fp); - return rdm; + return err; } #define NTP_EPOCH 2208988800U /* 1970 - 1900 in seconds */ @@ -476,11 +481,29 @@ } static uint64_t -get_next_rdm_monotonic(struct auth *auth) +get_next_rdm_monotonic(struct dhcpcd_ctx *ctx, struct auth *auth) { +#ifndef PRIVSEP + UNUSED(ctx); +#endif + + if (auth->options & DHCPCD_AUTH_RDM_COUNTER) { + uint64_t rdm; + int err; - if (auth->options & DHCPCD_AUTH_RDM_COUNTER) - return get_next_rdm_monotonic_counter(auth); +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx)) { + + err = ps_root_getauthrdm(ctx, &rdm); + } else +#endif + err = auth_get_rdm_monotonic(&rdm); + if (err == -1) + return ++auth->last_replay; + + auth->last_replay = rdm; + return rdm; + } return get_next_rdm_monotonic_clock(auth); } @@ -495,7 +518,8 @@ * data and dlen refer to the authentication option within the message. */ ssize_t -dhcp_auth_encode(struct auth *auth, const struct token *t, +dhcp_auth_encode(struct dhcpcd_ctx *ctx, struct auth *auth, + const struct token *t, void *vm, size_t mlen, int mp, int mt, void *vdata, size_t dlen) { @@ -611,11 +635,11 @@ *data++ = auth->rdm; switch (auth->rdm) { case AUTH_RDM_MONOTONIC: - rdm = get_next_rdm_monotonic(auth); + rdm = get_next_rdm_monotonic(ctx, auth); break; default: /* This block appeases gcc, clang doesn't need it */ - rdm = get_next_rdm_monotonic(auth); + rdm = get_next_rdm_monotonic(ctx, auth); break; } rdm = htonll(rdm); Index: contrib/dhcpcd/src/bpf.h =================================================================== --- contrib/dhcpcd/src/bpf.h +++ contrib/dhcpcd/src/bpf.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd: BPF arp and bootp filtering - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -29,10 +29,9 @@ #ifndef BPF_HEADER #define BPF_HEADER -#define BPF_READING (1U << 0) -#define BPF_EOF (1U << 1) -#define BPF_PARTIALCSUM (1U << 2) -#define BPF_BCAST (1U << 3) +#define BPF_EOF 0x01U +#define BPF_PARTIALCSUM 0x02U +#define BPF_BCAST 0x04U /* * Even though we program the BPF filter should we trust it? @@ -55,14 +54,28 @@ #include "dhcpcd.h" +struct bpf { + const struct interface *bpf_ifp; + int bpf_fd; + unsigned int bpf_flags; + void *bpf_buffer; + size_t bpf_size; + size_t bpf_len; + size_t bpf_pos; +}; + extern const char *bpf_name; size_t bpf_frame_header_len(const struct interface *); -int bpf_frame_bcast(const struct interface *, const char *frame); -int bpf_open(struct interface *, int (*)(struct interface *, int)); -int bpf_close(struct interface *, int); +void *bpf_frame_header_src(const struct interface *, void *, size_t *); +void *bpf_frame_header_dst(const struct interface *, void *, size_t *); +int bpf_frame_bcast(const struct interface *, const void *); +struct bpf * bpf_open(const struct interface *, + int (*)(const struct bpf *, const struct in_addr *), + const struct in_addr *); +void bpf_close(struct bpf *); int bpf_attach(int, void *, unsigned int); -ssize_t bpf_send(const struct interface *, int, uint16_t, const void *, size_t); -ssize_t bpf_read(struct interface *, int, void *, size_t, unsigned int *); -int bpf_arp(struct interface *, int); -int bpf_bootp(struct interface *, int); +ssize_t bpf_send(const struct bpf *, uint16_t, const void *, size_t); +ssize_t bpf_read(struct bpf *, void *, size_t); +int bpf_arp(const struct bpf *, const struct in_addr *); +int bpf_bootp(const struct bpf *, const struct in_addr *); #endif Index: contrib/dhcpcd/src/bpf.c =================================================================== --- contrib/dhcpcd/src/bpf.c +++ contrib/dhcpcd/src/bpf.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd: BPF arp and bootp filtering - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -57,8 +57,6 @@ #include "if.h" #include "logerr.h" -#define ARP_ADDRS_MAX 3 - /* BPF helper macros */ #ifdef __linux__ #define BPF_WHOLEPACKET 0x7fffffff /* work around buggy LPF filters */ @@ -72,20 +70,20 @@ (insn)->jt = 0; \ (insn)->jf = 0; \ (insn)->k = (uint32_t)(v); \ -}; +} #define BPF_SET_JUMP(insn, c, v, t, f) { \ (insn)->code = (c); \ (insn)->jt = (t); \ (insn)->jf = (f); \ (insn)->k = (uint32_t)(v); \ -}; +} size_t bpf_frame_header_len(const struct interface *ifp) { - switch (ifp->family) { + switch (ifp->hwtype) { case ARPHRD_ETHER: return sizeof(struct ether_header); default: @@ -93,16 +91,48 @@ } } +void * +bpf_frame_header_src(const struct interface *ifp, void *fh, size_t *len) +{ + uint8_t *f = fh; + + switch (ifp->hwtype) { + case ARPHRD_ETHER: + *len = sizeof(((struct ether_header *)0)->ether_shost); + return f + offsetof(struct ether_header, ether_shost); + default: + *len = 0; + errno = ENOTSUP; + return NULL; + } +} + +void * +bpf_frame_header_dst(const struct interface *ifp, void *fh, size_t *len) +{ + uint8_t *f = fh; + + switch (ifp->hwtype) { + case ARPHRD_ETHER: + *len = sizeof(((struct ether_header *)0)->ether_dhost); + return f + offsetof(struct ether_header, ether_dhost); + default: + *len = 0; + errno = ENOTSUP; + return NULL; + } +} + static const uint8_t etherbcastaddr[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; int -bpf_frame_bcast(const struct interface *ifp, const char *frame) +bpf_frame_bcast(const struct interface *ifp, const void *frame) { - switch (ifp->family) { + switch (ifp->hwtype) { case ARPHRD_ETHER: - return memcmp(frame + + return memcmp((const char *)frame + offsetof(struct ether_header, ether_dhost), etherbcastaddr, sizeof(etherbcastaddr)); default: @@ -116,15 +146,15 @@ const char *bpf_name = "Berkley Packet Filter"; -int -bpf_open(struct interface *ifp, int (*filter)(struct interface *, int)) +struct bpf * +bpf_open(const struct interface *ifp, + int (*filter)(const struct bpf *, const struct in_addr *), + const struct in_addr *ia) { - struct ipv4_state *state; - int fd = -1; - struct ifreq ifr; + struct bpf *bpf; + struct bpf_version pv = { .bv_major = 0, .bv_minor = 0 }; + struct ifreq ifr = { .ifr_flags = 0 }; int ibuf_len = 0; - size_t buf_len; - struct bpf_version pv; #ifdef BIOCIMMEDIATE unsigned int flags; #endif @@ -132,8 +162,13 @@ int fd_opts; #endif + bpf = calloc(1, sizeof(*bpf)); + if (bpf == NULL) + return NULL; + bpf->bpf_ifp = ifp; + #ifdef _PATH_BPF - fd = open(_PATH_BPF, O_RDWR | O_NONBLOCK + bpf->bpf_fd = open(_PATH_BPF, O_RDWR | O_NONBLOCK #ifdef O_CLOEXEC | O_CLOEXEC #endif @@ -144,27 +179,24 @@ do { snprintf(device, sizeof(device), "/dev/bpf%d", n++); - fd = open(device, O_RDWR | O_NONBLOCK + bpf->bpf_fd = open(device, O_RDWR | O_NONBLOCK #ifdef O_CLOEXEC | O_CLOEXEC #endif ); - } while (fd == -1 && errno == EBUSY); + } while (bpf->bpf_fd == -1 && errno == EBUSY); #endif - if (fd == -1) - return -1; + if (bpf->bpf_fd == -1) + goto eexit; #ifndef O_CLOEXEC - if ((fd_opts = fcntl(fd, F_GETFD)) == -1 || - fcntl(fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1) { - close(fd); - return -1; - } + if ((fd_opts = fcntl(bpf->bpf_fd, F_GETFD)) == -1 || + fcntl(bpf->bpf_fd, F_SETFD, fd_opts | FD_CLOEXEC) == -1) + goto eexit; #endif - memset(&pv, 0, sizeof(pv)); - if (ioctl(fd, BIOCVERSION, &pv) == -1) + if (ioctl(bpf->bpf_fd, BIOCVERSION, &pv) == -1) goto eexit; if (pv.bv_major != BPF_MAJOR_VERSION || pv.bv_minor < BPF_MINOR_VERSION) { @@ -172,95 +204,84 @@ goto eexit; } - if (filter(ifp, fd) != 0) - goto eexit; - - memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); - if (ioctl(fd, BIOCSETIF, &ifr) == -1) - goto eexit; - - /* Get the required BPF buffer length from the kernel. */ - if (ioctl(fd, BIOCGBLEN, &ibuf_len) == -1) - goto eexit; - buf_len = (size_t)ibuf_len; - state = ipv4_getstate(ifp); - if (state == NULL) + if (ioctl(bpf->bpf_fd, BIOCSETIF, &ifr) == -1) goto eexit; - if (state->buffer_size != buf_len) { - void *nb; - - if ((nb = realloc(state->buffer, buf_len)) == NULL) - goto eexit; - state->buffer = nb; - state->buffer_size = buf_len; - } #ifdef BIOCIMMEDIATE flags = 1; - if (ioctl(fd, BIOCIMMEDIATE, &flags) == -1) + if (ioctl(bpf->bpf_fd, BIOCIMMEDIATE, &flags) == -1) goto eexit; #endif - return fd; + if (filter(bpf, ia) != 0) + goto eexit; + + /* Get the required BPF buffer length from the kernel. */ + if (ioctl(bpf->bpf_fd, BIOCGBLEN, &ibuf_len) == -1) + goto eexit; + bpf->bpf_size = (size_t)ibuf_len; + bpf->bpf_buffer = malloc(bpf->bpf_size); + if (bpf->bpf_buffer == NULL) + goto eexit; + return bpf; eexit: - close(fd); - return -1; + if (bpf->bpf_fd != -1) + close(bpf->bpf_fd); + 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 interface *ifp, int fd, void *data, size_t len, - unsigned int *flags) +bpf_read(struct bpf *bpf, void *data, size_t len) { - ssize_t fl = (ssize_t)bpf_frame_header_len(ifp); ssize_t bytes; - struct ipv4_state *state = IPV4_STATE(ifp); - struct bpf_hdr packet; const char *payload; - *flags &= ~BPF_EOF; + bpf->bpf_flags &= ~BPF_EOF; for (;;) { - if (state->buffer_len == 0) { - bytes = read(fd, state->buffer, state->buffer_size); + if (bpf->bpf_len == 0) { + bytes = read(bpf->bpf_fd, bpf->bpf_buffer, + bpf->bpf_size); #if defined(__sun) /* After 2^31 bytes, the kernel offset overflows. * To work around this bug, lseek 0. */ if (bytes == -1 && errno == EINVAL) { - lseek(fd, 0, SEEK_SET); + lseek(bpf->bpf_fd, 0, SEEK_SET); continue; } #endif if (bytes == -1 || bytes == 0) return bytes; - state->buffer_len = (size_t)bytes; - state->buffer_pos = 0; + bpf->bpf_len = (size_t)bytes; + bpf->bpf_pos = 0; } bytes = -1; - memcpy(&packet, state->buffer + state->buffer_pos, - sizeof(packet)); - if (state->buffer_pos + packet.bh_caplen + packet.bh_hdrlen > - state->buffer_len) + payload = (const char *)bpf->bpf_buffer + bpf->bpf_pos; + memcpy(&packet, payload, sizeof(packet)); + if (bpf->bpf_pos + packet.bh_caplen + packet.bh_hdrlen > + bpf->bpf_len) goto next; /* Packet beyond buffer, drop. */ - payload = state->buffer + state->buffer_pos + packet.bh_hdrlen; - if (bpf_frame_bcast(ifp, payload) == 0) - *flags |= BPF_BCAST; - else - *flags &= ~BPF_BCAST; - payload += fl; - bytes = (ssize_t)packet.bh_caplen - fl; - if ((size_t)bytes > len) + payload += packet.bh_hdrlen; + if (packet.bh_caplen > len) bytes = (ssize_t)len; + else + bytes = (ssize_t)packet.bh_caplen; + if (bpf_frame_bcast(bpf->bpf_ifp, payload) == 0) + bpf->bpf_flags |= BPF_BCAST; + else + bpf->bpf_flags &= ~BPF_BCAST; memcpy(data, payload, (size_t)bytes); next: - state->buffer_pos += BPF_WORDALIGN(packet.bh_hdrlen + + bpf->bpf_pos += BPF_WORDALIGN(packet.bh_hdrlen + packet.bh_caplen); - if (state->buffer_pos >= state->buffer_len) { - state->buffer_len = state->buffer_pos = 0; - *flags |= BPF_EOF; + if (bpf->bpf_pos >= bpf->bpf_len) { + bpf->bpf_len = bpf->bpf_pos = 0; + bpf->bpf_flags |= BPF_EOF; } if (bytes != -1) return bytes; @@ -272,29 +293,38 @@ int bpf_attach(int fd, void *filter, unsigned int filter_len) { - struct bpf_program pf; + struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len }; /* Install the filter. */ - memset(&pf, 0, sizeof(pf)); - pf.bf_insns = filter; - pf.bf_len = filter_len; return ioctl(fd, BIOCSETF, &pf); } + +#ifdef BIOCSETWF +static int +bpf_wattach(int fd, void *filter, unsigned int filter_len) +{ + struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len }; + + /* Install the filter. */ + return ioctl(fd, BIOCSETWF, &pf); +} +#endif #endif #ifndef __sun /* SunOS is special too - sending via BPF goes nowhere. */ ssize_t -bpf_send(const struct interface *ifp, int fd, uint16_t protocol, +bpf_send(const struct bpf *bpf, uint16_t protocol, const void *data, size_t len) { struct iovec iov[2]; struct ether_header eh; - switch(ifp->family) { + switch(bpf->bpf_ifp->hwtype) { case ARPHRD_ETHER: memset(&eh.ether_dhost, 0xff, sizeof(eh.ether_dhost)); - memcpy(&eh.ether_shost, ifp->hwaddr, sizeof(eh.ether_shost)); + memcpy(&eh.ether_shost, bpf->bpf_ifp->hwaddr, + sizeof(eh.ether_shost)); eh.ether_type = htons(protocol); iov[0].iov_base = &eh; iov[0].iov_len = sizeof(eh); @@ -306,27 +336,24 @@ } iov[1].iov_base = UNCONST(data); iov[1].iov_len = len; - return writev(fd, iov, 2); + return writev(bpf->bpf_fd, iov, 2); } #endif -int -bpf_close(struct interface *ifp, int fd) +void +bpf_close(struct bpf *bpf) { - struct ipv4_state *state = IPV4_STATE(ifp); - /* Rewind the buffer on closing. */ - state->buffer_len = state->buffer_pos = 0; - return close(fd); + close(bpf->bpf_fd); + free(bpf->bpf_buffer); + free(bpf); } -/* Normally this is needed by bootp. - * Once that uses this again, the ARP guard here can be removed. */ #ifdef ARP #define BPF_CMP_HWADDR_LEN ((((HWADDR_LEN / 4) + 2) * 2) + 1) static unsigned int bpf_cmp_hwaddr(struct bpf_insn *bpf, size_t bpf_len, size_t off, - bool equal, uint8_t *hwaddr, size_t hwaddr_len) + bool equal, const uint8_t *hwaddr, size_t hwaddr_len) { struct bpf_insn *bp; size_t maclen, nlft, njmps; @@ -420,7 +447,7 @@ /* Load frame header length into X */ BPF_STMT(BPF_LDX + BPF_W + BPF_IMM, sizeof(struct ether_header)), - /* Make sure the hardware family matches. */ + /* Make sure the hardware type matches. */ BPF_STMT(BPF_LD + BPF_H + BPF_IND, offsetof(struct arphdr, ar_hrd)), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), BPF_STMT(BPF_RET + BPF_K, 0), @@ -451,26 +478,24 @@ }; #define BPF_ARP_FILTER_LEN __arraycount(bpf_arp_filter) -#define BPF_ARP_ADDRS_LEN 1 + (ARP_ADDRS_MAX * 2) + 3 + \ - (ARP_ADDRS_MAX * 2) + 1 +/* One address is two checks of two statements. */ +#define BPF_NADDRS 1 +#define BPF_ARP_ADDRS_LEN 5 + ((BPF_NADDRS * 2) * 2) #define BPF_ARP_LEN BPF_ARP_ETHER_LEN + BPF_ARP_FILTER_LEN + \ BPF_CMP_HWADDR_LEN + BPF_ARP_ADDRS_LEN -int -bpf_arp(struct interface *ifp, int fd) +static int +bpf_arp_rw(const struct bpf *bpf, const struct in_addr *ia, bool recv) { - struct bpf_insn bpf[BPF_ARP_LEN]; + const struct interface *ifp = bpf->bpf_ifp; + struct bpf_insn buf[BPF_ARP_LEN + 1]; struct bpf_insn *bp; - struct iarp_state *state; uint16_t arp_len; - if (fd == -1) - return 0; - - bp = bpf; + bp = buf; /* Check frame header. */ - switch(ifp->family) { + switch(ifp->hwtype) { case ARPHRD_ETHER: memcpy(bp, bpf_arp_ether, sizeof(bpf_arp_ether)); bp += BPF_ARP_ETHER_LEN; @@ -487,62 +512,58 @@ /* Ensure it's not from us. */ bp += bpf_cmp_hwaddr(bp, BPF_CMP_HWADDR_LEN, sizeof(struct arphdr), - false, ifp->hwaddr, ifp->hwlen); + !recv, ifp->hwaddr, ifp->hwlen); - state = ARP_STATE(ifp); - if (TAILQ_FIRST(&state->arp_states)) { - struct arp_state *astate; - size_t naddrs; + /* Match sender protocol address */ + BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, + sizeof(struct arphdr) + ifp->hwlen); + bp++; + BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1); + bp++; + BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len); + bp++; - /* Match sender protocol address */ - BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, - sizeof(struct arphdr) + ifp->hwlen); - bp++; - naddrs = 0; - TAILQ_FOREACH(astate, &state->arp_states, next) { - if (++naddrs > ARP_ADDRS_MAX) { - errno = ENOBUFS; - logerr(__func__); - break; - } - BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, - htonl(astate->addr.s_addr), 0, 1); - bp++; - BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len); - bp++; - } + /* If we didn't match sender, then we're only interested in + * ARP probes to us, so check the null host sender. */ + BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, INADDR_ANY, 1, 0); + bp++; + BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); + bp++; - /* If we didn't match sender, then we're only interested in - * ARP probes to us, so check the null host sender. */ - BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, INADDR_ANY, 1, 0); - bp++; - BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); - bp++; + /* Match target protocol address */ + BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, (sizeof(struct arphdr) + + (size_t)(ifp->hwlen * 2) + sizeof(in_addr_t))); + bp++; + BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, htonl(ia->s_addr), 0, 1); + bp++; + BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len); + bp++; - /* Match target protocol address */ - BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, - (sizeof(struct arphdr) - + (size_t)(ifp->hwlen * 2) + sizeof(in_addr_t))); - bp++; - naddrs = 0; - TAILQ_FOREACH(astate, &state->arp_states, next) { - if (++naddrs > ARP_ADDRS_MAX) { - /* Already logged error above. */ - break; - } - BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, - htonl(astate->addr.s_addr), 0, 1); - bp++; - BPF_SET_STMT(bp, BPF_RET + BPF_K, arp_len); - bp++; - } + /* No match, drop it */ + BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); + bp++; - /* Return nothing, no protocol address match. */ - BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); - bp++; - } +#ifdef BIOCSETWF + if (!recv) + return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); +#endif + + return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); +} + +int +bpf_arp(const struct bpf *bpf, const struct in_addr *ia) +{ - return bpf_attach(fd, bpf, (unsigned int)(bp - bpf)); +#ifdef BIOCSETWF + if (bpf_arp_rw(bpf, ia, true) == -1 || + bpf_arp_rw(bpf, ia, false) == -1 || + ioctl(bpf->bpf_fd, BIOCLOCK) == -1) + return -1; + return 0; +#else + return bpf_arp_rw(bpf, ia, true); +#endif } #endif @@ -564,10 +585,7 @@ }; #define BPF_BOOTP_ETHER_LEN __arraycount(bpf_bootp_ether) -#define BOOTP_MIN_SIZE sizeof(struct ip) + sizeof(struct udphdr) + \ - sizeof(struct bootp) - -static const struct bpf_insn bpf_bootp_filter[] = { +static const struct bpf_insn bpf_bootp_base[] = { /* Make sure it's an IPv4 packet. */ BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0), BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0xf0), @@ -590,35 +608,43 @@ BPF_STMT(BPF_ALU + BPF_MUL + BPF_K, 4), BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0), BPF_STMT(BPF_MISC + BPF_TAX, 0), +}; +#define BPF_BOOTP_BASE_LEN __arraycount(bpf_bootp_base) +static const struct bpf_insn bpf_bootp_read[] = { /* Make sure it's from and to the right port. */ BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPS << 16) + BOOTPC, 1, 0), BPF_STMT(BPF_RET + BPF_K, 0), }; +#define BPF_BOOTP_READ_LEN __arraycount(bpf_bootp_read) + +#ifdef BIOCSETWF +static const struct bpf_insn bpf_bootp_write[] = { + /* Make sure it's from and to the right port. */ + BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0), + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPC << 16) + BOOTPS, 1, 0), + BPF_STMT(BPF_RET + BPF_K, 0), +}; +#define BPF_BOOTP_WRITE_LEN __arraycount(bpf_bootp_write) +#endif -#define BPF_BOOTP_FILTER_LEN __arraycount(bpf_bootp_filter) #define BPF_BOOTP_CHADDR_LEN ((BOOTP_CHADDR_LEN / 4) * 3) #define BPF_BOOTP_XID_LEN 4 /* BOUND check is 4 instructions */ -#define BPF_BOOTP_LEN BPF_BOOTP_ETHER_LEN + BPF_BOOTP_FILTER_LEN \ - + BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4 +#define BPF_BOOTP_LEN BPF_BOOTP_ETHER_LEN + \ + BPF_BOOTP_BASE_LEN + BPF_BOOTP_READ_LEN + \ + BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4 -int -bpf_bootp(struct interface *ifp, int fd) +static int +bpf_bootp_rw(const struct bpf *bpf, bool read) { -#if 0 - const struct dhcp_state *state = D_CSTATE(ifp); -#endif - struct bpf_insn bpf[BPF_BOOTP_LEN]; + struct bpf_insn buf[BPF_BOOTP_LEN + 1]; struct bpf_insn *bp; - if (fd == -1) - return 0; - - bp = bpf; + bp = buf; /* Check frame header. */ - switch(ifp->family) { + switch(bpf->bpf_ifp->hwtype) { #ifdef ARPHRD_NONE case ARPHRD_NONE: memcpy(bp, bpf_bootp_none, sizeof(bpf_bootp_none)); @@ -635,48 +661,53 @@ } /* Copy in the main filter. */ - memcpy(bp, bpf_bootp_filter, sizeof(bpf_bootp_filter)); - bp += BPF_BOOTP_FILTER_LEN; - - /* These checks won't work when same IP exists on other interfaces. */ -#if 0 - if (ifp->hwlen <= sizeof(((struct bootp *)0)->chaddr)) - bp += bpf_cmp_hwaddr(bp, BPF_BOOTP_CHADDR_LEN, - offsetof(struct bootp, chaddr), - true, ifp->hwaddr, ifp->hwlen); - - /* Make sure the BOOTP packet is for us. */ - if (state->state == DHS_BOUND) { - /* If bound, we only expect FORCERENEW messages - * and they need to be unicast to us. - * Move back to the IP header in M0 and check dst. */ - BPF_SET_STMT(bp, BPF_LDX + BPF_W + BPF_MEM, 0); - bp++; - BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, - offsetof(struct ip, ip_dst)); - bp++; - BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, - htonl(state->lease.addr.s_addr), 1, 0); - bp++; - BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); - bp++; - } else { - /* As we're not bound, we need to check xid to ensure - * it's a reply to our transaction. */ - BPF_SET_STMT(bp, BPF_LD + BPF_W + BPF_IND, - offsetof(struct bootp, xid)); - bp++; - BPF_SET_JUMP(bp, BPF_JMP + BPF_JEQ + BPF_K, - state->xid, 1, 0); - bp++; - BPF_SET_STMT(bp, BPF_RET + BPF_K, 0); + memcpy(bp, bpf_bootp_base, sizeof(bpf_bootp_base)); + bp += BPF_BOOTP_BASE_LEN; + +#ifdef BIOCSETWF + if (!read) { + memcpy(bp, bpf_bootp_write, sizeof(bpf_bootp_write)); + bp += BPF_BOOTP_WRITE_LEN; + + /* All passed, return the packet. */ + BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET); bp++; + + return bpf_wattach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); } +#else + UNUSED(read); #endif + memcpy(bp, bpf_bootp_read, sizeof(bpf_bootp_read)); + bp += BPF_BOOTP_READ_LEN; + /* All passed, return the packet. */ BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET); bp++; - return bpf_attach(fd, bpf, (unsigned int)(bp - bpf)); + return bpf_attach(bpf->bpf_fd, buf, (unsigned int)(bp - buf)); +} + +int +bpf_bootp(const struct bpf *bpf, __unused const struct in_addr *ia) +{ + +#ifdef BIOCSETWF + if (bpf_bootp_rw(bpf, true) == -1 || + bpf_bootp_rw(bpf, false) == -1 || + ioctl(bpf->bpf_fd, BIOCLOCK) == -1) + return -1; + return 0; +#else +#ifdef PRIVSEP +#if defined(__sun) /* Solaris cannot send via BPF. */ +#elif defined(BIOCSETF) +#warning No BIOCSETWF support - a compromised BPF can be used as a raw socket +#else +#warning A compromised PF_PACKET socket can be used as a raw socket +#endif +#endif + return bpf_bootp_rw(bpf, true); +#endif } Index: contrib/dhcpcd/src/common.h =================================================================== --- contrib/dhcpcd/src/common.h +++ contrib/dhcpcd/src/common.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -31,9 +31,21 @@ #include #include +#include #include #include +/* Define eloop queues here, as other apps share eloop.h */ +#define ELOOP_DHCPCD 1 /* default queue */ +#define ELOOP_DHCP 2 +#define ELOOP_ARP 3 +#define ELOOP_IPV4LL 4 +#define ELOOP_IPV6 5 +#define ELOOP_IPV6ND 6 +#define ELOOP_IPV6RA_EXPIRE 7 +#define ELOOP_DHCP6 8 +#define ELOOP_IF 9 + #ifndef HOSTNAME_MAX_LEN #define HOSTNAME_MAX_LEN 250 /* 255 - 3 (FQDN) - 2 (DNS enc) */ #endif @@ -51,65 +63,10 @@ #define ROUNDUP4(a) (1 + (((a) - 1) | 3)) #define ROUNDUP8(a) (1 + (((a) - 1) | 7)) -#define USEC_PER_SEC 1000000L -#define USEC_PER_NSEC 1000L -#define NSEC_PER_SEC 1000000000L -#define NSEC_PER_MSEC 1000000L -#define MSEC_PER_SEC 1000L -#define CSEC_PER_SEC 100L -#define NSEC_PER_CSEC 10000000L - /* Some systems don't define timespec macros */ #ifndef timespecclear #define timespecclear(tsp) (tsp)->tv_sec = (time_t)((tsp)->tv_nsec = 0L) #define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) -#define timespeccmp(tsp, usp, cmp) \ - (((tsp)->tv_sec == (usp)->tv_sec) ? \ - ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ - ((tsp)->tv_sec cmp (usp)->tv_sec)) -#define timespecadd(tsp, usp, vsp) \ - do { \ - (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ - (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ - if ((vsp)->tv_nsec >= 1000000000L) { \ - (vsp)->tv_sec++; \ - (vsp)->tv_nsec -= 1000000000L; \ - } \ - } while (/* CONSTCOND */ 0) -#define timespecsub(tsp, usp, vsp) \ - do { \ - (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ - (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ - if ((vsp)->tv_nsec < 0) { \ - (vsp)->tv_sec--; \ - (vsp)->tv_nsec += 1000000000L; \ - } \ - } while (/* CONSTCOND */ 0) -#endif - -#define timespec_to_double(tv) \ - ((double)(tv)->tv_sec + (double)((tv)->tv_nsec) / 1000000000.0) -#define timespecnorm(tv) do { \ - while ((tv)->tv_nsec >= NSEC_PER_SEC) { \ - (tv)->tv_sec++; \ - (tv)->tv_nsec -= NSEC_PER_SEC; \ - } \ -} while (0 /* CONSTCOND */); -#define ts_to_ms(ms, tv) do { \ - ms = (tv)->tv_sec * MSEC_PER_SEC; \ - ms += (tv)->tv_nsec / NSEC_PER_MSEC; \ -} while (0 /* CONSTCOND */); -#define ms_to_ts(tv, ms) do { \ - (tv)->tv_sec = ms / MSEC_PER_SEC; \ - (tv)->tv_nsec = (suseconds_t)(ms - ((tv)->tv_sec * MSEC_PER_SEC)) \ - * NSEC_PER_MSEC; \ -} while (0 /* CONSTCOND */); - -#ifndef TIMEVAL_TO_TIMESPEC -#define TIMEVAL_TO_TIMESPEC(tv, ts) do { \ - (ts)->tv_sec = (tv)->tv_sec; \ - (ts)->tv_nsec = (tv)->tv_usec * USEC_PER_NSEC; \ -} while (0 /* CONSTCOND */) #endif #if __GNUC__ > 2 || defined(__INTEL_COMPILER) @@ -190,12 +147,11 @@ # endif #endif -void get_line_free(void); -extern int clock_monotonic; -int get_monotonic(struct timespec *); - const char *hwaddr_ntoa(const void *, size_t, char *, size_t); size_t hwaddr_aton(uint8_t *, const char *); -size_t read_hwaddr_aton(uint8_t **, const char *); +ssize_t readfile(const char *, void *, size_t); +ssize_t writefile(const char *, mode_t, const void *, size_t); +int filemtime(const char *, time_t *); +char *get_line(char ** __restrict, ssize_t * __restrict); int is_root_local(void); #endif Index: contrib/dhcpcd/src/common.c =================================================================== --- contrib/dhcpcd/src/common.c +++ contrib/dhcpcd/src/common.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -26,20 +26,19 @@ * SUCH DAMAGE. */ +#include #include #include #include +#include #include #include +#include #include "common.h" #include "dhcpcd.h" #include "if-options.h" -#include "logerr.h" - -/* Most route(4) messages are less than 256 bytes. */ -#define IOVEC_BUFSIZ 256 const char * hwaddr_ntoa(const void *hwaddr, size_t hwlen, char *buf, size_t buflen) @@ -47,7 +46,7 @@ const unsigned char *hp, *ep; char *p; - if (buf == NULL) + if (buf == NULL || hwlen == 0) return NULL; if (hwlen * 3 > buflen) { @@ -106,38 +105,97 @@ return len; } -size_t -read_hwaddr_aton(uint8_t **data, const char *path) +ssize_t +readfile(const char *file, void *data, size_t len) +{ + int fd; + ssize_t bytes; + + fd = open(file, O_RDONLY); + if (fd == -1) + return -1; + bytes = read(fd, data, len); + close(fd); + if ((size_t)bytes == len) { + errno = ENOBUFS; + return -1; + } + return bytes; +} + +ssize_t +writefile(const char *file, mode_t mode, const void *data, size_t len) +{ + int fd; + ssize_t bytes; + + fd = open(file, O_WRONLY | O_CREAT | O_TRUNC, mode); + if (fd == -1) + return -1; + bytes = write(fd, data, len); + close(fd); + return bytes; +} + +int +filemtime(const char *file, time_t *time) { - FILE *fp; - char *buf; - size_t buf_len, len; - - if ((fp = fopen(path, "r")) == NULL) - return 0; - - buf = NULL; - buf_len = len = 0; - *data = NULL; - while (getline(&buf, &buf_len, fp) != -1) { - if ((len = hwaddr_aton(NULL, buf)) != 0) { - if (buf_len >= len) - *data = (uint8_t *)buf; - else { - if ((*data = malloc(len)) == NULL) - len = 0; - } - if (len != 0) - (void)hwaddr_aton(*data, buf); - if (buf_len < len) - free(buf); + struct stat st; + + if (stat(file, &st) == -1) + return -1; + *time = st.st_mtime; + return 0; +} + +/* Handy routine to read very long lines in text files. + * This means we read the whole line and avoid any nasty buffer overflows. + * We strip leading space and avoid comment lines, making the code that calls + * us smaller. */ +char * +get_line(char ** __restrict buf, ssize_t * __restrict buflen) +{ + char *p, *c; + bool quoted; + + do { + p = *buf; + if (*buf == NULL) + return NULL; + c = memchr(*buf, '\n', (size_t)*buflen); + if (c == NULL) { + c = memchr(*buf, '\0', (size_t)*buflen); + if (c == NULL) + return NULL; + *buflen = c - *buf; + *buf = NULL; + } else { + *c++ = '\0'; + *buflen -= c - *buf; + *buf = c; + } + for (; *p == ' ' || *p == '\t'; p++) + ; + } while (*p == '\0' || *p == '\n' || *p == '#' || *p == ';'); + + /* Strip embedded comments unless in a quoted string or escaped */ + quoted = false; + for (c = p; *c != '\0'; c++) { + if (*c == '\\') { + c++; /* escaped */ + continue; + } + if (*c == '"') + quoted = !quoted; + else if (*c == '#' && !quoted) { + *c = '\0'; break; } } - fclose(fp); - return len; + return p; } + int is_root_local(void) { Index: contrib/dhcpcd/src/control.h =================================================================== --- contrib/dhcpcd/src/control.h +++ contrib/dhcpcd/src/control.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -47,6 +47,7 @@ void *data; size_t data_size; size_t data_len; + unsigned int data_flags; }; TAILQ_HEAD(fd_data_head, fd_data); @@ -56,21 +57,23 @@ int fd; unsigned int flags; struct fd_data_head queue; - size_t queue_len; #ifdef CTL_FREE_LIST struct fd_data_head free_queue; #endif }; TAILQ_HEAD(fd_list_head, fd_list); -#define FD_LISTEN (1<<0) -#define FD_UNPRIV (1<<1) +#define FD_LISTEN 0x01U +#define FD_UNPRIV 0x02U +#define FD_SENDLEN 0x04U -int control_start(struct dhcpcd_ctx *, const char *); +int control_start(struct dhcpcd_ctx *, const char *, sa_family_t); int control_stop(struct dhcpcd_ctx *); -int control_open(const char *); +int control_open(const char *, sa_family_t, bool); ssize_t control_send(struct dhcpcd_ctx *, int, char * const *); -int control_queue(struct fd_list *, void *, size_t, bool); -void control_close(struct dhcpcd_ctx *ctx); - +struct fd_list *control_new(struct dhcpcd_ctx *, int, unsigned int); +void control_free(struct fd_list *); +void control_delete(struct fd_list *); +int control_queue(struct fd_list *, void *, size_t); +void control_recvdata(struct fd_list *fd, char *, size_t); #endif Index: contrib/dhcpcd/src/control.c =================================================================== --- contrib/dhcpcd/src/control.c +++ contrib/dhcpcd/src/control.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -46,10 +46,11 @@ #include "eloop.h" #include "if.h" #include "logerr.h" +#include "privsep.h" #ifndef SUN_LEN #define SUN_LEN(su) \ - (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) + (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) #endif static void @@ -63,7 +64,6 @@ free(fdp->data); free(fdp); } - fd->queue_len = 0; #ifdef CTL_FREE_LIST while ((fdp = TAILQ_FIRST(&fd->free_queue))) { @@ -75,56 +75,119 @@ #endif } -static void -control_delete(struct fd_list *fd) +void +control_free(struct fd_list *fd) { +#ifdef PRIVSEP + if (fd->ctx->ps_control_client == fd) + fd->ctx->ps_control_client = NULL; +#endif + + if (eloop_event_remove_writecb(fd->ctx->eloop, fd->fd) == -1) + logerr(__func__); TAILQ_REMOVE(&fd->ctx->control_fds, fd, next); - eloop_event_delete(fd->ctx->eloop, fd->fd); - close(fd->fd); control_queue_free(fd); free(fd); } +void +control_delete(struct fd_list *fd) +{ + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(fd->ctx)) + return; +#endif + + eloop_event_delete(fd->ctx->eloop, fd->fd); + close(fd->fd); + control_free(fd); +} + static void control_handle_data(void *arg) { struct fd_list *fd = arg; - char buffer[1024], *e, *p, *argvp[255], **ap, *a; + char buffer[1024]; ssize_t bytes; - size_t len; - int argc; bytes = read(fd->fd, buffer, sizeof(buffer) - 1); + if (bytes == -1 || bytes == 0) { /* Control was closed or there was an error. * Remove it from our list. */ control_delete(fd); return; } - buffer[bytes] = '\0'; - p = buffer; - e = buffer + bytes; + +#ifdef PRIVSEP + if (IN_PRIVSEP(fd->ctx)) { + ssize_t err; + + fd->flags |= FD_SENDLEN; + err = ps_ctl_handleargs(fd, buffer, (size_t)bytes); + fd->flags &= ~FD_SENDLEN; + if (err == -1) { + logerr(__func__); + return; + } + if (err == 1 && + ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) { + logerr(__func__); + control_delete(fd); + } + return; + } +#endif + + control_recvdata(fd, buffer, (size_t)bytes); +} + +void +control_recvdata(struct fd_list *fd, char *data, size_t len) +{ + char *p = data, *e; + char *argvp[255], **ap; + int argc; /* Each command is \n terminated * Each argument is NULL separated */ - while (p < e) { + while (len != 0) { argc = 0; ap = argvp; - while (p < e) { - argc++; + while (len != 0) { + if (*p == '\0') { + p++; + len--; + continue; + } + e = memchr(p, '\0', len); + if (e == NULL) { + errno = EINVAL; + logerrx("%s: no terminator", __func__); + return; + } if ((size_t)argc >= sizeof(argvp) / sizeof(argvp[0])) { errno = ENOBUFS; + logerrx("%s: no arg buffer", __func__); return; } - a = *ap++ = p; - len = strlen(p); - p += len + 1; - if (len && a[len - 1] == '\n') { - a[len - 1] = '\0'; + *ap++ = p; + argc++; + e++; + len -= (size_t)(e - p); + p = e; + e--; + if (*(--e) == '\n') { + *e = '\0'; break; } } + if (argc == 0) { + logerrx("%s: no args", __func__); + continue; + } *ap = NULL; if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) { logerr(__func__); @@ -136,6 +199,26 @@ } } +struct fd_list * +control_new(struct dhcpcd_ctx *ctx, int fd, unsigned int flags) +{ + struct fd_list *l; + + l = malloc(sizeof(*l)); + if (l == NULL) + return NULL; + + l->ctx = ctx; + l->fd = fd; + l->flags = flags; + TAILQ_INIT(&l->queue); +#ifdef CTL_FREE_LIST + TAILQ_INIT(&l->free_queue); +#endif + TAILQ_INSERT_TAIL(&ctx->control_fds, l, next); + return l; +} + static void control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags) { @@ -154,20 +237,19 @@ fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) goto error; - l = malloc(sizeof(*l)); +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx) && !IN_PRIVSEP_SE(ctx)) + ; + else +#endif + fd_flags |= FD_SENDLEN; + + l = control_new(ctx, fd, fd_flags); if (l == NULL) goto error; - l->ctx = ctx; - l->fd = fd; - l->flags = fd_flags; - TAILQ_INIT(&l->queue); - l->queue_len = 0; -#ifdef CTL_FREE_LIST - TAILQ_INIT(&l->free_queue); -#endif - TAILQ_INSERT_TAIL(&ctx->control_fds, l, next); - eloop_event_add(ctx->eloop, l->fd, control_handle_data, l); + if (eloop_event_add(ctx->eloop, l->fd, control_handle_data, l) == -1) + logerr(__func__); return; error: @@ -193,22 +275,43 @@ } static int -make_sock(struct sockaddr_un *sa, const char *ifname, int unpriv) +make_path(char *path, size_t len, const char *ifname, sa_family_t family, + bool unpriv) +{ + const char *per; + const char *sunpriv; + + switch(family) { + case AF_INET: + per = "-4"; + break; + case AF_INET6: + per = "-6"; + break; + default: + per = ""; + break; + } + if (unpriv) + sunpriv = ifname ? ".unpriv" : "unpriv."; + else + sunpriv = ""; + return snprintf(path, len, CONTROLSOCKET, + ifname ? ifname : "", ifname ? per : "", + sunpriv, ifname ? "." : ""); +} + +static int +make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family, + bool unpriv) { int fd; -#define SOCK_FLAGS SOCK_CLOEXEC | SOCK_NONBLOCK - if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_FLAGS, 0)) == -1) + if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0)) == -1) return -1; -#undef SOCK_FLAGS memset(sa, 0, sizeof(*sa)); sa->sun_family = AF_UNIX; - if (unpriv) - strlcpy(sa->sun_path, UNPRIVSOCKET, sizeof(sa->sun_path)); - else { - snprintf(sa->sun_path, sizeof(sa->sun_path), CONTROLSOCKET, - ifname ? "-" : "", ifname ? ifname : ""); - } + make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family, unpriv); return fd; } @@ -216,14 +319,17 @@ #define S_UNPRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) static int -control_start1(struct dhcpcd_ctx *ctx, const char *ifname, mode_t fmode) +control_start1(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family, + mode_t fmode) { struct sockaddr_un sa; int fd; socklen_t len; - if ((fd = make_sock(&sa, ifname, (fmode & S_UNPRIV) == S_UNPRIV)) == -1) + fd = make_sock(&sa, ifname, family, (fmode & S_UNPRIV) == S_UNPRIV); + if (fd == -1) return -1; + len = (socklen_t)SUN_LEN(&sa); unlink(sa.sun_path); if (bind(fd, (struct sockaddr *)&sa, len) == -1 || @@ -237,76 +343,117 @@ return -1; } - if ((fmode & S_UNPRIV) != S_UNPRIV) +#ifdef PRIVSEP_RIGHTS + if (IN_PRIVSEP(ctx) && ps_rights_limit_fd_fctnl(fd) == -1) { + close(fd); + unlink(sa.sun_path); + return -1; + } +#endif + + if ((fmode & S_UNPRIV) == S_UNPRIV) + strlcpy(ctx->control_sock_unpriv, sa.sun_path, + sizeof(ctx->control_sock_unpriv)); + else strlcpy(ctx->control_sock, sa.sun_path, sizeof(ctx->control_sock)); return fd; } int -control_start(struct dhcpcd_ctx *ctx, const char *ifname) +control_start(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) { int fd; - if ((fd = control_start1(ctx, ifname, S_PRIV)) == -1) +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) { + make_path(ctx->control_sock, sizeof(ctx->control_sock), + ifname, family, false); + make_path(ctx->control_sock_unpriv, + sizeof(ctx->control_sock_unpriv), + ifname, family, true); + return 0; + } +#endif + + if ((fd = control_start1(ctx, ifname, family, S_PRIV)) == -1) return -1; ctx->control_fd = fd; eloop_event_add(ctx->eloop, fd, control_handle, ctx); - if (ifname == NULL && (fd = control_start1(ctx, NULL, S_UNPRIV)) != -1){ - /* We must be in master mode, so create an unpriviledged socket - * to allow normal users to learn the status of dhcpcd. */ + if ((fd = control_start1(ctx, ifname, family, S_UNPRIV)) != -1) { ctx->control_unpriv_fd = fd; eloop_event_add(ctx->eloop, fd, control_handle_unpriv, ctx); } return ctx->control_fd; } +static int +control_unlink(struct dhcpcd_ctx *ctx, const char *file) +{ + int retval = 0; + + errno = 0; +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx)) + retval = (int)ps_root_unlink(ctx, file); + else +#else + UNUSED(ctx); +#endif + retval = unlink(file); + + return retval == -1 && errno != ENOENT ? -1 : 0; +} + int control_stop(struct dhcpcd_ctx *ctx) { int retval = 0; struct fd_list *l; - if (ctx->options & DHCPCD_FORKED) - goto freeit; + while ((l = TAILQ_FIRST(&ctx->control_fds)) != NULL) { + control_free(l); + } - if (ctx->control_fd == -1) - return 0; - eloop_event_delete(ctx->eloop, ctx->control_fd); - close(ctx->control_fd); - ctx->control_fd = -1; - if (unlink(ctx->control_sock) == -1) - retval = -1; +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) { + if (ps_root_unlink(ctx, ctx->control_sock) == -1) + retval = -1; + if (ps_root_unlink(ctx, ctx->control_sock_unpriv) == -1) + retval = -1; + return retval; + } else if (ctx->options & DHCPCD_FORKED) + return retval; +#endif + + if (ctx->control_fd != -1) { + eloop_event_delete(ctx->eloop, ctx->control_fd); + close(ctx->control_fd); + ctx->control_fd = -1; + if (control_unlink(ctx, ctx->control_sock) == -1) + retval = -1; + } if (ctx->control_unpriv_fd != -1) { eloop_event_delete(ctx->eloop, ctx->control_unpriv_fd); close(ctx->control_unpriv_fd); ctx->control_unpriv_fd = -1; - if (unlink(UNPRIVSOCKET) == -1) + if (control_unlink(ctx, ctx->control_sock_unpriv) == -1) retval = -1; } -freeit: - while ((l = TAILQ_FIRST(&ctx->control_fds))) { - TAILQ_REMOVE(&ctx->control_fds, l, next); - eloop_event_delete(ctx->eloop, l->fd); - close(l->fd); - control_queue_free(l); - free(l); - } - return retval; } int -control_open(const char *ifname) +control_open(const char *ifname, sa_family_t family, bool unpriv) { struct sockaddr_un sa; int fd; - if ((fd = make_sock(&sa, ifname, 0)) != -1) { + if ((fd = make_sock(&sa, ifname, family, unpriv)) != -1) { socklen_t len; len = (socklen_t)SUN_LEN(&sa); @@ -347,23 +494,31 @@ { struct fd_list *fd; struct iovec iov[2]; + int iov_len; struct fd_data *data; fd = arg; data = TAILQ_FIRST(&fd->queue); - iov[0].iov_base = &data->data_len; - iov[0].iov_len = sizeof(size_t); - iov[1].iov_base = data->data; - iov[1].iov_len = data->data_len; - if (writev(fd->fd, iov, 2) == -1) { - logerr(__func__); - if (errno != EINTR && errno != EAGAIN) - control_delete(fd); + + if (data->data_flags & FD_SENDLEN) { + iov[0].iov_base = &data->data_len; + iov[0].iov_len = sizeof(size_t); + iov[1].iov_base = data->data; + iov[1].iov_len = data->data_len; + iov_len = 2; + } else { + iov[0].iov_base = data->data; + iov[0].iov_len = data->data_len; + iov_len = 1; + } + + if (writev(fd->fd, iov, iov_len) == -1) { + logerr("%s: write", __func__); + control_delete(fd); return; } TAILQ_REMOVE(&fd->queue, data, next); - fd->queue_len--; #ifdef CTL_FREE_LIST TAILQ_INSERT_TAIL(&fd->free_queue, data, next); #else @@ -372,30 +527,35 @@ free(data); #endif - if (TAILQ_FIRST(&fd->queue) == NULL) - eloop_event_remove_writecb(fd->ctx->eloop, fd->fd); + if (TAILQ_FIRST(&fd->queue) != NULL) + return; + + if (eloop_event_remove_writecb(fd->ctx->eloop, fd->fd) == -1) + logerr(__func__); +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(fd->ctx) && !(fd->flags & FD_LISTEN)) { + if (ps_ctl_sendeof(fd) == -1) + logerr(__func__); + control_free(fd); + } +#endif } int -control_queue(struct fd_list *fd, void *data, size_t data_len, bool fit) +control_queue(struct fd_list *fd, void *data, size_t data_len) { struct fd_data *d; - if (data_len == 0) - return 0; + if (data_len == 0) { + errno = EINVAL; + return -1; + } #ifdef CTL_FREE_LIST struct fd_data *df; d = NULL; TAILQ_FOREACH(df, &fd->free_queue, next) { - if (!fit) { - if (df->data_size == 0) { - d = df; - break; - } - continue; - } if (d == NULL || d->data_size < df->data_size) { d = df; if (d->data_size <= data_len) @@ -407,28 +567,11 @@ else #endif { - if (fd->queue_len == CONTROL_QUEUE_MAX) { - errno = ENOBUFS; - return -1; - } - fd->queue_len++; d = calloc(1, sizeof(*d)); if (d == NULL) return -1; } - if (!fit) { -#ifdef CTL_FREE_LIST - if (d->data_size != 0) { - free(d->data); - d->data_size = 0; - } -#endif - d->data = data; - d->data_len = data_len; - goto queue; - } - if (d->data_size == 0) d->data = NULL; if (d->data_size < data_len) { @@ -443,19 +586,9 @@ } memcpy(d->data, data, data_len); d->data_len = data_len; + d->data_flags = fd->flags & FD_SENDLEN; -queue: TAILQ_INSERT_TAIL(&fd->queue, d, next); eloop_event_add_w(fd->ctx->eloop, fd->fd, control_writeone, fd); return 0; } - -void -control_close(struct dhcpcd_ctx *ctx) -{ - - if (ctx->control_fd != -1) { - close(ctx->control_fd); - ctx->control_fd = -1; - } -} Index: contrib/dhcpcd/src/defs.h =================================================================== --- contrib/dhcpcd/src/defs.h +++ contrib/dhcpcd/src/defs.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -29,7 +29,11 @@ #define CONFIG_H #define PACKAGE "dhcpcd" -#define VERSION "8.1.0" +#define VERSION "9.4.1" + +#ifndef PRIVSEP_USER +# define PRIVSEP_USER "_" PACKAGE +#endif #ifndef CONFIG # define CONFIG SYSCONFDIR "/" PACKAGE ".conf" @@ -53,13 +57,10 @@ # define LEASEFILE6 LEASEFILE "6" #endif #ifndef PIDFILE -# define PIDFILE RUNDIR "/" PACKAGE "%s%s%s.pid" +# define PIDFILE RUNDIR "/%s%s%spid" #endif #ifndef CONTROLSOCKET -# define CONTROLSOCKET RUNDIR "/" PACKAGE "%s%s.sock" -#endif -#ifndef UNPRIVSOCKET -# define UNPRIVSOCKET RUNDIR "/" PACKAGE ".unpriv.sock" +# define CONTROLSOCKET RUNDIR "/%s%s%s%ssock" #endif #ifndef RDM_MONOFILE # define RDM_MONOFILE DBDIR "/rdm_monotonic" Index: contrib/dhcpcd/src/dev.h =================================================================== --- contrib/dhcpcd/src/dev.h +++ contrib/dhcpcd/src/dev.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -31,7 +31,7 @@ // dev plugin setup struct dev { const char *name; - int (*initialized)(const char *); + int (*initialised)(const char *); int (*listening)(void); int (*handle_device)(void *); int (*start)(void); @@ -47,15 +47,10 @@ // hooks for dhcpcd #ifdef PLUGIN_DEV #include "dhcpcd.h" -int dev_initialized(struct dhcpcd_ctx *, const char *); +int dev_initialised(struct dhcpcd_ctx *, const char *); int dev_listening(struct dhcpcd_ctx *); -int dev_start(struct dhcpcd_ctx *); +int dev_start(struct dhcpcd_ctx *, int (*)(void *, int, const char *)); void dev_stop(struct dhcpcd_ctx *); -#else -#define dev_initialized(a, b) (1) -#define dev_listening(a) (0) -#define dev_start(a) {} -#define dev_stop(a) {} #endif #endif Index: contrib/dhcpcd/src/dev.c =================================================================== --- contrib/dhcpcd/src/dev.c +++ /dev/null @@ -1,191 +0,0 @@ -/* SPDX-License-Identifier: BSD-2-Clause */ -/* - * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -#define _INDEV -#include "common.h" -#include "dev.h" -#include "eloop.h" -#include "dhcpcd.h" -#include "logerr.h" - -int -dev_initialized(struct dhcpcd_ctx *ctx, const char *ifname) -{ - - if (ctx->dev == NULL) - return 1; - return ctx->dev->initialized(ifname); -} - -int -dev_listening(struct dhcpcd_ctx *ctx) -{ - - if (ctx->dev == NULL) - return 0; - return ctx->dev->listening(); -} - -static void -dev_stop1(struct dhcpcd_ctx *ctx, int stop) -{ - - if (ctx->dev) { - if (stop) - logdebugx("dev: unloaded %s", ctx->dev->name); - eloop_event_delete(ctx->eloop, ctx->dev_fd); - ctx->dev->stop(); - free(ctx->dev); - ctx->dev = NULL; - ctx->dev_fd = -1; - } - if (ctx->dev_handle) { - dlclose(ctx->dev_handle); - ctx->dev_handle = NULL; - } -} - -void -dev_stop(struct dhcpcd_ctx *ctx) -{ - - dev_stop1(ctx,!(ctx->options & DHCPCD_FORKED)); -} - -static int -dev_start2(struct dhcpcd_ctx *ctx, const char *name) -{ - char file[PATH_MAX]; - void *h; - void (*fptr)(struct dev *, const struct dev_dhcpcd *); - int r; - struct dev_dhcpcd dev_dhcpcd; - - snprintf(file, sizeof(file), DEVDIR "/%s", name); - h = dlopen(file, RTLD_LAZY); - if (h == NULL) { - logerrx("dlopen: %s", dlerror()); - return -1; - } - fptr = (void (*)(struct dev *, const struct dev_dhcpcd *)) - dlsym(h, "dev_init"); - if (fptr == NULL) { - logerrx("dlsym: %s", dlerror()); - dlclose(h); - return -1; - } - if ((ctx->dev = calloc(1, sizeof(*ctx->dev))) == NULL) { - logerr("%s: calloc", __func__); - dlclose(h); - return -1; - } - dev_dhcpcd.handle_interface = &dhcpcd_handleinterface; - fptr(ctx->dev, &dev_dhcpcd); - if (ctx->dev->start == NULL || (r = ctx->dev->start()) == -1) { - free(ctx->dev); - ctx->dev = NULL; - dlclose(h); - return -1; - } - loginfox("dev: loaded %s", ctx->dev->name); - ctx->dev_handle = h; - return r; -} - -static int -dev_start1(struct dhcpcd_ctx *ctx) -{ - DIR *dp; - struct dirent *d; - int r; - - if (ctx->dev) { - logerrx("dev: already started %s", ctx->dev->name); - return -1; - } - - if (ctx->dev_load) - return dev_start2(ctx, ctx->dev_load); - - dp = opendir(DEVDIR); - if (dp == NULL) { - logdebug("dev: %s", DEVDIR); - return 0; - } - - r = 0; - while ((d = readdir(dp))) { - if (d->d_name[0] == '.') - continue; - - r = dev_start2(ctx, d->d_name); - if (r != -1) - break; - } - closedir(dp); - return r; -} - -static void -dev_handle_data(void *arg) -{ - struct dhcpcd_ctx *ctx; - - ctx = arg; - if (ctx->dev->handle_device(arg) == -1) { - /* XXX: an error occured. should we restart dev? */ - } -} - -int -dev_start(struct dhcpcd_ctx *ctx) -{ - - if (ctx->dev_fd != -1) { - logerrx("%s: already started on fd %d", __func__, ctx->dev_fd); - return ctx->dev_fd; - } - - ctx->dev_fd = dev_start1(ctx); - if (ctx->dev_fd != -1) { - if (eloop_event_add(ctx->eloop, ctx->dev_fd, - dev_handle_data, ctx) == -1) - { - logerr(__func__); - dev_stop1(ctx, 1); - return -1; - } - } - - return ctx->dev_fd; -} Index: contrib/dhcpcd/src/dhcp-common.h =================================================================== --- contrib/dhcpcd/src/dhcp-common.h +++ contrib/dhcpcd/src/dhcp-common.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -81,6 +81,9 @@ #define OT_BITFLAG (1 << 27) #define OT_RESERVED (1 << 28) +#define DHC_REQ(r, n, o) \ + (has_option_mask((r), (o)) && !has_option_mask((n), (o))) + #define DHC_REQOPT(o, r, n) \ (!((o)->type & OT_NOREQ) && \ ((o)->type & OT_REQUEST || has_option_mask((r), (o)->option)) && \ @@ -133,6 +136,11 @@ const uint8_t *, size_t, struct dhcp_opt **), const uint8_t *od, size_t ol); void dhcp_zero_index(struct dhcp_opt *); -size_t dhcp_read_lease_fd(int, void **); +ssize_t dhcp_readfile(struct dhcpcd_ctx *, const char *, void *, size_t); +ssize_t dhcp_writefile(struct dhcpcd_ctx *, const char *, mode_t, + const void *, size_t); +int dhcp_filemtime(struct dhcpcd_ctx *, const char *, time_t *); +int dhcp_unlink(struct dhcpcd_ctx *, const char *); +size_t dhcp_read_hwaddr_aton(struct dhcpcd_ctx *, uint8_t **, const char *); #endif Index: contrib/dhcpcd/src/dhcp-common.c =================================================================== --- contrib/dhcpcd/src/dhcp-common.c +++ contrib/dhcpcd/src/dhcp-common.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -26,7 +26,6 @@ * SUCH DAMAGE. */ -#include #include #include @@ -176,9 +175,10 @@ return -1; p += l; len -= (size_t)l; - l = if_machinearch(p, len); + l = if_machinearch(p + 1, len - 1); if (l == -1 || (size_t)(l + 1) > len) return -1; + *p = ':'; p += l; return p - str; } @@ -202,8 +202,8 @@ continue; if (strncmp(token, "dhcp6_", 6) == 0) token += 6; - if (strncmp(token, "nd6_", 4) == 0) - token += 4; + if (strncmp(token, "nd_", 3) == 0) + token += 3; match = 0; for (i = 0, opt = odopts; i < odopts_len; i++, opt++) { if (opt->var == NULL || opt->option == 0) @@ -615,7 +615,7 @@ } /* Trim any extra data. - * Maybe we need a settng to reject DHCP options with extra data? */ + * Maybe we need a setting to reject DHCP options with extra data? */ if (opt->type & OT_ARRAY) return (ssize_t)(dl - (dl % sz)); return (ssize_t)sz; @@ -945,38 +945,85 @@ dhcp_zero_index(o); } -size_t -dhcp_read_lease_fd(int fd, void **lease) +ssize_t +dhcp_readfile(struct dhcpcd_ctx *ctx, const char *file, void *data, size_t len) { - struct stat st; - size_t sz; - void *buf; - ssize_t len; - if (fstat(fd, &st) != 0) - goto out; - if (!S_ISREG(st.st_mode)) { - errno = EINVAL; - goto out; - } - if (st.st_size > UINT32_MAX) { - errno = E2BIG; - goto out; - } +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return ps_root_readfile(ctx, file, data, len); +#else + UNUSED(ctx); +#endif - sz = (size_t)st.st_size; - if (sz == 0) - goto out; - if ((buf = malloc(sz)) == NULL) - goto out; - if ((len = read(fd, buf, sz)) == -1) { - free(buf); - goto out; - } - *lease = buf; - return (size_t)len; + return readfile(file, data, len); +} -out: - *lease = NULL; - return 0; +ssize_t +dhcp_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode, + const void *data, size_t len) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return ps_root_writefile(ctx, file, mode, data, len); +#else + UNUSED(ctx); +#endif + + return writefile(file, mode, data, len); +} + +int +dhcp_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return (int)ps_root_filemtime(ctx, file, time); +#else + UNUSED(ctx); +#endif + + return filemtime(file, time); +} + +int +dhcp_unlink(struct dhcpcd_ctx *ctx, const char *file) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + return (int)ps_root_unlink(ctx, file); +#else + UNUSED(ctx); +#endif + + return unlink(file); +} + +size_t +dhcp_read_hwaddr_aton(struct dhcpcd_ctx *ctx, uint8_t **data, const char *file) +{ + char buf[BUFSIZ]; + ssize_t bytes; + size_t len; + + bytes = dhcp_readfile(ctx, file, buf, sizeof(buf)); + if (bytes == -1 || bytes == sizeof(buf)) + return 0; + + bytes[buf] = '\0'; + len = hwaddr_aton(NULL, buf); + if (len == 0) + return 0; + *data = malloc(len); + if (*data == NULL) + return 0; + hwaddr_aton(*data, buf); + return len; } Index: contrib/dhcpcd/src/dhcp.h =================================================================== --- contrib/dhcpcd/src/dhcp.h +++ contrib/dhcpcd/src/dhcp.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -41,6 +41,7 @@ #include #include "arp.h" +#include "bpf.h" #include "auth.h" #include "dhcp-common.h" @@ -115,6 +116,7 @@ DHO_RAPIDCOMMIT = 80, /* RFC 4039 */ DHO_FQDN = 81, DHO_AUTHENTICATION = 90, /* RFC 3118 */ + DHO_IPV6_PREFERRED_ONLY = 108, /* RFC 8925 */ DHO_AUTOCONFIGURE = 116, /* RFC 2563 */ DHO_DNSSEARCH = 119, /* RFC 3397 */ DHO_CSR = 121, /* RFC 3442 */ @@ -138,6 +140,8 @@ FQDN_BOTH = 0x31 }; +#define MIN_V6ONLY_WAIT 300 /* seconds, RFC 8925 */ + /* Sizes for BOOTP options */ #define BOOTP_CHADDR_LEN 16 #define BOOTP_SNAME_LEN 64 @@ -164,6 +168,8 @@ /* DHCP allows a variable length vendor area */ }; +#define DHCP_MIN_LEN (offsetof(struct bootp, vend) + 4) + struct bootp_pkt { struct ip ip; @@ -214,14 +220,13 @@ size_t old_len; struct dhcp_lease lease; const char *reason; - time_t interval; - time_t nakoff; + unsigned int interval; + unsigned int nakoff; uint32_t xid; int socket; - int bpf_fd; - unsigned int bpf_flags; - int udp_fd; + struct bpf *bpf; + int udp_rfd; struct ipv4_addr *addr; uint8_t added; @@ -253,6 +258,9 @@ ssize_t print_rfc3361(FILE *, const uint8_t *, size_t); ssize_t print_rfc3442(FILE *, const uint8_t *, size_t); +int dhcp_openudp(struct in_addr *); +void dhcp_packet(struct interface *, uint8_t *, size_t, unsigned int); +void dhcp_recvmsg(struct dhcpcd_ctx *, struct msghdr *); void dhcp_printoptions(const struct dhcpcd_ctx *, const struct dhcp_opt *, size_t); uint16_t dhcp_get_mtu(const struct interface *); Index: contrib/dhcpcd/src/dhcp.c =================================================================== --- contrib/dhcpcd/src/dhcp.c +++ contrib/dhcpcd/src/dhcp.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -28,7 +28,6 @@ #include #include -#include #include #include @@ -41,6 +40,10 @@ #include #undef __FAVOR_BSD +#ifdef AF_LINK +# include +#endif + #include #include #include @@ -52,8 +55,9 @@ #include #include #include +#include -#define ELOOP_QUEUE 2 +#define ELOOP_QUEUE ELOOP_DHCP #include "config.h" #include "arp.h" #include "bpf.h" @@ -67,6 +71,7 @@ #include "ipv4.h" #include "ipv4ll.h" #include "logerr.h" +#include "privsep.h" #include "sa.h" #include "script.h" @@ -132,9 +137,7 @@ #endif static void dhcp_handledhcp(struct interface *, struct bootp *, size_t, const struct in_addr *); -#ifdef IP_PKTINFO static void dhcp_handleifudp(void *); -#endif static int dhcp_initstate(struct interface *); void @@ -164,8 +167,6 @@ } } -#define get_option_raw(ctx, bootp, bootp_len, opt) \ - get_option((ctx), (bootp), (bootp_len), NULL) static const uint8_t * get_option(struct dhcpcd_ctx *ctx, const struct bootp *bootp, size_t bootp_len, @@ -176,6 +177,11 @@ const uint8_t *op; size_t bl; + if (bootp == NULL || bootp_len < DHCP_MIN_LEN) { + errno = EINVAL; + return NULL; + } + /* Check we have the magic cookie */ if (!IS_DHCP(bootp)) { errno = ENOTSUP; @@ -484,7 +490,7 @@ return -1; break; case 1: - if (dl == 0 || dl % 4 != 0) { + if (dl % 4 != 0) { errno = EINVAL; break; } @@ -496,7 +502,7 @@ memcpy(&addr.s_addr, data, sizeof(addr.s_addr)); if (fprintf(fp, "%s", inet_ntoa(addr)) == -1) return -1; - if (dl != 0) { + if (dl != sizeof(addr.s_addr)) { if (fputc(' ', fp) == EOF) return -1; } @@ -771,17 +777,14 @@ (type == DHCP_REQUEST && state->addr->mask.s_addr == lease->mask.s_addr && (state->new == NULL || IS_DHCP(state->new)) && - !(state->added & STATE_FAKE)))) + !(state->added & (STATE_FAKE | STATE_EXPIRED))))) bootp->ciaddr = state->addr->addr.s_addr; bootp->op = BOOTREQUEST; - bootp->htype = (uint8_t)ifp->family; - switch (ifp->family) { - case ARPHRD_ETHER: - case ARPHRD_IEEE802: + bootp->htype = (uint8_t)ifp->hwtype; + if (ifp->hwlen != 0 && ifp->hwlen < sizeof(bootp->chaddr)) { bootp->hlen = (uint8_t)ifp->hwlen; memcpy(&bootp->chaddr, &ifp->hwaddr, ifp->hwlen); - break; } if (ifo->options & DHCPCD_BROADCAST && @@ -792,13 +795,14 @@ if (type != DHCP_DECLINE && type != DHCP_RELEASE) { struct timespec tv; + unsigned long long secs; clock_gettime(CLOCK_MONOTONIC, &tv); - timespecsub(&tv, &state->started, &tv); - if (tv.tv_sec < 0 || tv.tv_sec > (time_t)UINT16_MAX) + secs = eloop_timespec_diff(&tv, &state->started, NULL); + if (secs > UINT16_MAX) bootp->secs = htons((uint16_t)UINT16_MAX); else - bootp->secs = htons((uint16_t)tv.tv_sec); + bootp->secs = htons((uint16_t)secs); } bootp->xid = htonl(state->xid); @@ -813,10 +817,6 @@ memcpy(p, &ul, sizeof(ul)); p += sizeof(ul); - *p++ = DHO_MESSAGETYPE; - *p++ = 1; - *p++ = type; - #define AREA_LEFT (size_t)(e - p) #define AREA_FIT(s) if ((s) > AREA_LEFT) goto toobig #define AREA_CHECK(s) if ((s) + 2UL > AREA_LEFT) goto toobig @@ -828,26 +828,29 @@ p += 4; \ } while (0 /* CONSTCOND */) - if (state->clientid) { - AREA_CHECK(state->clientid[0]); - *p++ = DHO_CLIENTID; - memcpy(p, state->clientid, (size_t)state->clientid[0] + 1); - p += state->clientid[0] + 1; - } + /* Options are listed in numerical order as per RFC 7844 Section 3.1 + * XXX: They should be randomised. */ + bool putip = false; if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) { if (type == DHCP_DECLINE || (type == DHCP_REQUEST && (state->addr == NULL || - state->added & STATE_FAKE || + state->added & (STATE_FAKE | STATE_EXPIRED) || lease->addr.s_addr != state->addr->addr.s_addr))) { + putip = true; PUT_ADDR(DHO_IPADDRESS, &lease->addr); - if (lease->server.s_addr) - PUT_ADDR(DHO_SERVERID, &lease->server); } + } + + AREA_CHECK(3); + *p++ = DHO_MESSAGETYPE; + *p++ = 1; + *p++ = type; - if (type == DHCP_RELEASE) { + if (lease->addr.s_addr && lease->cookie == htonl(MAGIC_COOKIE)) { + if (type == DHCP_RELEASE || putip) { if (lease->server.s_addr) PUT_ADDR(DHO_SERVERID, &lease->server); } @@ -863,32 +866,75 @@ } } - if (type == DHCP_DISCOVER && - !(ifp->ctx->options & DHCPCD_TEST) && - has_option_mask(ifo->requestmask, DHO_RAPIDCOMMIT)) - { - /* RFC 4039 Section 3 */ - AREA_CHECK(0); - *p++ = DHO_RAPIDCOMMIT; - *p++ = 0; +#define DHCP_DIR(type) ((type) == DHCP_DISCOVER || (type) == DHCP_INFORM || \ + (type) == DHCP_REQUEST) + + if (DHCP_DIR(type)) { + /* vendor is already encoded correctly, so just add it */ + if (ifo->vendor[0]) { + AREA_CHECK(ifo->vendor[0]); + *p++ = DHO_VENDOR; + memcpy(p, ifo->vendor, (size_t)ifo->vendor[0] + 1); + p += ifo->vendor[0] + 1; + } } if (type == DHCP_DISCOVER && ifo->options & DHCPCD_REQUEST) PUT_ADDR(DHO_IPADDRESS, &ifo->req_addr); - /* RFC 2563 Auto Configure */ - if (type == DHCP_DISCOVER && ifo->options & DHCPCD_IPV4LL) { - AREA_CHECK(1); - *p++ = DHO_AUTOCONFIGURE; - *p++ = 1; - *p++ = 1; - } + if (DHCP_DIR(type)) { + if (type != DHCP_INFORM) { + if (ifo->leasetime != 0) { + AREA_CHECK(4); + *p++ = DHO_LEASETIME; + *p++ = 4; + ul = htonl(ifo->leasetime); + memcpy(p, &ul, 4); + p += 4; + } + } - if (type == DHCP_DISCOVER || - type == DHCP_INFORM || - type == DHCP_REQUEST) - { - if (mtu != -1) { + AREA_CHECK(0); + *p++ = DHO_PARAMETERREQUESTLIST; + n_params = p; + *p++ = 0; + for (i = 0, opt = ifp->ctx->dhcp_opts; + i < ifp->ctx->dhcp_opts_len; + i++, opt++) + { + if (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask)) + continue; + if (type == DHCP_INFORM && + (opt->option == DHO_RENEWALTIME || + opt->option == DHO_REBINDTIME)) + continue; + AREA_FIT(1); + *p++ = (uint8_t)opt->option; + } + for (i = 0, opt = ifo->dhcp_override; + i < ifo->dhcp_override_len; + i++, opt++) + { + /* Check if added above */ + for (lp = n_params + 1; lp < p; lp++) + if (*lp == (uint8_t)opt->option) + break; + if (lp < p) + continue; + if (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask)) + continue; + if (type == DHCP_INFORM && + (opt->option == DHO_RENEWALTIME || + opt->option == DHO_REBINDTIME)) + continue; + AREA_FIT(1); + *p++ = (uint8_t)opt->option; + } + *n_params = (uint8_t)(p - n_params - 1); + + if (mtu != -1 && + !(has_option_mask(ifo->nomask, DHO_MAXMESSAGESIZE))) + { AREA_CHECK(2); *p++ = DHO_MAXMESSAGESIZE; *p++ = 2; @@ -897,40 +943,45 @@ p += 2; } - if (ifo->userclass[0]) { + if (ifo->userclass[0] && + !has_option_mask(ifo->nomask, DHO_USERCLASS)) + { AREA_CHECK(ifo->userclass[0]); *p++ = DHO_USERCLASS; memcpy(p, ifo->userclass, (size_t)ifo->userclass[0] + 1); p += ifo->userclass[0] + 1; } + } - if (ifo->vendorclassid[0]) { - AREA_CHECK(ifo->vendorclassid[0]); - *p++ = DHO_VENDORCLASSID; - memcpy(p, ifo->vendorclassid, - (size_t)ifo->vendorclassid[0] + 1); - p += ifo->vendorclassid[0] + 1; - } + if (state->clientid) { + AREA_CHECK(state->clientid[0]); + *p++ = DHO_CLIENTID; + memcpy(p, state->clientid, (size_t)state->clientid[0] + 1); + p += state->clientid[0] + 1; + } - if (ifo->mudurl[0]) { - AREA_CHECK(ifo->mudurl[0]); - *p++ = DHO_MUDURL; - memcpy(p, ifo->mudurl, (size_t)ifo->mudurl[0] + 1); - p += ifo->mudurl[0] + 1; - } + if (DHCP_DIR(type) && + !has_option_mask(ifo->nomask, DHO_VENDORCLASSID) && + ifo->vendorclassid[0]) + { + AREA_CHECK(ifo->vendorclassid[0]); + *p++ = DHO_VENDORCLASSID; + memcpy(p, ifo->vendorclassid, (size_t)ifo->vendorclassid[0]+1); + p += ifo->vendorclassid[0] + 1; + } - if (type != DHCP_INFORM) { - if (ifo->leasetime != 0) { - AREA_CHECK(4); - *p++ = DHO_LEASETIME; - *p++ = 4; - ul = htonl(ifo->leasetime); - memcpy(p, &ul, 4); - p += 4; - } - } + if (type == DHCP_DISCOVER && + !(ifp->ctx->options & DHCPCD_TEST) && + DHC_REQ(ifo->requestmask, ifo->nomask, DHO_RAPIDCOMMIT)) + { + /* RFC 4039 Section 3 */ + AREA_CHECK(0); + *p++ = DHO_RAPIDCOMMIT; + *p++ = 0; + } + if (DHCP_DIR(type)) { hostname = dhcp_get_hostname(hbuf, sizeof(hbuf), ifo); /* @@ -977,29 +1028,53 @@ memcpy(p, hostname, len); p += len; } - - /* vendor is already encoded correctly, so just add it */ - if (ifo->vendor[0]) { - AREA_CHECK(ifo->vendor[0]); - *p++ = DHO_VENDOR; - memcpy(p, ifo->vendor, (size_t)ifo->vendor[0] + 1); - p += ifo->vendor[0] + 1; - } + } #ifdef AUTH - if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != - DHCPCD_AUTH_SENDREQUIRE && - !has_option_mask(ifo->nomask, DHO_FORCERENEW_NONCE)) - { - /* We support HMAC-MD5 */ - AREA_CHECK(1); - *p++ = DHO_FORCERENEW_NONCE; - *p++ = 1; - *p++ = AUTH_ALG_HMAC_MD5; + auth = NULL; /* appease GCC */ + auth_len = 0; + if (ifo->auth.options & DHCPCD_AUTH_SEND) { + ssize_t alen = dhcp_auth_encode(ifp->ctx, &ifo->auth, + state->auth.token, + NULL, 0, 4, type, NULL, 0); + if (alen != -1 && alen > UINT8_MAX) { + errno = ERANGE; + alen = -1; } + if (alen == -1) + logerr("%s: dhcp_auth_encode", ifp->name); + else if (alen != 0) { + auth_len = (uint8_t)alen; + AREA_CHECK(auth_len); + *p++ = DHO_AUTHENTICATION; + *p++ = auth_len; + auth = p; + p += auth_len; + } + } #endif - if (ifo->vivco_len) { + /* RFC 2563 Auto Configure */ + if (type == DHCP_DISCOVER && ifo->options & DHCPCD_IPV4LL && + !(has_option_mask(ifo->nomask, DHO_AUTOCONFIGURE))) + { + AREA_CHECK(1); + *p++ = DHO_AUTOCONFIGURE; + *p++ = 1; + *p++ = 1; + } + + if (DHCP_DIR(type)) { + if (ifo->mudurl[0]) { + AREA_CHECK(ifo->mudurl[0]); + *p++ = DHO_MUDURL; + memcpy(p, ifo->mudurl, (size_t)ifo->mudurl[0] + 1); + p += ifo->mudurl[0] + 1; + } + + if (ifo->vivco_len && + !has_option_mask(ifo->nomask, DHO_VIVCO)) + { AREA_CHECK(sizeof(ul)); *p++ = DHO_VIVCO; lp = p++; @@ -1025,68 +1100,19 @@ } } - AREA_CHECK(0); - *p++ = DHO_PARAMETERREQUESTLIST; - n_params = p; - *p++ = 0; - for (i = 0, opt = ifp->ctx->dhcp_opts; - i < ifp->ctx->dhcp_opts_len; - i++, opt++) - { - if (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask)) - continue; - if (type == DHCP_INFORM && - (opt->option == DHO_RENEWALTIME || - opt->option == DHO_REBINDTIME)) - continue; - AREA_FIT(1); - *p++ = (uint8_t)opt->option; - } - for (i = 0, opt = ifo->dhcp_override; - i < ifo->dhcp_override_len; - i++, opt++) - { - /* Check if added above */ - for (lp = n_params + 1; lp < p; lp++) - if (*lp == (uint8_t)opt->option) - break; - if (lp < p) - continue; - if (!DHC_REQOPT(opt, ifo->requestmask, ifo->nomask)) - continue; - if (type == DHCP_INFORM && - (opt->option == DHO_RENEWALTIME || - opt->option == DHO_REBINDTIME)) - continue; - AREA_FIT(1); - *p++ = (uint8_t)opt->option; - } - *n_params = (uint8_t)(p - n_params - 1); - } - #ifdef AUTH - auth = NULL; /* appease GCC */ - auth_len = 0; - if (ifo->auth.options & DHCPCD_AUTH_SEND) { - ssize_t alen = dhcp_auth_encode(&ifo->auth, - state->auth.token, - NULL, 0, 4, type, NULL, 0); - if (alen != -1 && alen > UINT8_MAX) { - errno = ERANGE; - alen = -1; - } - if (alen == -1) - logerr("%s: dhcp_auth_encode", ifp->name); - else if (alen != 0) { - auth_len = (uint8_t)alen; - AREA_CHECK(auth_len); - *p++ = DHO_AUTHENTICATION; - *p++ = auth_len; - auth = p; - p += auth_len; + if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != + DHCPCD_AUTH_SENDREQUIRE && + !has_option_mask(ifo->nomask, DHO_FORCERENEW_NONCE)) + { + /* We support HMAC-MD5 */ + AREA_CHECK(1); + *p++ = DHO_FORCERENEW_NONCE; + *p++ = 1; + *p++ = AUTH_ALG_HMAC_MD5; } - } #endif + } *p++ = DHO_END; len = (size_t)(p - (uint8_t *)bootp); @@ -1103,7 +1129,7 @@ #ifdef AUTH if (ifo->auth.options & DHCPCD_AUTH_SEND && auth_len != 0) - dhcp_auth_encode(&ifo->auth, state->auth.token, + dhcp_auth_encode(ifp->ctx, &ifo->auth, state->auth.token, (uint8_t *)bootp, len, 4, type, auth, auth_len); #endif @@ -1115,30 +1141,15 @@ return -1; } -static ssize_t -write_lease(const struct interface *ifp, const struct bootp *bootp, size_t len) -{ - int fd; - ssize_t bytes; - const struct dhcp_state *state = D_CSTATE(ifp); - - logdebugx("%s: writing lease `%s'", ifp->name, state->leasefile); - - fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd == -1) - return -1; - bytes = write(fd, bootp, len); - close(fd); - return bytes; -} - static size_t read_lease(struct interface *ifp, struct bootp **bootp) { - int fd; - bool fd_opened; + union { + struct bootp bootp; + uint8_t buf[FRAMELEN_MAX]; + } buf; struct dhcp_state *state = D_STATE(ifp); - struct bootp *lease; + ssize_t sbytes; size_t bytes; uint8_t type; #ifdef AUTH @@ -1150,37 +1161,27 @@ *bootp = NULL; if (state->leasefile[0] == '\0') { - fd = fileno(stdin); - fd_opened = false; + logdebugx("reading standard input"); + sbytes = read(fileno(stdin), buf.buf, sizeof(buf.buf)); } else { - fd = open(state->leasefile, O_RDONLY); - fd_opened = true; + logdebugx("%s: reading lease: %s", + ifp->name, state->leasefile); + sbytes = dhcp_readfile(ifp->ctx, state->leasefile, + buf.buf, sizeof(buf.buf)); } - if (fd == -1) { + if (sbytes == -1) { if (errno != ENOENT) - logerr("%s: open `%s'", - ifp->name, state->leasefile); + logerr("%s: %s", ifp->name, state->leasefile); return 0; } - if (state->leasefile[0] == '\0') - logdebugx("reading standard input"); - else - logdebugx("%s: reading lease `%s'", - ifp->name, state->leasefile); - - bytes = dhcp_read_lease_fd(fd, (void **)&lease); - if (fd_opened) - close(fd); - if (bytes == 0) - return 0; + bytes = (size_t)sbytes; /* Ensure the packet is at lease BOOTP sized * with a vendor area of 4 octets * (it should be more, and our read packet enforces this so this * code should not be needed, but of course people could * scribble whatever in the stored lease file. */ - if (bytes < offsetof(struct bootp, vend) + 4) { - free(lease); + if (bytes < DHCP_MIN_LEN) { logerrx("%s: %s: truncated lease", ifp->name, __func__); return 0; } @@ -1189,20 +1190,19 @@ goto out; /* We may have found a BOOTP server */ - if (get_option_uint8(ifp->ctx, &type, (struct bootp *)lease, bytes, + if (get_option_uint8(ifp->ctx, &type, &buf.bootp, bytes, DHO_MESSAGETYPE) == -1) type = 0; #ifdef AUTH /* Authenticate the message */ - auth = get_option(ifp->ctx, (struct bootp *)lease, bytes, + auth = get_option(ifp->ctx, &buf.bootp, bytes, DHO_AUTHENTICATION, &auth_len); if (auth) { if (dhcp_auth_validate(&state->auth, &ifp->options->auth, - lease, bytes, 4, type, auth, auth_len) == NULL) + &buf.bootp, bytes, 4, type, auth, auth_len) == NULL) { logerr("%s: authentication failed", ifp->name); - free(lease); return 0; } if (state->auth.token) @@ -1214,13 +1214,17 @@ DHCPCD_AUTH_SENDREQUIRE) { logerrx("%s: authentication now required", ifp->name); - free(lease); return 0; } #endif out: - *bootp = (struct bootp *)lease; + *bootp = malloc(bytes); + if (*bootp == NULL) { + logerr(__func__); + return 0; + } + memcpy(*bootp, buf.buf, bytes); return bytes; } @@ -1516,6 +1520,44 @@ #endif } +static void +dhcp_closebpf(struct interface *ifp) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + struct dhcp_state *state = D_STATE(ifp); + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) + ps_bpf_closebootp(ifp); +#endif + + if (state->bpf != NULL) { + eloop_event_delete(ctx->eloop, state->bpf->bpf_fd); + bpf_close(state->bpf); + state->bpf = NULL; + } +} + +static void +dhcp_closeinet(struct interface *ifp) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + struct dhcp_state *state = D_STATE(ifp); + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) { + if (state->addr != NULL) + ps_inet_closebootp(state->addr); + } +#endif + + if (state->udp_rfd != -1) { + eloop_event_delete(ctx->eloop, state->udp_rfd); + close(state->udp_rfd); + state->udp_rfd = -1; + } +} + void dhcp_close(struct interface *ifp) { @@ -1524,53 +1566,48 @@ if (state == NULL) return; - if (state->bpf_fd != -1) { - eloop_event_delete(ifp->ctx->eloop, state->bpf_fd); - bpf_close(ifp, state->bpf_fd); - state->bpf_fd = -1; - state->bpf_flags |= BPF_EOF; - } - if (state->udp_fd != -1) { - eloop_event_delete(ifp->ctx->eloop, state->udp_fd); - close(state->udp_fd); - state->udp_fd = -1; - } + dhcp_closebpf(ifp); + dhcp_closeinet(ifp); state->interval = 0; } -static int -dhcp_openudp(struct interface *ifp) +int +dhcp_openudp(struct in_addr *ia) { int s; struct sockaddr_in sin; int n; - if ((s = xsocket(PF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_UDP)) == -1) + if ((s = xsocket(PF_INET, SOCK_DGRAM | SOCK_CXNB, IPPROTO_UDP)) == -1) return -1; n = 1; if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) - goto eexit; -#ifdef IP_RECVPKTINFO + goto errexit; +#ifdef IP_RECVIF + if (setsockopt(s, IPPROTO_IP, IP_RECVIF, &n, sizeof(n)) == -1) + goto errexit; +#else if (setsockopt(s, IPPROTO_IP, IP_RECVPKTINFO, &n, sizeof(n)) == -1) - goto eexit; + goto errexit; +#endif +#ifdef SO_RERROR + if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1) + goto errexit; #endif + memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(BOOTPC); - if (ifp) { - const struct dhcp_state *state = D_CSTATE(ifp); - - if (state->addr) - sin.sin_addr.s_addr = state->addr->addr.s_addr; - } + if (ia != NULL) + sin.sin_addr = *ia; if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) == -1) - goto eexit; + goto errexit; return s; -eexit: +errexit: close(s); return -1; } @@ -1649,40 +1686,36 @@ static ssize_t dhcp_sendudp(struct interface *ifp, struct in_addr *to, void *data, size_t len) { - int s; - struct msghdr msg; - struct sockaddr_in sin; - struct iovec iov[1]; - struct dhcp_state *state = D_STATE(ifp); - ssize_t r; - - iov[0].iov_base = data; - iov[0].iov_len = len; - - memset(&sin, 0, sizeof(sin)); - sin.sin_family = AF_INET; - sin.sin_addr = *to; - sin.sin_port = htons(BOOTPS); + struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_addr = *to, + .sin_port = htons(BOOTPS), #ifdef HAVE_SA_LEN - sin.sin_len = sizeof(sin); + .sin_len = sizeof(sin), #endif + }; + struct udphdr udp = { + .uh_sport = htons(BOOTPC), + .uh_dport = htons(BOOTPS), + .uh_ulen = htons((uint16_t)(sizeof(udp) + len)), + }; + struct iovec iov[] = { + { .iov_base = &udp, .iov_len = sizeof(udp), }, + { .iov_base = data, .iov_len = len, }, + }; + struct msghdr msg = { + .msg_name = (void *)&sin, + .msg_namelen = sizeof(sin), + .msg_iov = iov, + .msg_iovlen = __arraycount(iov), + }; + struct dhcpcd_ctx *ctx = ifp->ctx; - memset(&msg, 0, sizeof(msg)); - msg.msg_name = (void *)&sin; - msg.msg_namelen = sizeof(sin); - msg.msg_iov = iov; - msg.msg_iovlen = 1; - - s = state->udp_fd; - if (s == -1) { - s = dhcp_openudp(ifp); - if (s == -1) - return -1; - } - r = sendmsg(s, &msg, 0); - if (state->udp_fd == -1) - close(s); - return r; +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) + return ps_inet_sendbootp(ifp, &msg); +#endif + return sendmsg(ctx->udp_wfd, &msg, 0); } static void @@ -1696,16 +1729,17 @@ size_t len, ulen; ssize_t r; struct in_addr from, to; - struct timespec tv; + unsigned int RT; - if (!callback) { + if (callback == NULL) { /* No carrier? Don't bother sending the packet. */ - if (ifp->carrier <= LINK_DOWN) + if (!if_is_link_up(ifp)) return; logdebugx("%s: sending %s with xid 0x%x", ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type), state->xid); + RT = 0; /* bogus gcc warning */ } else { if (state->interval == 0) state->interval = 4; @@ -1714,19 +1748,17 @@ if (state->interval > 64) state->interval = 64; } - tv.tv_sec = state->interval + DHCP_RAND_MIN; - tv.tv_nsec = (suseconds_t)arc4random_uniform( - (DHCP_RAND_MAX - DHCP_RAND_MIN) * NSEC_PER_SEC); - timespecnorm(&tv); + RT = (state->interval * MSEC_PER_SEC) + + (arc4random_uniform(MSEC_PER_SEC * 2) - MSEC_PER_SEC); /* No carrier? Don't bother sending the packet. * However, we do need to advance the timeout. */ - if (ifp->carrier <= LINK_DOWN) + if (!if_is_link_up(ifp)) goto fail; logdebugx("%s: sending %s (xid 0x%x), next in %0.1f seconds", ifp->name, ifo->options & DHCPCD_BOOTP ? "BOOTP" : get_dhcp_op(type), state->xid, - timespec_to_double(&tv)); + (float)RT / MSEC_PER_SEC); } r = make_message(&bootp, ifp, type); @@ -1734,7 +1766,9 @@ goto fail; len = (size_t)r; - if (ipv4_iffindaddr(ifp, &state->lease.addr, NULL) != NULL) + if (!(state->added & (STATE_FAKE | STATE_EXPIRED)) && + state->addr != NULL && + ipv4_iffindaddr(ifp, &state->lease.addr, NULL) != NULL) from.s_addr = state->lease.addr.s_addr; else from.s_addr = INADDR_ANY; @@ -1751,14 +1785,13 @@ * even if they are setup to send them. * Broadcasting from UDP is only an optimisation for rebinding * and on BSD, at least, is reliant on the subnet route being - * correctly configured to recieve the unicast reply. + * correctly configured to receive the unicast reply. * As such, we always broadcast and receive the reply to it via BPF. * This also guarantees we have a DHCP server attached to the * interface we want to configure because we can't dictate the * interface via IP_PKTINFO unlike for IPv6. */ - if (to.s_addr != INADDR_BROADCAST) - { + if (to.s_addr != INADDR_BROADCAST) { if (dhcp_sendudp(ifp, &to, bootp, len) != -1) goto out; logerr("%s: dhcp_sendudp", ifp->name); @@ -1771,9 +1804,13 @@ if (udp == NULL) { logerr("%s: dhcp_makeudppacket", ifp->name); r = 0; +#ifdef PRIVSEP + } else if (ifp->ctx->options & DHCPCD_PRIVSEP) { + r = ps_bpf_sendbootp(ifp, udp, ulen); + free(udp); +#endif } else { - r = bpf_send(ifp, state->bpf_fd, - ETHERTYPE_IP, (uint8_t *)udp, ulen); + r = bpf_send(state->bpf, ETHERTYPE_IP, udp, ulen); free(udp); } /* If we failed to send a raw packet this normally means @@ -1782,7 +1819,7 @@ * As such we remove it from consideration without actually * stopping the interface. */ if (r == -1) { - logerr("%s: if_sendraw", ifp->name); + logerr("%s: bpf_send", ifp->name); switch(errno) { case ENETDOWN: case ENETRESET: @@ -1804,8 +1841,8 @@ fail: /* Even if we fail to send a packet we should continue as we are * as our failure timeouts will change out codepath when needed. */ - if (callback) - eloop_timeout_add_tv(ifp->ctx->eloop, &tv, callback, ifp); + if (callback != NULL) + eloop_timeout_add_msec(ifp->ctx->eloop, RT, callback, ifp); } static void @@ -1853,14 +1890,16 @@ state->state = DHS_DISCOVER; dhcp_new_xid(ifp); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); - if (ifo->fallback) - eloop_timeout_add_sec(ifp->ctx->eloop, - ifo->reboot, dhcp_fallback, ifp); + if (!(state->added & STATE_EXPIRED)) { + if (ifo->fallback) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, dhcp_fallback, ifp); #ifdef IPV4LL - else if (ifo->options & DHCPCD_IPV4LL) - eloop_timeout_add_sec(ifp->ctx->eloop, - ifo->reboot, ipv4ll_start, ifp); + else if (ifo->options & DHCPCD_IPV4LL) + eloop_timeout_add_sec(ifp->ctx->eloop, + ifo->reboot, ipv4ll_start, ifp); #endif + } if (ifo->options & DHCPCD_REQUEST) loginfox("%s: soliciting a DHCP lease (requesting %s)", ifp->name, inet_ntoa(ifo->req_addr)); @@ -1880,31 +1919,22 @@ send_request(ifp); } -static void -dhcp_expire1(struct interface *ifp) -{ - struct dhcp_state *state = D_STATE(ifp); - - eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); - dhcp_drop(ifp, "EXPIRE"); - unlink(state->leasefile); - state->interval = 0; - if (!(ifp->options->options & DHCPCD_LINK) || ifp->carrier > LINK_DOWN) - dhcp_discover(ifp); -} - static void dhcp_expire(void *arg) { struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { logwarnx("%s: DHCP lease expired, extending lease", ifp->name); - return; + state->added |= STATE_EXPIRED; + } else { + logerrx("%s: DHCP lease expired", ifp->name); + dhcp_drop(ifp, "EXPIRE"); + dhcp_unlink(ifp->ctx, state->leasefile); } - - logerrx("%s: DHCP lease expired", ifp->name); - dhcp_expire1(ifp); + state->interval = 0; + dhcp_discover(ifp); } #if defined(ARP) || defined(IN_IFF_DUPLICATED) @@ -1999,20 +2029,16 @@ } #endif - /* If we forked, stop here. */ - if (ifp->ctx->options & DHCPCD_FORKED) - return; - #ifdef IPV4LL /* Stop IPv4LL now we have a working DHCP address */ - ipv4ll_drop(ifp); + if (!IN_LINKLOCAL(ntohl(ia->s_addr))) + ipv4ll_drop(ifp); #endif if (ifp->options->options & DHCPCD_INFORM) dhcp_inform(ifp); } - static bool dhcp_addr_duplicated(struct interface *ifp, struct in_addr *ia) { @@ -2030,7 +2056,7 @@ /* RFC 2131 3.1.5, Client-server interaction */ logerrx("%s: DAD detected %s", ifp->name, inet_ntoa(*ia)); - unlink(state->leasefile); + dhcp_unlink(ifp->ctx, state->leasefile); if (!(opts & DHCPCD_STATIC) && !state->lease.frominfo) dhcp_decline(ifp); #ifdef IN_IFF_DUPLICATED @@ -2043,7 +2069,7 @@ if (opts & (DHCPCD_STATIC | DHCPCD_INFORM)) { state->reason = "EXPIRE"; script_runreason(ifp, state->reason); -#define NOT_ONLY_SELF (DHCPCD_MASTER | DHCPCD_IPV6RS | DHCPCD_DHCP6) +#define NOT_ONLY_SELF (DHCPCD_MANAGER | DHCPCD_IPV6RS | DHCPCD_DHCP6) if (!(ctx->options & NOT_ONLY_SELF)) eloop_exit(ifp->ctx->eloop, EXIT_FAILURE); return deleted; @@ -2054,32 +2080,93 @@ } #endif -#if defined(ARP) && (!defined(KERNEL_RFC5227) || defined(ARPING)) +#ifdef ARP +#ifdef KERNEL_RFC5227 +#ifdef ARPING static void -dhcp_arp_not_found(struct arp_state *astate) +dhcp_arp_announced(struct arp_state *state) { - struct interface *ifp; + + arp_free(state); +} +#endif +#else +static void +dhcp_arp_defend_failed(struct arp_state *astate) +{ + struct interface *ifp = astate->iface; + + dhcp_drop(ifp, "EXPIRED"); + dhcp_start1(ifp); +} +#endif + +#if !defined(KERNEL_RFC5227) || defined(ARPING) +static void dhcp_arp_not_found(struct arp_state *); + +static struct arp_state * +dhcp_arp_new(struct interface *ifp, struct in_addr *addr) +{ + struct arp_state *astate; + + astate = arp_new(ifp, addr); + if (astate == NULL) + return NULL; + + astate->found_cb = dhcp_arp_found; + astate->not_found_cb = dhcp_arp_not_found; +#ifdef KERNEL_RFC5227 + astate->announced_cb = dhcp_arp_announced; +#else + astate->announced_cb = NULL; + astate->defend_failed_cb = dhcp_arp_defend_failed; +#endif + return astate; +} +#endif + #ifdef ARPING +static int +dhcp_arping(struct interface *ifp) +{ struct dhcp_state *state; struct if_options *ifo; + struct arp_state *astate; + struct in_addr addr; + + state = D_STATE(ifp); + ifo = ifp->options; + + if (ifo->arping_len == 0 || state->arping_index > ifo->arping_len) + return 0; + + if (state->arping_index + 1 == ifo->arping_len) { + state->arping_index++; + dhcpcd_startinterface(ifp); + return 1; + } + + addr.s_addr = ifo->arping[++state->arping_index]; + astate = dhcp_arp_new(ifp, &addr); + if (astate == NULL) { + logerr(__func__); + return -1; + } + arp_probe(astate); + return 1; +} #endif +#if !defined(KERNEL_RFC5227) || defined(ARPING) +static void +dhcp_arp_not_found(struct arp_state *astate) +{ + struct interface *ifp; + ifp = astate->iface; #ifdef ARPING - state = D_STATE(ifp); - ifo = ifp->options; - if (ifo->arping_len && state->arping_index < ifo->arping_len) { - /* We didn't find a profile for this - * address or hwaddr, so move to the next - * arping profile */ - if (++state->arping_index < ifo->arping_len) { - astate->addr.s_addr = - ifo->arping[state->arping_index]; - arp_probe(astate); - return; - } + if (dhcp_arping(ifp) == 1) { arp_free(astate); - dhcpcd_startinterface(ifp); return; } #endif @@ -2129,15 +2216,8 @@ arp_free(astate); dhcp_addr_duplicated(ifp, &addr); } +#endif -#ifdef KERNEL_RFC5227 -static void -dhcp_arp_announced(struct arp_state *state) -{ - - arp_free(state); -} -#endif /* KERNEL_RFC5227 */ #endif /* ARP */ void @@ -2147,6 +2227,7 @@ struct dhcp_state *state = D_STATE(ifp); struct if_options *ifo = ifp->options; struct dhcp_lease *lease = &state->lease; + uint8_t old_state; state->reason = NULL; /* If we don't have an offer, we are re-binding a lease on preference, @@ -2207,7 +2288,7 @@ "rebind time, forcing to %"PRIu32" seconds", ifp->name, lease->renewaltime); } - if (state->addr && + if (state->state == DHS_RENEW && state->addr && lease->addr.s_addr == state->addr->addr.s_addr && !(state->added & STATE_FAKE)) logdebugx("%s: leased %s for %"PRIu32" seconds", @@ -2226,7 +2307,9 @@ return; } if (state->reason == NULL) { - if (state->old && !(state->added & STATE_FAKE)) { + if (state->old && + !(state->added & (STATE_FAKE | STATE_EXPIRED))) + { if (state->old->yiaddr == state->new->yiaddr && lease->server.s_addr && state->state != DHS_REBIND) @@ -2242,63 +2325,89 @@ lease->renewaltime = lease->rebindtime = lease->leasetime; else { eloop_timeout_add_sec(ctx->eloop, - (time_t)lease->renewaltime, dhcp_startrenew, ifp); + lease->renewaltime, dhcp_startrenew, ifp); eloop_timeout_add_sec(ctx->eloop, - (time_t)lease->rebindtime, dhcp_rebind, ifp); + lease->rebindtime, dhcp_rebind, ifp); eloop_timeout_add_sec(ctx->eloop, - (time_t)lease->leasetime, dhcp_expire, ifp); + lease->leasetime, dhcp_expire, ifp); logdebugx("%s: renew in %"PRIu32" seconds, rebind in %"PRIu32 " seconds", ifp->name, lease->renewaltime, lease->rebindtime); } state->state = DHS_BOUND; if (!state->lease.frominfo && - !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))) - if (write_lease(ifp, state->new, state->new_len) == -1) - logerr(__func__); + !(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))) { + logdebugx("%s: writing lease: %s", + ifp->name, state->leasefile); + if (dhcp_writefile(ifp->ctx, state->leasefile, 0640, + state->new, state->new_len) == -1) + logerr("dhcp_writefile: %s", state->leasefile); + } + + old_state = state->added; + + if (!(ifo->options & DHCPCD_CONFIGURE)) { + struct ipv4_addr *ia; + + script_runreason(ifp, state->reason); + dhcpcd_daemonise(ifp->ctx); + + /* We we are not configuring the address, we need to keep + * the BPF socket open if the address does not exist. */ + ia = ipv4_iffindaddr(ifp, &state->lease.addr, NULL); + if (ia != NULL) { + state->addr = ia; + state->added = STATE_ADDED; + dhcp_closebpf(ifp); + goto openudp; + } + return; + } - ipv4_applyaddr(ifp); + /* Add the address */ + if (ipv4_applyaddr(ifp) == NULL) { + /* There was an error adding the address. + * If we are in oneshot, exit with a failure. */ + if (ctx->options & DHCPCD_ONESHOT) { + loginfox("exiting due to oneshot"); + eloop_exit(ctx->eloop, EXIT_FAILURE); + } + return; + } -#ifdef IP_PKTINFO /* Close the BPF filter as we can now receive DHCP messages * on a UDP socket. */ - if (state->udp_fd == -1 || - (state->old != NULL && state->old->yiaddr != state->new->yiaddr)) - { - dhcp_close(ifp); - /* If not in master mode, open an address specific socket. */ - if (ctx->udp_fd == -1) { - state->udp_fd = dhcp_openudp(ifp); - if (state->udp_fd == -1) { - logerr(__func__); - /* Address sharing without master mode is - * not supported. It's also possible another - * DHCP client could be running which is - * even worse. - * We still need to work, so re-open BPF. */ - dhcp_openbpf(ifp); - } else - eloop_event_add(ctx->eloop, - state->udp_fd, dhcp_handleifudp, ifp); - } + dhcp_closebpf(ifp); + +openudp: + /* If not in manager mode, open an address specific socket. */ + if (ctx->options & DHCPCD_MANAGER || + ifo->options & DHCPCD_STATIC || + (state->old != NULL && + state->old->yiaddr == state->new->yiaddr && + old_state & STATE_ADDED && !(old_state & STATE_FAKE))) + return; + + dhcp_closeinet(ifp); +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ctx)) { + if (ps_inet_openbootp(state->addr) == -1) + logerr(__func__); + return; } #endif -} - -static void -dhcp_lastlease(void *arg) -{ - struct interface *ifp = arg; - struct dhcp_state *state = D_STATE(ifp); - loginfox("%s: timed out contacting a DHCP server, using last lease", - ifp->name); - dhcp_bind(ifp); - /* If we forked, stop here. */ - if (ifp->ctx->options & DHCPCD_FORKED) + state->udp_rfd = dhcp_openudp(&state->addr->addr); + if (state->udp_rfd == -1) { + logerr(__func__); + /* Address sharing without manager mode is not supported. + * It's also possible another DHCP client could be running, + * which is even worse. + * We still need to work, so re-open BPF. */ + dhcp_openbpf(ifp); return; - state->interval = 0; - dhcp_discover(ifp); + } + eloop_event_add(ctx->eloop, state->udp_rfd, dhcp_handleifudp, ifp); } static size_t @@ -2329,39 +2438,6 @@ return sizeof(**bootp); } -#ifdef ARP -#ifndef KERNEL_RFC5227 -static void -dhcp_arp_defend_failed(struct arp_state *astate) -{ - - dhcp_drop(astate->iface, "EXPIRED"); - dhcp_start1(astate->iface); -} -#endif - -#if !defined(KERNEL_RFC5227) || defined(ARPING) -static struct arp_state * -dhcp_arp_new(struct interface *ifp, struct in_addr *addr) -{ - struct arp_state *astate; - - astate = arp_new(ifp, addr); - if (astate == NULL) - return NULL; - - astate->found_cb = dhcp_arp_found; - astate->not_found_cb = dhcp_arp_not_found; -#ifdef KERNEL_RFC5227 - astate->announced_cb = dhcp_arp_announced; -#else - astate->defend_failed_cb = dhcp_arp_defend_failed; -#endif - return astate; -} -#endif -#endif /* ARP */ - #if defined(ARP) || defined(KERNEL_RFC5227) static int dhcp_arp_address(struct interface *ifp) @@ -2397,23 +2473,25 @@ } #else if (!(ifp->flags & IFF_NOARP) && - ifp->options->options & DHCPCD_ARP && - ia == NULL) + ifp->options->options & DHCPCD_ARP) { struct arp_state *astate; struct dhcp_lease l; + /* Even if the address exists, we need to defend it. */ astate = dhcp_arp_new(ifp, &addr); if (astate == NULL) return -1; - state->state = DHS_PROBE; - get_lease(ifp, &l, state->offer, state->offer_len); - loginfox("%s: probing address %s/%d", - ifp->name, inet_ntoa(l.addr), inet_ntocidr(l.mask)); - /* We need to handle DAD. */ - arp_probe(astate); - return 0; + if (ia == NULL) { + state->state = DHS_PROBE; + get_lease(ifp, &l, state->offer, state->offer_len); + loginfox("%s: probing address %s/%d", + ifp->name, inet_ntoa(l.addr), inet_ntocidr(l.mask)); + /* We need to handle DAD. */ + arp_probe(astate); + return 0; + } } #endif @@ -2430,6 +2508,26 @@ } #endif +static void +dhcp_lastlease(void *arg) +{ + struct interface *ifp = arg; + struct dhcp_state *state = D_STATE(ifp); + + loginfox("%s: timed out contacting a DHCP server, using last lease", + ifp->name); +#if defined(ARP) || defined(KERNEL_RFC5227) + dhcp_arp_bind(ifp); +#else + dhcp_bind(ifp); +#endif + /* Set expired here because dhcp_bind() -> ipv4_addaddr() will reset + * state */ + state->added |= STATE_EXPIRED; + state->interval = 0; + dhcp_discover(ifp); +} + static void dhcp_static(struct interface *ifp) { @@ -2472,7 +2570,6 @@ state = D_STATE(ifp); ifo = ifp->options; - state->state = DHS_INFORM; free(state->offer); state->offer = NULL; state->offer_len = 0; @@ -2504,7 +2601,7 @@ state->offer_len = dhcp_message_new(&state->offer, &ifo->req_addr, &ifo->req_mask); #ifdef ARP - if (dhcp_arp_address(ifp) == 0) + if (dhcp_arp_address(ifp) != 1) return; #endif ia = ipv4_iffindaddr(ifp, @@ -2513,6 +2610,7 @@ } } + state->state = DHS_INFORM; state->addr = ia; state->offer_len = dhcp_message_new(&state->offer, &ia->addr, &ia->mask); @@ -2586,7 +2684,7 @@ state->state = DHS_REBOOT; state->interval = 0; - if (ifo->options & DHCPCD_LINK && ifp->carrier <= LINK_DOWN) { + if (ifo->options & DHCPCD_LINK && !if_is_link_up(ifp)) { loginfox("%s: waiting for carrier", ifp->name); return; } @@ -2611,6 +2709,11 @@ ifp->name, inet_ntoa(state->lease.addr)); #ifdef ARP +#ifndef KERNEL_RFC5227 + /* Create the DHCP ARP state so we can defend it. */ + (void)dhcp_arp_new(ifp, &state->lease.addr); +#endif + /* If the address exists on the interface and no other interface * is currently using it then announce it to ensure this * interface gets the reply. */ @@ -2680,8 +2783,8 @@ return; state->state = DHS_RELEASE; - unlink(state->leasefile); - if (ifp->carrier > LINK_DOWN && + dhcp_unlink(ifp->ctx, state->leasefile); + if (if_is_link_up(ifp) && state->new != NULL && state->lease.server.s_addr != INADDR_ANY) { @@ -2697,12 +2800,28 @@ #endif } } +#ifdef AUTH + else if (state->auth.reconf != NULL) { + /* + * Drop the lease as the token may only be present + * in the initial reply message and not subsequent + * renewals. + * If dhcpcd is restarted, the token is lost. + * XXX persist this in another file? + */ + dhcp_unlink(ifp->ctx, state->leasefile); + } +#endif eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); #ifdef AUTH dhcp_auth_reset(&state->auth); #endif + /* Close DHCP ports so a changed interface family is picked + * up by a new BPF state. */ + dhcp_close(ifp); + state->state = DHS_NONE; free(state->offer); state->offer = NULL; @@ -2713,7 +2832,13 @@ state->new = NULL; state->new_len = 0; state->reason = reason; - ipv4_applyaddr(ifp); + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_applyaddr(ifp); + else { + state->addr = NULL; + state->added = 0; + script_runreason(ifp, state->reason); + } free(state->old); state->old = NULL; state->old_len = 0; @@ -2750,7 +2875,7 @@ } static void -log_dhcp(logfunc_t *logfunc, const char *msg, +log_dhcp(int loglevel, const char *msg, const struct interface *ifp, const struct bootp *bootp, size_t bootp_len, const struct in_addr *from, int ad) { @@ -2797,10 +2922,10 @@ print_string(sname, sizeof(sname), OT_STRING | OT_DOMAIN, bootp->sname, sizeof(bootp->sname)); if (a == NULL) - logfunc("%s: %s %s %s `%s'", + logmessage(loglevel, "%s: %s %s %s %s", ifp->name, msg, tfrom, inet_ntoa(addr), sname); else - logfunc("%s: %s %s %s %s `%s'", + logmessage(loglevel, "%s: %s %s %s %s %s", ifp->name, msg, a, tfrom, inet_ntoa(addr), sname); } else { if (r != 0) { @@ -2808,10 +2933,10 @@ addr = *from; } if (a == NULL) - logfunc("%s: %s %s %s", + logmessage(loglevel, "%s: %s %s %s", ifp->name, msg, tfrom, inet_ntoa(addr)); else - logfunc("%s: %s %s %s %s", + logmessage(loglevel, "%s: %s %s %s %s", ifp->name, msg, a, tfrom, inet_ntoa(addr)); } free(a); @@ -2830,6 +2955,8 @@ xid = ntohl(bootp->xid); TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (ifn == ifp) + continue; state = D_CSTATE(ifn); if (state == NULL || state->state == DHS_NONE) continue; @@ -2856,6 +2983,8 @@ unsigned int i; char *msg; bool bootp_copied; + uint32_t v6only_time = 0; + bool use_v6only = false; #ifdef AUTH const uint8_t *auth; size_t auth_len; @@ -2941,7 +3070,7 @@ (uint8_t *)bootp, bootp_len, 4, type, auth, auth_len) == NULL) { - LOGDHCP0(logerrx, "authentication failed"); + LOGDHCP0(LOG_ERR, "authentication failed"); return; } if (state->auth.token) @@ -2951,10 +3080,10 @@ loginfox("%s: accepted reconfigure key", ifp->name); } else if (ifo->auth.options & DHCPCD_AUTH_SEND) { if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) { - LOGDHCP0(logerrx, "no authentication"); + LOGDHCP0(LOG_ERR, "no authentication"); return; } - LOGDHCP0(logwarnx, "no authentication"); + LOGDHCP0(LOG_WARNING, "no authentication"); } #endif @@ -2963,20 +3092,20 @@ if (from->s_addr == INADDR_ANY || from->s_addr == INADDR_BROADCAST) { - LOGDHCP(logerrx, "discarding Force Renew"); + LOGDHCP(LOG_ERR, "discarding Force Renew"); return; } #ifdef AUTH if (auth == NULL) { - LOGDHCP(logerrx, "unauthenticated Force Renew"); + LOGDHCP(LOG_ERR, "unauthenticated Force Renew"); if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) return; } if (state->state != DHS_BOUND && state->state != DHS_INFORM) { - LOGDHCP(logdebugx, "not bound, ignoring Force Renew"); + LOGDHCP(LOG_DEBUG, "not bound, ignoring Force Renew"); return; } - LOGDHCP(loginfox, "Force Renew from"); + LOGDHCP(LOG_INFO, "Force Renew from"); /* The rebind and expire timings are still the same, we just * enter the renew state early */ if (state->state == DHS_BOUND) @@ -2987,19 +3116,19 @@ dhcp_inform(ifp); } #else - LOGDHCP(logerrx, "unauthenticated Force Renew"); + LOGDHCP(LOG_ERR, "unauthenticated Force Renew"); #endif return; } if (state->state == DHS_BOUND) { - LOGDHCP(logdebugx, "bound, ignoring"); + LOGDHCP(LOG_DEBUG, "bound, ignoring"); return; } if (state->state == DHS_PROBE) { /* Ignore any DHCP messages whilst probing a lease to bind. */ - LOGDHCP(logdebugx, "probing, ignoring"); + LOGDHCP(LOG_DEBUG, "probing, ignoring"); return; } @@ -3012,7 +3141,7 @@ get_option_uint8(ifp->ctx, &tmp, bootp, bootp_len, (uint8_t)i) == 0) { - LOGDHCP(logwarnx, "reject DHCP"); + LOGDHCP(LOG_WARNING, "reject DHCP"); return; } } @@ -3023,12 +3152,12 @@ get_option_addr(ifp->ctx, &addr, bootp, bootp_len, DHO_SERVERID) == -1) { - LOGDHCP(logwarnx, "reject NAK"); + LOGDHCP(LOG_WARNING, "reject NAK"); return; } /* We should restart on a NAK */ - LOGDHCP(logwarnx, "NAK:"); + LOGDHCP(LOG_WARNING, "NAK:"); if ((msg = get_option_string(ifp->ctx, bootp, bootp_len, DHO_MESSAGE))) { @@ -3039,7 +3168,7 @@ return; if (!(ifp->ctx->options & DHCPCD_TEST)) { dhcp_drop(ifp, "NAK"); - unlink(state->leasefile); + dhcp_unlink(ifp->ctx, state->leasefile); } /* If we constantly get NAKS then we should slowly back off */ @@ -3068,14 +3197,31 @@ * always true. */ if (type == 0 && i == DHO_SERVERID) continue; - LOGDHCP(logwarnx, "reject DHCP"); + LOGDHCP(LOG_WARNING, "reject DHCP"); return; } } + if (has_option_mask(ifo->requestmask, DHO_IPV6_PREFERRED_ONLY)) { + if (get_option_uint32(ifp->ctx, &v6only_time, bootp, bootp_len, + DHO_IPV6_PREFERRED_ONLY) == 0 && + (state->state == DHS_DISCOVER || state->state == DHS_REBOOT)) + { + char v6msg[128]; + + use_v6only = true; + if (v6only_time < MIN_V6ONLY_WAIT) + v6only_time = MIN_V6ONLY_WAIT; + snprintf(v6msg, sizeof(v6msg), + "IPv6-Only Preferred received (%u seconds)", + v6only_time); + LOGDHCP(LOG_INFO, v6msg); + } + } + /* DHCP Auto-Configure, RFC 2563 */ if (type == DHCP_OFFER && bootp->yiaddr == 0) { - LOGDHCP(logwarnx, "no address given"); + LOGDHCP(LOG_WARNING, "no address given"); if ((msg = get_option_string(ifp->ctx, bootp, bootp_len, DHO_MESSAGE))) { @@ -3089,14 +3235,14 @@ { switch (tmp) { case 0: - LOGDHCP(logwarnx, "IPv4LL disabled from"); + LOGDHCP(LOG_WARNING, "IPv4LL disabled from"); ipv4ll_drop(ifp); #ifdef ARP arp_drop(ifp); #endif break; case 1: - LOGDHCP(logwarnx, "IPv4LL enabled from"); + LOGDHCP(LOG_WARNING, "IPv4LL enabled from"); ipv4ll_start(ifp); break; default: @@ -3107,26 +3253,36 @@ } eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_add_sec(ifp->ctx->eloop, - DHCP_MAX, dhcp_discover, ifp); + use_v6only ? v6only_time : DHCP_MAX, + dhcp_discover, ifp); } #endif return; } + if (use_v6only) { + dhcp_drop(ifp, "EXPIRE"); + dhcp_unlink(ifp->ctx, state->leasefile); + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + eloop_timeout_add_sec(ifp->ctx->eloop, v6only_time, + dhcp_discover, ifp); + return; + } + /* Ensure that the address offered is valid */ if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) && (bootp->ciaddr == INADDR_ANY || bootp->ciaddr == INADDR_BROADCAST) && (bootp->yiaddr == INADDR_ANY || bootp->yiaddr == INADDR_BROADCAST)) { - LOGDHCP(logwarnx, "reject invalid address"); + LOGDHCP(LOG_WARNING, "reject invalid address"); return; } #ifdef IN_IFF_DUPLICATED ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); if (ia && ia->addr_flags & IN_IFF_DUPLICATED) { - LOGDHCP(logwarnx, "declined duplicate address"); + LOGDHCP(LOG_WARNING, "declined duplicate address"); if (type) dhcp_decline(ifp); ipv4_deladdr(ia, 0); @@ -3157,7 +3313,7 @@ goto rapidcommit; } - LOGDHCP(loginfox, "offered"); + LOGDHCP(LOG_INFO, "offered"); if (state->offer_len < bootp_len) { free(state->offer); if ((state->offer = malloc(bootp_len)) == NULL) { @@ -3180,7 +3336,7 @@ state->reason = "TEST"; script_runreason(ifp, state->reason); eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); - state->bpf_flags |= BPF_EOF; + state->bpf->bpf_flags |= BPF_EOF; return; } eloop_timeout_delete(ifp->ctx->eloop, send_discover, ifp); @@ -3199,13 +3355,13 @@ if (type) { if (type == DHCP_OFFER) { - LOGDHCP(logwarnx, "ignoring offer of"); + LOGDHCP(LOG_WARNING, "ignoring offer of"); return; } /* We should only be dealing with acks */ if (type != DHCP_ACK) { - LOGDHCP(logerr, "not ACK or OFFER"); + LOGDHCP(LOG_ERR, "not ACK or OFFER"); return; } @@ -3217,14 +3373,14 @@ DHO_RAPIDCOMMIT, NULL)) state->state = DHS_REQUEST; else { - LOGDHCP(logdebugx, "ignoring ack of"); + LOGDHCP(LOG_DEBUG, "ignoring ack of"); return; } } rapidcommit: if (!(ifo->options & DHCPCD_INFORM)) - LOGDHCP(logdebugx, "acknowledged"); + LOGDHCP(LOG_DEBUG, "acknowledged"); else ifo->options &= ~DHCPCD_STATIC; } @@ -3275,26 +3431,35 @@ { struct ip *ip = packet; size_t ip_hlen; - struct udphdr *udp; + struct udphdr udp; - if (sizeof(*ip) > plen) + if (plen < sizeof(*ip)) return false; if (ip->ip_v != IPVERSION || ip->ip_p != IPPROTO_UDP) return false; /* Sanity. */ - if (ntohs(ip->ip_len) != plen) + if (ntohs(ip->ip_len) > plen) return false; ip_hlen = (size_t)ip->ip_hl * 4; + if (ip_hlen < sizeof(*ip)) + return false; + /* Check we have a UDP header and BOOTP. */ - if (ip_hlen + sizeof(*udp) + offsetof(struct bootp, vend) > plen) + if (ip_hlen + sizeof(udp) + offsetof(struct bootp, vend) > plen) + return false; + + /* Sanity. */ + memcpy(&udp, (char *)ip + ip_hlen, sizeof(udp)); + if (ntohs(udp.uh_ulen) < sizeof(udp)) + return false; + if (ip_hlen + ntohs(udp.uh_ulen) > plen) return false; /* Check it's to and from the right ports. */ - udp = (struct udphdr *)(void *)((char *)ip + ip_hlen); - if (udp->uh_dport != htons(BOOTPC) || udp->uh_sport != htons(BOOTPS)) + if (udp.uh_dport != htons(BOOTPC) || udp.uh_sport != htons(BOOTPS)) return false; return true; @@ -3306,14 +3471,19 @@ struct in_addr *from, unsigned int flags) { struct ip *ip = packet; - struct ip pseudo_ip = { - .ip_p = IPPROTO_UDP, - .ip_src = ip->ip_src, - .ip_dst = ip->ip_dst + union pip { + struct ip ip; + uint16_t w[sizeof(struct ip) / 2]; + } pip = { + .ip = { + .ip_p = IPPROTO_UDP, + .ip_src = ip->ip_src, + .ip_dst = ip->ip_dst, + } }; size_t ip_hlen; - uint16_t udp_len, uh_sum; - struct udphdr *udp; + struct udphdr udp; + char *udpp, *uh_sump; uint32_t csum; if (from != NULL) @@ -3324,22 +3494,32 @@ return false; if (flags & BPF_PARTIALCSUM) - return 0; + return true; - udp = (struct udphdr *)(void *)((char *)ip + ip_hlen); - if (udp->uh_sum == 0) - return 0; + udpp = (char *)ip + ip_hlen; + memcpy(&udp, udpp, sizeof(udp)); + if (udp.uh_sum == 0) + return true; /* UDP checksum is based on a pseudo IP header alongside * the UDP header and payload. */ - udp_len = ntohs(udp->uh_ulen); - uh_sum = udp->uh_sum; - udp->uh_sum = 0; - pseudo_ip.ip_len = udp->uh_ulen; + pip.ip.ip_len = udp.uh_ulen; csum = 0; - in_cksum(&pseudo_ip, sizeof(pseudo_ip), &csum); - csum = in_cksum(udp, udp_len, &csum); - return csum == uh_sum; + + /* Need to zero the UDP sum in the packet for the checksum to work. */ + uh_sump = udpp + offsetof(struct udphdr, uh_sum); + memset(uh_sump, 0, sizeof(udp.uh_sum)); + + /* Checksum pseudo header and then UDP + payload. */ + in_cksum(pip.w, sizeof(pip.w), &csum); + csum = in_cksum(udpp, ntohs(udp.uh_ulen), &csum); + +#if 0 /* Not needed, just here for completeness. */ + /* Put the checksum back. */ + memcpy(uh_sump, &udp.uh_sum, sizeof(udp.uh_sum)); +#endif + + return csum == udp.uh_sum; } static void @@ -3354,6 +3534,13 @@ return; } + /* Unlikely, but appeases sanitizers. */ + if (len > FRAMELEN_MAX) { + logerrx("%s: packet exceeded frame length (%zu) from %s", + ifp->name, len, inet_ntoa(*from)); + return; + } + /* To make our IS_DHCP macro easy, ensure the vendor * area has at least 4 octets. */ v = len - offsetof(struct bootp, vend); @@ -3365,14 +3552,51 @@ dhcp_handledhcp(ifp, bootp, len, from); } -static void -dhcp_handlebpf(struct interface *ifp, uint8_t *data, size_t len) +void +dhcp_packet(struct interface *ifp, uint8_t *data, size_t len, + unsigned int bpf_flags) { struct bootp *bootp; struct in_addr from; size_t udp_len; + size_t fl = bpf_frame_header_len(ifp); +#ifdef PRIVSEP const struct dhcp_state *state = D_CSTATE(ifp); + /* It's possible that an interface departs and arrives in short + * order to receive a BPF frame out of order. + * There is a similar check in ARP, but much lower down the stack. + * It's not needed for other inet protocols because we send the + * message as a whole and select the interface off that and then + * check state. BPF on the other hand is very interface + * specific and we do need this check. */ + if (state == NULL) + return; + + /* Ignore double reads */ + if (IN_PRIVSEP(ifp->ctx)) { + switch (state->state) { + case DHS_BOUND: /* FALLTHROUGH */ + case DHS_RENEW: + return; + default: + break; + } + } +#endif + + /* Trim frame header */ + if (fl != 0) { + if (len < fl) { + logerrx("%s: %s: short frame header %zu", + __func__, ifp->name, len); + return; + } + len -= fl; + /* Move the data to avoid alignment errors. */ + memmove(data, data + fl, len); + } + /* Validate filter. */ if (!is_packet_udp_bootp(data, len)) { #ifdef BPF_DEBUG @@ -3381,7 +3605,7 @@ return; } - if (!checksums_valid(data, &from, state->bpf_flags)) { + if (!checksums_valid(data, &from, bpf_flags)) { logerrx("%s: checksum failure from %s", ifp->name, inet_ntoa(from)); return; @@ -3402,18 +3626,14 @@ dhcp_readbpf(void *arg) { struct interface *ifp = arg; - uint8_t buf[MTU_MAX]; + uint8_t buf[FRAMELEN_MAX]; ssize_t bytes; struct dhcp_state *state = D_STATE(ifp); + struct bpf *bpf = state->bpf; - /* Some RAW mechanisms are generic file descriptors, not sockets. - * This means we have no kernel call to just get one packet, - * so we have to process the entire buffer. */ - state->bpf_flags &= ~BPF_EOF; - state->bpf_flags |= BPF_READING; - while (!(state->bpf_flags & BPF_EOF)) { - bytes = bpf_read(ifp, state->bpf_fd, buf, sizeof(buf), - &state->bpf_flags); + bpf->bpf_flags &= ~BPF_EOF; + while (!(bpf->bpf_flags & BPF_EOF)) { + bytes = bpf_read(bpf, buf, sizeof(buf)); if (bytes == -1) { if (state->state != DHS_NONE) { logerr("%s: %s", __func__, ifp->name); @@ -3421,13 +3641,56 @@ } break; } - dhcp_handlebpf(ifp, buf, (size_t)bytes); + dhcp_packet(ifp, buf, (size_t)bytes, bpf->bpf_flags); /* Check we still have a state after processing. */ if ((state = D_STATE(ifp)) == NULL) break; + if ((bpf = state->bpf) == NULL) + break; } - if (state != NULL) - state->bpf_flags &= ~BPF_READING; +} + +void +dhcp_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg) +{ + struct sockaddr_in *from = (struct sockaddr_in *)msg->msg_name; + struct iovec *iov = &msg->msg_iov[0]; + struct interface *ifp; + const struct dhcp_state *state; + + ifp = if_findifpfromcmsg(ctx, msg, NULL); + if (ifp == NULL) { + logerr(__func__); + return; + } + state = D_CSTATE(ifp); + if (state == NULL) { + /* Try re-directing it to another interface. */ + dhcp_redirect_dhcp(ifp, (struct bootp *)iov->iov_base, + iov->iov_len, &from->sin_addr); + return; + } + + if (state->bpf != NULL) { + /* Avoid a duplicate read if BPF is open for the interface. */ + return; + } +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx)) { + switch (state->state) { + case DHS_BOUND: /* FALLTHROUGH */ + case DHS_RENEW: + break; + default: + /* Any other state we ignore it or will receive + * via BPF. */ + return; + } + } +#endif + + dhcp_handlebootp(ifp, iov->iov_base, iov->iov_len, + &from->sin_addr); } static void @@ -3435,30 +3698,35 @@ { const struct dhcp_state *state; struct sockaddr_in from; - unsigned char buf[10 * 1024]; /* Maximum MTU */ + union { + struct bootp bootp; + uint8_t buf[10 * 1024]; /* Maximum MTU */ + } iovbuf; struct iovec iov = { - .iov_base = buf, - .iov_len = sizeof(buf), + .iov_base = iovbuf.buf, + .iov_len = sizeof(iovbuf.buf), }; -#ifdef IP_PKTINFO - unsigned char ctl[CMSG_SPACE(sizeof(struct in_pktinfo))] = { 0 }; - char sfrom[INET_ADDRSTRLEN]; + union { + struct cmsghdr hdr; +#ifdef IP_RECVIF + uint8_t buf[CMSG_SPACE(sizeof(struct sockaddr_dl))]; +#else + uint8_t buf[CMSG_SPACE(sizeof(struct in_pktinfo))]; #endif + } cmsgbuf = { .buf = { 0 } }; struct msghdr msg = { .msg_name = &from, .msg_namelen = sizeof(from), .msg_iov = &iov, .msg_iovlen = 1, -#ifdef IP_PKTINFO - .msg_control = ctl, .msg_controllen = sizeof(ctl), -#endif + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), }; int s; ssize_t bytes; if (ifp != NULL) { state = D_CSTATE(ifp); - s = state->udp_fd; + s = state->udp_rfd; } else - s = ctx->udp_fd; + s = ctx->udp_rfd; bytes = recvmsg(s, &msg, 0); if (bytes == -1) { @@ -3466,31 +3734,8 @@ return; } -#ifdef IP_PKTINFO - inet_ntop(AF_INET, &from.sin_addr, sfrom, sizeof(sfrom)); - - if (ifp == NULL) { - ifp = if_findifpfromcmsg(ctx, &msg, NULL); - if (ifp == NULL) { - logerr(__func__); - return; - } - state = D_CSTATE(ifp); - if (state == NULL) { - logdebugx("%s: received BOOTP for inactive interface", - ifp->name); - return; - } - } - - if (state->bpf_fd != -1) { - /* Avoid a duplicate read if BPF is open for the interface. */ - return; - } - - dhcp_handlebootp(ifp, (struct bootp *)(void *)buf, (size_t)bytes, - &from.sin_addr); -#endif + iov.iov_len = (size_t)bytes; + dhcp_recvmsg(ctx, &msg); } static void @@ -3501,16 +3746,13 @@ dhcp_readudp(ctx, NULL); } -#ifdef IP_PKTINFO static void dhcp_handleifudp(void *arg) { struct interface *ifp = arg; dhcp_readudp(ifp->ctx, ifp); - } -#endif static int dhcp_openbpf(struct interface *ifp) @@ -3518,11 +3760,22 @@ struct dhcp_state *state; state = D_STATE(ifp); - if (state->bpf_fd != -1) + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ifp->ctx)) { + if (ps_bpf_openbootp(ifp) == -1) { + logerr(__func__); + return -1; + } + return 0; + } +#endif + + if (state->bpf != NULL) return 0; - state->bpf_fd = bpf_open(ifp, bpf_bootp); - if (state->bpf_fd == -1) { + state->bpf = bpf_open(ifp, bpf_bootp, NULL); + if (state->bpf == NULL) { if (errno == ENOENT) { logerrx("%s not found", bpf_name); /* May as well disable IPv4 entirely at @@ -3534,35 +3787,10 @@ } eloop_event_add(ifp->ctx->eloop, - state->bpf_fd, dhcp_readbpf, ifp); + state->bpf->bpf_fd, dhcp_readbpf, ifp); return 0; } -int -dhcp_dump(struct interface *ifp) -{ - struct dhcp_state *state; - - ifp->if_data[IF_DATA_DHCP] = state = calloc(1, sizeof(*state)); - if (state == NULL) - goto eexit; - state->bpf_fd = -1; - dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), - AF_INET, ifp); - state->new_len = read_lease(ifp, &state->new); - if (state->new == NULL) { - logerr("%s: %s", - *ifp->name ? ifp->name : state->leasefile, __func__); - return -1; - } - state->reason = "DUMP"; - return script_runreason(ifp, state->reason); - -eexit: - logerr(__func__); - return -1; -} - void dhcp_free(struct interface *ifp) { @@ -3593,10 +3821,14 @@ } } if (ifp == NULL) { - if (ctx->udp_fd != -1) { - eloop_event_delete(ctx->eloop, ctx->udp_fd); - close(ctx->udp_fd); - ctx->udp_fd = -1; + if (ctx->udp_rfd != -1) { + eloop_event_delete(ctx->eloop, ctx->udp_rfd); + close(ctx->udp_rfd); + ctx->udp_rfd = -1; + } + if (ctx->udp_wfd != -1) { + close(ctx->udp_wfd); + ctx->udp_wfd = -1; } free(ctx->opt_buffer); @@ -3620,8 +3852,7 @@ state->state = DHS_NONE; /* 0 is a valid fd, so init to -1 */ - state->bpf_fd = -1; - state->udp_fd = -1; + state->udp_rfd = -1; #ifdef ARPING state->arping_index = -1; #endif @@ -3632,7 +3863,7 @@ dhcp_init(struct interface *ifp) { struct dhcp_state *state; - const struct if_options *ifo; + struct if_options *ifo; uint8_t len; char buf[(sizeof(ifo->clientid) - 1) * 3]; @@ -3650,12 +3881,16 @@ /* We need to drop the leasefile so that dhcp_start * doesn't load it. */ if (ifo->options & DHCPCD_REQUEST) - unlink(state->leasefile); + dhcp_unlink(ifp->ctx, state->leasefile); free(state->clientid); state->clientid = NULL; - if (*ifo->clientid) { + if (ifo->options & DHCPCD_ANONYMOUS) { + /* Removing the option could show that we want anonymous. + * As such keep it as it's already in the hwaddr field. */ + goto make_clientid; + } else if (*ifo->clientid) { state->clientid = malloc((size_t)(ifo->clientid[0] + 1)); if (state->clientid == NULL) goto eexit; @@ -3672,12 +3907,13 @@ memcpy(state->clientid + 6, ifp->ctx->duid, ifp->ctx->duid_len); } else { +make_clientid: len = (uint8_t)(ifp->hwlen + 1); state->clientid = malloc((size_t)len + 1); if (state->clientid == NULL) goto eexit; state->clientid[0] = len; - state->clientid[1] = (uint8_t)ifp->family; + state->clientid[1] = (uint8_t)ifp->hwtype; memcpy(state->clientid + 2, ifp->hwaddr, ifp->hwlen); } @@ -3688,7 +3924,7 @@ * at device start. */ return 0; - if (ifo->options & DHCPCD_CLIENTID) + if (ifo->options & DHCPCD_CLIENTID && state->clientid != NULL) logdebugx("%s: using ClientID %s", ifp->name, hwaddr_ntoa(state->clientid + 1, state->clientid[0], buf, sizeof(buf))); @@ -3709,7 +3945,6 @@ struct dhcpcd_ctx *ctx = ifp->ctx; struct if_options *ifo = ifp->options; struct dhcp_state *state; - struct stat st; uint32_t l; int nolease; @@ -3718,18 +3953,24 @@ /* Listen on *.*.*.*:bootpc so that the kernel never sends an * ICMP port unreachable message back to the DHCP server. - * Only do this in master mode so we don't swallow messages + * Only do this in manager mode so we don't swallow messages * for dhcpcd running on another interface. */ - if (ctx->udp_fd == -1 && ctx->options & DHCPCD_MASTER) { - ctx->udp_fd = dhcp_openudp(NULL); - if (ctx->udp_fd == -1) { - /* Don't log an error if some other process - * is handling this. */ - if (errno != EADDRINUSE) - logerr("%s: dhcp_openudp", __func__); - } else - eloop_event_add(ctx->eloop, - ctx->udp_fd, dhcp_handleudp, ctx); + if ((ctx->options & (DHCPCD_MANAGER|DHCPCD_PRIVSEP)) == DHCPCD_MANAGER + && ctx->udp_rfd == -1) + { + ctx->udp_rfd = dhcp_openudp(NULL); + if (ctx->udp_rfd == -1) { + logerr(__func__); + return; + } + eloop_event_add(ctx->eloop, ctx->udp_rfd, dhcp_handleudp, ctx); + } + if (!IN_PRIVSEP(ctx) && ctx->udp_wfd == -1) { + ctx->udp_wfd = xsocket(PF_INET, SOCK_RAW|SOCK_CXNB,IPPROTO_UDP); + if (ctx->udp_wfd == -1) { + logerr(__func__); + return; + } } if (dhcp_init(ifp) == -1) { @@ -3746,11 +3987,7 @@ #ifdef ARPING if (ifo->arping_len && state->arping_index < ifo->arping_len) { - struct arp_state *astate; - - astate = dhcp_arp_new(ifp, NULL); - if (astate) - dhcp_arp_not_found(astate); + dhcp_arping(ifp); return; } #endif @@ -3795,6 +4032,7 @@ } if (state->offer) { struct ipv4_addr *ia; + time_t mtime; get_lease(ifp, &state->lease, state->offer, state->offer_len); state->lease.frominfo = 1; @@ -3822,14 +4060,14 @@ state->offer_len = 0; } else if (!(ifo->options & DHCPCD_LASTLEASE_EXTEND) && state->lease.leasetime != DHCP_INFINITE_LIFETIME && - stat(state->leasefile, &st) == 0) + dhcp_filemtime(ifp->ctx, state->leasefile, &mtime) == 0) { time_t now; /* Offset lease times and check expiry */ now = time(NULL); if (now == -1 || - (time_t)state->lease.leasetime < now - st.st_mtime) + (time_t)state->lease.leasetime < now - mtime) { logdebugx("%s: discarding expired lease", ifp->name); @@ -3854,7 +4092,7 @@ dhcp_drop(ifp, "EXPIRE"); #endif } else { - l = (uint32_t)(now - st.st_mtime); + l = (uint32_t)(now - mtime); state->lease.leasetime -= l; state->lease.renewaltime -= l; state->lease.rebindtime -= l; @@ -3870,7 +4108,9 @@ } #endif - if (state->offer == NULL || !IS_DHCP(state->offer)) + if (state->offer == NULL || + !IS_DHCP(state->offer) || + ifo->options & DHCPCD_ANONYMOUS) dhcp_discover(ifp); else dhcp_reboot(ifp); @@ -3879,7 +4119,7 @@ void dhcp_start(struct interface *ifp) { - struct timespec tv; + unsigned int delay; #ifdef ARPING const struct dhcp_state *state; #endif @@ -3896,7 +4136,7 @@ /* If we haven't specified a ClientID and our hardware address * length is greater than BOOTP CHADDR then we enforce a ClientID - * of the hardware address family and the hardware address. + * of the hardware address type and the hardware address. * If there is no hardware address and no ClientID set, * force a DUID based ClientID. */ if (ifp->hwlen > 16) @@ -3906,7 +4146,7 @@ /* Firewire and InfiniBand interfaces require ClientID and * the broadcast option being set. */ - switch (ifp->family) { + switch (ifp->hwtype) { case ARPHRD_IEEE1394: /* FALLTHROUGH */ case ARPHRD_INFINIBAND: ifp->options->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST; @@ -3934,15 +4174,12 @@ return; } #endif - - tv.tv_sec = DHCP_MIN_DELAY; - tv.tv_nsec = (suseconds_t)arc4random_uniform( - (DHCP_MAX_DELAY - DHCP_MIN_DELAY) * NSEC_PER_SEC); - timespecnorm(&tv); + delay = MSEC_PER_SEC + + (arc4random_uniform(MSEC_PER_SEC * 2) - MSEC_PER_SEC); logdebugx("%s: delaying IPv4 for %0.1f seconds", - ifp->name, timespec_to_double(&tv)); + ifp->name, (float)delay / MSEC_PER_SEC); - eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcp_start1, ifp); + eloop_timeout_add_msec(ifp->ctx->eloop, delay, dhcp_start1, ifp); } void @@ -3961,7 +4198,8 @@ if (state != NULL && state->added) { rt_build(ifp->ctx, AF_INET); #ifdef ARP - arp_announceaddr(ifp->ctx, &state->addr->addr); + if (ifp->options->options & DHCPCD_ARP) + arp_announceaddr(ifp->ctx, &state->addr->addr); #endif } } @@ -3983,12 +4221,13 @@ if (state->addr == ia) { loginfox("%s: pid %d deleted IP address %s", ifp->name, pid, ia->saddr); + dhcp_close(ifp); state->addr = NULL; /* Don't clear the added state as we need * to drop the lease. */ dhcp_drop(ifp, "EXPIRE"); dhcp_start1(ifp); - return NULL; + return ia; } } @@ -4003,37 +4242,73 @@ #endif ifo = ifp->options; + +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ifp->ctx) && + !(ifp->ctx->options & (DHCPCD_MANAGER | DHCPCD_CONFIGURE)) && + IN_ARE_ADDR_EQUAL(&state->lease.addr, &ia->addr)) + { + state->addr = ia; + state->added = STATE_ADDED; + dhcp_closebpf(ifp); + if (ps_inet_openbootp(ia) == -1) + logerr(__func__); + } +#endif + + /* If we have requested a specific address, return now. + * The below code is only for when inform or static has been + * requested without a specific address. */ + if (ifo->req_addr.s_addr != INADDR_ANY) + return ia; + + /* Only inform if we are NOT in the inform state or bound. */ if (ifo->options & DHCPCD_INFORM) { - if (state->state != DHS_INFORM) + if (state->state != DHS_INFORM && state->state != DHS_BOUND) dhcp_inform(ifp); return ia; } + /* Static and inform are mutually exclusive. If not static, return. */ if (!(ifo->options & DHCPCD_STATIC)) return ia; - if (ifo->req_addr.s_addr != INADDR_ANY) - return ia; free(state->old); state->old = state->new; state->new_len = dhcp_message_new(&state->new, &ia->addr, &ia->mask); if (state->new == NULL) return ia; + if (ifp->flags & IFF_POINTOPOINT) { for (i = 1; i < 255; i++) if (i != DHO_ROUTER && has_option_mask(ifo->dstmask,i)) dhcp_message_add_addr(state->new, i, ia->brd); } + state->reason = "STATIC"; rt_build(ifp->ctx, AF_INET); script_runreason(ifp, state->reason); - if (ifo->options & DHCPCD_INFORM) { - state->state = DHS_INFORM; - dhcp_new_xid(ifp); - state->lease.server.s_addr = INADDR_ANY; - state->addr = ia; - dhcp_inform(ifp); - } return ia; } + +#ifndef SMALL +int +dhcp_dump(struct interface *ifp) +{ + struct dhcp_state *state; + + ifp->if_data[IF_DATA_DHCP] = state = calloc(1, sizeof(*state)); + if (state == NULL) { + logerr(__func__); + return -1; + } + state->new_len = read_lease(ifp, &state->new); + if (state->new == NULL) { + logerr("read_lease"); + return -1; + } + state->reason = "DUMP"; + return script_runreason(ifp, state->reason); +} +#endif Index: contrib/dhcpcd/src/dhcp6.h =================================================================== --- contrib/dhcpcd/src/dhcp6.h +++ contrib/dhcpcd/src/dhcp6.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -151,8 +151,10 @@ #define IRT_DEFAULT 86400 #define IRT_MINIMUM 600 -#define DHCP6_RAND_MIN -100 -#define DHCP6_RAND_MAX 100 +/* These should give -.1 to .1 randomness */ +#define DHCP6_RAND_MIN -100 +#define DHCP6_RAND_MAX 100 +#define DHCP6_RAND_DIV 1000.0f enum DH6S { DH6S_INIT, @@ -166,27 +168,28 @@ DH6S_INFORMED, DH6S_RENEW_REQUESTED, DH6S_PROBE, + DH6S_DECLINE, DH6S_DELEGATED, - DH6S_TIMEDOUT, - DH6S_ITIMEDOUT, DH6S_RELEASE, - DH6S_RELEASED + DH6S_RELEASED, }; struct dhcp6_state { enum DH6S state; struct timespec started; - /* Message retransmission timings */ - struct timespec RT; + /* Message retransmission timings in seconds */ unsigned int IMD; unsigned int RTC; - time_t IRT; + unsigned int IRT; unsigned int MRC; - time_t MRT; + unsigned int MRT; void (*MRCcallback)(void *); - time_t sol_max_rt; - time_t inf_max_rt; + unsigned int sol_max_rt; + unsigned int inf_max_rt; + unsigned int RT; /* retransmission timer in milliseconds + * maximal RT is 1 day + RAND, + * so should be enough */ struct dhcp6_message *send; size_t send_len; @@ -209,7 +212,11 @@ const char *reason; uint16_t lerror; /* Last error received from DHCPv6 reply. */ bool has_no_binding; + bool failed; /* Entered the failed state - used to rate limit log. */ + bool new_start; /* New external start, to determine log type. */ +#ifdef AUTH struct authstate auth; +#endif }; #define D6_STATE(ifp) \ @@ -220,6 +227,9 @@ (D6_CSTATE((ifp)) && \ D6_CSTATE((ifp))->reason && dhcp6_dadcompleted((ifp))) +int dhcp6_openraw(void); +int dhcp6_openudp(unsigned int, struct in6_addr *); +void dhcp6_recvmsg(struct dhcpcd_ctx *, struct msghdr *, struct ipv6_addr *); void dhcp6_printoptions(const struct dhcpcd_ctx *, const struct dhcp_opt *, size_t); const struct ipv6_addr *dhcp6_iffindaddr(const struct interface *ifp, @@ -234,7 +244,7 @@ const struct dhcp6_message *, size_t); void dhcp6_free(struct interface *); void dhcp6_handleifa(int, struct ipv6_addr *, pid_t); -int dhcp6_dadcompleted(const struct interface *); +bool dhcp6_dadcompleted(const struct interface *); void dhcp6_abort(struct interface *); void dhcp6_drop(struct interface *, const char *); int dhcp6_dump(struct interface *); Index: contrib/dhcpcd/src/dhcp6.c =================================================================== --- contrib/dhcpcd/src/dhcp6.c +++ contrib/dhcpcd/src/dhcp6.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -26,12 +26,11 @@ * SUCH DAMAGE. */ -/* TODO: We should decline dupliate addresses detected */ - -#include #include +#include #include +#include #include #include @@ -44,8 +43,9 @@ #include #include #include +#include -#define ELOOP_QUEUE 4 +#define ELOOP_QUEUE ELOOP_DHCP6 #include "config.h" #include "common.h" #include "dhcp.h" @@ -56,6 +56,7 @@ #include "if-options.h" #include "ipv6nd.h" #include "logerr.h" +#include "privsep.h" #include "script.h" #ifdef HAVE_SYS_BITOPS_H @@ -137,6 +138,7 @@ { DHCP6_INFORMATION_REQ, "INFORM6" }, { DHCP6_RELEASE, "RELEASE6" }, { DHCP6_RECONFIGURE, "RECONFIGURE6" }, + { DHCP6_DECLINE, "DECLINE6" }, { 0, NULL } }; @@ -145,7 +147,7 @@ uint16_t dhcp6_opt; }; -const struct dhcp_compat dhcp_compats[] = { +static const struct dhcp_compat dhcp_compats[] = { { DHO_DNSSERVER, D6_OPTION_DNS_SERVERS }, { DHO_HOSTNAME, D6_OPTION_FQDN }, { DHO_DNSDOMAIN, D6_OPTION_FQDN }, @@ -171,8 +173,20 @@ static void dhcp6_bind(struct interface *, const char *, const char *); static void dhcp6_failinform(void *); -static int dhcp6_listen(struct dhcpcd_ctx *, struct ipv6_addr *); static void dhcp6_recvaddr(void *); +static void dhcp6_startdecline(struct interface *); + +#ifdef SMALL +#define dhcp6_hasprefixdelegation(a) (0) +#else +static int dhcp6_hasprefixdelegation(struct interface *); +#endif + +#define DECLINE_IA(ia) \ + ((ia)->addr_flags & IN6_IFF_DUPLICATED && \ + (ia)->ia_type != 0 && (ia)->ia_type != D6_OPTION_IA_PD && \ + !((ia)->flags & IPV6_AF_STALE) && \ + (ia)->prefix_vltime != 0) void dhcp6_printoptions(const struct dhcpcd_ctx *ctx, @@ -245,28 +259,27 @@ dhcp6_makevendor(void *data, const struct interface *ifp) { const struct if_options *ifo; - size_t len, i; + size_t len, vlen, i; uint8_t *p; - ssize_t vlen; const struct vivco *vivco; - char vendor[VENDORCLASSID_MAX_LEN]; struct dhcp6_option o; ifo = ifp->options; len = sizeof(uint32_t); /* IANA PEN */ if (ifo->vivco_en) { + vlen = 0; for (i = 0, vivco = ifo->vivco; i < ifo->vivco_len; i++, vivco++) - len += sizeof(uint16_t) + vivco->len; - vlen = 0; /* silence bogus gcc warning */ - } else { - vlen = dhcp_vendor(vendor, sizeof(vendor)); - if (vlen == -1) - vlen = 0; - else - len += sizeof(uint16_t) + (size_t)vlen; - } + vlen += sizeof(uint16_t) + vivco->len; + len += vlen; + } else if (ifo->vendorclassid[0] != '\0') { + /* dhcpcd owns DHCPCD_IANA_PEN. + * If you need your own string, get your own IANA PEN. */ + vlen = strlen(ifp->ctx->vendor); + len += sizeof(uint16_t) + vlen; + } else + return 0; if (len > UINT16_MAX) { logerrx("%s: DHCPv6 Vendor Class too big", ifp->name); @@ -297,11 +310,11 @@ memcpy(p, vivco->data, vivco->len); p += vivco->len; } - } else if (vlen) { + } else if (ifo->vendorclassid[0] != '\0') { hvlen = htons((uint16_t)vlen); memcpy(p, &hvlen, sizeof(hvlen)); p += sizeof(hvlen); - memcpy(p, vendor, (size_t)vlen); + memcpy(p, ifp->ctx->vendor, vlen); } } @@ -401,7 +414,7 @@ uint16_t opt_len; struct dhcp6_state *state; struct timespec tv; - time_t hsec; + unsigned long long hsec; uint16_t sec; opt = dhcp6_findmoption(m, len, D6_OPTION_ELAPSED, &opt_len); @@ -420,14 +433,17 @@ state->started = tv; hsec = 0; } else { - timespecsub(&tv, &state->started, &tv); + unsigned long long secs; + unsigned int nsecs; + + secs = eloop_timespec_diff(&tv, &state->started, &nsecs); /* Elapsed time is measured in centiseconds. * We need to be sure it will not potentially overflow. */ - if (tv.tv_sec >= (UINT16_MAX / CSEC_PER_SEC) + 1) + if (secs >= (UINT16_MAX / CSEC_PER_SEC) + 1) hsec = UINT16_MAX; else { - hsec = (tv.tv_sec * CSEC_PER_SEC) + - (tv.tv_nsec / NSEC_PER_CSEC); + hsec = (secs * CSEC_PER_SEC) + + (nsecs / NSEC_PER_CSEC); if (hsec > UINT16_MAX) hsec = UINT16_MAX; } @@ -525,12 +541,12 @@ state->reason = "DELEGATED6"; } - if (sla == NULL || sla->sla_set == 0) { + if (sla == NULL || !sla->sla_set) { /* No SLA set, so make an assumption of * desired SLA and prefix length. */ asla.sla = ifp->index; asla.prefix_len = 0; - asla.sla_set = 0; + asla.sla_set = false; sla = &asla; } else if (sla->prefix_len == 0) { /* An SLA was given, but prefix length was not. @@ -538,7 +554,7 @@ * potentially more than one interface. */ asla.sla = sla->sla; asla.prefix_len = 0; - asla.sla_set = 0; + asla.sla_set = sla->sla_set; sla = &asla; } @@ -546,16 +562,15 @@ uint32_t sla_max; int bits; - if (ia->sla_max == 0) { + sla_max = ia->sla_max; + if (sla_max == 0 && (sla == NULL || !sla->sla_set)) { const struct interface *ifi; - sla_max = 0; TAILQ_FOREACH(ifi, ifp->ctx->ifaces, next) { if (ifi->index > sla_max) sla_max = ifi->index; } - } else - sla_max = ia->sla_max; + } bits = fls32(sla_max); @@ -622,7 +637,7 @@ uint8_t type; uint16_t si_len, uni_len, n_options; uint8_t *o_lenp; - struct if_options *ifo; + struct if_options *ifo = ifp->options; const struct dhcp_opt *opt, *opt2; const struct ipv6_addr *ap; char hbuf[HOSTNAME_MAX_LEN + 1]; @@ -634,6 +649,8 @@ #ifdef AUTH uint16_t auth_len; #endif + uint8_t duid[DUID_LEN]; + size_t duid_len = 0; state = D6_STATE(ifp); if (state->send) { @@ -641,8 +658,50 @@ state->send = NULL; } - ifo = ifp->options; - fqdn = ifo->fqdn; + switch(state->state) { + case DH6S_INIT: /* FALLTHROUGH */ + case DH6S_DISCOVER: + type = DHCP6_SOLICIT; + break; + case DH6S_REQUEST: + type = DHCP6_REQUEST; + break; + case DH6S_CONFIRM: + type = DHCP6_CONFIRM; + break; + case DH6S_REBIND: + type = DHCP6_REBIND; + break; + case DH6S_RENEW: + type = DHCP6_RENEW; + break; + case DH6S_INFORM: + type = DHCP6_INFORMATION_REQ; + break; + case DH6S_RELEASE: + type = DHCP6_RELEASE; + break; + case DH6S_DECLINE: + type = DHCP6_DECLINE; + break; + default: + errno = EINVAL; + return -1; + } + + /* RFC 4704 Section 5 says we can only send FQDN for these + * message types. */ + switch(type) { + case DHCP6_SOLICIT: + case DHCP6_REQUEST: + case DHCP6_RENEW: + case DHCP6_REBIND: + fqdn = ifo->fqdn; + break; + default: + fqdn = FQDN_DISABLE; + break; + } if (fqdn == FQDN_DISABLE && ifo->options & DHCPCD_HOSTNAME) { /* We're sending the DHCPv4 hostname option, so send FQDN as @@ -660,7 +719,7 @@ len = 0; si = NULL; hl = 0; /* Appease gcc */ - if (state->state != DH6S_RELEASE) { + if (state->state != DH6S_RELEASE && state->state != DH6S_DECLINE) { for (l = 0, opt = ifp->ctx->dhcp6_opts; l < ifp->ctx->dhcp6_opts_len; l++, opt++) @@ -702,19 +761,29 @@ len += sizeof(o) + 1 + hl; } - if (ifo->mudurl[0]) + if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) && + ifo->mudurl[0]) len += sizeof(o) + ifo->mudurl[0]; #ifdef AUTH if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != - DHCPCD_AUTH_SENDREQUIRE) + DHCPCD_AUTH_SENDREQUIRE && + DHC_REQ(ifo->requestmask6, ifo->nomask6, + D6_OPTION_RECONF_ACCEPT)) len += sizeof(o); /* Reconfigure Accept */ #endif } len += sizeof(*state->send); - len += sizeof(o) + ifp->ctx->duid_len; len += sizeof(o) + sizeof(uint16_t); /* elapsed */ + + if (ifo->options & DHCPCD_ANONYMOUS) { + duid_len = duid_make(duid, ifp, DUID_LL); + len += sizeof(o) + duid_len; + } else { + len += sizeof(o) + ifp->ctx->duid_len; + } + if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS)) len += dhcp6_makeuser(NULL, ifp); if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS)) @@ -728,6 +797,8 @@ m = state->recv; ml = state->recv_len; /* FALLTHROUGH */ + case DH6S_DECLINE: + /* FALLTHROUGH */ case DH6S_RELEASE: /* FALLTHROUGH */ case DH6S_RENEW: @@ -752,8 +823,11 @@ TAILQ_FOREACH(ap, &state->addrs, next) { if (ap->flags & IPV6_AF_STALE) continue; - if (ap->prefix_vltime == 0 && - !(ap->flags & IPV6_AF_REQUEST)) + if (!(ap->flags & IPV6_AF_REQUEST) && + (ap->prefix_vltime == 0 || + state->state == DH6S_DISCOVER)) + continue; + if (DECLINE_IA(ap) && state->state != DH6S_DECLINE) continue; if (ap->ia_type == D6_OPTION_IA_PD) { #ifndef SMALL @@ -782,7 +856,7 @@ if (state->state == DH6S_DISCOVER && !(ifp->ctx->options & DHCPCD_TEST) && - has_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT)) + DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT)) len += sizeof(o); if (m == NULL) { @@ -790,34 +864,6 @@ ml = state->new_len; } - switch(state->state) { - case DH6S_INIT: /* FALLTHROUGH */ - case DH6S_DISCOVER: - type = DHCP6_SOLICIT; - break; - case DH6S_REQUEST: - type = DHCP6_REQUEST; - break; - case DH6S_CONFIRM: - type = DHCP6_CONFIRM; - break; - case DH6S_REBIND: - type = DHCP6_REBIND; - break; - case DH6S_RENEW: - type = DHCP6_RENEW; - break; - case DH6S_INFORM: - type = DHCP6_INFORMATION_REQ; - break; - case DH6S_RELEASE: - type = DHCP6_RELEASE; - break; - default: - errno = EINVAL; - return -1; - } - switch(state->state) { case DH6S_REQUEST: /* FALLTHROUGH */ case DH6S_RENEW: /* FALLTHROUGH */ @@ -833,11 +879,11 @@ break; } - /* In non master mode we listen and send from fixed addresses. + /* In non manager mode we listen and send from fixed addresses. * We should try and match an address we have to unicast to, * but for now this is the safest policy. */ - if (unicast != NULL && !(ifp->ctx->options & DHCPCD_MASTER)) { - logdebugx("%s: ignoring unicast option as not master", + if (unicast != NULL && !(ifp->ctx->options & DHCPCD_MANAGER)) { + logdebugx("%s: ignoring unicast option as not manager", ifp->name); unicast = NULL; } @@ -845,7 +891,7 @@ #ifdef AUTH auth_len = 0; if (ifo->auth.options & DHCPCD_AUTH_SEND) { - ssize_t alen = dhcp_auth_encode(&ifo->auth, + ssize_t alen = dhcp_auth_encode(ifp->ctx, &ifo->auth, state->auth.token, NULL, 0, 6, type, NULL, 0); if (alen != -1 && alen > UINT16_MAX) { errno = ERANGE; @@ -881,35 +927,29 @@ memcpy(p, &o, sizeof(o)); \ p += sizeof(o); \ } -#define COPYIN(_code, _data, _len) { \ +#define COPYIN(_code, _data, _len) do { \ COPYIN1((_code), (_len)); \ if ((_len) != 0) { \ memcpy(p, (_data), (_len)); \ p += (_len); \ } \ -} +} while (0 /* CONSTCOND */) #define NEXTLEN (p + offsetof(struct dhcp6_option, len)) + /* Options are listed in numerical order as per RFC 7844 Section 4.1 + * XXX: They should be randomised. */ + p = (uint8_t *)state->send + sizeof(*state->send); - COPYIN(D6_OPTION_CLIENTID, ifp->ctx->duid, - (uint16_t)ifp->ctx->duid_len); + if (ifo->options & DHCPCD_ANONYMOUS) + COPYIN(D6_OPTION_CLIENTID, duid, + (uint16_t)duid_len); + else + COPYIN(D6_OPTION_CLIENTID, ifp->ctx->duid, + (uint16_t)ifp->ctx->duid_len); if (si != NULL) COPYIN(D6_OPTION_SERVERID, si, si_len); - si_len = 0; - COPYIN(D6_OPTION_ELAPSED, &si_len, sizeof(si_len)); - - if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS)) - p += dhcp6_makeuser(p, ifp); - if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS)) - p += dhcp6_makevendor(p, ifp); - - if (state->state == DH6S_DISCOVER && - !(ifp->ctx->options & DHCPCD_TEST) && - has_option_mask(ifo->requestmask6, D6_OPTION_RAPID_COMMIT)) - COPYIN1(D6_OPTION_RAPID_COMMIT, 0); - for (l = 0; IA && l < ifo->ia_len; l++) { ifia = &ifo->ia[l]; o_lenp = NEXTLEN; @@ -928,8 +968,11 @@ TAILQ_FOREACH(ap, &state->addrs, next) { if (ap->flags & IPV6_AF_STALE) continue; - if (ap->prefix_vltime == 0 && - !(ap->flags & IPV6_AF_REQUEST)) + if (!(ap->flags & IPV6_AF_REQUEST) && + (ap->prefix_vltime == 0 || + state->state == DH6S_DISCOVER)) + continue; + if (DECLINE_IA(ap) && state->state != DH6S_DECLINE) continue; if (ap->ia_type != ifia->ia_type) continue; @@ -992,7 +1035,74 @@ memcpy(o_lenp, &ia_na_len, sizeof(ia_na_len)); } - if (state->send->type != DHCP6_RELEASE) { + if (state->send->type != DHCP6_RELEASE && + state->send->type != DHCP6_DECLINE && + n_options) + { + o_lenp = NEXTLEN; + o.len = 0; + COPYIN1(D6_OPTION_ORO, 0); + for (l = 0, opt = ifp->ctx->dhcp6_opts; + l < ifp->ctx->dhcp6_opts_len; + l++, opt++) + { +#ifndef SMALL + for (n = 0, opt2 = ifo->dhcp6_override; + n < ifo->dhcp6_override_len; + n++, opt2++) + { + if (opt->option == opt2->option) + break; + } + if (n < ifo->dhcp6_override_len) + continue; +#endif + if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + continue; + o.code = htons((uint16_t)opt->option); + memcpy(p, &o.code, sizeof(o.code)); + p += sizeof(o.code); + o.len = (uint16_t)(o.len + sizeof(o.code)); + } +#ifndef SMALL + for (l = 0, opt = ifo->dhcp6_override; + l < ifo->dhcp6_override_len; + l++, opt++) + { + if (!DHC_REQOPT(opt, ifo->requestmask6, ifo->nomask6)) + continue; + o.code = htons((uint16_t)opt->option); + memcpy(p, &o.code, sizeof(o.code)); + p += sizeof(o.code); + o.len = (uint16_t)(o.len + sizeof(o.code)); + } + if (dhcp6_findselfsla(ifp)) { + o.code = htons(D6_OPTION_PD_EXCLUDE); + memcpy(p, &o.code, sizeof(o.code)); + p += sizeof(o.code); + o.len = (uint16_t)(o.len + sizeof(o.code)); + } +#endif + o.len = htons(o.len); + memcpy(o_lenp, &o.len, sizeof(o.len)); + } + + si_len = 0; + COPYIN(D6_OPTION_ELAPSED, &si_len, sizeof(si_len)); + + if (state->state == DH6S_DISCOVER && + !(ifp->ctx->options & DHCPCD_TEST) && + DHC_REQ(ifo->requestmask6, ifo->nomask6, D6_OPTION_RAPID_COMMIT)) + COPYIN1(D6_OPTION_RAPID_COMMIT, 0); + + if (!has_option_mask(ifo->nomask6, D6_OPTION_USER_CLASS)) + p += dhcp6_makeuser(p, ifp); + if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS)) + p += dhcp6_makevendor(p, ifp); + + if (state->send->type != DHCP6_RELEASE && + state->send->type != DHCP6_DECLINE) + { if (fqdn != FQDN_DISABLE) { o_lenp = NEXTLEN; COPYIN1(D6_OPTION_FQDN, 0); @@ -1018,67 +1128,19 @@ memcpy(o_lenp, &o.len, sizeof(o.len)); } - if (ifo->mudurl[0]) + if (!has_option_mask(ifo->nomask6, D6_OPTION_MUDURL) && + ifo->mudurl[0]) COPYIN(D6_OPTION_MUDURL, ifo->mudurl + 1, ifo->mudurl[0]); #ifdef AUTH if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) != DHCPCD_AUTH_SENDREQUIRE && - !has_option_mask(ifo->nomask6, D6_OPTION_RECONF_ACCEPT)) + DHC_REQ(ifo->requestmask6, ifo->nomask6, + D6_OPTION_RECONF_ACCEPT)) COPYIN1(D6_OPTION_RECONF_ACCEPT, 0); #endif - if (n_options) { - o_lenp = NEXTLEN; - o.len = 0; - COPYIN1(D6_OPTION_ORO, 0); - for (l = 0, opt = ifp->ctx->dhcp6_opts; - l < ifp->ctx->dhcp6_opts_len; - l++, opt++) - { -#ifndef SMALL - for (n = 0, opt2 = ifo->dhcp6_override; - n < ifo->dhcp6_override_len; - n++, opt2++) - { - if (opt->option == opt2->option) - break; - } - if (n < ifo->dhcp6_override_len) - continue; -#endif - if (!DHC_REQOPT(opt, ifo->requestmask6, - ifo->nomask6)) - continue; - o.code = htons((uint16_t)opt->option); - memcpy(p, &o.code, sizeof(o.code)); - p += sizeof(o.code); - o.len = (uint16_t)(o.len + sizeof(o.code)); - } -#ifndef SMALL - for (l = 0, opt = ifo->dhcp6_override; - l < ifo->dhcp6_override_len; - l++, opt++) - { - if (!DHC_REQOPT(opt, ifo->requestmask6, - ifo->nomask6)) - continue; - o.code = htons((uint16_t)opt->option); - memcpy(p, &o.code, sizeof(o.code)); - p += sizeof(o.code); - o.len = (uint16_t)(o.len + sizeof(o.code)); - } - if (dhcp6_findselfsla(ifp)) { - o.code = htons(D6_OPTION_PD_EXCLUDE); - memcpy(p, &o.code, sizeof(o.code)); - p += sizeof(o.code); - o.len = (uint16_t)(o.len + sizeof(o.code)); - } -#endif - o.len = htons(o.len); - memcpy(o_lenp, &o.len, sizeof(o.len)); - } } #ifdef AUTH @@ -1144,152 +1206,132 @@ return -1; state = D6_STATE(ifp); - return dhcp_auth_encode(&ifp->options->auth, state->auth.token, - (uint8_t *)state->send, state->send_len, - 6, state->send->type, opt, opt_len); + return dhcp_auth_encode(ifp->ctx, &ifp->options->auth, + state->auth.token, (uint8_t *)state->send, state->send_len, 6, + state->send->type, opt, opt_len); } #endif +static const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT; static int dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *)) { struct dhcp6_state *state = D6_STATE(ifp); struct dhcpcd_ctx *ctx = ifp->ctx; + unsigned int RT; + bool broadcast = true; struct sockaddr_in6 dst = { .sin6_family = AF_INET6, + /* Setting the port on Linux gives EINVAL when sending. + * This looks like a kernel bug as the equivalent works + * fine with the DHCP counterpart. */ +#ifndef __linux__ .sin6_port = htons(DHCP6_SERVER_PORT), +#endif }; - struct timespec RTprev; - double rnd; - time_t ms; - uint8_t neg; - const char *broad_uni; - const struct in6_addr alldhcp = IN6ADDR_LINKLOCAL_ALLDHCP_INIT; - struct ipv6_addr *lla; - int s; - struct iovec iov = { - .iov_base = state->send, .iov_len = state->send_len, + struct udphdr udp = { + .uh_sport = htons(DHCP6_CLIENT_PORT), + .uh_dport = htons(DHCP6_SERVER_PORT), + .uh_ulen = htons((uint16_t)(sizeof(udp) + state->send_len)), }; - unsigned char ctl[CMSG_SPACE(sizeof(struct in6_pktinfo))] = { 0 }; + struct iovec iov[] = { + { .iov_base = &udp, .iov_len = sizeof(udp), }, + { .iov_base = state->send, .iov_len = state->send_len, }, + }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf = { .buf = { 0 } }; struct msghdr msg = { .msg_name = &dst, .msg_namelen = sizeof(dst), - .msg_iov = &iov, .msg_iovlen = 1, + .msg_iov = iov, .msg_iovlen = __arraycount(iov), }; + char uaddr[INET6_ADDRSTRLEN]; - if (!callback && ifp->carrier <= LINK_DOWN) + if (!callback && !if_is_link_up(ifp)) return 0; -#ifdef HAVE_SA_LEN - dst.sin6_len = sizeof(dst); -#endif - - lla = ipv6_linklocal(ifp); - /* We need to ensure we have sufficient scope to unicast the address */ - /* XXX FIXME: We should check any added addresses we have like from - * a Router Advertisement */ - if (IN6_IS_ADDR_UNSPECIFIED(&state->unicast) || - (state->state == DH6S_REQUEST && - (!IN6_IS_ADDR_LINKLOCAL(&state->unicast) || lla == NULL))) - { - dst.sin6_addr = alldhcp; - broad_uni = "broadcasting"; - } else { - dst.sin6_addr = state->unicast; - broad_uni = "unicasting"; + if (!IN6_IS_ADDR_UNSPECIFIED(&state->unicast)) { + switch (state->send->type) { + case DHCP6_SOLICIT: /* FALLTHROUGH */ + case DHCP6_CONFIRM: /* FALLTHROUGH */ + case DHCP6_REBIND: + /* Unicasting is denied for these types. */ + break; + default: + broadcast = false; + inet_ntop(AF_INET6, &state->unicast, uaddr, + sizeof(uaddr)); + break; + } } + dst.sin6_addr = broadcast ? alldhcp : state->unicast; - if (!callback) - logdebugx("%s: %s %s with xid 0x%02x%02x%02x", + if (!callback) { + logdebugx("%s: %s %s with xid 0x%02x%02x%02x%s%s", ifp->name, - broad_uni, + broadcast ? "broadcasting" : "unicasting", dhcp6_get_op(state->send->type), state->send->xid[0], state->send->xid[1], - state->send->xid[2]); - else { + state->send->xid[2], + !broadcast ? " " : "", + !broadcast ? uaddr : ""); + RT = 0; + } else { if (state->IMD && !(ifp->options->options & DHCPCD_INITIAL_DELAY)) state->IMD = 0; if (state->IMD) { + state->RT = state->IMD * MSEC_PER_SEC; /* Some buggy PPP servers close the link too early * after sending an invalid status in their reply * which means this host won't see it. * 1 second grace seems to be the sweet spot. */ if (ifp->flags & IFF_POINTOPOINT) - state->RT.tv_sec = 1; - else - state->RT.tv_sec = 0; - state->RT.tv_nsec = (suseconds_t)arc4random_uniform( - (uint32_t)(state->IMD * NSEC_PER_SEC)); - timespecnorm(&state->RT); - broad_uni = "delaying"; - goto logsend; - } - if (state->RTC == 0) { - RTprev.tv_sec = state->IRT; - RTprev.tv_nsec = 0; - state->RT.tv_sec = RTprev.tv_sec; - state->RT.tv_nsec = 0; - } else { - RTprev = state->RT; - timespecadd(&state->RT, &state->RT, &state->RT); - } + state->RT += MSEC_PER_SEC; + } else if (state->RTC == 0) + state->RT = state->IRT * MSEC_PER_SEC; - rnd = DHCP6_RAND_MIN; - rnd += (suseconds_t)arc4random_uniform( - DHCP6_RAND_MAX - DHCP6_RAND_MIN); - rnd /= MSEC_PER_SEC; - neg = (rnd < 0.0); - if (neg) - rnd = -rnd; - ts_to_ms(ms, &RTprev); - ms = (time_t)((double)ms * rnd); - ms_to_ts(&RTprev, ms); - if (neg) - timespecsub(&state->RT, &RTprev, &state->RT); - else - timespecadd(&state->RT, &RTprev, &state->RT); - - if (state->MRT != 0 && state->RT.tv_sec > state->MRT) { - RTprev.tv_sec = state->MRT; - RTprev.tv_nsec = 0; - state->RT.tv_sec = state->MRT; - state->RT.tv_nsec = 0; - ts_to_ms(ms, &RTprev); - ms = (time_t)((double)ms * rnd); - ms_to_ts(&RTprev, ms); - if (neg) - timespecsub(&state->RT, &RTprev, &state->RT); - else - timespecadd(&state->RT, &RTprev, &state->RT); + if (state->MRT != 0) { + unsigned int mrt = state->MRT * MSEC_PER_SEC; + + if (state->RT > mrt) + state->RT = mrt; } -logsend: - if (ifp->carrier > LINK_DOWN) - logdebugx("%s: %s %s (xid 0x%02x%02x%02x)," + /* Add -.1 to .1 * RT randomness as per RFC8415 section 15 */ + uint32_t lru = arc4random_uniform( + state->RTC == 0 ? DHCP6_RAND_MAX + : DHCP6_RAND_MAX - DHCP6_RAND_MIN); + int lr = (int)lru - (state->RTC == 0 ? 0 : DHCP6_RAND_MAX); + RT = state->RT + + (unsigned int)((float)state->RT + * ((float)lr / DHCP6_RAND_DIV)); + + if (if_is_link_up(ifp)) + logdebugx("%s: %s %s (xid 0x%02x%02x%02x)%s%s," " next in %0.1f seconds", ifp->name, - broad_uni, + state->IMD != 0 ? "delaying" : + broadcast ? "broadcasting" : "unicasting", dhcp6_get_op(state->send->type), state->send->xid[0], state->send->xid[1], state->send->xid[2], - timespec_to_double(&state->RT)); - - /* This sometimes happens when we delegate to this interface - * AND run DHCPv6 on it normally. */ - assert(timespec_to_double(&state->RT) != 0); + state->IMD == 0 && !broadcast ? " " : "", + state->IMD == 0 && !broadcast ? uaddr : "", + (float)RT / MSEC_PER_SEC); /* Wait the initial delay */ if (state->IMD != 0) { state->IMD = 0; - eloop_timeout_add_tv(ctx->eloop, - &state->RT, callback, ifp); + eloop_timeout_add_msec(ctx->eloop, RT, callback, ifp); return 0; } } - if (ifp->carrier <= LINK_DOWN) + if (!if_is_link_up(ifp)) return 0; /* Update the elapsed time */ @@ -1305,13 +1347,13 @@ #endif /* Set the outbound interface */ - if (IN6_ARE_ADDR_EQUAL(&dst.sin6_addr, &alldhcp)) { + if (broadcast) { struct cmsghdr *cm; struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; dst.sin6_scope_id = ifp->index; - msg.msg_control = ctl; - msg.msg_controllen = sizeof(ctl); + msg.msg_control = cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); cm = CMSG_FIRSTHDR(&msg); if (cm == NULL) /* unlikely */ return -1; @@ -1321,16 +1363,15 @@ memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); } - if (ctx->dhcp6_fd != -1) - s = ctx->dhcp6_fd; - else if (lla != NULL && lla->dhcp6_fd != -1) - s = lla->dhcp6_fd; - else { - logerrx("%s: no socket to send from", ifp->name); - return -1; +#ifdef PRIVSEP + if (IN_PRIVSEP(ifp->ctx)) { + if (ps_inet_senddhcp6(ifp, &msg) == -1) + logerr(__func__); + goto sent; } +#endif - if (sendmsg(s, &msg, 0) == -1) { + if (sendmsg(ctx->dhcp6_wfd, &msg, 0) == -1) { logerr("%s: %s: sendmsg", __func__, ifp->name); /* Allow DHCPv6 to continue .... the errors * would be rate limited by the protocol. @@ -1338,14 +1379,20 @@ * associate with an access point. */ } +#ifdef PRIVSEP +sent: +#endif state->RTC++; if (callback) { + state->RT = RT * 2; + if (state->RT < RT) /* Check overflow */ + state->RT = RT; if (state->MRC == 0 || state->RTC < state->MRC) - eloop_timeout_add_tv(ctx->eloop, - &state->RT, callback, ifp); + eloop_timeout_add_msec(ctx->eloop, + RT, callback, ifp); else if (state->MRC != 0 && state->MRCcallback) - eloop_timeout_add_tv(ctx->eloop, - &state->RT, state->MRCcallback, ifp); + eloop_timeout_add_msec(ctx->eloop, + RT, state->MRCcallback, ifp); else logwarnx("%s: sent %d times with no reply", ifp->name, state->RTC); @@ -1395,6 +1442,13 @@ dhcp6_sendmessage(arg, dhcp6_sendconfirm); } +static void +dhcp6_senddecline(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_senddecline); +} + static void dhcp6_sendrelease(void *arg) { @@ -1439,7 +1493,7 @@ dhcp6_startrenew(ifp); } -int +bool dhcp6_dadcompleted(const struct interface *ifp) { const struct dhcp6_state *state; @@ -1449,9 +1503,9 @@ TAILQ_FOREACH(ap, &state->addrs, next) { if (ap->flags & IPV6_AF_ADDED && !(ap->flags & IPV6_AF_DADCOMPLETED)) - return 0; + return false; } - return 1; + return true; } static void @@ -1460,54 +1514,60 @@ struct ipv6_addr *ia = arg; struct interface *ifp; struct dhcp6_state *state; - int wascompleted, valid; + struct ipv6_addr *ia2; + bool completed, valid, oneduplicated; - wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED); + completed = (ia->flags & IPV6_AF_DADCOMPLETED); ia->flags |= IPV6_AF_DADCOMPLETED; - if (ia->flags & IPV6_AF_DUPLICATED) { - /* XXX FIXME - * We should decline the address */ + if (ia->addr_flags & IN6_IFF_DUPLICATED) logwarnx("%s: DAD detected %s", ia->iface->name, ia->saddr); - } - if (!wascompleted) { - ifp = ia->iface; +#ifdef ND6_ADVERTISE + else + ipv6nd_advertise(ia); +#endif + if (completed) + return; - state = D6_STATE(ifp); - if (state->state == DH6S_BOUND || - state->state == DH6S_DELEGATED) - { - struct ipv6_addr *ia2; + ifp = ia->iface; + state = D6_STATE(ifp); + if (state->state != DH6S_BOUND && state->state != DH6S_DELEGATED) + return; #ifdef SMALL - valid = true; + valid = true; #else - valid = (ia->delegating_prefix == NULL); -#endif - TAILQ_FOREACH(ia2, &state->addrs, next) { - if (ia2->flags & IPV6_AF_ADDED && - !(ia2->flags & IPV6_AF_DADCOMPLETED)) - { - wascompleted = 1; - break; - } - } - if (!wascompleted) { - logdebugx("%s: DHCPv6 DAD completed", - ifp->name); - script_runreason(ifp, -#ifndef SMALL - ia->delegating_prefix ? "DELEGATED6" : -#endif - state->reason); - if (valid) - dhcpcd_daemonise(ifp->ctx); - } -#ifdef ND6_ADVERTISE - ipv6nd_advertise(ia); + valid = (ia->delegating_prefix == NULL); #endif + completed = true; + oneduplicated = false; + TAILQ_FOREACH(ia2, &state->addrs, next) { + if (ia2->flags & IPV6_AF_ADDED && + !(ia2->flags & IPV6_AF_DADCOMPLETED)) + { + completed = false; + break; } + if (DECLINE_IA(ia)) + oneduplicated = true; + } + if (!completed) + return; + + logdebugx("%s: DHCPv6 DAD completed", ifp->name); + + if (oneduplicated && state->state == DH6S_BOUND) { + dhcp6_startdecline(ifp); + return; } + + script_runreason(ifp, +#ifndef SMALL + ia->delegating_prefix ? "DELEGATED6" : +#endif + state->reason); + if (valid) + dhcpcd_daemonise(ifp->ctx); } static void @@ -1549,13 +1609,19 @@ { struct interface *ifp; struct dhcp6_state *state; + int llevel; ifp = arg; + state = D6_STATE(ifp); #ifndef SMALL - dhcp6_delete_delegates(ifp); + if (state->reason == NULL || strcmp(state->reason, "TIMEOUT6") != 0) + dhcp6_delete_delegates(ifp); #endif - loginfox("%s: soliciting a DHCPv6 lease", ifp->name); - state = D6_STATE(ifp); + if (state->new == NULL && !state->failed) + llevel = LOG_INFO; + else + llevel = LOG_DEBUG; + logmessage(llevel, "%s: soliciting a DHCPv6 lease", ifp->name); state->state = DH6S_DISCOVER; state->RTC = 0; state->IMD = SOL_MAX_DELAY; @@ -1579,11 +1645,15 @@ { struct interface *ifp; struct dhcp6_state *state; + int llevel; ifp = arg; state = D6_STATE(ifp); - if (state->new == NULL || ifp->options->options & DHCPCD_DEBUG) - loginfox("%s: requesting DHCPv6 information", ifp->name); + if (state->new_start || (state->new == NULL && !state->failed)) + llevel = LOG_INFO; + else + llevel = LOG_DEBUG; + logmessage(llevel, "%s: requesting DHCPv6 information", ifp->name); state->state = DH6S_INFORM; state->RTC = 0; state->IMD = INF_MAX_DELAY; @@ -1606,11 +1676,42 @@ INF_MAX_RD, dhcp6_failinform, ifp); } +static bool +dhcp6_startdiscoinform(struct interface *ifp) +{ + unsigned long long opts = ifp->options->options; + + if (opts & DHCPCD_IA_FORCED || ipv6nd_hasradhcp(ifp, true)) + dhcp6_startdiscover(ifp); + else if (opts & DHCPCD_INFORM6 || ipv6nd_hasradhcp(ifp, false)) + dhcp6_startinform(ifp); + else + return false; + return true; +} + +static void +dhcp6_leaseextend(struct interface *ifp) +{ + struct dhcp6_state *state = D6_STATE(ifp); + struct ipv6_addr *ia; + + logwarnx("%s: extending DHCPv6 lease", ifp->name); + TAILQ_FOREACH(ia, &state->addrs, next) { + ia->flags |= IPV6_AF_EXTENDED; + /* Set infinite lifetimes. */ + ia->prefix_pltime = ND6_INFINITE_LIFETIME; + ia->prefix_vltime = ND6_INFINITE_LIFETIME; + } +} + static void dhcp6_fail(struct interface *ifp) { struct dhcp6_state *state = D6_STATE(ifp); + state->failed = true; + /* RFC3315 18.1.2 says that prior addresses SHOULD be used on failure. * RFC2131 3.2.3 says that MAY chose to use the prior address. * Because dhcpcd was written first for RFC2131, we have the LASTLEASE @@ -1618,79 +1719,81 @@ * mobile clients. * dhcpcd also has LASTLEASE_EXTEND to extend this lease past it's * expiry, but this is strictly not RFC compliant in any way or form. */ - if (state->new == NULL || - !(ifp->options->options & DHCPCD_LASTLEASE)) + if (state->new != NULL && + ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { + dhcp6_leaseextend(ifp); + dhcp6_bind(ifp, NULL, NULL); + } else { + dhcp6_freedrop_addrs(ifp, 1, NULL); #ifndef SMALL dhcp6_delete_delegates(ifp); #endif - if (state->state != DH6S_INFORM) - dhcp6_startdiscover(ifp); - return; + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = NULL; + state->new_len = 0; + if (state->old != NULL) + script_runreason(ifp, "EXPIRE6"); + dhcp_unlink(ifp->ctx, state->leasefile); + dhcp6_addrequestedaddrs(ifp); } - switch (state->state) { - case DH6S_INFORM: - case DH6S_INFORMED: - state->state = DH6S_ITIMEDOUT; - break; - default: - state->state = DH6S_TIMEDOUT; - break; + if (!dhcp6_startdiscoinform(ifp)) { + logwarnx("%s: no advertising IPv6 router wants DHCP",ifp->name); + state->state = DH6S_INIT; + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); } +} - dhcp6_bind(ifp, NULL, NULL); +static int +dhcp6_failloglevel(struct interface *ifp) +{ + const struct dhcp6_state *state = D6_CSTATE(ifp); - switch (state->state) { - case DH6S_BOUND: - case DH6S_INFORMED: - break; - default: - dhcp6_startdiscover(ifp); - break; - } + return state->failed ? LOG_DEBUG : LOG_ERR; } static void dhcp6_failconfirm(void *arg) { - struct interface *ifp; + struct interface *ifp = arg; + int llevel = dhcp6_failloglevel(ifp); - ifp = arg; - logerrx("%s: failed to confirm prior address", ifp->name); + logmessage(llevel, "%s: failed to confirm prior DHCPv6 address", + ifp->name); dhcp6_fail(ifp); } static void dhcp6_failrequest(void *arg) { - struct interface *ifp; + struct interface *ifp = arg; + int llevel = dhcp6_failloglevel(ifp); - ifp = arg; - logerrx("%s: failed to request address", ifp->name); + logmessage(llevel, "%s: failed to request DHCPv6 address", ifp->name); dhcp6_fail(ifp); } static void dhcp6_failinform(void *arg) { - struct interface *ifp; + struct interface *ifp = arg; + int llevel = dhcp6_failloglevel(ifp); - ifp = arg; - logerrx("%s: failed to request information", ifp->name); + logmessage(llevel, "%s: failed to request DHCPv6 information", + ifp->name); dhcp6_fail(ifp); } -#ifdef SMALL -#define dhcp6_hasprefixdelegation(a) (0) -#else +#ifndef SMALL static void dhcp6_failrebind(void *arg) { - struct interface *ifp; + struct interface *ifp = arg; - ifp = arg; - logerrx("%s: failed to rebind prior delegation", ifp->name); + logerrx("%s: failed to rebind prior DHCPv6 delegation", ifp->name); dhcp6_fail(ifp); } @@ -1789,8 +1892,18 @@ dhcp6_startconfirm(struct interface *ifp) { struct dhcp6_state *state; + struct ipv6_addr *ia; state = D6_STATE(ifp); + + TAILQ_FOREACH(ia, &state->addrs, next) { + if (!DECLINE_IA(ia)) + continue; + logerrx("%s: prior DHCPv6 has a duplicated address", ifp->name); + dhcp6_startdecline(ifp); + return; + } + state->state = DH6S_CONFIRM; state->RTC = 0; state->IMD = CNF_MAX_DELAY; @@ -1799,6 +1912,7 @@ state->MRC = CNF_MAX_RC; loginfox("%s: confirming prior DHCPv6 lease", ifp->name); + if (dhcp6_makemessage(ifp) == -1) { logerr("%s: %s", __func__, ifp->name); return; @@ -1808,21 +1922,6 @@ CNF_MAX_RD, dhcp6_failconfirm, ifp); } -static void -dhcp6_leaseextend(struct interface *ifp) -{ - struct dhcp6_state *state = D6_STATE(ifp); - struct ipv6_addr *ia; - - logwarnx("%s: extending DHCPv6 lease", ifp->name); - TAILQ_FOREACH(ia, &state->addrs, next) { - ia->flags |= IPV6_AF_EXTENDED; - /* Set infinite lifetimes. */ - ia->prefix_pltime = ND6_INFINITE_LIFETIME; - ia->prefix_vltime = ND6_INFINITE_LIFETIME; - } -} - static void dhcp6_startexpire(void *arg) { @@ -1832,24 +1931,37 @@ eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrebind, ifp); logerrx("%s: DHCPv6 lease expired", ifp->name); - if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { - struct dhcp6_state *state = D6_STATE(ifp); + dhcp6_fail(ifp); +} - dhcp6_leaseextend(ifp); - ipv6_addaddrs(&state->addrs); - } else { - dhcp6_freedrop_addrs(ifp, 1, NULL); -#ifndef SMALL - dhcp6_delete_delegates(ifp); -#endif - script_runreason(ifp, "EXPIRE6"); - } - if (!(ifp->options->options & DHCPCD_IPV6RS) || - ipv6nd_hasradhcp(ifp) || - dhcp6_hasprefixdelegation(ifp)) - dhcp6_startdiscover(ifp); +static void +dhcp6_faildecline(void *arg) +{ + struct interface *ifp = arg; + + logerrx("%s: failed to decline duplicated DHCPv6 addresses", ifp->name); + dhcp6_fail(ifp); +} + +static void +dhcp6_startdecline(struct interface *ifp) +{ + struct dhcp6_state *state; + + state = D6_STATE(ifp); + loginfox("%s: declining failed DHCPv6 addresses", ifp->name); + state->state = DH6S_DECLINE; + state->RTC = 0; + state->IMD = 0; + state->IRT = DEC_TIMEOUT; + state->MRT = 0; + state->MRC = DEC_MAX_RC; + state->MRCcallback = dhcp6_faildecline; + + if (dhcp6_makemessage(ifp) == -1) + logerr("%s: %s", __func__, ifp->name); else - logwarnx("%s: no advertising IPv6 router wants DHCP",ifp->name); + dhcp6_senddecline(ifp); } static void @@ -1907,7 +2019,7 @@ void * (*f)(void *, size_t, uint16_t, uint16_t *), *farg; char buf[32], *sbuf; const char *status; - logfunc_t *logfunc; + int loglevel; state = D6_STATE(ifp); f = p ? dhcp6_findoption : dhcp6_findmoption; @@ -1956,13 +2068,18 @@ } if (state->lerror == code || state->state == DH6S_INIT) - logfunc = logdebugx; + loglevel = LOG_DEBUG; else - logfunc = logerrx; - logfunc("%s: DHCPv6 REPLY: %s", ifp->name, status); + loglevel = LOG_ERR; + logmessage(loglevel, "%s: DHCPv6 REPLY: %s", ifp->name, status); free(sbuf); state->lerror = code; errno = 0; + + /* code cannot be D6_STATUS_OK, so there is a failure */ + if (ifp->ctx->options & DHCPCD_TEST) + eloop_exit(ifp->ctx->eloop, EXIT_FAILURE); + return (int)code; } @@ -2132,13 +2249,10 @@ a->dadcallback = dhcp6_dadcallback; a->ia_type = D6_OPTION_IA_PD; memcpy(a->iaid, iaid, sizeof(a->iaid)); - TAILQ_INIT(&a->pd_pfxs); TAILQ_INSERT_TAIL(&state->addrs, a, next); } else { - if (!(a->flags & IPV6_AF_DELEGATEDPFX)) { + if (!(a->flags & IPV6_AF_DELEGATEDPFX)) a->flags |= IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX; - TAILQ_INIT(&a->pd_pfxs); - } a->flags &= ~(IPV6_AF_STALE | IPV6_AF_EXTENDED | IPV6_AF_REQUEST); @@ -2333,13 +2447,53 @@ (ia.t2 < state->rebind || state->rebind == 0)) state->rebind = ia.t2; } - i++; + i++; + } + + if (i == 0 && e) + return -1; + return i; +} + +#ifndef SMALL +static void +dhcp6_deprecatedele(struct ipv6_addr *ia) +{ + struct ipv6_addr *da, *dan, *dda; + struct timespec now; + struct dhcp6_state *state; + + timespecclear(&now); + TAILQ_FOREACH_SAFE(da, &ia->pd_pfxs, pd_next, dan) { + if (ia->prefix_vltime == 0) { + if (da->prefix_vltime != 0) + da->prefix_vltime = 0; + else + continue; + } else if (da->prefix_pltime != 0) + da->prefix_pltime = 0; + else + continue; + + if (ipv6_doaddr(da, &now) != -1) + continue; + + /* Delegation deleted, forget it. */ + TAILQ_REMOVE(&ia->pd_pfxs, da, pd_next); + + /* Delete it from the interface. */ + state = D6_STATE(da->iface); + TAILQ_FOREACH(dda, &state->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&dda->addr, &da->addr)) + break; + } + if (dda != NULL) { + TAILQ_REMOVE(&state->addrs, dda, next); + ipv6_freeaddr(dda); + } } - - if (i == 0 && e) - return -1; - return i; } +#endif static void dhcp6_deprecateaddrs(struct ipv6_addrhead *addrs) @@ -2353,6 +2507,9 @@ if (ia->prefix_vltime != 0) logdebugx("%s: %s: became stale", ia->iface->name, ia->saddr); + /* Technically this violates RFC 8415 18.2.10.1, + * but we need a mechanism to tell the kernel to + * try and prefer other addresses. */ ia->prefix_pltime = 0; } else if (ia->prefix_vltime == 0) loginfox("%s: %s: no valid lifetime", @@ -2363,30 +2520,14 @@ #ifndef SMALL /* If we delegated from this prefix, deprecate or remove * the delegations. */ - if (ia->flags & IPV6_AF_DELEGATEDPFX) { - struct ipv6_addr *da; - bool touched = false; - - TAILQ_FOREACH(da, &ia->pd_pfxs, pd_next) { - if (ia->prefix_vltime == 0) { - if (da->prefix_vltime != 0) { - da->prefix_vltime = 0; - touched = true; - } - } else if (da->prefix_pltime != 0) { - da->prefix_pltime = 0; - touched = true; - } - } - if (touched) - ipv6_addaddrs(&ia->pd_pfxs); - } + if (ia->flags & IPV6_AF_DELEGATEDPFX) + dhcp6_deprecatedele(ia); #endif if (ia->flags & IPV6_AF_REQUEST) { ia->prefix_vltime = ia->prefix_pltime = 0; eloop_q_timeout_delete(ia->iface->ctx->eloop, - 0, NULL, ia); + ELOOP_QUEUE_ALL, NULL, ia); continue; } TAILQ_REMOVE(addrs, ia, next); @@ -2443,108 +2584,77 @@ } static ssize_t -dhcp6_writelease(const struct interface *ifp) -{ - const struct dhcp6_state *state; - int fd; - ssize_t bytes; - - state = D6_CSTATE(ifp); - logdebugx("%s: writing lease `%s'", ifp->name, state->leasefile); - - fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd == -1) { - logerr(__func__); - return -1; - } - bytes = write(fd, state->new, state->new_len); - close(fd); - return bytes; -} - -static int dhcp6_readlease(struct interface *ifp, int validate) { + union { + struct dhcp6_message dhcp6; + uint8_t buf[UDPLEN_MAX]; + } buf; struct dhcp6_state *state; - struct stat st; + ssize_t bytes; int fd; - time_t now; - int retval; - bool read_stdin, fd_opened; + time_t mtime, now; #ifdef AUTH uint8_t *o; uint16_t ol; #endif state = D6_STATE(ifp); - read_stdin = state->leasefile[0] == '\0'; - if (read_stdin) { + if (state->leasefile[0] == '\0') { logdebugx("reading standard input"); - fd = fileno(stdin); - fd_opened = false; + bytes = read(fileno(stdin), buf.buf, sizeof(buf.buf)); } else { - logdebugx("%s: reading lease `%s'", ifp->name,state->leasefile); - fd = open(state->leasefile, O_RDONLY); - if (fd != -1 && fstat(fd, &st) == -1) { - close(fd); - fd = -1; - } - fd_opened = true; + logdebugx("%s: reading lease: %s", + ifp->name, state->leasefile); + bytes = dhcp_readfile(ifp->ctx, state->leasefile, + buf.buf, sizeof(buf.buf)); } - if (fd == -1) - return -1; - retval = -1; - free(state->new); - state->new_len = dhcp_read_lease_fd(fd, (void **)&state->new); - if (fd_opened) - close(fd); + if (bytes == -1) + goto ex; - if (ifp->ctx->options & DHCPCD_DUMPLEASE || read_stdin) - return 0; + if (ifp->ctx->options & DHCPCD_DUMPLEASE || state->leasefile[0] == '\0') + goto out; - if (state->new_len == 0) { - retval = 0; + if (bytes == 0) goto ex; - } /* If not validating IA's and if they have expired, * skip to the auth check. */ - if (!validate) { - fd = 0; + if (!validate) goto auth; - } + if (dhcp_filemtime(ifp->ctx, state->leasefile, &mtime) == -1) + goto ex; clock_gettime(CLOCK_MONOTONIC, &state->acquired); if ((now = time(NULL)) == -1) goto ex; - state->acquired.tv_sec -= now - st.st_mtime; + state->acquired.tv_sec -= now - mtime; /* Check to see if the lease is still valid */ - fd = dhcp6_validatelease(ifp, state->new, state->new_len, NULL, + fd = dhcp6_validatelease(ifp, &buf.dhcp6, (size_t)bytes, NULL, &state->acquired); if (fd == -1) goto ex; if (state->expire != ND6_INFINITE_LIFETIME && - (time_t)state->expire < now - st.st_mtime && + (time_t)state->expire < now - mtime && !(ifp->options->options & DHCPCD_LASTLEASE_EXTEND)) { logdebugx("%s: discarding expired lease", ifp->name); - retval = 0; + bytes = 0; goto ex; } auth: - retval = 0; #ifdef AUTH /* Authenticate the message */ - o = dhcp6_findmoption(state->new, state->new_len, D6_OPTION_AUTH, &ol); + o = dhcp6_findmoption(&buf.dhcp6, (size_t)bytes, D6_OPTION_AUTH, &ol); if (o) { if (dhcp_auth_validate(&state->auth, &ifp->options->auth, - (uint8_t *)state->new, state->new_len, 6, state->new->type, - o, ol) == NULL) + buf.buf, (size_t)bytes, 6, buf.dhcp6.type, o, ol) == NULL) { logerr("%s: authentication failed", ifp->name); + bytes = 0; goto ex; } if (state->auth.token) @@ -2560,22 +2670,33 @@ } #endif - return fd; +out: + free(state->new); + state->new = malloc((size_t)bytes); + if (state->new == NULL) { + logerr(__func__); + goto ex; + } + + memcpy(state->new, buf.buf, (size_t)bytes); + state->new_len = (size_t)bytes; + return bytes; ex: dhcp6_freedrop_addrs(ifp, 0, NULL); - unlink(state->leasefile); + dhcp_unlink(ifp->ctx, state->leasefile); free(state->new); state->new = NULL; state->new_len = 0; - return retval; + dhcp6_addrequestedaddrs(ifp); + return bytes == 0 ? 0 : -1; } static void dhcp6_startinit(struct interface *ifp) { struct dhcp6_state *state; - int r; + ssize_t r; uint8_t has_ta, has_non_ta; size_t i; @@ -2602,9 +2723,11 @@ { r = dhcp6_readlease(ifp, 1); if (r == -1) { - if (errno != ENOENT) + if (errno != ENOENT && errno != ESRCH) logerr("%s: %s", __func__, state->leasefile); - } else if (r != 0) { + } else if (r != 0 && + !(ifp->options->options & DHCPCD_ANONYMOUS)) + { /* RFC 3633 section 12.1 */ #ifndef SMALL if (dhcp6_hasprefixdelegation(ifp)) @@ -2615,7 +2738,7 @@ return; } } - dhcp6_startdiscover(ifp); + dhcp6_startdiscoinform(ifp); } #ifndef SMALL @@ -2658,7 +2781,7 @@ vl |= sla->suffix; be64enc(daddr.s6_addr + 8, vl); } else { - dadcounter = ipv6_makeaddr(&daddr, ifp, &addr, pfxlen); + dadcounter = ipv6_makeaddr(&daddr, ifp, &addr, pfxlen, 0); if (dadcounter == -1) { logerrx("%s: error adding slaac to prefix_len %d", ifp->name, pfxlen); @@ -2770,22 +2893,24 @@ TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) { if (!ifd->active) continue; + if (!(ifd->options->options & DHCPCD_CONFIGURE)) + continue; k = 0; carrier_warned = false; TAILQ_FOREACH(ap, &state->addrs, next) { if (!(ap->flags & IPV6_AF_DELEGATEDPFX)) continue; if (!(ap->flags & IPV6_AF_DELEGATEDLOG)) { - logfunc_t *logfunc; + int loglevel; if (ap->flags & IPV6_AF_NEW) - logfunc = loginfox; + loglevel = LOG_INFO; else - logfunc = logdebugx; + loglevel = LOG_DEBUG; /* We only want to log this the once as we loop * through many interfaces first. */ ap->flags |= IPV6_AF_DELEGATEDLOG; - logfunc("%s: delegated prefix %s", + logmessage(loglevel, "%s: delegated prefix %s", ifp->name, ap->saddr); ap->flags &= ~IPV6_AF_NEW; } @@ -2799,7 +2924,7 @@ if (ia->sla_len == 0) { /* no SLA configured, so lets * automate it */ - if (ifd->carrier != LINK_UP) { + if (!if_is_link_up(ifd)) { logdebugx( "%s: has no carrier, cannot" " delegate addresses", @@ -2815,7 +2940,7 @@ sla = &ia->sla[j]; if (strcmp(ifd->name, sla->ifname)) continue; - if (ifd->carrier != LINK_UP) { + if (!if_is_link_up(ifd)) { logdebugx( "%s: has no carrier, cannot" " delegate addresses", @@ -2863,6 +2988,10 @@ struct if_sla *sla; struct interface *ifd; + if (ifp->options != NULL && + !(ifp->options->options & DHCPCD_CONFIGURE)) + return 0; + k = 0; TAILQ_FOREACH(ifd, ifp->ctx->ifaces, next) { ifo = ifd->options; @@ -2917,36 +3046,50 @@ dhcp6_bind(struct interface *ifp, const char *op, const char *sfrom) { struct dhcp6_state *state = D6_STATE(ifp); - bool has_new = false; + bool timedout = (op == NULL), confirmed; struct ipv6_addr *ia; - logfunc_t *lognewinfo; + int loglevel; struct timespec now; - TAILQ_FOREACH(ia, &state->addrs, next) { - if (ia->flags & IPV6_AF_NEW) { - has_new = true; - break; + if (state->state == DH6S_RENEW && !state->new_start) { + loglevel = LOG_DEBUG; + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_NEW) { + loglevel = LOG_INFO; + break; + } } - } - lognewinfo = has_new ? loginfox : logdebugx; - if (op != NULL) - lognewinfo("%s: %s received from %s", ifp->name, op, sfrom); + } else if (state->state == DH6S_INFORM) + loglevel = state->new_start ? LOG_INFO : LOG_DEBUG; + else + loglevel = LOG_INFO; + state->new_start = false; + + if (!timedout) { + logmessage(loglevel, "%s: %s received from %s", + ifp->name, op, sfrom); +#ifndef SMALL + /* If we delegated from an unconfirmed lease we MUST drop + * them now. Hopefully we have new delegations. */ + if (state->reason != NULL && + strcmp(state->reason, "TIMEOUT6") == 0) + dhcp6_delete_delegates(ifp); +#endif + state->reason = NULL; + } else + state->reason = "TIMEOUT6"; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); + clock_gettime(CLOCK_MONOTONIC, &now); - state->reason = NULL; - if (state->state != DH6S_ITIMEDOUT) - eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); switch(state->state) { case DH6S_INFORM: - if (state->reason == NULL) - state->reason = "INFORM6"; - /* FALLTHROUGH */ - case DH6S_ITIMEDOUT: { struct dhcp6_option *o; uint16_t ol; if (state->reason == NULL) - state->reason = "ITIMEDOUT"; + state->reason = "INFORM6"; o = dhcp6_findmoption(state->new, state->new_len, D6_OPTION_INFO_REFRESH_TIME, &ol); if (o == NULL || ol != sizeof(uint32_t)) @@ -2978,18 +3121,15 @@ case DH6S_CONFIRM: if (state->reason == NULL) state->reason = "REBOOT6"; - /* FALLTHROUGH */ - case DH6S_TIMEDOUT: - if (state->reason == NULL) - state->reason = "TIMEOUT6"; if (state->renew != 0) { bool all_expired = true; TAILQ_FOREACH(ia, &state->addrs, next) { if (ia->flags & IPV6_AF_STALE) continue; - if (!(state->renew == ND6_INFINITE_LIFETIME && - ia->prefix_vltime == ND6_INFINITE_LIFETIME) + if (!(state->renew == ND6_INFINITE_LIFETIME + && ia->prefix_vltime == ND6_INFINITE_LIFETIME) + && ia->prefix_vltime != 0 && ia->prefix_vltime <= state->renew) logwarnx( "%s: %s will expire before renewal", @@ -3026,130 +3166,95 @@ break; } - clock_gettime(CLOCK_MONOTONIC, &now); - if (state->state == DH6S_TIMEDOUT || state->state == DH6S_ITIMEDOUT) { - struct timespec diff; - uint32_t diffsec; + if (state->state != DH6S_CONFIRM && !timedout) { + state->acquired = now; + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = state->recv; + state->new_len = state->recv_len; + state->recv = NULL; + state->recv_len = 0; + confirmed = false; + } else { + /* Reduce timers based on when we got the lease. */ + uint32_t elapsed; - /* Reduce timers */ - timespecsub(&now, &state->acquired, &diff); - diffsec = (uint32_t)diff.tv_sec; + elapsed = (uint32_t)eloop_timespec_diff(&now, + &state->acquired, NULL); if (state->renew && state->renew != ND6_INFINITE_LIFETIME) { - if (state->renew > diffsec) - state->renew -= diffsec; + if (state->renew > elapsed) + state->renew -= elapsed; else state->renew = 0; } if (state->rebind && state->rebind != ND6_INFINITE_LIFETIME) { - if (state->rebind > diffsec) - state->rebind -= diffsec; + if (state->rebind > elapsed) + state->rebind -= elapsed; else state->rebind = 0; } if (state->expire && state->expire != ND6_INFINITE_LIFETIME) { - if (state->expire > diffsec) - state->expire -= diffsec; - else { - if (!(ifp->options->options & - DHCPCD_LASTLEASE_EXTEND)) - return; - state->expire = ND6_INFINITE_LIFETIME; - } - } - if (state->expire == ND6_INFINITE_LIFETIME && - ifp->options->options & DHCPCD_LASTLEASE_EXTEND) - dhcp6_leaseextend(ifp); - - /* Restart rebind or renew phases in a second. */ - if (state->expire != ND6_INFINITE_LIFETIME) { - if (state->rebind == 0 && - state->rebind != ND6_INFINITE_LIFETIME) - state->rebind = 1; - else if (state->renew == 0 && - state->renew != ND6_INFINITE_LIFETIME) - state->renew = 1; + if (state->expire > elapsed) + state->expire -= elapsed; + else + state->expire = 0; } - } else - state->acquired = now; - - switch (state->state) { - case DH6S_CONFIRM: - case DH6S_TIMEDOUT: - case DH6S_ITIMEDOUT: - break; - default: - free(state->old); - state->old = state->new; - state->old_len = state->new_len; - state->new = state->recv; - state->new_len = state->recv_len; - state->recv = NULL; - state->recv_len = 0; - break; + confirmed = true; } if (ifp->ctx->options & DHCPCD_TEST) script_runreason(ifp, "TEST"); else { - bool timed_out; - - switch(state->state) { - case DH6S_TIMEDOUT: - case DH6S_ITIMEDOUT: - timed_out = true; - break; - default: - timed_out = false; - break; - } - - switch(state->state) { - case DH6S_INFORM: - case DH6S_ITIMEDOUT: + if (state->state == DH6S_INFORM) state->state = DH6S_INFORMED; - break; - default: + else state->state = DH6S_BOUND; - break; - } + state->failed = false; if (state->renew && state->renew != ND6_INFINITE_LIFETIME) eloop_timeout_add_sec(ifp->ctx->eloop, - (time_t)state->renew, + state->renew, state->state == DH6S_INFORMED ? dhcp6_startinform : dhcp6_startrenew, ifp); if (state->rebind && state->rebind != ND6_INFINITE_LIFETIME) eloop_timeout_add_sec(ifp->ctx->eloop, - (time_t)state->rebind, dhcp6_startrebind, ifp); + state->rebind, dhcp6_startrebind, ifp); if (state->expire != ND6_INFINITE_LIFETIME) eloop_timeout_add_sec(ifp->ctx->eloop, - (time_t)state->expire, dhcp6_startexpire, ifp); - else if (timed_out) - eloop_timeout_add_sec(ifp->ctx->eloop, - (time_t)state->expire, dhcp6_startdiscover, ifp); + state->expire, dhcp6_startexpire, ifp); - ipv6_addaddrs(&state->addrs); - dhcp6_deprecateaddrs(&state->addrs); + if (ifp->options->options & DHCPCD_CONFIGURE) { + ipv6_addaddrs(&state->addrs); + if (!timedout) + dhcp6_deprecateaddrs(&state->addrs); + } if (state->state == DH6S_INFORMED) - lognewinfo("%s: refresh in %"PRIu32" seconds", + logmessage(loglevel, "%s: refresh in %"PRIu32" seconds", ifp->name, state->renew); else if (state->renew == ND6_INFINITE_LIFETIME) - lognewinfo("%s: leased for infinity", ifp->name); + logmessage(loglevel, "%s: leased for infinity", + ifp->name); else if (state->renew || state->rebind) - lognewinfo("%s: renew in %"PRIu32", " + logmessage(loglevel, "%s: renew in %"PRIu32", " "rebind in %"PRIu32", " "expire in %"PRIu32" seconds", ifp->name, state->renew, state->rebind, state->expire); else if (state->expire == 0) - lognewinfo("%s: will expire", ifp->name); + logmessage(loglevel, "%s: will expire", ifp->name); else - lognewinfo("%s: expire in %"PRIu32" seconds", + logmessage(loglevel, "%s: expire in %"PRIu32" seconds", ifp->name, state->expire); rt_build(ifp->ctx, AF_INET6); - if (!timed_out) - dhcp6_writelease(ifp); + if (!confirmed && !timedout) { + logdebugx("%s: writing lease: %s", + ifp->name, state->leasefile); + if (dhcp_writefile(ifp->ctx, state->leasefile, 0640, + state->new, state->new_len) == -1) + logerr("dhcp_writefile: %s",state->leasefile); + } #ifndef SMALL dhcp6_delegate_prefix(ifp); #endif @@ -3158,7 +3263,7 @@ if (ifp->ctx->options & DHCPCD_TEST || (ifp->options->options & DHCPCD_INFORM && - !(ifp->ctx->options & DHCPCD_MASTER))) + !(ifp->ctx->options & DHCPCD_MANAGER))) { eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); } @@ -3244,7 +3349,7 @@ loginfox("%s: accepted reconfigure key", ifp->name); } else if (ifo->auth.options & DHCPCD_AUTH_SEND) { if (ifo->auth.options & DHCPCD_AUTH_REQUIRE) { - logerr("%s: no authentication from %s", + logerrx("%s: no authentication from %s", ifp->name, sfrom); return; } @@ -3264,7 +3369,7 @@ case DH6S_CONFIRM: if (dhcp6_validatelease(ifp, r, len, sfrom, NULL) == -1) { - dhcp6_startdiscover(ifp); + dhcp6_startdiscoinform(ifp); return; } break; @@ -3303,7 +3408,7 @@ * until a new one is found. */ if (state->state != DH6S_DISCOVER) - dhcp6_startdiscover(ifp); + dhcp6_startdiscoinform(ifp); return; } /* RFC8415 18.2.10.1 */ @@ -3317,6 +3422,13 @@ if (state->state == DH6S_DISCOVER) state->state = DH6S_REQUEST; break; + case DH6S_DECLINE: + /* This isnt really a failure, but an + * acknowledgement of one. */ + loginfox("%s: %s acknowledged DECLINE6", + ifp->name, sfrom); + dhcp6_fail(ifp); + return; default: valid_op = false; break; @@ -3339,7 +3451,7 @@ ifp->name, (unsigned long long)state->sol_max_rt, max_rt); - state->sol_max_rt = (time_t)max_rt; + state->sol_max_rt = max_rt; } else logerr("%s: invalid SOL_MAX_RT %u", ifp->name, max_rt); @@ -3355,7 +3467,7 @@ ifp->name, (unsigned long long)state->inf_max_rt, max_rt); - state->inf_max_rt = (time_t)max_rt; + state->inf_max_rt = max_rt; } else logerrx("%s: invalid INF_MAX_RT %u", ifp->name, max_rt); @@ -3435,13 +3547,11 @@ memcpy(state->recv, r, len); state->recv_len = len; - switch (r->type) { - case DHCP6_ADVERTISE: - { + if (r->type == DHCP6_ADVERTISE) { struct ipv6_addr *ia; if (state->state == DH6S_REQUEST) /* rapid commit */ - break; + goto bind; TAILQ_FOREACH(ia, &state->addrs, next) { if (!(ia->flags & (IPV6_AF_STALE | IPV6_AF_REQUEST))) break; @@ -3454,34 +3564,19 @@ else loginfox("%s: ADV %s from %s", ifp->name, ia->saddr, sfrom); - if (ifp->ctx->options & DHCPCD_TEST) - break; dhcp6_startrequest(ifp); return; } - } +bind: dhcp6_bind(ifp, op, sfrom); } -static void -dhcp6_recv(struct dhcpcd_ctx *ctx, struct ipv6_addr *ia) +void +dhcp6_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg, struct ipv6_addr *ia) { - struct sockaddr_in6 from; - unsigned char buf[64 * 1024]; /* Maximum UDP message size */ - struct iovec iov = { - .iov_base = buf, - .iov_len = sizeof(buf), - }; - unsigned char ctl[CMSG_SPACE(sizeof(struct in6_pktinfo))] = { 0 }; - struct msghdr msg = { - .msg_name = &from, .msg_namelen = sizeof(from), - .msg_iov = &iov, .msg_iovlen = 1, - .msg_control = ctl, .msg_controllen = sizeof(ctl), - }; - int s; - size_t len; - ssize_t bytes; + struct sockaddr_in6 *from = msg->msg_name; + size_t len = msg->msg_iov[0].iov_len; char sfrom[INET6_ADDRSTRLEN]; struct interface *ifp; struct dhcp6_message *r; @@ -3489,14 +3584,7 @@ uint8_t *o; uint16_t ol; - s = ia != NULL ? ia->dhcp6_fd : ctx->dhcp6_fd; - bytes = recvmsg(s, &msg, 0); - if (bytes == -1) { - logerr(__func__); - return; - } - len = (size_t)bytes; - inet_ntop(AF_INET6, &from.sin6_addr, sfrom, sizeof(sfrom)); + inet_ntop(AF_INET6, &from->sin6_addr, sfrom, sizeof(sfrom)); if (len < sizeof(struct dhcp6_message)) { logerrx("DHCPv6 packet too short from %s", sfrom); return; @@ -3505,18 +3593,26 @@ if (ia != NULL) ifp = ia->iface; else { - ifp = if_findifpfromcmsg(ctx, &msg, NULL); + ifp = if_findifpfromcmsg(ctx, msg, NULL); if (ifp == NULL) { logerr(__func__); return; } } - r = (struct dhcp6_message *)buf; + r = (struct dhcp6_message *)msg->msg_iov[0].iov_base; + + uint8_t duid[DUID_LEN], *dp; + size_t duid_len; o = dhcp6_findmoption(r, len, D6_OPTION_CLIENTID, &ol); - if (o == NULL || ol != ctx->duid_len || - memcmp(o, ctx->duid, ol) != 0) - { + if (ifp->options->options & DHCPCD_ANONYMOUS) { + duid_len = duid_make(duid, ifp, DUID_LL); + dp = duid; + } else { + duid_len = ctx->duid_len; + dp = ctx->duid; + } + if (o == NULL || ol != duid_len || memcmp(o, dp, ol) != 0) { logdebugx("%s: incorrect client ID from %s", ifp->name, sfrom); return; @@ -3529,15 +3625,12 @@ } if (r->type == DHCP6_RECONFIGURE) { - logdebugx("%s: RECONFIGURE6 recv from %s," - " sending to all interfaces", - ifp->name, sfrom); - TAILQ_FOREACH(ifp, ctx->ifaces, next) { - state = D6_CSTATE(ifp); - if (state != NULL && state->send != NULL) - dhcp6_recvif(ifp, sfrom, r, len); + if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) { + logerrx("%s: RECONFIGURE6 recv from %s, not LL", + ifp->name, sfrom); + return; } - return; + goto recvif; } state = D6_CSTATE(ifp); @@ -3577,9 +3670,80 @@ ifp = ifp1; } +#if 0 + /* + * Handy code to inject raw DHCPv6 packets over responses + * from our server. + * This allows me to take a 3rd party wireshark trace and + * replay it in my code. + */ + static int replyn = 0; + char fname[PATH_MAX], tbuf[UDPLEN_MAX]; + int fd; + ssize_t tlen; + uint8_t *si1, *si2; + uint16_t si_len1, si_len2; + + snprintf(fname, sizeof(fname), + "/tmp/dhcp6.reply%d.raw", replyn++); + fd = open(fname, O_RDONLY, 0); + if (fd == -1) { + logerr("%s: open: %s", __func__, fname); + return; + } + tlen = read(fd, tbuf, sizeof(tbuf)); + if (tlen == -1) + logerr("%s: read: %s", __func__, fname); + close(fd); + + /* Copy across ServerID so we can work with our own server. */ + si1 = dhcp6_findmoption(r, len, D6_OPTION_SERVERID, &si_len1); + si2 = dhcp6_findmoption(tbuf, (size_t)tlen, + D6_OPTION_SERVERID, &si_len2); + if (si1 != NULL && si2 != NULL && si_len1 == si_len2) + memcpy(si2, si1, si_len2); + r = (struct dhcp6_message *)tbuf; + len = (size_t)tlen; +#endif + +recvif: dhcp6_recvif(ifp, sfrom, r, len); } +static void +dhcp6_recv(struct dhcpcd_ctx *ctx, struct ipv6_addr *ia) +{ + struct sockaddr_in6 from; + union { + struct dhcp6_message dhcp6; + uint8_t buf[UDPLEN_MAX]; /* Maximum UDP message size */ + } iovbuf; + struct iovec iov = { + .iov_base = iovbuf.buf, .iov_len = sizeof(iovbuf.buf), + }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf = { .buf = { 0 } }; + struct msghdr msg = { + .msg_name = &from, .msg_namelen = sizeof(from), + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), + }; + int s; + ssize_t bytes; + + s = ia != NULL ? ia->dhcp6_fd : ctx->dhcp6_rfd; + bytes = recvmsg(s, &msg, 0); + if (bytes == -1) { + logerr(__func__); + return; + } + + iov.iov_len = (size_t)bytes; + dhcp6_recvmsg(ctx, &msg, ia); +} + static void dhcp6_recvaddr(void *arg) { @@ -3596,22 +3760,40 @@ dhcp6_recv(ctx, NULL); } -static int -dhcp6_listen(struct dhcpcd_ctx *ctx, struct ipv6_addr *ia) +int +dhcp6_openraw(void) +{ + int fd, v; + + fd = socket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_UDP); + if (fd == -1) + return -1; + + v = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &v, sizeof(v)) == -1) + goto errexit; + + v = offsetof(struct udphdr, uh_sum); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_CHECKSUM, &v, sizeof(v)) == -1) + goto errexit; + + return fd; + +errexit: + close(fd); + return -1; +} + +int +dhcp6_openudp(unsigned int ifindex, struct in6_addr *ia) { struct sockaddr_in6 sa; int n, s; -#define SOCK_FLAGS SOCK_CLOEXEC | SOCK_NONBLOCK - s = xsocket(PF_INET6, SOCK_DGRAM | SOCK_FLAGS, IPPROTO_UDP); -#undef SOCK_FLAGS + s = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CXNB, IPPROTO_UDP); if (s == -1) goto errexit; - n = 1; - if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n)) == -1) - goto errexit; - memset(&sa, 0, sizeof(sa)); sa.sin6_family = AF_INET6; sa.sin6_port = htons(DHCP6_CLIENT_PORT); @@ -3620,8 +3802,8 @@ #endif if (ia != NULL) { - memcpy(&sa.sin6_addr, &ia->addr, sizeof(sa.sin6_addr)); - sa.sin6_scope_id = ia->iface->index; + memcpy(&sa.sin6_addr, ia, sizeof(sa.sin6_addr)); + ipv6_setscope(&sa, ifindex); } if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) @@ -3631,10 +3813,11 @@ if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &n, sizeof(n)) == -1) goto errexit; - if (ia != NULL) { - ia->dhcp6_fd = s; - eloop_event_add(ctx->eloop, s, dhcp6_recvaddr, ia); - } +#ifdef SO_RERROR + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_RERROR, &n, sizeof(n)) == -1) + goto errexit; +#endif return s; @@ -3687,11 +3870,23 @@ size_t i; const struct dhcp_compat *dhc; - if (ctx->dhcp6_fd == -1 && ctx->options & DHCPCD_MASTER) { - ctx->dhcp6_fd = dhcp6_listen(ctx, NULL); - if (ctx->dhcp6_fd == -1) + if ((ctx->options & (DHCPCD_MANAGER|DHCPCD_PRIVSEP)) == DHCPCD_MANAGER && + ctx->dhcp6_rfd == -1) + { + ctx->dhcp6_rfd = dhcp6_openudp(0, NULL); + if (ctx->dhcp6_rfd == -1) { + logerr(__func__); + return; + } + eloop_event_add(ctx->eloop, ctx->dhcp6_rfd, dhcp6_recvctx, ctx); + } + + if (!IN_PRIVSEP(ctx) && ctx->dhcp6_wfd == -1) { + ctx->dhcp6_wfd = dhcp6_openraw(); + if (ctx->dhcp6_wfd == -1) { + logerr(__func__); return; - eloop_event_add(ctx->eloop, ctx->dhcp6_fd, dhcp6_recvctx, ctx); + } } state = D6_STATE(ifp); @@ -3703,12 +3898,11 @@ } if (i == sizeof(ifo->requestmask6)) { for (dhc = dhcp_compats; dhc->dhcp_opt; dhc++) { - if (has_option_mask(ifo->requestmask, dhc->dhcp_opt)) + if (DHC_REQ(ifo->requestmask, ifo->nomask, dhc->dhcp_opt)) add_option_mask(ifo->requestmask6, dhc->dhcp6_opt); } - if (ifo->fqdn != FQDN_DISABLE || - ifo->options & DHCPCD_HOSTNAME) + if (ifo->fqdn != FQDN_DISABLE || ifo->options & DHCPCD_HOSTNAME) add_option_mask(ifo->requestmask6, D6_OPTION_FQDN); } @@ -3742,12 +3936,23 @@ case DH6S_INIT: goto gogogo; case DH6S_INFORM: - if (state->state == DH6S_INFORMED) + if (state->state == DH6S_INIT || + state->state == DH6S_INFORMED || + (state->state == DH6S_DISCOVER && + !(ifp->options->options & DHCPCD_IA_FORCED) && + !ipv6nd_hasradhcp(ifp, true))) + { + /* We don't want log spam when the RA + * has just adjusted it's prefix times. */ + if (state->state != DH6S_INFORMED) + state->new_start = true; dhcp6_startinform(ifp); + } break; case DH6S_REQUEST: if (ifp->options->options & DHCPCD_DHCP6 && - (state->state == DH6S_INFORM || + (state->state == DH6S_INIT || + state->state == DH6S_INFORM || state->state == DH6S_INFORMED || state->state == DH6S_DELEGATED)) { @@ -3791,13 +3996,14 @@ TAILQ_INIT(&state->addrs); gogogo: + state->new_start = true; state->state = init_state; state->lerror = 0; + state->failed = false; dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), AF_INET6, ifp); if (ipv6_linklocal(ifp) == NULL) { - logdebugx("%s: delaying DHCPv6 soliciation for LL address", - ifp->name); + logdebugx("%s: delaying DHCPv6 for LL address", ifp->name); ipv6_addlinklocalcallback(ifp, dhcp6_start1, ifp); return 0; } @@ -3820,11 +4026,8 @@ case DH6S_BOUND: dhcp6_startrebind(ifp); break; - case DH6S_INFORMED: - dhcp6_startinform(ifp); - break; default: - dhcp6_startdiscover(ifp); + dhcp6_startdiscoinform(ifp); break; } } @@ -3864,15 +4067,28 @@ if (drop && options & DHCPCD_RELEASE && state->state != DH6S_DELEGATED) { - if (ifp->carrier == LINK_UP && + if (if_is_link_up(ifp) && state->state != DH6S_RELEASED && state->state != DH6S_INFORMED) { dhcp6_startrelease(ifp); return; } - unlink(state->leasefile); + dhcp_unlink(ifp->ctx, state->leasefile); + } +#ifdef AUTH + else if (state->auth.reconf != NULL) { + /* + * Drop the lease as the token may only be present + * in the initial reply message and not subsequent + * renewals. + * If dhcpcd is restarted, the token is lost. + * XXX persist this in another file? + */ + dhcp_unlink(ifp->ctx, state->leasefile); } +#endif + dhcp6_freedrop_addrs(ifp, drop, NULL); free(state->old); state->old = state->new; @@ -3902,10 +4118,10 @@ break; } } - if (ifp == NULL && ctx->dhcp6_fd != -1) { - eloop_event_delete(ctx->eloop, ctx->dhcp6_fd); - close(ctx->dhcp6_fd); - ctx->dhcp6_fd = -1; + if (ifp == NULL && ctx->dhcp6_rfd != -1) { + eloop_event_delete(ctx->eloop, ctx->dhcp6_rfd); + close(ctx->dhcp6_rfd); + ctx->dhcp6_rfd = -1; } } @@ -3926,20 +4142,36 @@ void dhcp6_abort(struct interface *ifp) { -#ifdef ND6_ADVERTISE struct dhcp6_state *state; +#ifdef ND6_ADVERTISE struct ipv6_addr *ia; #endif eloop_timeout_delete(ifp->ctx->eloop, dhcp6_start1, ifp); -#ifdef ND6_ADVERTISE state = D6_STATE(ifp); if (state == NULL) return; + +#ifdef ND6_ADVERTISE TAILQ_FOREACH(ia, &state->addrs, next) { ipv6nd_advertise(ia); } #endif + + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startdiscover, ifp); + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_senddiscover, ifp); + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_startinform, ifp); + eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendinform, ifp); + + switch (state->state) { + case DH6S_DISCOVER: /* FALLTHROUGH */ + case DH6S_REQUEST: /* FALLTHROUGH */ + case DH6S_INFORM: + state->state = DH6S_INIT; + break; + default: + break; + } } void @@ -3948,14 +4180,29 @@ struct dhcp6_state *state; struct interface *ifp = ia->iface; - /* If not running in master mode, listen to this address */ + /* If not running in manager mode, listen to this address */ if (cmd == RTM_NEWADDR && !(ia->addr_flags & IN6_IFF_NOTUSEABLE) && ifp->active == IF_ACTIVE_USER && - !(ifp->ctx->options & DHCPCD_MASTER) && - ifp->options->options & DHCPCD_DHCP6 && - ia->dhcp6_fd == -1) - dhcp6_listen(ia->iface->ctx, ia); + !(ifp->ctx->options & DHCPCD_MANAGER) && + ifp->options->options & DHCPCD_DHCP6) + { +#ifdef PRIVSEP + if (IN_PRIVSEP_SE(ifp->ctx)) { + if (ps_inet_opendhcp6(ia) == -1) + logerr(__func__); + } else +#endif + { + if (ia->dhcp6_fd == -1) + ia->dhcp6_fd = dhcp6_openudp(ia->iface->index, + &ia->addr); + if (ia->dhcp6_fd != -1) + eloop_event_add(ia->iface->ctx->eloop, + ia->dhcp6_fd, dhcp6_recvaddr, ia); + } + } + if ((state = D6_STATE(ifp)) != NULL) ipv6_handleifa_addrs(cmd, &state->addrs, ia, pid); @@ -4093,7 +4340,9 @@ return 1; } +#endif +#ifndef SMALL int dhcp6_dump(struct interface *ifp) { @@ -4105,11 +4354,8 @@ return -1; } TAILQ_INIT(&state->addrs); - dhcp_set_leasefile(state->leasefile, sizeof(state->leasefile), - AF_INET6, ifp); if (dhcp6_readlease(ifp, 0) == -1) { - logerr("%s: %s", __func__, - *ifp->name ? ifp->name : state->leasefile); + logerr("dhcp6_readlease"); return -1; } state->reason = "DUMP6"; Index: contrib/dhcpcd/src/dhcpcd-embedded.h =================================================================== --- contrib/dhcpcd/src/dhcpcd-embedded.h +++ contrib/dhcpcd/src/dhcpcd-embedded.h @@ -1,6 +1,6 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -30,9 +30,9 @@ #define INITDEFINENDS 6 #define INITDEFINE6S 14 #else -#define INITDEFINES 124 -#define INITDEFINENDS 6 +#define INITDEFINES 125 +#define INITDEFINENDS 7 #define INITDEFINE6S 69 #endif -extern const char * const dhcpcd_embedded_conf[]; +extern const char dhcpcd_embedded_conf[]; Index: contrib/dhcpcd/src/dhcpcd-embedded.c =================================================================== --- contrib/dhcpcd/src/dhcpcd-embedded.c +++ contrib/dhcpcd/src/dhcpcd-embedded.c @@ -6,7 +6,7 @@ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -33,458 +33,463 @@ #include -const char * const dhcpcd_embedded_conf[] = { +const char dhcpcd_embedded_conf[] = #ifdef SMALL -"define 1 request ipaddress subnet_mask", -"define 121 rfc3442 classless_static_routes", -"define 249 rfc3442 ms_classless_static_routes", -"define 33 request array ipaddress static_routes", -"define 3 request array ipaddress routers", -"define 6 array ipaddress domain_name_servers", -"define 12 dname host_name", -"define 15 array dname domain_name", -"define 26 uint16 interface_mtu", -"define 28 request ipaddress broadcast_address", -"define 50 ipaddress dhcp_requested_address", -"define 51 request uint32 dhcp_lease_time", -"define 52 byte dhcp_option_overload", -"define 53 byte dhcp_message_type", -"define 54 ipaddress dhcp_server_identifier", -"define 55 array byte dhcp_parameter_request_list", -"define 56 string dhcp_message", -"define 57 uint16 dhcp_max_message_size", -"define 58 request uint32 dhcp_renewal_time", -"define 59 request uint32 dhcp_rebinding_time", -"define 60 string vendor_class_identifier", -"define 61 binhex dhcp_client_identifier", -"define 80 norequest flag rapid_commit", -"define 81 embed fqdn", -"embed bitflags=0000NEOS flags", -"embed byte rcode1", -"embed byte rcode2", -"embed optional domain fqdn", -"define 119 array domain domain_search", -"definend 1 binhex source_address", -"definend 2 binhex target_address", -"definend 3 index embed prefix_information", -"embed byte length", -"embed bitflags=LA flags", -"embed uint32 vltime", -"embed uint32 pltime", -"embed uint32 reserved", -"embed array ip6address prefix", -"definend 5 embed mtu", -"embed uint16 reserved", -"embed uint32 mtu", -"definend 25 index embed rdnss", -"embed uint16 reserved", -"embed uint32 lifetime", -"embed array ip6address servers", -"definend 31 index embed dnssl", -"embed uint16 reserved", -"embed uint32 lifetime", -"embed domain search", -"define6 1 binhex client_id", -"define6 2 binhex server_id", -"define6 3 norequest index embed ia_na", -"embed binhex:4 iaid", -"embed uint32 t1", -"embed uint32 t2", -"encap 5 option", -"encap 13 option", -"define6 4 norequest index embed ia_ta", -"embed uint32 iaid", -"encap 5 option", -"encap 13 option", -"define6 5 norequest index embed ia_addr", -"embed ip6address ia_addr", -"embed uint32 pltime", -"embed uint32 vltime", -"encap 13 option", -"define6 12 ip6address unicast", -"define6 13 norequest embed status_code", -"embed uint16 status_code", -"embed optional string message", -"define6 18 binhex interface_id", -"define6 19 byte reconfigure_msg", -"define6 20 flag reconfigure_accept", -"define6 23 array ip6address name_servers", -"define6 24 array domain domain_search", -"define6 39 embed fqdn", -"embed bitflags=00000NOS flags", -"embed optional domain fqdn", -"define6 82 request uint32 sol_max_rt", -"define6 83 request uint32 inf_max_rt", +"define 1 request ipaddress subnet_mask\n" +"define 121 rfc3442 classless_static_routes\n" +"define 3 request array ipaddress routers\n" +"define 6 array ipaddress domain_name_servers\n" +"define 12 dname host_name\n" +"define 15 array dname domain_name\n" +"define 26 uint16 interface_mtu\n" +"define 28 request ipaddress broadcast_address\n" +"define 33 request array ipaddress static_routes\n" +"define 50 ipaddress dhcp_requested_address\n" +"define 51 request uint32 dhcp_lease_time\n" +"define 52 byte dhcp_option_overload\n" +"define 53 byte dhcp_message_type\n" +"define 54 ipaddress dhcp_server_identifier\n" +"define 55 array byte dhcp_parameter_request_list\n" +"define 56 string dhcp_message\n" +"define 57 uint16 dhcp_max_message_size\n" +"define 58 request uint32 dhcp_renewal_time\n" +"define 59 request uint32 dhcp_rebinding_time\n" +"define 60 string vendor_class_identifier\n" +"define 61 binhex dhcp_client_identifier\n" +"define 80 norequest flag rapid_commit\n" +"define 81 embed fqdn\n" +"embed bitflags=0000NEOS flags\n" +"embed byte rcode1\n" +"embed byte rcode2\n" +"embed optional domain fqdn\n" +"define 119 array domain domain_search\n" +"define 249 rfc3442 ms_classless_static_routes\n" +"definend 1 binhex source_address\n" +"definend 2 binhex target_address\n" +"definend 3 index embed prefix_information\n" +"embed byte length\n" +"embed bitflags=LAH flags\n" +"embed uint32 vltime\n" +"embed uint32 pltime\n" +"embed uint32 reserved\n" +"embed array ip6address prefix\n" +"definend 5 embed mtu\n" +"embed uint16 reserved\n" +"embed uint32 mtu\n" +"definend 25 index embed rdnss\n" +"embed uint16 reserved\n" +"embed uint32 lifetime\n" +"embed array ip6address servers\n" +"definend 31 index embed dnssl\n" +"embed uint16 reserved\n" +"embed uint32 lifetime\n" +"embed domain search\n" +"define6 1 binhex client_id\n" +"define6 2 binhex server_id\n" +"define6 3 norequest index embed ia_na\n" +"embed binhex:4 iaid\n" +"embed uint32 t1\n" +"embed uint32 t2\n" +"encap 5 option\n" +"encap 13 option\n" +"define6 4 norequest index embed ia_ta\n" +"embed uint32 iaid\n" +"encap 5 option\n" +"encap 13 option\n" +"define6 5 norequest index embed ia_addr\n" +"embed ip6address ia_addr\n" +"embed uint32 pltime\n" +"embed uint32 vltime\n" +"encap 13 option\n" +"define6 12 ip6address unicast\n" +"define6 13 norequest embed status_code\n" +"embed uint16 status_code\n" +"embed optional string message\n" +"define6 18 binhex interface_id\n" +"define6 19 byte reconfigure_msg\n" +"define6 20 flag reconfigure_accept\n" +"define6 23 array ip6address name_servers\n" +"define6 24 array domain domain_search\n" +"define6 39 embed fqdn\n" +"embed bitflags=00000NOS flags\n" +"embed optional domain fqdn\n" +"define6 82 request uint32 sol_max_rt\n" +"define6 83 request uint32 inf_max_rt\n" #else -"define 1 request ipaddress subnet_mask", -"define 121 rfc3442 classless_static_routes", -"define 249 rfc3442 ms_classless_static_routes", -"define 33 request array ipaddress static_routes", -"define 3 request array ipaddress routers", -"define 2 uint32 time_offset", -"define 4 array ipaddress time_servers", -"define 5 array ipaddress ien116_name_servers", -"define 6 array ipaddress domain_name_servers", -"define 7 array ipaddress log_servers", -"define 8 array ipaddress cookie_servers", -"define 9 array ipaddress lpr_servers", -"define 10 array ipaddress impress_servers", -"define 11 array ipaddress resource_location_servers", -"define 12 dname host_name", -"define 13 uint16 boot_size", -"define 14 string merit_dump", -"define 15 array dname domain_name", -"define 16 ipaddress swap_server", -"define 17 string root_path", -"define 18 string extensions_path", -"define 19 byte ip_forwarding", -"define 20 byte non_local_source_routing", -"define 21 array ipaddress policy_filter", -"define 22 uint16 max_dgram_reassembly", -"define 23 byte default_ip_ttl", -"define 24 uint32 path_mtu_aging_timeout", -"define 25 array uint16 path_mtu_plateau_table", -"define 26 uint16 interface_mtu", -"define 27 byte all_subnets_local", -"define 28 request ipaddress broadcast_address", -"define 29 byte perform_mask_discovery", -"define 30 byte mask_supplier", -"define 31 byte router_discovery", -"define 32 ipaddress router_solicitation_address", -"define 34 byte trailer_encapsulation", -"define 35 uint32 arp_cache_timeout", -"define 36 uint16 ieee802_3_encapsulation", -"define 37 byte default_tcp_ttl", -"define 38 uint32 tcp_keepalive_interval", -"define 39 byte tcp_keepalive_garbage", -"define 40 string nis_domain", -"define 41 array ipaddress nis_servers", -"define 42 array ipaddress ntp_servers", -"define 43 binhex vendor_encapsulated_options", -"define 44 array ipaddress netbios_name_servers", -"define 45 ipaddress netbios_dd_server", -"define 46 byte netbios_node_type", -"define 47 string netbios_scope", -"define 48 array ipaddress font_servers", -"define 49 array ipaddress x_display_manager", -"define 50 ipaddress dhcp_requested_address", -"define 51 request uint32 dhcp_lease_time", -"define 52 byte dhcp_option_overload", -"define 53 byte dhcp_message_type", -"define 54 ipaddress dhcp_server_identifier", -"define 55 array byte dhcp_parameter_request_list", -"define 56 string dhcp_message", -"define 57 uint16 dhcp_max_message_size", -"define 58 request uint32 dhcp_renewal_time", -"define 59 request uint32 dhcp_rebinding_time", -"define 60 string vendor_class_identifier", -"define 61 binhex dhcp_client_identifier", -"define 64 string nisplus_domain", -"define 65 array ipaddress nisplus_servers", -"define 66 dname tftp_server_name", -"define 67 string bootfile_name", -"define 68 array ipaddress mobile_ip_home_agent", -"define 69 array ipaddress smtp_server", -"define 70 array ipaddress pop_server", -"define 71 array ipaddress nntp_server", -"define 72 array ipaddress www_server", -"define 73 array ipaddress finger_server", -"define 74 array ipaddress irc_server", -"define 75 array ipaddress streettalk_server", -"define 76 array ipaddress streettalk_directory_assistance_server", -"define 77 binhex user_class", -"define 78 embed slp_agent", -"embed byte mandatory", -"embed array ipaddress address", -"define 79 embed slp_service", -"embed byte mandatory", -"embed ascii scope_list", -"define 80 norequest flag rapid_commit", -"define 81 embed fqdn", -"embed bitflags=0000NEOS flags", -"embed byte rcode1", -"embed byte rcode2", -"embed optional domain fqdn", -"define 83 embed isns", -"embed byte reserved1", -"embed bitflags=00000SAE functions", -"embed byte reserved2", -"embed bitflags=00fFsSCE dd", -"embed byte reserved3", -"embed bitflags=0000DMHE admin", -"embed uint16 reserved4", -"embed byte reserved5", -"embed bitflags=0TXPAMSE server_security", -"embed array ipaddress servers", -"define 85 array ipaddress nds_servers", -"define 86 raw nds_tree_name", -"define 87 raw nds_context", -"define 88 array domain bcms_controller_names", -"define 89 array ipaddress bcms_controller_address", -"define 90 embed auth", -"embed byte protocol", -"embed byte algorithm", -"embed byte rdm", -"embed binhex:8 replay", -"embed binhex information", -"define 91 uint32 client_last_transaction_time", -"define 92 array ipaddress associated_ip", -"define 98 string uap_servers", -"define 99 encap geoconf_civic", -"embed byte what", -"embed uint16 country_code", -"define 100 string posix_timezone", -"define 101 string tzdb_timezone", -"define 116 byte auto_configure", -"define 117 array uint16 name_service_search", -"define 118 ipaddress subnet_selection", -"define 119 array domain domain_search", -"define 120 rfc3361 sip_server", -"define 122 encap tsp", -"encap 1 ipaddress dhcp_server", -"encap 2 ipaddress dhcp_secondary_server", -"encap 3 rfc3361 provisioning_server", -"encap 4 embed as_req_as_rep_backoff", -"embed uint32 nominal", -"embed uint32 maximum", -"embed uint32 retry", -"encap 5 embed ap_req_ap_rep_backoff", -"embed uint32 nominal", -"embed uint32 maximum", -"embed uint32 retry", -"encap 6 domain kerberos_realm", -"encap 7 byte ticket_granting_server_utilization", -"encap 8 byte provisioning_timer", -"define 123 binhex geoconf", -"define 124 binhex vivco", -"define 125 embed vivso", -"embed uint32 enterprise_number", -"define 136 array ipaddress pana_agent", -"define 137 domain lost_server", -"define 138 array ipaddress capwap_ac", -"define 139 encap mos_ip", -"encap 1 array ipaddress is", -"encap 2 array ipaddress cs", -"encap 3 array ipaddress es", -"define 140 encap mos_domain", -"encap 1 domain is", -"encap 2 domain cs", -"encap 3 domain es", -"define 141 array domain sip_ua_cs_list", -"define 142 array ipaddress andsf", -"define 143 array ip6address andsf6", -"define 144 binhex geoloc", -"define 145 array byte forcerenew_nonce_capable", -"define 146 embed rdnss_selection", -"embed byte prf", -"embed ipaddress primary", -"embed ipaddress secondary", -"embed array domain domains", -"define 150 array ipaddress tftp_servers", -"define 161 string mudurl", -"define 208 binhex pxelinux_magic", -"define 209 string config_file", -"define 210 string path_prefix", -"define 211 uint32 reboot_time", -"define 212 embed sixrd", -"embed byte mask_len", -"embed byte prefix_len", -"embed ip6address prefix", -"embed array ipaddress brip_address", -"define 213 domain access_domain", -"define 221 encap vss", -"encap 0 string nvt", -"encap 1 binhex vpn_id", -"encap 255 flag global", -"define 252 string wpad_url", -"definend 1 binhex source_address", -"definend 2 binhex target_address", -"definend 3 index embed prefix_information", -"embed byte length", -"embed bitflags=LA flags", -"embed uint32 vltime", -"embed uint32 pltime", -"embed uint32 reserved", -"embed array ip6address prefix", -"definend 5 embed mtu", -"embed uint16 reserved", -"embed uint32 mtu", -"definend 25 index embed rdnss", -"embed uint16 reserved", -"embed uint32 lifetime", -"embed array ip6address servers", -"definend 31 index embed dnssl", -"embed uint16 reserved", -"embed uint32 lifetime", -"embed domain search", -"define6 1 binhex client_id", -"define6 2 binhex server_id", -"define6 3 norequest index embed ia_na", -"embed binhex:4 iaid", -"embed uint32 t1", -"embed uint32 t2", -"encap 5 option", -"encap 13 option", -"define6 4 norequest index embed ia_ta", -"embed uint32 iaid", -"encap 5 option", -"encap 13 option", -"define6 5 norequest index embed ia_addr", -"embed ip6address ia_addr", -"embed uint32 pltime", -"embed uint32 vltime", -"encap 13 option", -"define6 6 array uint16 option_request", -"define6 7 byte preference", -"define6 8 uint16 elased_time", -"define6 9 binhex dhcp_relay_msg", -"define6 11 embed auth", -"embed byte protocol", -"embed byte algorithm", -"embed byte rdm", -"embed binhex:8 replay", -"embed binhex information", -"define6 12 ip6address unicast", -"define6 13 norequest embed status_code", -"embed uint16 status_code", -"embed optional string message", -"define6 14 norequest flag rapid_commit", -"define6 15 binhex user_class", -"define6 16 binhex vivco", -"define6 17 embed vivso", -"embed uint32 enterprise_number", -"define6 18 binhex interface_id", -"define6 19 byte reconfigure_msg", -"define6 20 flag reconfigure_accept", -"define6 21 array domain sip_servers_names", -"define6 22 array ip6address sip_servers_addresses", -"define6 23 array ip6address name_servers", -"define6 24 array domain domain_search", -"define6 25 norequest index embed ia_pd", -"embed binhex:4 iaid", -"embed uint32 t1", -"embed uint32 t2", -"encap 26 option", -"define6 26 index embed prefix", -"embed uint32 pltime", -"embed uint32 vltime", -"embed byte length", -"embed ip6address prefix", -"encap 13 option", -"encap 67 option", -"define6 27 array ip6address nis_servers", -"define6 28 array ip6address nisp_servers", -"define6 29 string nis_domain_name", -"define6 30 string nisp_domain_name", -"define6 31 array ip6address sntp_servers", -"define6 32 uint32 info_refresh_time", -"define6 33 array domain bcms_server_d", -"define6 34 array ip6address bcms_server_a", -"define6 36 encap geoconf_civic", -"embed byte what", -"embed uint16 country_code", -"define6 37 embed remote_id", -"embed uint32 enterprise_number", -"embed binhex remote_id", -"define6 38 binhex subscriber_id", -"define6 39 embed fqdn", -"embed bitflags=00000NOS flags", -"embed optional domain fqdn", -"define6 40 array ip6address pana_agent", -"define6 41 string posix_timezone", -"define6 42 string tzdb_timezone", -"define6 43 array uint16 ero", -"define6 49 domain mip6_hnidf", -"define6 50 encap mip6_vdinf", -"encap 71 option", -"encap 72 option", -"encap 73 option", -"define6 51 domain lost_server", -"define6 52 array ip6address capwap_ac", -"define6 53 binhex relay_id", -"define6 54 encap mos_ip", -"encap 1 array ip6address is", -"encap 2 array ip6address cs", -"encap 3 array ip6address es", -"define6 55 encap mos_domain", -"encap 1 domain is", -"encap 2 domain cs", -"encap 3 domain es", -"define6 56 encap ntp_server", -"encap 1 ip6address addr", -"encap 2 ip6address mcast_addr", -"encap 3 ip6address fqdn", -"define6 57 domain access_domain", -"define6 58 array domain sip_ua_cs_list", -"define6 59 string bootfile_url", -"define6 60 binhex bootfile_param", -"define6 61 array uint16 architecture_types", -"define6 62 embed nii", -"embed byte type", -"embed byte major", -"embed byte minor", -"define6 63 binhex geoloc", -"define6 64 domain aftr_name", -"define6 67 embed pd_exclude", -"embed byte prefix_len", -"embed binhex subnetID", -"define6 69 encap mip6_idinf", -"encap 71 option", -"encap 72 option", -"encap 73 option", -"define6 70 encap mip6_udinf", -"encap 71 option", -"encap 72 option", -"encap 73 option", -"define6 71 embed mip6_hnp", -"embed byte prefix_len", -"embed ip6address prefix", -"define6 72 ip6address mip6_haa", -"define6 73 domain mip6_haf", -"define6 74 embed rdnss_selection", -"embed ip6address server", -"embed byte prf", -"embed array domain domains", -"define6 75 string krb_principal_name", -"define6 76 string krb_realm_name", -"define6 78 embed krb_kdc", -"embed uint16 priority", -"embed uint16 weight", -"embed byte transport_type", -"embed uint16 port", -"embed ip6address address", -"embed string realm_name", -"define6 80 ip6address link_address", -"define6 82 request uint32 sol_max_rt", -"define6 83 request uint32 inf_max_rt", -"define6 89 embed s46_rule", -"embed bitflags=0000000F flags", -"embed byte ea_len", -"embed byte prefix4_len", -"embed ipaddress ipv4_prefix", -"embed ip6address ipv6_prefix", -"define6 90 ip6address s64_br", -"define6 91 embed s46_dmr", -"embed byte prefix_len", -"embed binhex prefix", -"define6 92 embed s46_v4v6bind", -"embed ipaddress ipv4_address", -"embed byte ipv6_prefix_len", -"embed binhex ipv6_prefix_and_options", -"define6 93 embed s46_portparams", -"embed byte offset", -"embed byte psid_len", -"embed uint16 psid", -"define6 94 embed s46_cont_mape", -"encap 89 option", -"encap 90 option", -"define6 95 embed s46_cont_mapt", -"encap 89 option", -"encap 91 option", -"define6 96 embed s46_cont_lw", -"encap 90 option", -"encap 92 option", -"define6 112 string mudurl", +"define 1 request ipaddress subnet_mask\n" +"define 121 rfc3442 classless_static_routes\n" +"define 2 uint32 time_offset\n" +"define 3 request array ipaddress routers\n" +"define 4 array ipaddress time_servers\n" +"define 5 array ipaddress ien116_name_servers\n" +"define 6 array ipaddress domain_name_servers\n" +"define 7 array ipaddress log_servers\n" +"define 8 array ipaddress cookie_servers\n" +"define 9 array ipaddress lpr_servers\n" +"define 10 array ipaddress impress_servers\n" +"define 11 array ipaddress resource_location_servers\n" +"define 12 dname host_name\n" +"define 13 uint16 boot_size\n" +"define 14 string merit_dump\n" +"define 15 array dname domain_name\n" +"define 16 ipaddress swap_server\n" +"define 17 string root_path\n" +"define 18 string extensions_path\n" +"define 19 byte ip_forwarding\n" +"define 20 byte non_local_source_routing\n" +"define 21 array ipaddress policy_filter\n" +"define 22 uint16 max_dgram_reassembly\n" +"define 23 byte default_ip_ttl\n" +"define 24 uint32 path_mtu_aging_timeout\n" +"define 25 array uint16 path_mtu_plateau_table\n" +"define 26 uint16 interface_mtu\n" +"define 27 byte all_subnets_local\n" +"define 28 request ipaddress broadcast_address\n" +"define 29 byte perform_mask_discovery\n" +"define 30 byte mask_supplier\n" +"define 31 byte router_discovery\n" +"define 32 ipaddress router_solicitation_address\n" +"define 33 request array ipaddress static_routes\n" +"define 34 byte trailer_encapsulation\n" +"define 35 uint32 arp_cache_timeout\n" +"define 36 uint16 ieee802_3_encapsulation\n" +"define 37 byte default_tcp_ttl\n" +"define 38 uint32 tcp_keepalive_interval\n" +"define 39 byte tcp_keepalive_garbage\n" +"define 40 string nis_domain\n" +"define 41 array ipaddress nis_servers\n" +"define 42 array ipaddress ntp_servers\n" +"define 43 binhex vendor_encapsulated_options\n" +"define 44 array ipaddress netbios_name_servers\n" +"define 45 ipaddress netbios_dd_server\n" +"define 46 byte netbios_node_type\n" +"define 47 string netbios_scope\n" +"define 48 array ipaddress font_servers\n" +"define 49 array ipaddress x_display_manager\n" +"define 50 ipaddress dhcp_requested_address\n" +"define 51 request uint32 dhcp_lease_time\n" +"define 52 byte dhcp_option_overload\n" +"define 53 byte dhcp_message_type\n" +"define 54 ipaddress dhcp_server_identifier\n" +"define 55 array byte dhcp_parameter_request_list\n" +"define 56 string dhcp_message\n" +"define 57 uint16 dhcp_max_message_size\n" +"define 58 request uint32 dhcp_renewal_time\n" +"define 59 request uint32 dhcp_rebinding_time\n" +"define 60 string vendor_class_identifier\n" +"define 61 binhex dhcp_client_identifier\n" +"define 64 string nisplus_domain\n" +"define 65 array ipaddress nisplus_servers\n" +"define 66 dname tftp_server_name\n" +"define 67 string bootfile_name\n" +"define 68 array ipaddress mobile_ip_home_agent\n" +"define 69 array ipaddress smtp_server\n" +"define 70 array ipaddress pop_server\n" +"define 71 array ipaddress nntp_server\n" +"define 72 array ipaddress www_server\n" +"define 73 array ipaddress finger_server\n" +"define 74 array ipaddress irc_server\n" +"define 75 array ipaddress streettalk_server\n" +"define 76 array ipaddress streettalk_directory_assistance_server\n" +"define 77 binhex user_class\n" +"define 78 embed slp_agent\n" +"embed byte mandatory\n" +"embed array ipaddress address\n" +"define 79 embed slp_service\n" +"embed byte mandatory\n" +"embed ascii scope_list\n" +"define 80 norequest flag rapid_commit\n" +"define 81 embed fqdn\n" +"embed bitflags=0000NEOS flags\n" +"embed byte rcode1\n" +"embed byte rcode2\n" +"embed optional domain fqdn\n" +"define 83 embed isns\n" +"embed byte reserved1\n" +"embed bitflags=00000SAE functions\n" +"embed byte reserved2\n" +"embed bitflags=00fFsSCE dd\n" +"embed byte reserved3\n" +"embed bitflags=0000DMHE admin\n" +"embed uint16 reserved4\n" +"embed byte reserved5\n" +"embed bitflags=0TXPAMSE server_security\n" +"embed array ipaddress servers\n" +"define 85 array ipaddress nds_servers\n" +"define 86 raw nds_tree_name\n" +"define 87 raw nds_context\n" +"define 88 array domain bcms_controller_names\n" +"define 89 array ipaddress bcms_controller_address\n" +"define 90 embed auth\n" +"embed byte protocol\n" +"embed byte algorithm\n" +"embed byte rdm\n" +"embed binhex:8 replay\n" +"embed binhex information\n" +"define 91 uint32 client_last_transaction_time\n" +"define 92 array ipaddress associated_ip\n" +"define 98 string uap_servers\n" +"define 99 encap geoconf_civic\n" +"embed byte what\n" +"embed uint16 country_code\n" +"define 100 string posix_timezone\n" +"define 101 string tzdb_timezone\n" +"define 108 uint32 ipv6_only_preferred\n" +"define 116 byte auto_configure\n" +"define 117 array uint16 name_service_search\n" +"define 118 ipaddress subnet_selection\n" +"define 119 array domain domain_search\n" +"define 120 rfc3361 sip_server\n" +"define 122 encap tsp\n" +"encap 1 ipaddress dhcp_server\n" +"encap 2 ipaddress dhcp_secondary_server\n" +"encap 3 rfc3361 provisioning_server\n" +"encap 4 embed as_req_as_rep_backoff\n" +"embed uint32 nominal\n" +"embed uint32 maximum\n" +"embed uint32 retry\n" +"encap 5 embed ap_req_ap_rep_backoff\n" +"embed uint32 nominal\n" +"embed uint32 maximum\n" +"embed uint32 retry\n" +"encap 6 domain kerberos_realm\n" +"encap 7 byte ticket_granting_server_utilization\n" +"encap 8 byte provisioning_timer\n" +"define 123 binhex geoconf\n" +"define 124 binhex vivco\n" +"define 125 embed vivso\n" +"embed uint32 enterprise_number\n" +"define 136 array ipaddress pana_agent\n" +"define 137 domain lost_server\n" +"define 138 array ipaddress capwap_ac\n" +"define 139 encap mos_ip\n" +"encap 1 array ipaddress is\n" +"encap 2 array ipaddress cs\n" +"encap 3 array ipaddress es\n" +"define 140 encap mos_domain\n" +"encap 1 domain is\n" +"encap 2 domain cs\n" +"encap 3 domain es\n" +"define 141 array domain sip_ua_cs_list\n" +"define 142 array ipaddress andsf\n" +"define 143 array ip6address andsf6\n" +"define 144 binhex geoloc\n" +"define 145 array byte forcerenew_nonce_capable\n" +"define 146 embed rdnss_selection\n" +"embed byte prf\n" +"embed ipaddress primary\n" +"embed ipaddress secondary\n" +"embed array domain domains\n" +"define 150 array ipaddress tftp_servers\n" +"define 161 string mudurl\n" +"define 208 binhex pxelinux_magic\n" +"define 209 string config_file\n" +"define 210 string path_prefix\n" +"define 211 uint32 reboot_time\n" +"define 212 embed sixrd\n" +"embed byte mask_len\n" +"embed byte prefix_len\n" +"embed ip6address prefix\n" +"embed array ipaddress brip_address\n" +"define 213 domain access_domain\n" +"define 221 encap vss\n" +"encap 0 string nvt\n" +"encap 1 binhex vpn_id\n" +"encap 255 flag global\n" +"define 249 rfc3442 ms_classless_static_routes\n" +"define 252 string wpad_url\n" +"definend 1 binhex source_address\n" +"definend 2 binhex target_address\n" +"definend 3 index embed prefix_information\n" +"embed byte length\n" +"embed bitflags=LAH flags\n" +"embed uint32 vltime\n" +"embed uint32 pltime\n" +"embed uint32 reserved\n" +"embed array ip6address prefix\n" +"definend 5 embed mtu\n" +"embed uint16 reserved\n" +"embed uint32 mtu\n" +"definend 8 embed homeagent_information\n" +"embed uint16 reserved\n" +"embed uint16 preference\n" +"embed uint16 lifetime\n" +"definend 25 index embed rdnss\n" +"embed uint16 reserved\n" +"embed uint32 lifetime\n" +"embed array ip6address servers\n" +"definend 31 index embed dnssl\n" +"embed uint16 reserved\n" +"embed uint32 lifetime\n" +"embed domain search\n" +"define6 1 binhex client_id\n" +"define6 2 binhex server_id\n" +"define6 3 norequest index embed ia_na\n" +"embed binhex:4 iaid\n" +"embed uint32 t1\n" +"embed uint32 t2\n" +"encap 5 option\n" +"encap 13 option\n" +"define6 4 norequest index embed ia_ta\n" +"embed uint32 iaid\n" +"encap 5 option\n" +"encap 13 option\n" +"define6 5 norequest index embed ia_addr\n" +"embed ip6address ia_addr\n" +"embed uint32 pltime\n" +"embed uint32 vltime\n" +"encap 13 option\n" +"define6 6 array uint16 option_request\n" +"define6 7 byte preference\n" +"define6 8 uint16 elased_time\n" +"define6 9 binhex dhcp_relay_msg\n" +"define6 11 embed auth\n" +"embed byte protocol\n" +"embed byte algorithm\n" +"embed byte rdm\n" +"embed binhex:8 replay\n" +"embed binhex information\n" +"define6 12 ip6address unicast\n" +"define6 13 norequest embed status_code\n" +"embed uint16 status_code\n" +"embed optional string message\n" +"define6 14 norequest flag rapid_commit\n" +"define6 15 binhex user_class\n" +"define6 16 binhex vivco\n" +"define6 17 embed vivso\n" +"embed uint32 enterprise_number\n" +"define6 18 binhex interface_id\n" +"define6 19 byte reconfigure_msg\n" +"define6 20 flag reconfigure_accept\n" +"define6 21 array domain sip_servers_names\n" +"define6 22 array ip6address sip_servers_addresses\n" +"define6 23 array ip6address name_servers\n" +"define6 24 array domain domain_search\n" +"define6 25 norequest index embed ia_pd\n" +"embed binhex:4 iaid\n" +"embed uint32 t1\n" +"embed uint32 t2\n" +"encap 26 option\n" +"define6 26 index embed prefix\n" +"embed uint32 pltime\n" +"embed uint32 vltime\n" +"embed byte length\n" +"embed ip6address prefix\n" +"encap 13 option\n" +"encap 67 option\n" +"define6 27 array ip6address nis_servers\n" +"define6 28 array ip6address nisp_servers\n" +"define6 29 string nis_domain_name\n" +"define6 30 string nisp_domain_name\n" +"define6 31 array ip6address sntp_servers\n" +"define6 32 uint32 info_refresh_time\n" +"define6 33 array domain bcms_server_d\n" +"define6 34 array ip6address bcms_server_a\n" +"define6 36 encap geoconf_civic\n" +"embed byte what\n" +"embed uint16 country_code\n" +"define6 37 embed remote_id\n" +"embed uint32 enterprise_number\n" +"embed binhex remote_id\n" +"define6 38 binhex subscriber_id\n" +"define6 39 embed fqdn\n" +"embed bitflags=00000NOS flags\n" +"embed optional domain fqdn\n" +"define6 40 array ip6address pana_agent\n" +"define6 41 string posix_timezone\n" +"define6 42 string tzdb_timezone\n" +"define6 43 array uint16 ero\n" +"define6 49 domain mip6_hnidf\n" +"define6 50 encap mip6_vdinf\n" +"encap 71 option\n" +"encap 72 option\n" +"encap 73 option\n" +"define6 51 domain lost_server\n" +"define6 52 array ip6address capwap_ac\n" +"define6 53 binhex relay_id\n" +"define6 54 encap mos_ip\n" +"encap 1 array ip6address is\n" +"encap 2 array ip6address cs\n" +"encap 3 array ip6address es\n" +"define6 55 encap mos_domain\n" +"encap 1 domain is\n" +"encap 2 domain cs\n" +"encap 3 domain es\n" +"define6 56 encap ntp_server\n" +"encap 1 ip6address addr\n" +"encap 2 ip6address mcast_addr\n" +"encap 3 domain fqdn\n" +"define6 57 domain access_domain\n" +"define6 58 array domain sip_ua_cs_list\n" +"define6 59 string bootfile_url\n" +"define6 60 binhex bootfile_param\n" +"define6 61 array uint16 architecture_types\n" +"define6 62 embed nii\n" +"embed byte type\n" +"embed byte major\n" +"embed byte minor\n" +"define6 63 binhex geoloc\n" +"define6 64 domain aftr_name\n" +"define6 67 embed pd_exclude\n" +"embed byte prefix_len\n" +"embed binhex subnetID\n" +"define6 69 encap mip6_idinf\n" +"encap 71 option\n" +"encap 72 option\n" +"encap 73 option\n" +"define6 70 encap mip6_udinf\n" +"encap 71 option\n" +"encap 72 option\n" +"encap 73 option\n" +"define6 71 embed mip6_hnp\n" +"embed byte prefix_len\n" +"embed ip6address prefix\n" +"define6 72 ip6address mip6_haa\n" +"define6 73 domain mip6_haf\n" +"define6 74 embed rdnss_selection\n" +"embed ip6address server\n" +"embed byte prf\n" +"embed array domain domains\n" +"define6 75 string krb_principal_name\n" +"define6 76 string krb_realm_name\n" +"define6 78 embed krb_kdc\n" +"embed uint16 priority\n" +"embed uint16 weight\n" +"embed byte transport_type\n" +"embed uint16 port\n" +"embed ip6address address\n" +"embed string realm_name\n" +"define6 80 ip6address link_address\n" +"define6 82 request uint32 sol_max_rt\n" +"define6 83 request uint32 inf_max_rt\n" +"define6 89 embed s46_rule\n" +"embed bitflags=0000000F flags\n" +"embed byte ea_len\n" +"embed byte prefix4_len\n" +"embed ipaddress ipv4_prefix\n" +"embed ip6address ipv6_prefix\n" +"define6 90 ip6address s64_br\n" +"define6 91 embed s46_dmr\n" +"embed byte prefix_len\n" +"embed binhex prefix\n" +"define6 92 embed s46_v4v6bind\n" +"embed ipaddress ipv4_address\n" +"embed byte ipv6_prefix_len\n" +"embed binhex ipv6_prefix_and_options\n" +"define6 93 embed s46_portparams\n" +"embed byte offset\n" +"embed byte psid_len\n" +"embed uint16 psid\n" +"define6 94 embed s46_cont_mape\n" +"encap 89 option\n" +"encap 90 option\n" +"define6 95 embed s46_cont_mapt\n" +"encap 89 option\n" +"encap 91 option\n" +"define6 96 embed s46_cont_lw\n" +"encap 90 option\n" +"encap 92 option\n" +"define6 112 string mudurl\n" #endif -NULL -}; +"\0"; + Index: contrib/dhcpcd/src/dhcpcd.h =================================================================== --- contrib/dhcpcd/src/dhcpcd.h +++ contrib/dhcpcd/src/dhcpcd.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -32,6 +32,8 @@ #include #include +#include + #include "config.h" #ifdef HAVE_SYS_QUEUE_H #include @@ -53,7 +55,6 @@ #define LINK_UP 1 #define LINK_UNKNOWN 0 #define LINK_DOWN -1 -#define LINK_DOWN_IFFUP -2 #define IF_DATA_IPV4 0 #define IF_DATA_ARP 1 @@ -64,10 +65,6 @@ #define IF_DATA_DHCP6 6 #define IF_DATA_MAX 7 -/* If the interface does not support carrier status (ie PPP), - * dhcpcd can poll it for the relevant flags periodically */ -#define IF_POLL_UP 100 /* milliseconds */ - #ifdef __QNX__ /* QNX carries defines for, but does not actually support PF_LINK */ #undef IFLR_ACTIVE @@ -80,7 +77,7 @@ unsigned int index; unsigned int active; unsigned int flags; - sa_family_t family; + uint16_t hwtype; /* ARPHRD_ETHER for example */ unsigned char hwaddr[HWADDR_LEN]; uint8_t hwlen; unsigned short vlanid; @@ -96,8 +93,8 @@ }; TAILQ_HEAD(if_head, interface); +#include "privsep.h" -#ifdef INET6 /* dhcpcd requires CMSG_SPACE to evaluate to a compile time constant. */ #if defined(__QNX) || \ (defined(__NetBSD_Version__) && __NetBSD_Version__ < 600000000) @@ -114,12 +111,16 @@ #define CMSG_SPACE(len) (ALIGN(sizeof(struct cmsghdr)) + ALIGN(len)) #endif -#define IP6BUFLEN (CMSG_SPACE(sizeof(struct in6_pktinfo)) + \ - CMSG_SPACE(sizeof(int))) -#endif +struct passwd; struct dhcpcd_ctx { char pidfile[sizeof(PIDFILE) + IF_NAMESIZE + 1]; + char vendor[256]; + bool stdin_valid; /* It's possible stdin, stdout and stderr */ + bool stdout_valid; /* could be closed when dhcpcd starts. */ + bool stderr_valid; + int stderr_fd; /* FD for logging to stderr */ + int fork_fd; /* FD for the fork init signal pipe */ const char *cffile; unsigned long long options; char *logfile; @@ -133,10 +134,16 @@ char **ifv; /* listed interfaces */ int ifcc; /* configured interfaces */ char **ifcv; /* configured interfaces */ + uint8_t duid_type; unsigned char *duid; size_t duid_len; struct if_head *ifaces; + char *ctl_buf; + size_t ctl_buflen; + size_t ctl_bufpos; + size_t ctl_extra; + rb_tree_t routes; /* our routes */ #ifdef RT_FREE_ROUTE_TABLE rb_tree_t froutes; /* free routes for re-use */ @@ -144,6 +151,9 @@ size_t rt_order; /* route order storage */ int pf_inet_fd; +#ifdef PF_LINK + int pf_link_fd; +#endif void *priv; int link_fd; #ifndef SMALL @@ -157,6 +167,7 @@ #endif struct eloop *eloop; + char *script; #ifdef HAVE_OPEN_MEMSTREAM FILE *script_fp; #endif @@ -169,6 +180,7 @@ int control_unpriv_fd; struct fd_list_head control_fds; char control_sock[sizeof(CONTROLSOCKET) + IF_NAMESIZE]; + char control_sock_unpriv[sizeof(CONTROLSOCKET) + IF_NAMESIZE + 7]; gid_t control_group; /* DHCP Enterprise options, RFC3925 */ @@ -177,11 +189,36 @@ char *randomstate; /* original state */ + /* For filtering RTM_MISS messages per router */ +#ifdef BSD + uint8_t *rt_missfilter; + size_t rt_missfilterlen; + size_t rt_missfiltersize; +#endif + +#ifdef PRIVSEP + struct passwd *ps_user; /* struct passwd for privsep user */ + pid_t ps_root_pid; + int ps_root_fd; /* Privileged Proxy commands */ + int ps_log_fd; /* chroot logging */ + int ps_data_fd; /* Data from root spawned processes */ + struct eloop *ps_eloop; /* eloop for polling root data */ + struct ps_process_head ps_processes; /* List of spawned processes */ + pid_t ps_inet_pid; + int ps_inet_fd; /* Network Proxy commands and data */ + pid_t ps_control_pid; + int ps_control_fd; /* Control Proxy - generic listener */ + int ps_control_data_fd; /* Control Proxy - data query */ + struct fd_list *ps_control; /* Queue for the above */ + struct fd_list *ps_control_client; /* Queue for the above */ +#endif + #ifdef INET struct dhcp_opt *dhcp_opts; size_t dhcp_opts_len; - int udp_fd; + int udp_rfd; + int udp_wfd; /* Our aggregate option buffer. * We ONLY use this when options are split, which for most purposes is @@ -198,11 +235,11 @@ #endif struct ra_head *ra_routers; - int dhcp6_fd; - struct dhcp_opt *nd_opts; size_t nd_opts_len; #ifdef DHCP6 + int dhcp6_rfd; + int dhcp6_wfd; struct dhcp_opt *dhcp6_opts; size_t dhcp6_opts_len; #endif @@ -223,18 +260,21 @@ #ifdef USE_SIGNALS extern const int dhcpcd_signals[]; extern const size_t dhcpcd_signals_len; +extern const int dhcpcd_signals_ignore[]; +extern const size_t dhcpcd_signals_ignore_len; #endif +extern const char *dhcpcd_default_script; + int dhcpcd_ifafwaiting(const struct interface *); int dhcpcd_afwaiting(const struct dhcpcd_ctx *); -pid_t dhcpcd_daemonise(struct dhcpcd_ctx *); +void dhcpcd_daemonise(struct dhcpcd_ctx *); void dhcpcd_linkoverflow(struct dhcpcd_ctx *); int dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **); -void dhcpcd_handlecarrier(struct dhcpcd_ctx *, int, unsigned int, const char *); +void dhcpcd_handlecarrier(struct interface *, int, unsigned int); int dhcpcd_handleinterface(void *, int, const char *); -void dhcpcd_handlehwaddr(struct dhcpcd_ctx *, const char *, - const void *, uint8_t); +void dhcpcd_handlehwaddr(struct interface *, uint16_t, const void *, uint8_t); void dhcpcd_dropinterface(struct interface *, const char *); int dhcpcd_selectprofile(struct interface *, const char *); Index: contrib/dhcpcd/src/dhcpcd.8 =================================================================== --- contrib/dhcpcd/src/dhcpcd.8 +++ contrib/dhcpcd/src/dhcpcd.8 @@ -1,6 +1,6 @@ .\" SPDX-License-Identifier: BSD-2-Clause .\" -.\" Copyright (c) 2006-2019 Roy Marples +.\" Copyright (c) 2006-2021 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -24,7 +24,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd October 9, 2019 +.Dd August 23, 2021 .Dt DHCPCD 8 .Os .Sh NAME @@ -62,6 +62,8 @@ .Op Fl Z , Fl Fl denyinterfaces Ar pattern .Op Fl z , Fl Fl allowinterfaces Ar pattern .Op Fl Fl inactive +.Op Fl Fl configure +.Op Fl Fl noconfigure .Op interface .Op ... .Nm @@ -72,7 +74,7 @@ .Op interface .Nm .Fl U , Fl Fl dumplease -.Ar interface +.Op Ar interface .Nm .Fl Fl version .Nm @@ -109,7 +111,7 @@ .Pp If any interface reports a working carrier then .Nm -will try and obtain a lease before forking to the background, +will try to obtain a lease before forking to the background, otherwise it will fork right away. This behaviour can be modified with the .Fl b , Fl Fl background @@ -170,7 +172,7 @@ When .Nm not limited to one interface on the command line, -it is running in Master mode. +it is running in Manager mode. The .Nm dhcpcd-ui project expects dhcpcd to be running this way. @@ -190,9 +192,9 @@ and .Fl x options, where the same interface will need to be specified, as a lack of an -interface will imply Master mode which this is not. -To force starting in Master mode with only one interface, the -.Fl M , Fl Fl master +interface will imply Manager mode which this is not. +To force starting in Manager mode with only one interface, the +.Fl M , Fl Fl manager option can be used. .Pp Interfaces are preferred by carrier, DHCP lease/IPv4LL and then lowest metric. @@ -211,16 +213,16 @@ be listed in .Fl Fl allowinterfaces or have an interface directive in -.Pa @SYSCONFDIR@/dhcpcd.conf . +.Pa /etc/dhcpcd.conf . .Ss Hooking into events .Nm runs -.Pa @SCRIPT@ , +.Pa /libexec/dhcpcd-run-hooks , or the script specified by the .Fl c , Fl Fl script option. This script runs each script found in -.Pa @HOOKDIR@ +.Pa /libexec/dhcpcd-hooks in a lexical order. The default installation supplies the scripts .Pa 01-test , @@ -238,9 +240,9 @@ currently ignores the exit code of the script. .Pp More scripts are supplied in -.Pa @DATADIR@/dhcpcd/hooks +.Pa /usr/share/dhcpcd/hooks and need to be copied to -.Pa @HOOKDIR@ +.Pa /libexec/dhcpcd-hooks if you intend to use them. For example, you could install .Pa 29-lookup-hostname @@ -261,19 +263,30 @@ Use this .Ar script instead of the default -.Pa @SCRIPT@ . -.It Fl D , Fl Fl duid +.Pa /libexec/dhcpcd-run-hooks . +.It Fl D , Fl Fl duid Op Ar ll | lt | uuid | value Use a DHCP Unique Identifier. If a system UUID is available, that will be used to create a DUID-UUID, otheriwse if persistent storage is available then a DUID-LLT (link local address + time) is generated, otherwise DUID-LL is generated (link local address). +The DUID type can be hinted as an optional parameter if the file +.Pa /var/db/dhcpcd/duid +does not exist. +If not +.Va ll , +.Va lt +or +.Va uuid +then +.Va value +will be converted from 00:11:22:33 format. This, plus the IAID will be used as the .Fl I , Fl Fl clientid . The DUID generated will be held in -.Pa @DBDIR@/duid +.Pa /var/db/dhcpcd/duid and should not be copied to other hosts. -This file also takes precedence over the above rules. +This file also takes precedence over the above rules except for setting a value. .It Fl d , Fl Fl debug Echo debug messages to the stderr and syslog. .It Fl E , Fl Fl lastlease @@ -320,7 +333,7 @@ .Li RFC 1035 . .It Fl f , Fl Fl config Ar file Specify a config to load instead of -.Pa @SYSCONFDIR@/dhcpcd.conf . +.Pa /etc/dhcpcd.conf . .Nm always processes the config file before any command line options. .It Fl h , Fl Fl hostname Ar hostname @@ -357,8 +370,9 @@ To work around it, try and impersonate Windows by using the MSFT vendorclassid. .It Fl j , Fl Fl logfile Ar logfile Writes to the specified -.Ar logfile -rather than +.Ar logfile . +.Nm +still writes to .Xr syslog 3 . The .Ar logfile @@ -379,28 +393,31 @@ option. If no .Ar interface -is specified then this applies to all interfaces in Master mode. +is specified then this applies to all interfaces in Manager mode. If no interfaces are left running, .Nm will exit. .It Fl l , Fl Fl leasetime Ar seconds -Request a specific lease time in +Request a lease time of .Ar seconds . +.Ar -1 +represents an infinite lease time. By default .Nm does not request any lease time and leaves it in the hands of the DHCP server. -.It Fl M , Fl Fl master +.It Fl M , Fl Fl manager Start .Nm -in Master mode even if only one interface specified on the command line. +in Manager mode even if only one interface specified on the command line. See the Multiple Interfaces section above. .It Fl m , Fl Fl metric Ar metric Metrics are used to prefer an interface over another one, lowest wins. .Nm -will supply a default metic of 200 + +will supply a default metric of 1000 + .Xr if_nametoindex 3 . -An extra 100 will be added for wireless interfaces. +This will be offset by 2000 for wireless interfaces, with additional offsets +of 1000000 for IPv4LL and 2000000 for roaming interfaces. .It Fl n , Fl Fl rebind Op Ar interface Notifies .Nm @@ -408,7 +425,7 @@ .Ar interface . If no .Ar interface -is specified then this applies to all interfaces in Master mode. +is specified then this applies to all interfaces in Manager mode. If .Nm is not running, then it starts up as normal. @@ -419,7 +436,7 @@ .Ar interface . If no .Ar interface -is specified then this applies to all interfaces in Master mode. +is specified then this applies to all interfaces in Manager mode. If .Nm is not running, then it starts up as normal. @@ -432,7 +449,7 @@ Request the DHCP .Ar option variable for use in -.Pa @SCRIPT@ . +.Pa /libexec/dhcpcd-run-hooks . .It Fl p , Fl Fl persistent .Nm normally de-configures the @@ -562,7 +579,7 @@ to exit. If no .Ar interface -is specified, then the above is applied to all interfaces in Master mode. +is specified, then the above is applied to all interfaces in Manager mode. See the .Fl p , Fl Fl persistent option to control configuration persistence on exit, @@ -668,10 +685,12 @@ Quiet .Nm on the command line, only warnings and errors will be displayed. -The messages are still logged though. +If this option is used another time then all console output is disabled. +These messages are still logged via +.Xr syslog 3 . .It Fl T , Fl Fl test On receipt of DHCP messages just call -.Pa @SCRIPT@ +.Pa /libexec/dhcpcd-run-hooks with the reason of TEST which echos the DHCP variables found in the message to the console. The interface configuration isn't touched and neither are any configuration @@ -682,16 +701,20 @@ To test INFORM the interface needs to be configured with the desired address before starting .Nm . -.It Fl U , Fl Fl dumplease Ar interface -Dumps the last lease for the +.It Fl U , Fl Fl dumplease Op Ar interface +Dumps the current lease for the .Ar interface to stdout. -If omitted, standard input is used to read a DHCP wire formatted message. +If no +.Ar interface +is given then all interfaces are dumped. Use the .Fl 4 or .Fl 6 flags to specify an address family. +If a lease is piped in via standard input then that is dumped. +In this case, specifying an address family is mandatory. .It Fl V , Fl Fl variables Display a list of option codes, the associated variable and encoding for use in .Xr dhcpcd-run-hooks 8 . @@ -725,9 +748,28 @@ Don't start any interfaces other than those specified on the command line. This allows .Nm -to be started in Master mode and then wait for subsequent +to be started in Manager mode and then wait for subsequent .Nm commands to start each interface as required. +.It Fl Fl configure +Allows +.Nm +to configure the system. +This is the default behaviour and sets +.Ev if_configured=true . +.It Fl Fl noconfigure +.Nm +will not configure the system at all. +This is only of use if the +.Fl Fl script +that +.Nm +calls at each network event configures the system instead. +This is different from +.Fl T , Fl Fl test +mode in that it's not one shot and the only change to the environment is the +addition of +.Ev if_configured=false . .It Fl Fl nodev Don't load any .Pa /dev @@ -770,55 +812,57 @@ .Nm sends to match. If using a DUID in place of the ClientID, edit -.Pa @DBDIR@/duid +.Pa /var/db/dhcpcd/duid accordingly. .Sh FILES .Bl -ohang -.It Pa @SYSCONFDIR@/dhcpcd.conf +.It Pa /etc/dhcpcd.conf Configuration file for dhcpcd. If you always use the same options, put them here. -.It Pa @SCRIPT@ +.It Pa /libexec/dhcpcd-run-hooks Bourne shell script that is run to configure or de-configure an interface. -.It Pa @LIBDIR@/dhcpcd/dev +.It Pa /lib/dhcpcd/dev Linux .Pa /dev management modules. -.It Pa @HOOKDIR@ +.It Pa /libexec/dhcpcd-hooks A directory containing bourne shell scripts that are run by the above script. Each script can be disabled by using the .Fl C , Fl Fl nohook option described above. -.It Pa @DBDIR@/duid +.It Pa /var/db/dhcpcd/duid Text file that holds the DUID used to identify the host. -.It Pa @DBDIR@/secret +.It Pa /var/db/dhcpcd/secret Text file that holds a secret key known only to the host. -.It Pa @DBDIR@/ Ns Ar interface Ns Ar -ssid Ns .lease +.It Pa /var/db/dhcpcd/ Ns Ar interface Ns Ar -ssid Ns .lease The actual DHCP message sent by the server. We use this when reading the last lease and use the file's mtime as when it was issued. -.It Pa @DBDIR@/ Ns Ar interface Ns Ar -ssid Ns .lease6 +.It Pa /var/db/dhcpcd/ Ns Ar interface Ns Ar -ssid Ns .lease6 The actual DHCPv6 message sent by the server. We use this when reading the last lease and use the file's mtime as when it was issued. -.It Pa @DBDIR@/rdm_monotonic +.It Pa /var/db/dhcpcd/rdm_monotonic Stores the monotonic counter used in the .Ar replay field in Authentication Options. -.It Pa @RUNDIR@/dhcpcd.pid +.It Pa /var/run/dhcpcd/pid Stores the PID of .Nm running on all interfaces. -.It Pa @RUNDIR@/dhcpcd\- Ns Ar interface Ns .pid +.It Pa /var/run/dhcpcd/ Ns Ar interface Ns .pid Stores the PID of .Nm running on the .Ar interface . -.It Pa @RUNDIR@/dhcpcd.sock -Control socket to the master daemon. -.It Pa @RUNDIR@/dhcpcd.unpriv.sock -Unprivileged socket to the master daemon, only allows state retrieval. -.It Pa @RUNDIR@/dhcpcd\- Ns Ar interface Ns .sock +.It Pa /var/run/dhcpcd/sock +Control socket to the manager daemon. +.It Pa /var/run/dhcpcd/unpriv.sock +Unprivileged socket to the manager daemon, only allows state retrieval. +.It Pa /var/run/dhcpcd/ Ns Ar interface Ns .sock Control socket to per interface daemon. +.It Pa /var/run/dhcpcd/ Ns Ar interface Ns .unpriv.sock +Unprivileged socket to per interface daemon, only allows state retrieval. .El .Sh SEE ALSO .Xr fnmatch 3 , @@ -833,7 +877,7 @@ RFC\ 3397, RFC\ 3442, RFC\ 3495, RFC\ 3925, RFC\ 3927, RFC\ 4039, RFC\ 4075, RFC\ 4242, RFC\ 4361, RFC\ 4390, RFC\ 4702, RFC\ 4074, RFC\ 4861, RFC\ 4833, RFC\ 4941, RFC\ 5227, RFC\ 5942, RFC\ 5969, RFC\ 6106, RFC\ 6334, RFC\ 6355, -RFC\ 6603, RFC\ 6704, RFC\ 7217, RFC\ 7550. +RFC\ 6603, RFC\ 6704, RFC\ 7217, RFC\ 7550, RFC\ 7844. .Sh AUTHORS .An Roy Marples Aq Mt roy@marples.name .Sh BUGS Index: contrib/dhcpcd/src/dhcpcd.c =================================================================== --- contrib/dhcpcd/src/dhcpcd.c +++ contrib/dhcpcd/src/dhcpcd.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -26,14 +26,16 @@ * SUCH DAMAGE. */ -const char dhcpcd_copyright[] = "Copyright (c) 2006-2019 Roy Marples"; +static const char dhcpcd_copyright[] = "Copyright (c) 2006-2021 Roy Marples"; #include +#include #include #include #include #include #include +#include #include #include @@ -45,6 +47,7 @@ #include #include #include +#include #include #include @@ -66,8 +69,12 @@ #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" +#include "privsep.h" #include "script.h" +#ifdef HAVE_CAPSICUM +#include +#endif #ifdef HAVE_UTIL_H #include #endif @@ -80,13 +87,17 @@ SIGHUP, SIGUSR1, SIGUSR2, - SIGPIPE + SIGCHLD, }; const size_t dhcpcd_signals_len = __arraycount(dhcpcd_signals); + +const int dhcpcd_signals_ignore[] = { + SIGPIPE, +}; +const size_t dhcpcd_signals_ignore_len = __arraycount(dhcpcd_signals_ignore); #endif -#define IF_UPANDRUNNING(a) \ - (((a)->flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) +const char *dhcpcd_default_script = SCRIPT; static void usage(void) @@ -96,7 +107,7 @@ "\t\t[-C, --nohook hook] [-c, --script script]\n" "\t\t[-e, --env value] [-F, --fqdn FQDN] [-f, --config file]\n" "\t\t[-h, --hostname hostname] [-I, --clientid clientid]\n" - "\t\t[-i, --vendorclassid vendorclassid] [-j, --logfile logfile]\n" + "\t\t[-i, --vendorclassid vendorclassid] [-j, --logfile logfile]\n" "\t\t[-l, --leasetime seconds] [-m, --metric metric]\n" "\t\t[-O, --nooption option] [-o, --option option]\n" "\t\t[-Q, --require option] [-r, --request address]\n" @@ -185,7 +196,13 @@ ctx = arg; logerrx("timed out"); - if (!(ctx->options & DHCPCD_MASTER)) { + if (!(ctx->options & DHCPCD_MANAGER)) { + struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (ifp->active == IF_ACTIVE_USER) + script_runreason(ifp, "STOPPED"); + } eloop_exit(ctx->eloop, EXIT_FAILURE); return; } @@ -310,81 +327,59 @@ } /* Returns the pid of the child, otherwise 0. */ -pid_t +void dhcpcd_daemonise(struct dhcpcd_ctx *ctx) { #ifdef THERE_IS_NO_FORK eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); errno = ENOSYS; - return 0; + return; #else - pid_t pid, lpid; - char buf = '\0'; - int sidpipe[2], fd; + int i; + unsigned int logopts = loggetopts(); if (ctx->options & DHCPCD_DAEMONISE && !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP))) { if (!dhcpcd_ipwaited(ctx)) - return 0; + return; } if (ctx->options & DHCPCD_ONESHOT) { loginfox("exiting due to oneshot"); eloop_exit(ctx->eloop, EXIT_SUCCESS); - return 0; + return; } eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); if (ctx->options & DHCPCD_DAEMONISED || !(ctx->options & DHCPCD_DAEMONISE)) - return 0; - logdebugx("forking to background"); - - /* Setup a signal pipe so parent knows when to exit. */ - if (pipe(sidpipe) == -1) { - logerr("%s: pipe", __func__); - return 0; - } + return; - switch (pid = fork()) { - case -1: - logerr("%s: fork", __func__); - return 0; - case 0: - if ((lpid = pidfile_lock(ctx->pidfile)) != 0) - logerr("%s: pidfile_lock %d", __func__, lpid); - setsid(); - /* Notify parent it's safe to exit as we've detached. */ - close(sidpipe[0]); - if (write(sidpipe[1], &buf, 1) == -1) - logerr("%s: write", __func__); - close(sidpipe[1]); - /* Some polling methods don't survive after forking, - * so ensure we can requeue all our events. */ - if (eloop_requeue(ctx->eloop) == -1) { - logerr("%s: eloop_requeue", __func__); - eloop_exit(ctx->eloop, EXIT_FAILURE); - } - if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - close(fd); - } - ctx->options |= DHCPCD_DAEMONISED; - return 0; - default: - /* Wait for child to detach */ - close(sidpipe[1]); - if (read(sidpipe[0], &buf, 1) == -1) - logerr("%s: read", __func__); - close(sidpipe[0]); - loginfox("forked to background, child pid %d", pid); - ctx->options |= DHCPCD_FORKED; - eloop_exit(ctx->eloop, EXIT_SUCCESS); - return pid; - } + /* Don't use loginfo because this makes no sense in a log. */ + if (!(logopts & LOGERR_QUIET) && ctx->stderr_valid) + (void)fprintf(stderr, + "forked to background, child pid %d\n", getpid()); + i = EXIT_SUCCESS; + if (write(ctx->fork_fd, &i, sizeof(i)) == -1) + logerr("write"); + ctx->options |= DHCPCD_DAEMONISED; + eloop_event_delete(ctx->eloop, ctx->fork_fd); + close(ctx->fork_fd); + ctx->fork_fd = -1; + + /* + * Stop writing to stderr. + * On the happy path, only the manager process writes to stderr, + * so this just stops wasting fprintf calls to nowhere. + * All other calls - ie errors in privsep processes or script output, + * will error when printing. + * If we *really* want to fix that, then we need to suck + * stderr/stdout in the manager process and either disacrd it or pass + * it to the launcher process and then to stderr. + */ + logopts &= ~LOGERR_ERR; + logsetopts(logopts); #endif } @@ -414,7 +409,7 @@ } static void -stop_interface(struct interface *ifp) +stop_interface(struct interface *ifp, const char *reason) { struct dhcpcd_ctx *ctx; @@ -423,21 +418,16 @@ ifp->options->options |= DHCPCD_STOPPING; dhcpcd_drop(ifp, 1); - if (ifp->options->options & DHCPCD_DEPARTED) - script_runreason(ifp, "DEPARTED"); - else - script_runreason(ifp, "STOPPED"); + script_runreason(ifp, reason == NULL ? "STOPPED" : reason); /* Delete all timeouts for the interfaces */ - eloop_q_timeout_delete(ctx->eloop, 0, NULL, ifp); + eloop_q_timeout_delete(ctx->eloop, ELOOP_QUEUE_ALL, NULL, ifp); /* De-activate the interface */ ifp->active = IF_INACTIVE; ifp->options->options &= ~DHCPCD_STOPPING; - /* Set the link state to unknown as we're no longer tracking it. */ - ifp->carrier = LINK_UNKNOWN; - if (!(ctx->options & (DHCPCD_MASTER | DHCPCD_TEST))) + if (!(ctx->options & (DHCPCD_MANAGER | DHCPCD_TEST))) eloop_exit(ctx->eloop, EXIT_FAILURE); } @@ -461,25 +451,11 @@ if (!(ifo->options & (DHCPCD_INFORM | DHCPCD_WANTDHCP))) ifo->options |= DHCPCD_STATIC; } - if (!(ifo->options & DHCPCD_ARP) || - ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) - ifo->options &= ~DHCPCD_IPV4LL; if (ifo->metric != -1) ifp->metric = (unsigned int)ifo->metric; - if (!(ifo->options & DHCPCD_IPV4)) - ifo->options &= ~(DHCPCD_DHCP | DHCPCD_IPV4LL | DHCPCD_WAITIP4); - #ifdef INET6 - if (!(ifo->options & DHCPCD_IPV6)) - ifo->options &= - ~(DHCPCD_IPV6RS | DHCPCD_DHCP6 | DHCPCD_WAITIP6); - - if (!(ifo->options & DHCPCD_IPV6RS)) - ifo->options &= - ~(DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS); - /* We want to setup INET6 on the interface as soon as possible. */ if (ifp->active == IF_ACTIVE_USER && ifo->options & DHCPCD_IPV6 && @@ -539,7 +515,9 @@ * so we don't conflict with an interface index. */ vlanid = htonl(ifp->vlanid | 0xff000000); memcpy(ifo->iaid, &vlanid, sizeof(vlanid)); - } else if (ifp->hwlen >= sizeof(ifo->iaid)) { + } else if (ifo->options & DHCPCD_ANONYMOUS) + memset(ifo->iaid, 0, sizeof(ifo->iaid)); + else if (ifp->hwlen >= sizeof(ifo->iaid)) { memcpy(ifo->iaid, ifp->hwaddr + ifp->hwlen - sizeof(ifo->iaid), sizeof(ifo->iaid)); @@ -655,7 +633,7 @@ /* If the mtime has changed drop any old lease */ if (old != 0 && ifp->options->mtime != old) { - logwarnx("%s: confile file changed, expiring leases", + logwarnx("%s: config file changed, expiring leases", ifp->name); dhcpcd_drop(ifp, 0); } @@ -702,100 +680,144 @@ dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options); } -void -dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags, - const char *ifname) +static void +dhcpcd_reportssid(struct interface *ifp) { - struct interface *ifp; + char pssid[IF_SSIDLEN * 4]; - ifp = if_find(ctx->ifaces, ifname); - if (ifp == NULL || - ifp->options == NULL || !(ifp->options->options & DHCPCD_LINK) || - !ifp->active) + if (print_string(pssid, sizeof(pssid), OT_ESCSTRING, + ifp->ssid, ifp->ssid_len) == -1) + { + logerr(__func__); return; + } + + loginfox("%s: connected to Access Point: %s", ifp->name, pssid); +} + +static void +dhcpcd_nocarrier_roaming(struct interface *ifp) +{ + + loginfox("%s: carrier lost - roaming", ifp->name); - if (carrier == LINK_UNKNOWN) { - if (ifp->wireless) { - carrier = LINK_DOWN; - ifp->flags = flags; - } else - carrier = if_carrier(ifp); - } else - ifp->flags = flags; - if (carrier == LINK_UNKNOWN) - carrier = IF_UPANDRUNNING(ifp) ? LINK_UP : LINK_DOWN; - - if (carrier == LINK_DOWN || (ifp->flags & IFF_UP) == 0) { - if (ifp->carrier != LINK_DOWN) { - if (ifp->carrier == LINK_UP) - loginfox("%s: carrier lost", ifp->name); -#ifdef NOCARRIER_PRESERVE_IP - if (ifp->flags & IFF_UP) - ifp->carrier = LINK_DOWN_IFFUP; - else -#endif - ifp->carrier = LINK_DOWN; - script_runreason(ifp, "NOCARRIER"); -#ifdef NOCARRIER_PRESERVE_IP - if (ifp->flags & IFF_UP) { #ifdef ARP - arp_drop(ifp); + arp_drop(ifp); #endif #ifdef INET - dhcp_abort(ifp); + dhcp_abort(ifp); #endif #ifdef DHCP6 - dhcp6_abort(ifp); -#endif - } else + dhcp6_abort(ifp); #endif - dhcpcd_drop(ifp, 0); + + rt_build(ifp->ctx, AF_UNSPEC); + script_runreason(ifp, "NOCARRIER_ROAMING"); +} + +void +dhcpcd_handlecarrier(struct interface *ifp, int carrier, unsigned int flags) +{ + bool was_link_up = if_is_link_up(ifp); + bool was_roaming = if_roaming(ifp); + + ifp->carrier = carrier; + ifp->flags = flags; + + if (!if_is_link_up(ifp)) { + if (!ifp->active || (!was_link_up && !was_roaming)) + return; + + /* + * If the interface is roaming (generally on wireless) + * then while we are not up, we are not down either. + * Preserve the network state until we either disconnect + * or re-connect. + */ + if (!ifp->options->randomise_hwaddr && if_roaming(ifp)) { + dhcpcd_nocarrier_roaming(ifp); + return; } - } else if (carrier == LINK_UP && ifp->flags & IFF_UP) { - if (ifp->carrier != LINK_UP) { + + loginfox("%s: carrier lost", ifp->name); + script_runreason(ifp, "NOCARRIER"); + dhcpcd_drop(ifp, 0); + + if (ifp->options->randomise_hwaddr) { + bool is_up = ifp->flags & IFF_UP; + + if (is_up) + if_down(ifp); + if (if_randomisemac(ifp) == -1 && errno != ENXIO) + logerr(__func__); + if (is_up) + if_up(ifp); + } + + return; + } + + /* + * At this point carrier is NOT DOWN and we have IFF_UP. + * We should treat LINK_UNKNOWN as up as the driver may not support + * link state changes. + * The consideration of any other information about carrier should + * be handled in the OS specific if_carrier() function. + */ + if (was_link_up) + return; + + if (ifp->active) { + if (carrier == LINK_UNKNOWN) + loginfox("%s: carrier unknown, assuming up", ifp->name); + else loginfox("%s: carrier acquired", ifp->name); - ifp->carrier = LINK_UP; + } + #if !defined(__linux__) && !defined(__NetBSD__) - /* BSD does not emit RTM_NEWADDR or RTM_CHGADDR when the - * hardware address changes so we have to go - * through the disovery process to work it out. */ - dhcpcd_handleinterface(ctx, 0, ifp->name); -#endif - if (ifp->wireless) { - uint8_t ossid[IF_SSIDLEN]; - size_t olen; - - olen = ifp->ssid_len; - memcpy(ossid, ifp->ssid, ifp->ssid_len); - if_getssid(ifp); - - /* If we changed SSID network, drop leases */ - if (ifp->ssid_len != olen || - memcmp(ifp->ssid, ossid, ifp->ssid_len)) - { -#ifdef NOCARRIER_PRESERVE_IP - dhcpcd_drop(ifp, 0); + /* BSD does not emit RTM_NEWADDR or RTM_CHGADDR when the + * hardware address changes so we have to go + * through the disovery process to work it out. */ + dhcpcd_handleinterface(ifp->ctx, 0, ifp->name); #endif + + if (ifp->wireless) { + uint8_t ossid[IF_SSIDLEN]; + size_t olen; + + olen = ifp->ssid_len; + memcpy(ossid, ifp->ssid, ifp->ssid_len); + if_getssid(ifp); + + /* If we changed SSID network, drop leases */ + if ((ifp->ssid_len != olen || + memcmp(ifp->ssid, ossid, ifp->ssid_len)) && ifp->active) + { + dhcpcd_reportssid(ifp); + dhcpcd_drop(ifp, 0); #ifdef IPV4LL - ipv4ll_reset(ifp); + ipv4ll_reset(ifp); #endif - } - } - dhcpcd_initstate(ifp, 0); - script_runreason(ifp, "CARRIER"); + } + } + + if (!ifp->active) + return; + + dhcpcd_initstate(ifp, 0); + script_runreason(ifp, "CARRIER"); + #ifdef INET6 -#ifdef NOCARRIER_PRESERVE_IP - /* Set any IPv6 Routers we remembered to expire - * faster than they would normally as we - * maybe on a new network. */ - ipv6nd_startexpire(ifp); + /* Set any IPv6 Routers we remembered to expire faster than they + * would normally as we maybe on a new network. */ + ipv6nd_startexpire(ifp); +#ifdef IPV6_MANAGETEMPADDR + /* RFC4941 Section 3.5 */ + ipv6_regentempaddrs(ifp); #endif - /* RFC4941 Section 3.5 */ - ipv6_gentempifid(ifp); #endif - dhcpcd_startinterface(ifp); - } - } + + dhcpcd_startinterface(ifp); } static void @@ -810,6 +832,8 @@ TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { if (ifn == ifp || !ifn->active) continue; + if (ifn->options->options & DHCPCD_ANONYMOUS) + continue; if (ia_type == 0 && memcmp(ifn->options->iaid, iaid, sizeof(ifn->options->iaid)) == 0) @@ -830,67 +854,55 @@ ifp->name, ifn->name); } +static void +dhcpcd_initduid(struct dhcpcd_ctx *ctx, struct interface *ifp) +{ + char buf[DUID_LEN * 3]; + + if (ctx->duid != NULL) { + if (ifp == NULL) + goto log; + return; + } + + duid_init(ctx, ifp); + if (ctx->duid == NULL) + return; + +log: + loginfox("DUID %s", + hwaddr_ntoa(ctx->duid, ctx->duid_len, buf, sizeof(buf))); +} + void dhcpcd_startinterface(void *arg) { struct interface *ifp = arg; struct if_options *ifo = ifp->options; - char buf[DUID_LEN * 3]; - int carrier; - - if (ifo->options & DHCPCD_LINK) { - switch (ifp->carrier) { - case LINK_UP: - break; - case LINK_DOWN: - loginfox("%s: waiting for carrier", ifp->name); - return; - case LINK_UNKNOWN: - /* No media state available. - * Loop until both IFF_UP and IFF_RUNNING are set */ - carrier = if_carrier(ifp); - if (carrier == LINK_UNKNOWN) { - if (IF_UPANDRUNNING(ifp)) - carrier = LINK_UP; - else { - struct timespec tv; - - tv.tv_sec = 0; - tv.tv_nsec = IF_POLL_UP * NSEC_PER_MSEC; - eloop_timeout_add_tv(ifp->ctx->eloop, - &tv, dhcpcd_startinterface, ifp); - return; - } - } - dhcpcd_handlecarrier(ifp->ctx, carrier, - ifp->flags, ifp->name); - return; - } - } - if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6)) { - /* Report client DUID */ - if (ifp->ctx->duid == NULL) { - if (duid_init(ifp) == 0) - return; - loginfox("DUID %s", - hwaddr_ntoa(ifp->ctx->duid, - ifp->ctx->duid_len, - buf, sizeof(buf))); - } + if (ifo->options & DHCPCD_LINK && !if_is_link_up(ifp)) { + loginfox("%s: waiting for carrier", ifp->name); + return; } - if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6)) { + if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6) && + !(ifo->options & DHCPCD_ANONYMOUS)) + { + char buf[sizeof(ifo->iaid) * 3]; #ifdef INET6 size_t i; struct if_ia *ia; #endif + /* Try and init DUID from the interface hardware address */ + dhcpcd_initduid(ifp->ctx, ifp); + /* Report IAIDs */ loginfox("%s: IAID %s", ifp->name, hwaddr_ntoa(ifo->iaid, sizeof(ifo->iaid), buf, sizeof(buf))); warn_iaid_conflict(ifp, 0, ifo->iaid); + #ifdef INET6 for (i = 0; i < ifo->ia_len; i++) { ia = &ifo->ia[i]; @@ -920,9 +932,12 @@ } #ifdef DHCP6 - if (ifo->options & DHCPCD_DHCP6) { + /* DHCPv6 could be turned off, but the interface + * is still delegated to. */ + if (ifp->active) dhcp6_find_delegates(ifp); + if (ifo->options & DHCPCD_DHCP6) { if (ifp->active == IF_ACTIVE_USER) { enum DH6S d6_state; @@ -953,11 +968,28 @@ dhcpcd_prestartinterface(void *arg) { struct interface *ifp = arg; + struct dhcpcd_ctx *ctx = ifp->ctx; + bool randmac_down; - if ((!(ifp->ctx->options & DHCPCD_MASTER) || - ifp->options->options & DHCPCD_IF_UP) && - if_up(ifp) == -1) - logerr("%s: %s", __func__, ifp->name); + if (ifp->carrier <= LINK_DOWN && + ifp->options->randomise_hwaddr && + ifp->flags & IFF_UP) + { + if_down(ifp); + randmac_down = true; + } else + randmac_down = false; + + if ((!(ctx->options & DHCPCD_MANAGER) || + ifp->options->options & DHCPCD_IF_UP || randmac_down) && + !(ifp->flags & IFF_UP)) + { + if (ifp->options->randomise_hwaddr && + if_randomisemac(ifp) == -1) + logerr(__func__); + if (if_up(ifp) == -1) + logerr(__func__); + } dhcpcd_startinterface(ifp); } @@ -970,7 +1002,8 @@ return; script_runreason(ifp, "PREINIT"); - + if (ifp->wireless && if_is_link_up(ifp)) + dhcpcd_reportssid(ifp); if (ifp->options->options & DHCPCD_LINK && ifp->carrier != LINK_UNKNOWN) script_runreason(ifp, ifp->carrier == LINK_UP ? "CARRIER" : "NOCARRIER"); @@ -980,30 +1013,32 @@ dhcpcd_activateinterface(struct interface *ifp, unsigned long long options) { - if (!ifp->active) { - ifp->active = IF_ACTIVE; - dhcpcd_initstate2(ifp, options); - /* It's possible we might not have been able to load - * a config. */ - if (ifp->active) { - configure_interface1(ifp); - run_preinit(ifp); - dhcpcd_prestartinterface(ifp); - } - } + if (ifp->active) + return; + + ifp->active = IF_ACTIVE; + dhcpcd_initstate2(ifp, options); + + /* It's possible we might not have been able to load + * a config. */ + if (!ifp->active) + return; + + configure_interface1(ifp); + run_preinit(ifp); + dhcpcd_prestartinterface(ifp); } int dhcpcd_handleinterface(void *arg, int action, const char *ifname) { - struct dhcpcd_ctx *ctx; + struct dhcpcd_ctx *ctx = arg; struct ifaddrs *ifaddrs; struct if_head *ifs; struct interface *ifp, *iff; const char * const argv[] = { ifname }; int e; - ctx = arg; if (action == -1) { ifp = if_find(ctx->ifaces, ifname); if (ifp == NULL) { @@ -1012,8 +1047,7 @@ } if (ifp->active) { logdebugx("%s: interface departed", ifp->name); - ifp->options->options |= DHCPCD_DEPARTED; - stop_interface(ifp); + stop_interface(ifp, "DEPARTED"); } TAILQ_REMOVE(ctx->ifaces, ifp, next); if_free(ifp); @@ -1093,14 +1127,14 @@ static void dhcpcd_checkcarrier(void *arg) { - struct interface *ifp = arg; - int carrier; + struct interface *ifp0 = arg, *ifp; + + ifp = if_find(ifp0->ctx->ifaces, ifp0->name); + if (ifp == NULL || ifp->carrier == ifp0->carrier) + return; - /* Check carrier here rather than setting LINK_UNKNOWN. - * This is because we force LINK_UNKNOWN as down for wireless which - * we do not want when dealing with a route socket overflow. */ - carrier = if_carrier(ifp); - dhcpcd_handlecarrier(ifp->ctx, carrier, ifp->flags, ifp->name); + dhcpcd_handlecarrier(ifp, ifp0->carrier, ifp0->flags); + if_free(ifp0); } #ifndef SMALL @@ -1112,6 +1146,9 @@ if (ctx->link_rcvbuf == 0) return; + logdebugx("setting route socket receive buffer size to %d bytes", + ctx->link_rcvbuf); + socklen = sizeof(ctx->link_rcvbuf); if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RCVBUF, &ctx->link_rcvbuf, socklen) == -1) @@ -1119,30 +1156,51 @@ } #endif +static void +dhcpcd_runprestartinterface(void *arg) +{ + struct interface *ifp = arg; + + run_preinit(ifp); + dhcpcd_prestartinterface(ifp); +} + void dhcpcd_linkoverflow(struct dhcpcd_ctx *ctx) { + socklen_t socklen; + int rcvbuflen; + char buf[2048]; + ssize_t rlen; + size_t rcnt; struct if_head *ifaces; struct ifaddrs *ifaddrs; struct interface *ifp, *ifn, *ifp1; - logerrx("route socket overflowed - learning interface state"); - - /* Close the existing socket and open a new one. - * This is easier than draining the kernel buffer of an - * in-determinate size. */ - eloop_event_delete(ctx->eloop, ctx->link_fd); - close(ctx->link_fd); - if_closesockets_os(ctx); - if (if_opensockets_os(ctx) == -1) { - logerr("%s: if_opensockets", __func__); - eloop_exit(ctx->eloop, EXIT_FAILURE); - return; + socklen = sizeof(rcvbuflen); + if (getsockopt(ctx->link_fd, SOL_SOCKET, + SO_RCVBUF, &rcvbuflen, &socklen) == -1) { + logerr("%s: getsockopt", __func__); + rcvbuflen = 0; } -#ifndef SMALL - dhcpcd_setlinkrcvbuf(ctx); +#ifdef __linux__ + else + rcvbuflen /= 2; #endif - eloop_event_add(ctx->eloop, ctx->link_fd, dhcpcd_handlelink, ctx); + + logerrx("route socket overflowed (rcvbuflen %d)" + " - learning interface state", rcvbuflen); + + /* Drain the socket. + * We cannot open a new one due to privsep. */ + rcnt = 0; + do { + rlen = read(ctx->link_fd, buf, sizeof(buf)); + if (++rcnt % 1000 == 0) + logwarnx("drained %zu messages", rcnt); + } while (rlen != -1 || errno == ENOBUFS || errno == ENOMEM); + if (rcnt % 1000 != 0) + logwarnx("drained %zu messages", rcnt); /* Work out the current interfaces. */ ifaces = if_discover(ctx, &ifaddrs, ctx->ifc, ctx->ifv); @@ -1164,16 +1222,18 @@ ifp1 = if_find(ctx->ifaces, ifp->name); if (ifp1 != NULL) { /* If the interface already exists, - * check carrier state. */ + * check carrier state. + * dhcpcd_checkcarrier will free ifp. */ eloop_timeout_add_sec(ctx->eloop, 0, - dhcpcd_checkcarrier, ifp1); - if_free(ifp); + dhcpcd_checkcarrier, ifp); continue; } TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); - if (ifp->active) + if (ifp->active) { + dhcpcd_initstate(ifp, 0); eloop_timeout_add_sec(ctx->eloop, 0, - dhcpcd_prestartinterface, ifp); + dhcpcd_runprestartinterface, ifp); + } } free(ifaces); @@ -1184,17 +1244,12 @@ } void -dhcpcd_handlehwaddr(struct dhcpcd_ctx *ctx, const char *ifname, - const void *hwaddr, uint8_t hwlen) +dhcpcd_handlehwaddr(struct interface *ifp, + uint16_t hwtype, const void *hwaddr, uint8_t hwlen) { - struct interface *ifp; char buf[sizeof(ifp->hwaddr) * 3]; - ifp = if_find(ctx->ifaces, ifname); - if (ifp == NULL) - return; - - if (!if_valid_hwaddr(hwaddr, hwlen)) + if (hwaddr == NULL || !if_valid_hwaddr(hwaddr, hwlen)) hwlen = 0; if (hwlen > sizeof(ifp->hwaddr)) { @@ -1203,13 +1258,26 @@ return; } - if (ifp->hwlen == hwlen && memcmp(ifp->hwaddr, hwaddr, hwlen) == 0) + if (ifp->hwtype != hwtype) { + if (ifp->active) + loginfox("%s: hardware address type changed" + " from %d to %d", ifp->name, ifp->hwtype, hwtype); + ifp->hwtype = hwtype; + } + + if (ifp->hwlen == hwlen && + (hwlen == 0 || memcmp(ifp->hwaddr, hwaddr, hwlen) == 0)) return; - loginfox("%s: new hardware address: %s", ifp->name, - hwaddr_ntoa(hwaddr, hwlen, buf, sizeof(buf))); + if (ifp->active) { + loginfox("%s: old hardware address: %s", ifp->name, + hwaddr_ntoa(ifp->hwaddr, ifp->hwlen, buf, sizeof(buf))); + loginfox("%s: new hardware address: %s", ifp->name, + hwaddr_ntoa(hwaddr, hwlen, buf, sizeof(buf))); + } ifp->hwlen = hwlen; - memcpy(ifp->hwaddr, hwaddr, hwlen); + if (hwaddr != NULL) + memcpy(ifp->hwaddr, hwaddr, hwlen); } static void @@ -1240,11 +1308,15 @@ if ((ifo = read_config(ctx, NULL, NULL, NULL)) == NULL) return; add_options(ctx, NULL, ifo, ctx->argc, ctx->argv); - /* We need to preserve these two options. */ - if (ctx->options & DHCPCD_MASTER) - ifo->options |= DHCPCD_MASTER; + /* We need to preserve these options. */ + if (ctx->options & DHCPCD_STARTED) + ifo->options |= DHCPCD_STARTED; + if (ctx->options & DHCPCD_MANAGER) + ifo->options |= DHCPCD_MANAGER; if (ctx->options & DHCPCD_DAEMONISED) ifo->options |= DHCPCD_DAEMONISED; + if (ctx->options & DHCPCD_PRIVSEP) + ifo->options |= DHCPCD_PRIVSEP; ctx->options = ifo->options; free_options(ctx, ifo); } @@ -1284,15 +1356,18 @@ struct interface *ifp; ctx->options |= DHCPCD_EXITING; + if (ctx->ifaces == NULL) + return; + /* Drop the last interface first */ TAILQ_FOREACH_REVERSE(ifp, ctx->ifaces, if_head, next) { - if (ifp->active) { - ifp->options->options |= opts; - if (ifp->options->options & DHCPCD_RELEASE) - ifp->options->options &= ~DHCPCD_PERSISTENT; - ifp->options->options |= DHCPCD_EXITING; - stop_interface(ifp); - } + if (!ifp->active) + continue; + ifp->options->options |= opts; + if (ifp->options->options & DHCPCD_RELEASE) + ifp->options->options &= ~DHCPCD_PERSISTENT; + ifp->options->options |= DHCPCD_EXITING; + stop_interface(ifp, NULL); } } @@ -1303,8 +1378,7 @@ if (!ifp->active) return; - if (ifp->options->options & DHCPCD_LINK && - ifp->carrier == LINK_DOWN) + if (ifp->options->options & DHCPCD_LINK && !if_is_link_up(ifp)) return; #ifdef INET @@ -1333,12 +1407,24 @@ #ifdef USE_SIGNALS #define sigmsg "received %s, %s" static void -signal_cb(int sig, void *arg) +dhcpcd_signal_cb(int sig, void *arg) { struct dhcpcd_ctx *ctx = arg; unsigned long long opts; int exit_code; + if (ctx->options & DHCPCD_DUMPLEASE) { + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + + if (sig != SIGCHLD && ctx->options & DHCPCD_FORKED) { + if (sig != SIGHUP && + write(ctx->fork_fd, &sig, sizeof(sig)) == -1) + logerr("%s: write", __func__); + return; + } + opts = 0; exit_code = EXIT_FAILURE; switch (sig) { @@ -1368,12 +1454,19 @@ return; case SIGUSR2: loginfox(sigmsg, "SIGUSR2", "reopening log"); - logclose(); +#ifdef PRIVSEP + if (IN_PRIVSEP(ctx)) { + if (ps_root_logreopen(ctx) == -1) + logerr("ps_root_logreopen"); + return; + } +#endif if (logopen(ctx->logfile) == -1) - logerr(__func__); + logerr("logopen"); return; - case SIGPIPE: - logwarnx("received SIGPIPE"); + case SIGCHLD: + while (waitpid(-1, NULL, WNOHANG) > 0) + ; return; default: logerrx("received signal %d but don't know what to do with it", @@ -1387,83 +1480,35 @@ } #endif -static void -dhcpcd_getinterfaces(void *arg) -{ - struct fd_list *fd = arg; - struct interface *ifp; - size_t len; - - len = 0; - TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) { - if (!ifp->active) - continue; - len++; -#ifdef INET - if (D_STATE_RUNNING(ifp)) - len++; -#endif -#ifdef IPV4LL - if (IPV4LL_STATE_RUNNING(ifp)) - len++; -#endif -#ifdef INET6 - if (IPV6_STATE_RUNNING(ifp)) - len++; - if (RS_STATE_RUNNING(ifp)) - len++; -#endif -#ifdef DHCP6 - if (D6_STATE_RUNNING(ifp)) - len++; -#endif - } - if (write(fd->fd, &len, sizeof(len)) != sizeof(len)) - return; - eloop_event_remove_writecb(fd->ctx->eloop, fd->fd); - TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) { - if (!ifp->active) - continue; - if (send_interface(fd, ifp) == -1) - logerr(__func__); - } -} - int dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, char **argv) { struct interface *ifp; unsigned long long opts; - int opt, oi, do_reboot, do_renew; - size_t len, l; + int opt, oi, do_reboot, do_renew, af = AF_UNSPEC; + size_t len, l, nifaces; char *tmp, *p; /* Special commands for our control socket * as the other end should be blocking until it gets the * expected reply we should be safely able just to change the * write callback on the fd */ + /* Make any change here in privsep-control.c as well. */ if (strcmp(*argv, "--version") == 0) { return control_queue(fd, UNCONST(VERSION), - strlen(VERSION) + 1, false); + strlen(VERSION) + 1); } else if (strcmp(*argv, "--getconfigfile") == 0) { return control_queue(fd, UNCONST(fd->ctx->cffile), - strlen(fd->ctx->cffile) + 1, false); + strlen(fd->ctx->cffile) + 1); } else if (strcmp(*argv, "--getinterfaces") == 0) { - eloop_event_add_w(fd->ctx->eloop, fd->fd, - dhcpcd_getinterfaces, fd); - return 0; + optind = argc = 0; + goto dumplease; } else if (strcmp(*argv, "--listen") == 0) { fd->flags |= FD_LISTEN; return 0; } - /* Only priviledged users can control dhcpcd via the socket. */ - if (fd->flags & FD_UNPRIV) { - errno = EPERM; - return -1; - } - /* Log the command */ len = 1; for (opt = 0; opt < argc; opt++) @@ -1508,27 +1553,81 @@ case 'N': do_renew = 1; break; + case 'U': + opts |= DHCPCD_DUMPLEASE; + break; + case '4': + af = AF_INET; + break; + case '6': + af = AF_INET6; + break; } } - if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) { - if (optind == argc) { - stop_all_interfaces(ctx, opts); - eloop_exit(ctx->eloop, EXIT_SUCCESS); - return 0; - } - for (oi = optind; oi < argc; oi++) { - if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) - continue; + if (opts & DHCPCD_DUMPLEASE) { + ctx->options |= DHCPCD_DUMPLEASE; +dumplease: + nifaces = 0; + TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) continue; - ifp->options->options |= opts; - if (opts & DHCPCD_RELEASE) - ifp->options->options &= ~DHCPCD_PERSISTENT; - stop_interface(ifp); + for (oi = optind; oi < argc; oi++) { + if (strcmp(ifp->name, argv[oi]) == 0) + break; + } + if (optind == argc || oi < argc) { + opt = send_interface(NULL, ifp, af); + if (opt == -1) + goto dumperr; + nifaces += (size_t)opt; + } } - return 0; - } + if (write(fd->fd, &nifaces, sizeof(nifaces)) != sizeof(nifaces)) + goto dumperr; + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (!ifp->active) + continue; + for (oi = optind; oi < argc; oi++) { + if (strcmp(ifp->name, argv[oi]) == 0) + break; + } + if (optind == argc || oi < argc) { + if (send_interface(fd, ifp, af) == -1) + goto dumperr; + } + } + ctx->options &= ~DHCPCD_DUMPLEASE; + return 0; +dumperr: + ctx->options &= ~DHCPCD_DUMPLEASE; + return -1; + } + + /* Only privileged users can control dhcpcd via the socket. */ + if (fd->flags & FD_UNPRIV) { + errno = EPERM; + return -1; + } + + if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) { + if (optind == argc) { + stop_all_interfaces(ctx, opts); + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return 0; + } + for (oi = optind; oi < argc; oi++) { + if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) + continue; + if (!ifp->active) + continue; + ifp->options->options |= opts; + if (opts & DHCPCD_RELEASE) + ifp->options->options &= ~DHCPCD_PERSISTENT; + stop_interface(ifp, NULL); + } + return 0; + } if (do_renew) { if (optind == argc) { @@ -1549,24 +1648,186 @@ return 0; } +static void dhcpcd_readdump1(void *); + +static void +dhcpcd_readdump2(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t len; + int exit_code = EXIT_FAILURE; + + len = read(ctx->control_fd, ctx->ctl_buf + ctx->ctl_bufpos, + ctx->ctl_buflen - ctx->ctl_bufpos); + if (len == -1) { + logerr(__func__); + goto finished; + } else if (len == 0) + goto finished; + if ((size_t)len + ctx->ctl_bufpos != ctx->ctl_buflen) { + ctx->ctl_bufpos += (size_t)len; + return; + } + + if (ctx->ctl_buf[ctx->ctl_buflen - 1] != '\0') /* unlikely */ + ctx->ctl_buf[ctx->ctl_buflen - 1] = '\0'; + script_dump(ctx->ctl_buf, ctx->ctl_buflen); + fflush(stdout); + if (--ctx->ctl_extra != 0) { + putchar('\n'); + eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump1, ctx); + return; + } + exit_code = EXIT_SUCCESS; + +finished: + shutdown(ctx->control_fd, SHUT_RDWR); + eloop_exit(ctx->eloop, exit_code); +} + +static void +dhcpcd_readdump1(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t len; + + len = read(ctx->control_fd, &ctx->ctl_buflen, sizeof(ctx->ctl_buflen)); + if (len != sizeof(ctx->ctl_buflen)) { + if (len != -1) + errno = EINVAL; + goto err; + } + if (ctx->ctl_buflen > SSIZE_MAX) { + errno = ENOBUFS; + goto err; + } + + free(ctx->ctl_buf); + ctx->ctl_buf = malloc(ctx->ctl_buflen); + if (ctx->ctl_buf == NULL) + goto err; + + ctx->ctl_bufpos = 0; + eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump2, ctx); + return; + +err: + logerr(__func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); +} + +static void +dhcpcd_readdump0(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t len; + + len = read(ctx->control_fd, &ctx->ctl_extra, sizeof(ctx->ctl_extra)); + if (len != sizeof(ctx->ctl_extra)) { + if (len != -1) + errno = EINVAL; + logerr(__func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + + if (ctx->ctl_extra == 0) { + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return; + } + + eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump1, ctx); +} + +static void +dhcpcd_readdumptimeout(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + logerrx(__func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); +} + +static int +dhcpcd_readdump(struct dhcpcd_ctx *ctx) +{ + + ctx->options |= DHCPCD_FORKED; + if (eloop_timeout_add_sec(ctx->eloop, 5, + dhcpcd_readdumptimeout, ctx) == -1) + return -1; + return eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump0, ctx); +} + +static void +dhcpcd_fork_cb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + int exit_code; + ssize_t len; + + len = read(ctx->fork_fd, &exit_code, sizeof(exit_code)); + if (len == -1) { + logerr(__func__); + exit_code = EXIT_FAILURE; + } else if ((size_t)len < sizeof(exit_code)) { + logerrx("%s: truncated read %zd (expected %zu)", + __func__, len, sizeof(exit_code)); + exit_code = EXIT_FAILURE; + } + if (ctx->options & DHCPCD_FORKED) + eloop_exit(ctx->eloop, exit_code); + else + dhcpcd_signal_cb(exit_code, ctx); +} + +static void +dhcpcd_stderr_cb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + char log[BUFSIZ]; + ssize_t len; + + len = read(ctx->stderr_fd, log, sizeof(log)); + if (len == -1) { + if (errno != ECONNRESET) + logerr(__func__); + return; + } + + log[len] = '\0'; + fprintf(stderr, "%s", log); +} + int -main(int argc, char **argv) +main(int argc, char **argv, char **envp) { struct dhcpcd_ctx ctx; struct ifaddrs *ifaddrs = NULL; struct if_options *ifo; struct interface *ifp; - uint16_t family = 0; + sa_family_t family = AF_UNSPEC; int opt, oi = 0, i; - unsigned int logopts; - time_t t; + unsigned int logopts, t; ssize_t len; #if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK) pid_t pid; + int fork_fd[2], stderr_fd[2]; #endif #ifdef USE_SIGNALS int sig = 0; const char *siga = NULL; + size_t si; +#endif + +#ifdef SETPROCTITLE_H + setproctitle_init(argc, argv, envp); +#else + UNUSED(envp); #endif /* Test for --help and --version */ @@ -1597,6 +1858,9 @@ #endif #ifdef AUTH " AUTH" +#endif +#ifdef PRIVSEP + " PRIVSEP" #endif "\n"); return EXIT_SUCCESS; @@ -1607,20 +1871,48 @@ ifo = NULL; ctx.cffile = CONFIG; + ctx.script = UNCONST(dhcpcd_default_script); ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1; ctx.pf_inet_fd = -1; +#ifdef PF_LINK + ctx.pf_link_fd = -1; +#endif TAILQ_INIT(&ctx.control_fds); +#ifdef USE_SIGNALS + ctx.fork_fd = -1; +#endif #ifdef PLUGIN_DEV ctx.dev_fd = -1; #endif #ifdef INET - ctx.udp_fd = -1; + ctx.udp_rfd = -1; + ctx.udp_wfd = -1; #endif - rt_init(&ctx); +#if defined(INET6) && !defined(__sun) + ctx.nd_fd = -1; +#endif +#ifdef DHCP6 + ctx.dhcp6_rfd = -1; + ctx.dhcp6_wfd = -1; +#endif +#ifdef PRIVSEP + ctx.ps_root_fd = ctx.ps_log_fd = ctx.ps_data_fd = -1; + ctx.ps_inet_fd = ctx.ps_control_fd = -1; + TAILQ_INIT(&ctx.ps_processes); +#endif + + /* Check our streams for validity */ + ctx.stdin_valid = fcntl(STDIN_FILENO, F_GETFD) != -1; + ctx.stdout_valid = fcntl(STDOUT_FILENO, F_GETFD) != -1; + ctx.stderr_valid = fcntl(STDERR_FILENO, F_GETFD) != -1; + + logopts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID; + if (ctx.stderr_valid) + logopts |= LOGERR_ERR; - logopts = LOGERR_ERR|LOGERR_LOG|LOGERR_LOG_DATE|LOGERR_LOG_PID; i = 0; + while ((opt = getopt_long(argc, argv, ctx.options & DHCPCD_PRINT_PIDFILE ? NOERR_IF_OPTS : IF_OPTS, cf_options, &oi)) != -1) @@ -1655,7 +1947,14 @@ i = 4; break; case 'q': - logopts |= LOGERR_QUIET; + /* -qq disables console output entirely. + * This is important for systemd because it logs + * both console AND syslog to the same log + * resulting in untold confusion. */ + if (logopts & LOGERR_QUIET) + logopts &= ~LOGERR_ERR; + else + logopts |= LOGERR_QUIET; break; case 'x': sig = SIGTERM; @@ -1688,6 +1987,9 @@ } } + if (optind != argc - 1) + ctx.options |= DHCPCD_MANAGER; + logsetopts(logopts); logopen(ctx.logfile); @@ -1696,12 +1998,15 @@ ctx.ifc = argc - optind; ctx.ifv = argv + optind; + rt_init(&ctx); + ifo = read_config(&ctx, NULL, NULL, NULL); if (ifo == NULL) { if (ctx.options & DHCPCD_PRINT_PIDFILE) goto printpidfile; goto exit_failure; } + opt = add_options(&ctx, NULL, ifo, argc, argv); if (opt != 1) { if (ctx.options & DHCPCD_PRINT_PIDFILE) @@ -1742,6 +2047,7 @@ goto exit_success; } ctx.options |= ifo->options; + if (i == 1 || i == 3) { if (i == 1) ctx.options |= DHCPCD_TEST; @@ -1762,7 +2068,7 @@ printpidfile: /* If we have any other args, we should run as a single dhcpcd * instance for that interface. */ - if (optind == argc - 1 && !(ctx.options & DHCPCD_MASTER)) { + if (optind == argc - 1 && !(ctx.options & DHCPCD_MANAGER)) { const char *per; const char *ifname; @@ -1784,11 +2090,11 @@ per = ""; } snprintf(ctx.pidfile, sizeof(ctx.pidfile), - PIDFILE, "-", ifname, per); + PIDFILE, ifname, per, "."); } else { snprintf(ctx.pidfile, sizeof(ctx.pidfile), PIDFILE, "", "", ""); - ctx.options |= DHCPCD_MASTER; + ctx.options |= DHCPCD_MANAGER; } if (ctx.options & DHCPCD_PRINT_PIDFILE) { printf("%s\n", ctx.pidfile); @@ -1797,7 +2103,7 @@ } if (chdir("/") == -1) - logerr("%s: chdir `/'", __func__); + logerr("%s: chdir: /", __func__); /* Freeing allocated addresses from dumping leases can trigger * eloop removals as well, so init here. */ @@ -1805,161 +2111,165 @@ logerr("%s: eloop_init", __func__); goto exit_failure; } + #ifdef USE_SIGNALS + for (si = 0; si < dhcpcd_signals_ignore_len; si++) + signal(dhcpcd_signals_ignore[si], SIG_IGN); + /* Save signal mask, block and redirect signals to our handler */ - if (eloop_signal_set_cb(ctx.eloop, + eloop_signal_set_cb(ctx.eloop, dhcpcd_signals, dhcpcd_signals_len, - signal_cb, &ctx) == -1) - { - logerr("%s: eloop_signal_set_cb", __func__); - goto exit_failure; - } + dhcpcd_signal_cb, &ctx); if (eloop_signal_mask(ctx.eloop, &ctx.sigset) == -1) { logerr("%s: eloop_signal_mask", __func__); goto exit_failure; } -#endif - if (ctx.options & DHCPCD_DUMPLEASE) { - /* Open sockets so we can dump something about - * valid interfaces. */ - if (if_opensockets(&ctx) == -1) { - logerr("%s: if_opensockets", __func__); - goto exit_failure; - } - if (optind != argc) { - /* We need to try and find the interface so we can load - * the hardware address to compare automated IAID */ - ctx.ifaces = if_discover(&ctx, &ifaddrs, - argc - optind, argv + optind); + if (sig != 0) { + pid = pidfile_read(ctx.pidfile); + if (pid != 0 && pid != -1) + loginfox("sending signal %s to pid %d", siga, pid); + if (pid == 0 || pid == -1 || kill(pid, sig) != 0) { + if (sig != SIGHUP && sig != SIGUSR1 && errno != EPERM) + logerrx(PACKAGE" not running"); + if (pid != 0 && pid != -1 && errno != ESRCH) { + logerr("kill"); + goto exit_failure; + } + unlink(ctx.pidfile); + if (sig != SIGHUP && sig != SIGUSR1) + goto exit_failure; } else { - if ((ctx.ifaces = malloc(sizeof(*ctx.ifaces))) != NULL) - TAILQ_INIT(ctx.ifaces); - } - if (ctx.ifaces == NULL) { - logerr("%s: if_discover", __func__); + struct timespec ts; + + if (sig == SIGHUP || sig == SIGUSR1) + goto exit_success; + /* Spin until it exits */ + loginfox("waiting for pid %d to exit", pid); + ts.tv_sec = 0; + ts.tv_nsec = 100000000; /* 10th of a second */ + for(i = 0; i < 100; i++) { + nanosleep(&ts, NULL); + if (pidfile_read(ctx.pidfile) == -1) + goto exit_success; + } + logerrx("pid %d failed to exit", pid); goto exit_failure; } - ifp = if_find(ctx.ifaces, argv[optind]); + } +#endif + +#ifdef PRIVSEP + ps_init(&ctx); +#endif + +#ifndef SMALL + if (ctx.options & DHCPCD_DUMPLEASE && + ioctl(fileno(stdin), FIONREAD, &i, sizeof(i)) == 0 && + i > 0) + { + ctx.options |= DHCPCD_FORKED; /* pretend child process */ +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1) + goto exit_failure; +#endif + ifp = calloc(1, sizeof(*ifp)); if (ifp == NULL) { - ifp = calloc(1, sizeof(*ifp)); - if (ifp == NULL) { - logerr(__func__); - goto exit_failure; - } - if (optind != argc) - strlcpy(ctx.pidfile, argv[optind], - sizeof(ctx.pidfile)); - ifp->ctx = &ctx; - TAILQ_INSERT_HEAD(ctx.ifaces, ifp, next); - if (family == 0) { - if (ctx.pidfile[0] != '\0' && - ctx.pidfile[strlen(ctx.pidfile) - 1] == '6') - family = AF_INET6; - else - family = AF_INET; - } + logerr(__func__); + goto exit_failure; } - configure_interface(ifp, ctx.argc, ctx.argv, 0); - i = 0; - if (family == 0 || family == AF_INET) { + ifp->ctx = &ctx; + ifp->options = ifo; + switch (family) { + case AF_INET: #ifdef INET if (dhcp_dump(ifp) == -1) - i = -1; + goto exit_failure; + break; #else - if (family == AF_INET) - logerrx("No INET support"); + logerrx("No DHCP support"); + goto exit_failure; #endif - } - if (family == 0 || family == AF_INET6) { + case AF_INET6: #ifdef DHCP6 if (dhcp6_dump(ifp) == -1) - i = -1; + goto exit_failure; + break; #else - if (family == AF_INET6) - logerrx("No DHCP6 support"); + logerrx("No DHCP6 support"); + goto exit_failure; #endif - } - if (i == -1) + default: + logerrx("Family not specified. Please use -4 or -6."); goto exit_failure; + } goto exit_success; } +#endif -#ifdef USE_SIGNALS /* Test against siga instead of sig to avoid gcc * warning about a bogus potential signed overflow. * The end result will be the same. */ if ((siga == NULL || i == 4 || ctx.ifc != 0) && !(ctx.options & DHCPCD_TEST)) { -#endif - if (!(ctx.options & DHCPCD_MASTER)) - ctx.control_fd = control_open(argv[optind]); + ctx.options |= DHCPCD_FORKED; /* avoid socket unlink */ + if (!(ctx.options & DHCPCD_MANAGER)) + ctx.control_fd = control_open(argv[optind], family, + ctx.options & DHCPCD_DUMPLEASE); + if (!(ctx.options & DHCPCD_MANAGER) && ctx.control_fd == -1) + ctx.control_fd = control_open(argv[optind], AF_UNSPEC, + ctx.options & DHCPCD_DUMPLEASE); if (ctx.control_fd == -1) - ctx.control_fd = control_open(NULL); + ctx.control_fd = control_open(NULL, AF_UNSPEC, + ctx.options & DHCPCD_DUMPLEASE); if (ctx.control_fd != -1) { - loginfox("sending commands to master dhcpcd process"); +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && + ps_managersandbox(&ctx, NULL) == -1) + goto exit_failure; +#endif + if (!(ctx.options & DHCPCD_DUMPLEASE)) + loginfox("sending commands to dhcpcd process"); len = control_send(&ctx, argc, argv); - control_close(&ctx); - if (len > 0) { + if (len > 0) logdebugx("send OK"); - goto exit_success; - } else { + else { logerr("%s: control_send", __func__); goto exit_failure; } + if (ctx.options & DHCPCD_DUMPLEASE) { + if (dhcpcd_readdump(&ctx) == -1) { + logerr("%s: dhcpcd_readdump", __func__); + goto exit_failure; + } + goto run_loop; + } + goto exit_success; } else { if (errno != ENOENT) logerr("%s: control_open", __func__); - } -#ifdef USE_SIGNALS - } -#endif - -#ifdef USE_SIGNALS - if (sig != 0) { - pid = pidfile_read(ctx.pidfile); - if (pid != 0 && pid != -1) - loginfox("sending signal %s to pid %d", siga, pid); - if (pid == 0 || pid == -1 || kill(pid, sig) != 0) { - if (sig != SIGHUP && sig != SIGUSR1 && errno != EPERM) - logerrx(PACKAGE" not running"); - if (pid != 0 && pid != -1 && errno != ESRCH) { - logerr("kill"); + if (ctx.options & DHCPCD_DUMPLEASE) { + if (errno == ENOENT) + logerrx("dhcpcd is not running"); goto exit_failure; } - unlink(ctx.pidfile); - if (sig != SIGHUP && sig != SIGUSR1) + if (errno == EPERM || errno == EACCES) goto exit_failure; - } else { - struct timespec ts; - - if (sig == SIGHUP || sig == SIGUSR1) - goto exit_success; - /* Spin until it exits */ - loginfox("waiting for pid %d to exit", pid); - ts.tv_sec = 0; - ts.tv_nsec = 100000000; /* 10th of a second */ - for(i = 0; i < 100; i++) { - nanosleep(&ts, NULL); - if (pidfile_read(ctx.pidfile) == -1) - goto exit_success; - } - logerrx("pid %d failed to exit", pid); - goto exit_failure; } + ctx.options &= ~DHCPCD_FORKED; } if (!(ctx.options & DHCPCD_TEST)) { /* Ensure we have the needed directories */ + if (mkdir(DBDIR, 0750) == -1 && errno != EEXIST) + logerr("%s: mkdir: %s", __func__, DBDIR); if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) - logerr("%s: mkdir `%s'", __func__, RUNDIR); - if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST) - logerr("%s: mkdir `%s'", __func__, DBDIR); - + logerr("%s: mkdir: %s", __func__, RUNDIR); if ((pid = pidfile_lock(ctx.pidfile)) != 0) { if (pid == -1) - logerr("%s: pidfile_lock", __func__); + logerr("%s: pidfile_lock: %s", + __func__, ctx.pidfile); else logerrx(PACKAGE " already running on pid %d (%s)", @@ -1968,28 +2278,157 @@ } } - if (ctx.options & DHCPCD_MASTER) { - if (control_start(&ctx, NULL) == -1) - logerr("%s: control_start", __func__); - } -#else - if (control_start(&ctx, - ctx.options & DHCPCD_MASTER ? NULL : argv[optind]) == -1) + loginfox(PACKAGE "-" VERSION " starting"); + if (ctx.stdin_valid && freopen(_PATH_DEVNULL, "w", stdin) == NULL) + logwarn("freopen stdin"); + +#if defined(USE_SIGNALS) && !defined(THERE_IS_NO_FORK) + if (!(ctx.options & DHCPCD_DAEMONISE)) + goto start_manager; + + if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fork_fd) == -1 || + (ctx.stderr_valid && + xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, stderr_fd) == -1)) { - logerr("%s: control_start", __func__); + logerr("socketpair"); goto exit_failure; } + switch (pid = fork()) { + case -1: + logerr("fork"); + goto exit_failure; + case 0: + ctx.fork_fd = fork_fd[1]; + close(fork_fd[0]); +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd(ctx.fork_fd) == -1) { + logerr("ps_rights_limit_fdpair"); + goto exit_failure; + } #endif + eloop_event_add(ctx.eloop, ctx.fork_fd, dhcpcd_fork_cb, &ctx); - logdebugx(PACKAGE "-" VERSION " starting"); + /* + * Redirect stderr to the stderr socketpair. + * Redirect stdout as well. + * dhcpcd doesn't output via stdout, but something in + * a called script might. + */ + if (ctx.stderr_valid) { + if (dup2(stderr_fd[1], STDERR_FILENO) == -1 || + (ctx.stdout_valid && + dup2(stderr_fd[1], STDOUT_FILENO) == -1)) + logerr("dup2"); + close(stderr_fd[0]); + close(stderr_fd[1]); + } else if (ctx.stdout_valid) { + if (freopen(_PATH_DEVNULL, "w", stdout) == NULL) + logerr("freopen stdout"); + } + if (setsid() == -1) { + logerr("%s: setsid", __func__); + goto exit_failure; + } + /* Ensure we can never get a controlling terminal */ + switch (pid = fork()) { + case -1: + logerr("fork"); + goto exit_failure; + case 0: + break; + default: + ctx.options |= DHCPCD_FORKED; /* A lie */ + i = EXIT_SUCCESS; + goto exit1; + } + break; + default: + setproctitle("[launcher]"); + ctx.options |= DHCPCD_FORKED | DHCPCD_LAUNCHER; + ctx.fork_fd = fork_fd[0]; + close(fork_fd[1]); +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd(ctx.fork_fd) == -1) { + logerr("ps_rights_limit_fd"); + goto exit_failure; + } +#endif + eloop_event_add(ctx.eloop, ctx.fork_fd, dhcpcd_fork_cb, &ctx); + + if (ctx.stderr_valid) { + ctx.stderr_fd = stderr_fd[0]; + close(stderr_fd[1]); +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd(ctx.stderr_fd) == 1) { + logerr("ps_rights_limit_fd"); + goto exit_failure; + } +#endif + eloop_event_add(ctx.eloop, ctx.stderr_fd, + dhcpcd_stderr_cb, &ctx); + } +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, NULL) == -1) + goto exit_failure; +#endif + goto run_loop; + } + + /* We have now forked, setsid, forked once more. + * From this point on, we are the controlling daemon. */ + logdebugx("spawned manager process on PID %d", getpid()); +start_manager: ctx.options |= DHCPCD_STARTED; + if ((pid = pidfile_lock(ctx.pidfile)) != 0) { + logerr("%s: pidfile_lock %d", __func__, pid); +#ifdef PRIVSEP + /* privsep has not started ... */ + ctx.options &= ~DHCPCD_PRIVSEP; +#endif + goto exit_failure; + } +#endif + + os_init(); + +#if defined(BSD) && defined(INET6) + /* Disable the kernel RTADV sysctl as early as possible. */ + if (ctx.options & DHCPCD_IPV6 && ctx.options & DHCPCD_IPV6RS) + if_disable_rtadv(); +#endif + +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && ps_start(&ctx) == -1) { + logerr("ps_start"); + goto exit_failure; + } + if (ctx.options & DHCPCD_FORKED) + goto run_loop; +#endif + + if (!(ctx.options & DHCPCD_TEST)) { + if (control_start(&ctx, + ctx.options & DHCPCD_MANAGER ? + NULL : argv[optind], family) == -1) + { + logerr("%s: control_start", __func__); + goto exit_failure; + } + } + +#ifdef PLUGIN_DEV + /* Start any dev listening plugin which may want to + * change the interface name provided by the kernel */ + if (!IN_PRIVSEP(&ctx) && + (ctx.options & (DHCPCD_MANAGER | DHCPCD_DEV)) == + (DHCPCD_MANAGER | DHCPCD_DEV)) + dev_start(&ctx, dhcpcd_handleinterface); +#endif -#ifdef HAVE_SETPROCTITLE setproctitle("%s%s%s", - ctx.options & DHCPCD_MASTER ? "[master]" : argv[optind], + ctx.options & DHCPCD_MANAGER ? "[manager]" : argv[optind], ctx.options & DHCPCD_IPV4 ? " [ip4]" : "", ctx.options & DHCPCD_IPV6 ? " [ip6]" : ""); -#endif if (if_opensockets(&ctx) == -1) { logerr("%s: if_opensockets", __func__); @@ -1999,20 +2438,26 @@ dhcpcd_setlinkrcvbuf(&ctx); #endif - /* When running dhcpcd against a single interface, we need to retain - * the old behaviour of waiting for an IP address */ - if (ctx.ifc == 1 && !(ctx.options & DHCPCD_BACKGROUND)) - ctx.options |= DHCPCD_WAITIP; + /* Try and create DUID from the machine UUID. */ + dhcpcd_initduid(&ctx, NULL); + + /* Cache the default vendor option. */ + if (dhcp_vendor(ctx.vendor, sizeof(ctx.vendor)) == -1) + logerr("dhcp_vendor"); /* Start handling kernel messages for interfaces, addresses and * routes. */ eloop_event_add(ctx.eloop, ctx.link_fd, dhcpcd_handlelink, &ctx); - /* Start any dev listening plugin which may want to - * change the interface name provided by the kernel */ - if ((ctx.options & (DHCPCD_MASTER | DHCPCD_DEV)) == - (DHCPCD_MASTER | DHCPCD_DEV)) - dev_start(&ctx); +#ifdef PRIVSEP + if (IN_PRIVSEP(&ctx) && ps_managersandbox(&ctx, "stdio route") == -1) + goto exit_failure; +#endif + + /* When running dhcpcd against a single interface, we need to retain + * the old behaviour of waiting for an IP address */ + if (ctx.ifc == 1 && !(ctx.options & DHCPCD_BACKGROUND)) + ctx.options |= DHCPCD_WAITIP; ctx.ifaces = if_discover(&ctx, &ifaddrs, ctx.ifc, ctx.ifv); if (ctx.ifaces == NULL) { @@ -2020,9 +2465,11 @@ goto exit_failure; } for (i = 0; i < ctx.ifc; i++) { - if ((ifp = if_find(ctx.ifaces, ctx.ifv[i])) == NULL || - !ifp->active) - logerrx("%s: interface not found or invalid", + if ((ifp = if_find(ctx.ifaces, ctx.ifv[i])) == NULL) + logerrx("%s: interface not found", + ctx.ifv[i]); + else if (!ifp->active) + logerrx("%s: interface has an invalid configuration", ctx.ifv[i]); } TAILQ_FOREACH(ifp, ctx.ifaces, next) { @@ -2031,11 +2478,12 @@ } if (ifp == NULL) { if (ctx.ifc == 0) { - logfunc_t *logfunc; + int loglevel; - logfunc = ctx.options & DHCPCD_INACTIVE ? - logdebugx : logerrx; - logfunc("no valid interfaces found"); + loglevel = ctx.options & DHCPCD_INACTIVE ? + LOG_DEBUG : LOG_ERR; + logmessage(loglevel, "no valid interfaces found"); + dhcpcd_daemonise(&ctx); } else goto exit_failure; if (!(ctx.options & DHCPCD_LINK)) { @@ -2050,21 +2498,20 @@ } if_learnaddrs(&ctx, ctx.ifaces, &ifaddrs); - if (ctx.options & DHCPCD_BACKGROUND && dhcpcd_daemonise(&ctx)) - goto exit_success; + if (ctx.options & DHCPCD_BACKGROUND) + dhcpcd_daemonise(&ctx); opt = 0; TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) { run_preinit(ifp); - if (!(ifp->options->options & DHCPCD_LINK) || - ifp->carrier != LINK_DOWN) + if (if_is_link_up(ifp)) opt = 1; } } if (!(ctx.options & DHCPCD_BACKGROUND)) { - if (ctx.options & DHCPCD_MASTER) + if (ctx.options & DHCPCD_MANAGER) t = ifo->timeout; else { t = 0; @@ -2079,13 +2526,12 @@ ctx.options & DHCPCD_LINK && !(ctx.options & DHCPCD_WAITIP)) { - logfunc_t *logfunc; + int loglevel; - logfunc = ctx.options & DHCPCD_INACTIVE ? - logdebugx : logwarnx; - logfunc("no interfaces have a carrier"); - if (dhcpcd_daemonise(&ctx)) - goto exit_success; + loglevel = ctx.options & DHCPCD_INACTIVE ? + LOG_DEBUG : LOG_WARNING; + logmessage(loglevel, "no interfaces have a carrier"); + dhcpcd_daemonise(&ctx); } else if (t > 0 && /* Test mode removes the daemonise bit, so check for both */ ctx.options & (DHCPCD_DAEMONISE | DHCPCD_TEST)) @@ -2103,6 +2549,7 @@ dhcpcd_prestartinterface, ifp); } +run_loop: i = eloop_start(ctx.eloop, &ctx.sigset); if (i < 0) { logerr("%s: eloop_start", __func__); @@ -2118,10 +2565,22 @@ i = EXIT_FAILURE; exit1: - if (ifaddrs != NULL) - freeifaddrs(ifaddrs); - if (control_stop(&ctx) == -1) + if (!(ctx.options & DHCPCD_TEST) && control_stop(&ctx) == -1) logerr("%s: control_stop", __func__); + if (ifaddrs != NULL) { +#ifdef PRIVSEP_GETIFADDRS + if (IN_PRIVSEP(&ctx)) + free(ifaddrs); + else +#endif + freeifaddrs(ifaddrs); + } + /* ps_stop will clear DHCPCD_PRIVSEP but we need to + * remember it to avoid attemping to remove the pidfile */ + oi = ctx.options & DHCPCD_PRIVSEP ? 1 : 0; +#ifdef PRIVSEP + ps_stop(&ctx); +#endif /* Free memory and close fd's */ if (ctx.ifaces) { while ((ifp = TAILQ_FIRST(ctx.ifaces))) { @@ -2129,6 +2588,7 @@ if_free(ifp); } free(ctx.ifaces); + ctx.ifaces = NULL; } free_options(&ctx, ifo); #ifdef HAVE_OPEN_MEMSTREAM @@ -2148,15 +2608,31 @@ #ifdef INET6 ipv6_ctxfree(&ctx); #endif +#ifdef PLUGIN_DEV dev_stop(&ctx); +#endif +#ifdef PRIVSEP + eloop_free(ctx.ps_eloop); +#endif eloop_free(ctx.eloop); - + if (ctx.script != dhcpcd_default_script) + free(ctx.script); if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED)) loginfox(PACKAGE " exited"); logclose(); free(ctx.logfile); + free(ctx.ctl_buf); +#ifdef SETPROCTITLE_H + setproctitle_fini(); +#endif #ifdef USE_SIGNALS - if (ctx.options & DHCPCD_FORKED) + if (ctx.options & DHCPCD_STARTED) { + /* Try to detach from the launch process. */ + if (ctx.fork_fd != -1 && + write(ctx.fork_fd, &i, sizeof(i)) == -1) + logerr("%s: write", __func__); + } + if (ctx.options & DHCPCD_FORKED || oi != 0) _exit(i); /* so atexit won't remove our pidfile */ #endif return i; Index: contrib/dhcpcd/src/dhcpcd.conf =================================================================== --- contrib/dhcpcd/src/dhcpcd.conf +++ contrib/dhcpcd/src/dhcpcd.conf @@ -5,7 +5,7 @@ #controlgroup wheel # Inform the DHCP server of our hostname for DDNS. -hostname +#hostname # Use the hardware address of the interface for the Client ID. #clientid @@ -18,20 +18,27 @@ # Persist interface configuration when dhcpcd exits. persistent -# Rapid commit support. -# Safe to enable by default because it requires the equivalent option set -# on the server to actually work. -option rapid_commit +# vendorclassid is set to blank to avoid sending the default of +# dhcpcd-::: +vendorclassid # A list of options to request from the DHCP server. -option domain_name_servers, domain_name, domain_search, host_name +option domain_name_servers, domain_name, domain_search option classless_static_routes # Respect the network MTU. This is applied to DHCP routes. option interface_mtu +# Request a hostname from the network +option host_name + # Most distributions have NTP support. #option ntp_servers +# Rapid commit support. +# Safe to enable by default because it requires the equivalent option set +# on the server to actually work. +option rapid_commit + # A ServerID is required by RFC2131. require dhcp_server_identifier Index: contrib/dhcpcd/src/dhcpcd.conf.5 =================================================================== --- contrib/dhcpcd/src/dhcpcd.conf.5 +++ contrib/dhcpcd/src/dhcpcd.conf.5 @@ -1,6 +1,6 @@ .\" SPDX-License-Identifier: BSD-2-Clause .\" -.\" Copyright (c) 2006-2019 Roy Marples +.\" Copyright (c) 2006-2021 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -24,7 +24,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd September 8, 2019 +.Dd August 23, 2021 .Dt DHCPCD.CONF 5 .Os .Sh NAME @@ -59,6 +59,28 @@ .Ar pattern which is a space or comma separated list of patterns passed to .Xr fnmatch 3 . +.It Ic anonymous +Enables Anonymity Profiles for DHCP, RFC 7844. +Any DUID is ignored and ClientID is set to LL only. +All non essential options are then masked at this point, +but they could be unmasked by explicitly requesting the option +.Sy after +the +.Ic anonymous +option is processed. +As such, the +.Ic anonymous +option +.Sy should +be the last option in the configuration unless you really want to +send something which could identify you. +.Nm dhcpcd +will not try and reboot an old lease, it will go straight into +DISCOVER/SOLICIT. +.It Ic randomise_hwaddr +Forces a hardware address randomisation when the interface is brought up +or when the carrier is lost. +This is generally used in tandem with the anonymous option. .It Ic arping Ar address Op address .Nm dhcpcd will arping each address in order before attempting DHCP. @@ -85,7 +107,7 @@ .Ar token then .Ar algorithm is -snd_secretid/rcv_secretid so you can send and recieve different tokens. +snd_secretid/rcv_secretid so you can send and receive different tokens. .It Ic authtoken Ar secretid Ar realm Ar expire Ar key Define a shared key for use in authentication. .Ar realm @@ -139,7 +161,7 @@ will set this automatically. .It Ic controlgroup Ar group Sets the group ownership of -.Pa @RUNDIR@/dhcpcd.sock +.Pa /var/run/dhcpcd/sock so that users other than root can connect to .Nm dhcpcd . .It Ic debug @@ -191,18 +213,29 @@ sends a default .Ar clientid of the hardware family and the hardware address. -.It Ic duid +.It Ic duid Op ll | lt | uuid | value Use a DHCP Unique Identifier. If a system UUID is available, that will be used to create a DUID-UUID, otheriwse if persistent storage is available then a DUID-LLT (link local address + time) is generated, otherwise DUID-LL is generated (link local address). +The DUID type can be hinted as an optional parameter if the file +.Pa /var/db/dhcpcd/duid +does not exist. +If not +.Va ll , +.Va lt +or +.Va uuid +then +.Va value +will be converted from 00:11:22:33 format. This, plus the IAID will be used as the .Ic clientid . The DUID generated will be held in -.Pa @DBDIR@/duid +.Pa /var/db/dhcpcd/duid and should not be copied to other hosts. -This file also takes precedence over the above rules. +This file also takes precedence over the above rules except for setting a value. .It Ic iaid Ar iaid Set the Interface Association Identifier to .Ar iaid . @@ -422,14 +455,20 @@ RDNSS option and a valid prefix or no DHCPv6 instruction. Set this option so to make .Nm dhcpcd -always fork on an RA. +always fork on a RA. .It Ic ipv6rs Enables IPv6 Router Advertisement solicitation. This is on by default, but is documented here in the case where it is disabled globally but needs to be enabled for one interface. .It Ic leasetime Ar seconds -Request a leasetime of +Request a lease time of .Ar seconds . +.Ar -1 +represents an infinite lease time. +By default +.Nm dhcpcd +does not request any lease time and leaves it in the hands of the +DHCP server. .It Ic link_rcvbuf Ar size Override the size of the link receive buffer from the kernel default. While @@ -438,8 +477,9 @@ this may not be desirable on heavily loaded systems. .It Ic logfile Ar logfile Writes to the specified -.Ar logfile -rather than +.Ar logfile . +.Nm dhcpcd +still writes to .Xr syslog 3 . The .Ar logfile @@ -451,9 +491,10 @@ .It Ic metric Ar metric Metrics are used to prefer an interface over another one, lowest wins. .Nm dhcpcd -will supply a default metric of 200 + +will supply a default metric of 1000 + .Xr if_nametoindex 3 . -An extra 100 will be added for wireless interfaces. +This will be offset by 2000 for wireless interfaces, with additional offsets +of 1000000 for IPv4LL and 2000000 for roaming interfaces. .It Ic mudurl Ar url Specifies the URL for a Manufacturer Usage Description (MUD). The description is used by upstream network devices to instantiate any @@ -510,13 +551,7 @@ Don't receive link messages about carrier status. You should only set this for buggy interface drivers. .It Ic noup -Don't bring the interface up when in master mode. -If -.Nm -cannot determine the carrier state, -.Nm -will enter a tight polling loop until the interface is marked up and running -or a valid carrier state is reported. +Don't bring the interface up when in manager mode. .It Ic option Ar option Requests the .Ar option @@ -582,18 +617,24 @@ .It Ic reboot Ar seconds Allow .Ar reboot -seconds before moving to the DISCOVER phase if we have an old lease to use -and moving from DISCOVER to IPv4LL if no reply. +seconds before moving to the DISCOVER phase if we have an old lease to use. +Allow +.Ar reboot +seconds before starting fallback states from the DISCOVER phase. +IPv4LL is started when the first +.Ar reboot +timeout is reached. The default is 5 seconds. A setting of 0 seconds causes -.Nm dhcpcd -to skip the REBOOT phase and go straight into DISCOVER. +.Nm +to skip the reboot phase and go straight into DISCOVER. This is desirable for mobile users because if you change from network A to network B and they use the same subnet and the address from network A isn't in use on network B, then the DHCP server will remain silent even if authoritative which means .Nm dhcpcd will timeout before moving back to the DISCOVER phase. +This has no effect on DHCPv6 other than skipping the reboot phase. .It Ic release .Nm dhcpcd will release the lease prior to stopping the interface. @@ -601,15 +642,18 @@ Use .Ar script instead of the default -.Pa @SCRIPT@ . +.Pa /libexec/dhcpcd-run-hooks . .It Ic ssid Ar ssid Subsequent options are only parsed for this wireless .Ar ssid . -.It Ic slaac Op Ar hwaddr | Ar private +.It Ic slaac Ar hwaddr | Ar private Op Ar temp | Ar temporary Selects the interface identifier used for SLAAC generated IPv6 addresses. If .Ar private -is used, an RFC 7217 address is generated. +is used, a RFC 7217 address is generated. +The +.Ar temporary +directive will create a temporary address for the prefix as well. .It Ic static Ar value Configures a static .Ar value . @@ -669,8 +713,17 @@ start the IPv4LL process after the timeout and then wait a little longer before really timing out. .It Ic userclass Ar string -Tag the DHCPv4 messages with the userclass. +Tag the DHCPv4 message with the userclass. You can specify more than one. +.It Ic msuserclass Ar string +Tag the DHCPv4 mesasge with the Microsoft userclass. +Unlike the +.Ic userclass +option, this one can only be added once. +It should only be used for Microsoft DHCP servers and the +.Ic vendorclassid +should be set to "MSFT 98" or "MSFT 5.0". +This option is not RFC compliant. .It Ic vendor Ar code , Ns Ar value Add an encapsulated vendor option. .Ar code @@ -759,7 +812,7 @@ exported to .Xr dhcpcd-run-hooks 8 , with a prefix of -.Va _nd . +.Va nd_ . .It Ic define6 Ar code Ar type Ar variable Defines the DHCPv6 option .Ar code @@ -770,7 +823,7 @@ exported to .Xr dhcpcd-run-hooks 8 , with a prefix of -.Va _dhcp6 . +.Va dhcp6_ . .It Ic vendopt Ar code Ar type Ar variable Defines the Vendor-Identifying Vendor Options. The @@ -931,7 +984,7 @@ .Bl -tag -width -indent .It Ic monocounter Read the number in the file -.Pa @DBDIR@/dhcpcd-rdm.monotonic +.Pa /var/db/dhcpcd/dhcpcd-rdm.monotonic and add one to it. .It Ic monotime Create an NTP timestamp from the system time. Index: contrib/dhcpcd/src/duid.h =================================================================== --- contrib/dhcpcd/src/duid.h +++ contrib/dhcpcd/src/duid.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2015 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -30,7 +30,12 @@ #define DUID_H #define DUID_LEN 128 + 2 +#define DUID_DEFAULT 0 +#define DUID_LLT 1 +#define DUID_LL 3 +#define DUID_UUID 4 -size_t duid_init(const struct interface *); +size_t duid_make(void *, const struct interface *, uint16_t); +size_t duid_init(struct dhcpcd_ctx *, const struct interface *); #endif Index: contrib/dhcpcd/src/duid.c =================================================================== --- contrib/dhcpcd/src/duid.c +++ contrib/dhcpcd/src/duid.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -28,9 +28,6 @@ #define UUID_LEN 36 #define DUID_TIME_EPOCH 946684800 -#define DUID_LLT 1 -#define DUID_LL 3 -#define DUID_UUID 4 #include #include @@ -118,51 +115,52 @@ return l; } -static size_t -duid_make(uint8_t *d, const struct interface *ifp, uint16_t type) +size_t +duid_make(void *d, const struct interface *ifp, uint16_t type) { uint8_t *p; uint16_t u16; time_t t; uint32_t u32; + if (ifp->hwlen == 0) + return 0; + p = d; u16 = htons(type); - memcpy(p, &u16, 2); - p += 2; - u16 = htons(ifp->family); - memcpy(p, &u16, 2); - p += 2; + memcpy(p, &u16, sizeof(u16)); + p += sizeof(u16); + u16 = htons(ifp->hwtype); + memcpy(p, &u16, sizeof(u16)); + p += sizeof(u16); if (type == DUID_LLT) { /* time returns seconds from jan 1 1970, but DUID-LLT is * seconds from jan 1 2000 modulo 2^32 */ t = time(NULL) - DUID_TIME_EPOCH; u32 = htonl((uint32_t)t & 0xffffffff); - memcpy(p, &u32, 4); - p += 4; + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); } /* Finally, add the MAC address of the interface */ memcpy(p, ifp->hwaddr, ifp->hwlen); p += ifp->hwlen; - return (size_t)(p - d); + return (size_t)(p - (uint8_t *)d); } #define DUID_STRLEN DUID_LEN * 3 static size_t -duid_get(uint8_t **d, const struct interface *ifp) +duid_get(struct dhcpcd_ctx *ctx, const struct interface *ifp) { - FILE *fp; uint8_t *data; - size_t len; - int x = 0; + size_t len, slen; char line[DUID_STRLEN]; const struct interface *ifp2; /* If we already have a DUID then use it as it's never supposed * to change once we have one even if the interfaces do */ - if ((len = read_hwaddr_aton(&data, DUID)) != 0) { + if ((len = dhcp_read_hwaddr_aton(ctx, &data, DUID)) != 0) { if (len <= DUID_LEN) { - *d = data; + ctx->duid = data; return len; } logerrx("DUID too big (max %u): %s", DUID_LEN, DUID); @@ -176,13 +174,22 @@ } } - /* Regardless of what happens we will create a DUID to use. */ - *d = data; - /* No file? OK, lets make one based the machines UUID */ - len = duid_make_uuid(data); - if (len > 0) + if (ifp == NULL) { + if (ctx->duid_type != DUID_DEFAULT && + ctx->duid_type != DUID_UUID) + len = 0; + else + len = duid_make_uuid(data); + if (len == 0) + free(data); + else + ctx->duid = data; return len; + } + + /* Regardless of what happens we will create a DUID to use. */ + ctx->duid = data; /* No UUID? OK, lets make one based on our interface */ if (ifp->hwlen == 0) { @@ -196,33 +203,34 @@ logwarnx("picked interface %s to generate a DUID", ifp->name); } else { - logwarnx("no interfaces have a fixed hardware " - "address"); + if (ctx->duid_type != DUID_LL) + logwarnx("no interfaces have a fixed hardware " + "address"); return duid_make(data, ifp, DUID_LL); } } - if (!(fp = fopen(DUID, "w"))) { - logerr("%s", DUID); - return duid_make(data, ifp, DUID_LL); + len = duid_make(data, ifp, + ctx->duid_type == DUID_LL ? DUID_LL : DUID_LLT); + hwaddr_ntoa(data, len, line, sizeof(line)); + slen = strlen(line); + if (slen < sizeof(line) - 2) { + line[slen++] = '\n'; + line[slen] = '\0'; } - len = duid_make(data, ifp, DUID_LLT); - x = fprintf(fp, "%s\n", hwaddr_ntoa(data, len, line, sizeof(line))); - if (fclose(fp) == EOF) - x = -1; - /* Failed to write the duid? scrub it, we cannot use it */ - if (x < 1) { - logerr("%s", DUID); - unlink(DUID); - return duid_make(data, ifp, DUID_LL); + if (dhcp_writefile(ctx, DUID, 0640, line, slen) == -1) { + logerr("%s: cannot write duid", __func__); + if (ctx->duid_type != DUID_LL) + return duid_make(data, ifp, DUID_LL); } return len; } -size_t duid_init(const struct interface *ifp) +size_t +duid_init(struct dhcpcd_ctx *ctx, const struct interface *ifp) { - if (ifp->ctx->duid == NULL) - ifp->ctx->duid_len = duid_get(&ifp->ctx->duid, ifp); - return ifp->ctx->duid_len; + if (ctx->duid == NULL) + ctx->duid_len = duid_get(ctx, ifp); + return ctx->duid_len; } Index: contrib/dhcpcd/src/eloop.h =================================================================== --- contrib/dhcpcd/src/eloop.h +++ contrib/dhcpcd/src/eloop.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -31,33 +31,12 @@ #include -/* Some systems don't define timespec macros */ -#ifndef timespecclear -#define timespecclear(tsp) (tsp)->tv_sec = (time_t)((tsp)->tv_nsec = 0L) -#define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) -#define timespeccmp(tsp, usp, cmp) \ - (((tsp)->tv_sec == (usp)->tv_sec) ? \ - ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ - ((tsp)->tv_sec cmp (usp)->tv_sec)) -#define timespecadd(tsp, usp, vsp) \ - do { \ - (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ - (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ - if ((vsp)->tv_nsec >= 1000000000L) { \ - (vsp)->tv_sec++; \ - (vsp)->tv_nsec -= 1000000000L; \ - } \ - } while (/* CONSTCOND */ 0) -#define timespecsub(tsp, usp, vsp) \ - do { \ - (vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \ - (vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \ - if ((vsp)->tv_nsec < 0) { \ - (vsp)->tv_sec--; \ - (vsp)->tv_nsec += 1000000000L; \ - } \ - } while (/* CONSTCOND */ 0) -#endif +/* Handy macros to create subsecond timeouts */ +#define CSEC_PER_SEC 100 +#define MSEC_PER_SEC 1000 +#define NSEC_PER_CSEC 10000000 +#define NSEC_PER_MSEC 1000000 +#define NSEC_PER_SEC 1000000000 /* eloop queues are really only for deleting timeouts registered * for a function or object. @@ -67,9 +46,15 @@ #define ELOOP_QUEUE 1 #endif +/* Used for deleting a timeout for all queues. */ +#define ELOOP_QUEUE_ALL 0 + /* Forward declare eloop - the content should be invisible to the outside */ struct eloop; +unsigned long long eloop_timespec_diff(const struct timespec *tsp, + const struct timespec *usp, unsigned int *nsp); +size_t eloop_event_count(const struct eloop *); int eloop_event_add_rw(struct eloop *, int, void (*)(void *), void *, void (*)(void *), void *); @@ -94,19 +79,20 @@ int eloop_q_timeout_add_tv(struct eloop *, int, const struct timespec *, void (*)(void *), void *); int eloop_q_timeout_add_sec(struct eloop *, int, - time_t, void (*)(void *), void *); + unsigned int, void (*)(void *), void *); int eloop_q_timeout_add_msec(struct eloop *, int, - long, void (*)(void *), void *); + unsigned long, void (*)(void *), void *); int eloop_q_timeout_delete(struct eloop *, int, void (*)(void *), void *); -int eloop_signal_set_cb(struct eloop *, const int *, size_t, +void eloop_signal_set_cb(struct eloop *, const int *, size_t, void (*)(int, void *), void *); int eloop_signal_mask(struct eloop *, sigset_t *oldset); struct eloop * eloop_new(void); -int eloop_requeue(struct eloop *); +void eloop_clear(struct eloop *); void eloop_free(struct eloop *); void eloop_exit(struct eloop *, int); +void eloop_enter(struct eloop *); int eloop_start(struct eloop *, sigset_t *); #endif Index: contrib/dhcpcd/src/eloop.c =================================================================== --- contrib/dhcpcd/src/eloop.c +++ contrib/dhcpcd/src/eloop.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * eloop - portable event based main loop. - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved. * Redistribution and use in source and binary forms, with or without @@ -26,60 +26,31 @@ * SUCH DAMAGE. */ -#if (defined(__unix__) || defined(unix)) && !defined(USG) -#include -#endif #include #include #include #include +#include +#include #include #include #include #include #include -/* config.h should define HAVE_KQUEUE, HAVE_EPOLL, etc. */ +/* config.h should define HAVE_PPOLL, etc. */ #if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_H) #include "config.h" #endif -/* Attempt to autodetect kqueue or epoll. - * Failing that, fall back to pselect. */ -#if !defined(HAVE_KQUEUE) && !defined(HAVE_EPOLL) && !defined(HAVE_PSELECT) && \ - !defined(HAVE_POLLTS) && !defined(HAVE_PPOLL) -#if defined(BSD) -/* Assume BSD has a working sys/queue.h and kqueue(2) interface. */ -#define HAVE_SYS_QUEUE_H -#define HAVE_KQUEUE -#define WARN_SELECT -#elif defined(__linux__) || defined(__sun) -/* Assume Linux and Solaris have a working epoll(3) interface. */ -#define HAVE_EPOLL -#define WARN_SELECT -#else -/* pselect(2) is a POSIX standard. */ +#if defined(HAVE_PPOLL) +#elif defined(HAVE_POLLTS) +#define ppoll pollts +#elif !defined(HAVE_PSELECT) +#pragma message("Compiling eloop with pselect(2) support.") #define HAVE_PSELECT -#define WARN_SELECT -#endif -#endif - -/* pollts and ppoll require poll. - * pselect is wrapped in a pollts/ppoll style interface - * and as such require poll as well. */ -#if defined(HAVE_PSELECT) || defined(HAVE_POLLTS) || defined(HAVE_PPOLL) -#ifndef HAVE_POLL -#define HAVE_POLL -#endif -#if defined(HAVE_POLLTS) -#define POLLTS pollts -#elif defined(HAVE_PPOLL) -#define POLLTS ppoll -#else -#define POLLTS eloop_pollts -#define ELOOP_NEED_POLLTS -#endif +#define ppoll eloop_ppoll #endif #include "eloop.h" @@ -95,47 +66,9 @@ #endif #endif -#ifndef MSEC_PER_SEC -#define MSEC_PER_SEC 1000L -#define NSEC_PER_MSEC 1000000L -#endif - -#if defined(HAVE_KQUEUE) -#include -#include -#ifdef __NetBSD__ -/* udata is void * except on NetBSD. - * lengths are int except on NetBSD. */ -#define UPTR(x) ((intptr_t)(x)) -#define LENC(x) (x) -#else -#define UPTR(x) (x) -#define LENC(x) ((int)(x)) -#endif -#elif defined(HAVE_EPOLL) -#include -#elif defined(HAVE_POLL) -#if defined(HAVE_PSELECT) +#ifdef HAVE_PSELECT #include #endif -#include -#endif - -#ifdef WARN_SELECT -#if defined(HAVE_KQUEUE) -#pragma message("Compiling eloop with kqueue(2) support.") -#elif defined(HAVE_EPOLL) -#pragma message("Compiling eloop with epoll(7) support.") -#elif defined(HAVE_PSELECT) -#pragma message("Compiling eloop with pselect(2) support.") -#elif defined(HAVE_PPOLL) -#pragma message("Compiling eloop with ppoll(2) support.") -#elif defined(HAVE_POLLTS) -#pragma message("Compiling eloop with pollts(2) support.") -#else -#error Unknown select mechanism for eloop -#endif -#endif /* Our structures require TAILQ macros, which really every libc should * ship as they are useful beyond belief. @@ -156,6 +89,31 @@ #endif #endif +#ifdef ELOOP_DEBUG +#include +#endif + +/* + * Allow a backlog of signals. + * If you use many eloops in the same process, they should all + * use the same signal handler or have the signal handler unset. + * Otherwise the signal might not behave as expected. + */ +#define ELOOP_NSIGNALS 5 + +/* + * time_t is a signed integer of an unspecified size. + * To adjust for time_t wrapping, we need to work the maximum signed + * value and use that as a maximum. + */ +#ifndef TIME_MAX +#define TIME_MAX ((1ULL << (sizeof(time_t) * NBBY - 1)) - 1) +#endif +/* The unsigned maximum is then simple - multiply by two and add one. */ +#ifndef UTIME_MAX +#define UTIME_MAX (TIME_MAX * 2) + 1 +#endif + struct eloop_event { TAILQ_ENTRY(eloop_event) next; int fd; @@ -163,39 +121,35 @@ void *read_cb_arg; void (*write_cb)(void *); void *write_cb_arg; + struct pollfd *pollfd; }; struct eloop_timeout { TAILQ_ENTRY(eloop_timeout) next; - struct timespec when; + unsigned int seconds; + unsigned int nseconds; void (*callback)(void *); void *arg; int queue; }; struct eloop { - size_t events_len; TAILQ_HEAD (event_head, eloop_event) events; + size_t nevents; struct event_head free_events; - int events_maxfd; - struct eloop_event **event_fds; + bool events_need_setup; + struct timespec now; TAILQ_HEAD (timeout_head, eloop_timeout) timeouts; struct timeout_head free_timeouts; - void (*timeout0)(void *); - void *timeout0_arg; const int *signals; size_t signals_len; void (*signal_cb)(int, void *); void *signal_cb_ctx; -#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) - int poll_fd; -#elif defined(HAVE_POLL) struct pollfd *fds; - size_t fds_len; -#endif + size_t nfds; int exitnow; int exitcode; @@ -219,30 +173,10 @@ } #endif -#ifdef HAVE_POLL -static void -eloop_event_setup_fds(struct eloop *eloop) -{ - struct eloop_event *e; - size_t i; - - i = 0; - TAILQ_FOREACH(e, &eloop->events, next) { - eloop->fds[i].fd = e->fd; - eloop->fds[i].events = 0; - if (e->read_cb) - eloop->fds[i].events |= POLLIN; - if (e->write_cb) - eloop->fds[i].events |= POLLOUT; - eloop->fds[i].revents = 0; - i++; - } -} - -#ifdef ELOOP_NEED_POLLTS -/* Wrapper around pselect, to imitate the NetBSD pollts call. */ +#ifdef HAVE_PSELECT +/* Wrapper around pselect, to imitate the ppoll call. */ static int -eloop_pollts(struct pollfd * fds, nfds_t nfds, +eloop_ppoll(struct pollfd * fds, nfds_t nfds, const struct timespec *ts, const sigset_t *sigmask) { fd_set read_fds, write_fds; @@ -277,10 +211,112 @@ return r; } -#endif /* pollts */ -#else /* !HAVE_POLL */ -#define eloop_event_setup_fds(a) {} -#endif /* HAVE_POLL */ +#endif + +unsigned long long +eloop_timespec_diff(const struct timespec *tsp, const struct timespec *usp, + unsigned int *nsp) +{ + unsigned long long tsecs, usecs, secs; + long nsecs; + + if (tsp->tv_sec < 0) /* time wreapped */ + tsecs = UTIME_MAX - (unsigned long long)(-tsp->tv_sec); + else + tsecs = (unsigned long long)tsp->tv_sec; + if (usp->tv_sec < 0) /* time wrapped */ + usecs = UTIME_MAX - (unsigned long long)(-usp->tv_sec); + else + usecs = (unsigned long long)usp->tv_sec; + + if (usecs > tsecs) /* time wrapped */ + secs = (UTIME_MAX - usecs) + tsecs; + else + secs = tsecs - usecs; + + nsecs = tsp->tv_nsec - usp->tv_nsec; + if (nsecs < 0) { + if (secs == 0) + nsecs = 0; + else { + secs--; + nsecs += NSEC_PER_SEC; + } + } + if (nsp != NULL) + *nsp = (unsigned int)nsecs; + return secs; +} + +static void +eloop_reduce_timers(struct eloop *eloop) +{ + struct timespec now; + unsigned long long secs; + unsigned int nsecs; + struct eloop_timeout *t; + + clock_gettime(CLOCK_MONOTONIC, &now); + secs = eloop_timespec_diff(&now, &eloop->now, &nsecs); + + TAILQ_FOREACH(t, &eloop->timeouts, next) { + if (secs > t->seconds) { + t->seconds = 0; + t->nseconds = 0; + } else { + t->seconds -= (unsigned int)secs; + if (nsecs > t->nseconds) { + if (t->seconds == 0) + t->nseconds = 0; + else { + t->seconds--; + t->nseconds = NSEC_PER_SEC + - (nsecs - t->nseconds); + } + } else + t->nseconds -= nsecs; + } + } + + eloop->now = now; +} + +static void +eloop_event_setup_fds(struct eloop *eloop) +{ + struct eloop_event *e, *ne; + struct pollfd *pfd; + + pfd = eloop->fds; + TAILQ_FOREACH_SAFE(e, &eloop->events, next, ne) { + if (e->fd == -1) { + TAILQ_REMOVE(&eloop->events, e, next); + TAILQ_INSERT_TAIL(&eloop->free_events, e, next); + continue; + } +#ifdef ELOOP_DEBUG + fprintf(stderr, "%s(%d) fd=%d, rcb=%p, wcb=%p\n", + __func__, getpid(), e->fd, e->read_cb, e->write_cb); +#endif + e->pollfd = pfd; + pfd->fd = e->fd; + pfd->events = 0; + if (e->read_cb != NULL) + pfd->events |= POLLIN; + if (e->write_cb != NULL) + pfd->events |= POLLOUT; + pfd->revents = 0; + pfd++; + } + eloop->events_need_setup = false; +} + +size_t +eloop_event_count(const struct eloop *eloop) +{ + + return eloop->nevents; +} int eloop_event_add_rw(struct eloop *eloop, int fd, @@ -288,13 +324,7 @@ void (*write_cb)(void *), void *write_cb_arg) { struct eloop_event *e; -#if defined(HAVE_KQUEUE) - struct kevent ke[2]; -#elif defined(HAVE_EPOLL) - struct epoll_event epe; -#elif defined(HAVE_POLL) - struct pollfd *nfds; -#endif + struct pollfd *pfd; assert(eloop != NULL); assert(read_cb != NULL || write_cb != NULL); @@ -303,122 +333,52 @@ return -1; } -#ifdef HAVE_EPOLL - memset(&epe, 0, sizeof(epe)); - epe.data.fd = fd; - epe.events = EPOLLIN; - if (write_cb) - epe.events |= EPOLLOUT; -#endif + TAILQ_FOREACH(e, &eloop->events, next) { + if (e->fd == fd) + break; + } - /* We should only have one callback monitoring the fd. */ - if (fd <= eloop->events_maxfd) { - if ((e = eloop->event_fds[fd]) != NULL) { - int error; - -#if defined(HAVE_KQUEUE) - EV_SET(&ke[0], (uintptr_t)fd, EVFILT_READ, EV_ADD, - 0, 0, UPTR(e)); - if (write_cb) - EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE, - EV_ADD, 0, 0, UPTR(e)); - else if (e->write_cb) - EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE, - EV_DELETE, 0, 0, UPTR(e)); - error = kevent(eloop->poll_fd, ke, - e->write_cb || write_cb ? 2 : 1, NULL, 0, NULL); -#elif defined(HAVE_EPOLL) - epe.data.ptr = e; - error = epoll_ctl(eloop->poll_fd, EPOLL_CTL_MOD, - fd, &epe); -#else - error = 0; -#endif - if (read_cb) { - e->read_cb = read_cb; - e->read_cb_arg = read_cb_arg; - } - if (write_cb) { - e->write_cb = write_cb; - e->write_cb_arg = write_cb_arg; - } - eloop_event_setup_fds(eloop); - return error; + if (e == NULL) { + if (eloop->nevents + 1 > eloop->nfds) { + pfd = eloop_realloca(eloop->fds, eloop->nevents + 1, + sizeof(*pfd)); + if (pfd == NULL) + return -1; + eloop->fds = pfd; + eloop->nfds++; } - } else { - struct eloop_event **new_fds; - int maxfd, i; - - /* Reserve ourself and 4 more. */ - maxfd = fd + 4; - new_fds = eloop_realloca(eloop->event_fds, - ((size_t)maxfd + 1), sizeof(*eloop->event_fds)); - if (new_fds == NULL) - return -1; - - /* set new entries NULL as the fd's may not be contiguous. */ - for (i = maxfd; i > eloop->events_maxfd; i--) - new_fds[i] = NULL; - eloop->event_fds = new_fds; - eloop->events_maxfd = maxfd; + e = TAILQ_FIRST(&eloop->free_events); + if (e != NULL) + TAILQ_REMOVE(&eloop->free_events, e, next); + else { + e = malloc(sizeof(*e)); + if (e == NULL) + return -1; + } + TAILQ_INSERT_HEAD(&eloop->events, e, next); + eloop->nevents++; + e->fd = fd; + e->read_cb = read_cb; + e->read_cb_arg = read_cb_arg; + e->write_cb = write_cb; + e->write_cb_arg = write_cb_arg; + goto setup; } - /* Allocate a new event if no free ones already allocated. */ - if ((e = TAILQ_FIRST(&eloop->free_events))) { - TAILQ_REMOVE(&eloop->free_events, e, next); - } else { - e = malloc(sizeof(*e)); - if (e == NULL) - goto err; + if (read_cb) { + e->read_cb = read_cb; + e->read_cb_arg = read_cb_arg; } - - /* Ensure we can actually listen to it. */ - eloop->events_len++; -#ifdef HAVE_POLL - if (eloop->events_len > eloop->fds_len) { - nfds = eloop_realloca(eloop->fds, - (eloop->fds_len + 5), sizeof(*eloop->fds)); - if (nfds == NULL) - goto err; - eloop->fds_len += 5; - eloop->fds = nfds; + if (write_cb) { + e->write_cb = write_cb; + e->write_cb_arg = write_cb_arg; } -#endif - - /* Now populate the structure and add it to the list. */ - e->fd = fd; - e->read_cb = read_cb; - e->read_cb_arg = read_cb_arg; - e->write_cb = write_cb; - e->write_cb_arg = write_cb_arg; - -#if defined(HAVE_KQUEUE) - if (read_cb != NULL) - EV_SET(&ke[0], (uintptr_t)fd, EVFILT_READ, - EV_ADD, 0, 0, UPTR(e)); - if (write_cb != NULL) - EV_SET(&ke[1], (uintptr_t)fd, EVFILT_WRITE, - EV_ADD, 0, 0, UPTR(e)); - if (kevent(eloop->poll_fd, ke, write_cb ? 2 : 1, NULL, 0, NULL) == -1) - goto err; -#elif defined(HAVE_EPOLL) - epe.data.ptr = e; - if (epoll_ctl(eloop->poll_fd, EPOLL_CTL_ADD, fd, &epe) == -1) - goto err; -#endif - TAILQ_INSERT_HEAD(&eloop->events, e, next); - eloop->event_fds[e->fd] = e; - eloop_event_setup_fds(eloop); +setup: + e->pollfd = NULL; + eloop->events_need_setup = true; return 0; - -err: - if (e) { - eloop->events_len--; - TAILQ_INSERT_TAIL(&eloop->free_events, e, next); - } - return -1; } int @@ -441,85 +401,57 @@ eloop_event_delete_write(struct eloop *eloop, int fd, int write_only) { struct eloop_event *e; -#if defined(HAVE_KQUEUE) - struct kevent ke[2]; -#elif defined(HAVE_EPOLL) - struct epoll_event epe; -#endif assert(eloop != NULL); + if (fd == -1) { + errno = EINVAL; + return -1; + } - if (fd > eloop->events_maxfd || - (e = eloop->event_fds[fd]) == NULL) - { + TAILQ_FOREACH(e, &eloop->events, next) { + if (e->fd == fd) + break; + } + if (e == NULL) { errno = ENOENT; return -1; } if (write_only) { - if (e->write_cb == NULL) - return 0; if (e->read_cb == NULL) goto remove; e->write_cb = NULL; e->write_cb_arg = NULL; -#if defined(HAVE_KQUEUE) - EV_SET(&ke[0], (uintptr_t)e->fd, - EVFILT_WRITE, EV_DELETE, 0, 0, UPTR(NULL)); - kevent(eloop->poll_fd, ke, 1, NULL, 0, NULL); -#elif defined(HAVE_EPOLL) - memset(&epe, 0, sizeof(epe)); - epe.data.fd = e->fd; - epe.data.ptr = e; - epe.events = EPOLLIN; - epoll_ctl(eloop->poll_fd, EPOLL_CTL_MOD, fd, &epe); -#endif - eloop_event_setup_fds(eloop); + if (e->pollfd != NULL) { + e->pollfd->events &= ~POLLOUT; + e->pollfd->revents &= ~POLLOUT; + } return 1; } remove: - TAILQ_REMOVE(&eloop->events, e, next); - eloop->event_fds[e->fd] = NULL; - TAILQ_INSERT_TAIL(&eloop->free_events, e, next); - eloop->events_len--; - -#if defined(HAVE_KQUEUE) - EV_SET(&ke[0], (uintptr_t)fd, EVFILT_READ, - EV_DELETE, 0, 0, UPTR(NULL)); - if (e->write_cb) - EV_SET(&ke[1], (uintptr_t)fd, - EVFILT_WRITE, EV_DELETE, 0, 0, UPTR(NULL)); - kevent(eloop->poll_fd, ke, e->write_cb ? 2 : 1, NULL, 0, NULL); -#elif defined(HAVE_EPOLL) - /* NULL event is safe because we - * rely on epoll_pwait which as added - * after the delete without event was fixed. */ - epoll_ctl(eloop->poll_fd, EPOLL_CTL_DEL, fd, NULL); -#endif - - eloop_event_setup_fds(eloop); + e->fd = -1; + eloop->nevents--; + eloop->events_need_setup = true; return 1; } -int -eloop_q_timeout_add_tv(struct eloop *eloop, int queue, - const struct timespec *when, void (*callback)(void *), void *arg) +/* + * This implementation should cope with UINT_MAX seconds on a system + * where time_t is INT32_MAX. It should also cope with the monotonic timer + * wrapping, although this is highly unlikely. + * unsigned int should match or be greater than any on wire specified timeout. + */ +static int +eloop_q_timeout_add(struct eloop *eloop, int queue, + unsigned int seconds, unsigned int nseconds, + void (*callback)(void *), void *arg) { - struct timespec now, w; struct eloop_timeout *t, *tt = NULL; assert(eloop != NULL); - assert(when != NULL); assert(callback != NULL); - - clock_gettime(CLOCK_MONOTONIC, &now); - timespecadd(&now, when, &w); - /* Check for time_t overflow. */ - if (timespeccmp(&w, &now, <)) { - errno = ERANGE; - return -1; - } + assert(nseconds <= NSEC_PER_SEC); /* Remove existing timeout if present. */ TAILQ_FOREACH(t, &eloop->timeouts, next) { @@ -539,7 +471,10 @@ } } - t->when = w; + eloop_reduce_timers(eloop); + + t->seconds = seconds; + t->nseconds = nseconds; t->callback = callback; t->arg = arg; t->queue = queue; @@ -547,7 +482,9 @@ /* The timeout list should be in chronological order, * soonest first. */ TAILQ_FOREACH(tt, &eloop->timeouts, next) { - if (timespeccmp(&t->when, &tt->when, <)) { + if (t->seconds < tt->seconds || + (t->seconds == tt->seconds && t->nseconds < tt->nseconds)) + { TAILQ_INSERT_BEFORE(tt, t, next); return 0; } @@ -557,39 +494,48 @@ } int -eloop_q_timeout_add_sec(struct eloop *eloop, int queue, time_t when, - void (*callback)(void *), void *arg) +eloop_q_timeout_add_tv(struct eloop *eloop, int queue, + const struct timespec *when, void (*callback)(void *), void *arg) { - struct timespec tv; - tv.tv_sec = when; - tv.tv_nsec = 0; - return eloop_q_timeout_add_tv(eloop, queue, &tv, callback, arg); + if (when->tv_sec < 0 || (unsigned long)when->tv_sec > UINT_MAX) { + errno = EINVAL; + return -1; + } + if (when->tv_nsec < 0 || when->tv_nsec > NSEC_PER_SEC) { + errno = EINVAL; + return -1; + } + + return eloop_q_timeout_add(eloop, queue, + (unsigned int)when->tv_sec, (unsigned int)when->tv_sec, + callback, arg); } int -eloop_q_timeout_add_msec(struct eloop *eloop, int queue, long when, +eloop_q_timeout_add_sec(struct eloop *eloop, int queue, unsigned int seconds, void (*callback)(void *), void *arg) { - struct timespec tv; - tv.tv_sec = when / MSEC_PER_SEC; - tv.tv_nsec = (when % MSEC_PER_SEC) * NSEC_PER_MSEC; - return eloop_q_timeout_add_tv(eloop, queue, &tv, callback, arg); + return eloop_q_timeout_add(eloop, queue, seconds, 0, callback, arg); } -#if !defined(HAVE_KQUEUE) -static int -eloop_timeout_add_now(struct eloop *eloop, +int +eloop_q_timeout_add_msec(struct eloop *eloop, int queue, unsigned long when, void (*callback)(void *), void *arg) { + unsigned long seconds, nseconds; - assert(eloop->timeout0 == NULL); - eloop->timeout0 = callback; - eloop->timeout0_arg = arg; - return 0; + seconds = when / MSEC_PER_SEC; + if (seconds > UINT_MAX) { + errno = EINVAL; + return -1; + } + + nseconds = (when % MSEC_PER_SEC) * NSEC_PER_MSEC; + return eloop_q_timeout_add(eloop, queue, + (unsigned int)seconds, (unsigned int)nseconds, callback, arg); } -#endif int eloop_q_timeout_delete(struct eloop *eloop, int queue, @@ -624,106 +570,14 @@ eloop->exitnow = 1; } -#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) -static int -eloop_open(struct eloop *eloop) -{ - -#if defined(HAVE_KQUEUE1) - return (eloop->poll_fd = kqueue1(O_CLOEXEC)); -#elif defined(HAVE_KQUEUE) - int i; - - if ((eloop->poll_fd = kqueue()) == -1) - return -1; - if ((i = fcntl(eloop->poll_fd, F_GETFD, 0)) == -1 || - fcntl(eloop->poll_fd, F_SETFD, i | FD_CLOEXEC) == -1) - { - close(eloop->poll_fd); - eloop->poll_fd = -1; - } - - return eloop->poll_fd; -#elif defined (HAVE_EPOLL) - return (eloop->poll_fd = epoll_create1(EPOLL_CLOEXEC)); -#else - return (eloop->poll_fd = -1); -#endif -} -#endif - -int -eloop_requeue(struct eloop *eloop) +void +eloop_enter(struct eloop *eloop) { -#if defined(HAVE_POLL) - UNUSED(eloop); - return 0; -#else /* !HAVE_POLL */ - struct eloop_event *e; - int error; -#if defined(HAVE_KQUEUE) - size_t i; - struct kevent *ke; -#elif defined(HAVE_EPOLL) - struct epoll_event epe; -#endif - - assert(eloop != NULL); - - if (eloop->poll_fd != -1) - close(eloop->poll_fd); - if (eloop_open(eloop) == -1) - return -1; -#if defined (HAVE_KQUEUE) - i = eloop->signals_len; - TAILQ_FOREACH(e, &eloop->events, next) { - i++; - if (e->write_cb) - i++; - } - - if ((ke = malloc(sizeof(*ke) * i)) == NULL) - return -1; - - for (i = 0; i < eloop->signals_len; i++) - EV_SET(&ke[i], (uintptr_t)eloop->signals[i], - EVFILT_SIGNAL, EV_ADD, 0, 0, UPTR(NULL)); - - TAILQ_FOREACH(e, &eloop->events, next) { - EV_SET(&ke[i], (uintptr_t)e->fd, EVFILT_READ, - EV_ADD, 0, 0, UPTR(e)); - i++; - if (e->write_cb) { - EV_SET(&ke[i], (uintptr_t)e->fd, EVFILT_WRITE, - EV_ADD, 0, 0, UPTR(e)); - i++; - } - } - - error = kevent(eloop->poll_fd, ke, LENC(i), NULL, 0, NULL); - free(ke); - -#elif defined(HAVE_EPOLL) - - error = 0; - TAILQ_FOREACH(e, &eloop->events, next) { - memset(&epe, 0, sizeof(epe)); - epe.data.fd = e->fd; - epe.events = EPOLLIN; - if (e->write_cb) - epe.events |= EPOLLOUT; - epe.data.ptr = e; - if (epoll_ctl(eloop->poll_fd, EPOLL_CTL_ADD, e->fd, &epe) == -1) - error = -1; - } -#endif - - return error; -#endif /* HAVE_POLL */ + eloop->exitnow = 0; } -int +void eloop_signal_set_cb(struct eloop *eloop, const int *signals, size_t signals_len, void (*signal_cb)(int, void *), void *signal_cb_ctx) @@ -735,47 +589,35 @@ eloop->signals_len = signals_len; eloop->signal_cb = signal_cb; eloop->signal_cb_ctx = signal_cb_ctx; - return eloop_requeue(eloop); } -#ifndef HAVE_KQUEUE -struct eloop_siginfo { - int sig; - struct eloop *eloop; -}; -static struct eloop_siginfo _eloop_siginfo; -static struct eloop *_eloop; - -static void -eloop_signal1(void *arg) -{ - struct eloop_siginfo *si = arg; - - si->eloop->signal_cb(si->sig, si->eloop->signal_cb_ctx); -} +static volatile int _eloop_sig[ELOOP_NSIGNALS]; +static volatile size_t _eloop_nsig; static void eloop_signal3(int sig, __unused siginfo_t *siginfo, __unused void *arg) { - /* So that we can operate safely under a signal we instruct - * eloop to pass a copy of the siginfo structure to handle_signal1 - * as the very first thing to do. */ - _eloop_siginfo.eloop = _eloop; - _eloop_siginfo.sig = sig; - eloop_timeout_add_now(_eloop_siginfo.eloop, - eloop_signal1, &_eloop_siginfo); -} + if (_eloop_nsig == __arraycount(_eloop_sig)) { +#ifdef ELOOP_DEBUG + fprintf(stderr, "%s: signal storm, discarding signal %d\n", + __func__, sig); #endif + return; + } + + _eloop_sig[_eloop_nsig++] = sig; +} int eloop_signal_mask(struct eloop *eloop, sigset_t *oldset) { sigset_t newset; size_t i; -#ifndef HAVE_KQUEUE - struct sigaction sa; -#endif + struct sigaction sa = { + .sa_sigaction = eloop_signal3, + .sa_flags = SA_SIGINFO, + }; assert(eloop != NULL); @@ -785,19 +627,12 @@ if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1) return -1; -#ifndef HAVE_KQUEUE - _eloop = eloop; - - memset(&sa, 0, sizeof(sa)); - sa.sa_sigaction = eloop_signal3; - sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); for (i = 0; i < eloop->signals_len; i++) { if (sigaction(eloop->signals[i], &sa, NULL) == -1) return -1; } -#endif return 0; } @@ -805,32 +640,28 @@ eloop_new(void) { struct eloop *eloop; - struct timespec now; - /* Check we have a working monotonic clock. */ - if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) + eloop = calloc(1, sizeof(*eloop)); + if (eloop == NULL) return NULL; - eloop = calloc(1, sizeof(*eloop)); - if (eloop) { - TAILQ_INIT(&eloop->events); - eloop->events_maxfd = -1; - TAILQ_INIT(&eloop->free_events); - TAILQ_INIT(&eloop->timeouts); - TAILQ_INIT(&eloop->free_timeouts); - eloop->exitcode = EXIT_FAILURE; -#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) - if (eloop_open(eloop) == -1) { - eloop_free(eloop); - return NULL; - } -#endif + /* Check we have a working monotonic clock. */ + if (clock_gettime(CLOCK_MONOTONIC, &eloop->now) == -1) { + free(eloop); + return NULL; } + TAILQ_INIT(&eloop->events); + TAILQ_INIT(&eloop->free_events); + TAILQ_INIT(&eloop->timeouts); + TAILQ_INIT(&eloop->free_timeouts); + eloop->exitcode = EXIT_FAILURE; + return eloop; } -void eloop_free(struct eloop *eloop) +void +eloop_clear(struct eloop *eloop) { struct eloop_event *e; struct eloop_timeout *t; @@ -838,7 +669,10 @@ if (eloop == NULL) return; - free(eloop->event_fds); + eloop->nevents = 0; + eloop->signals = NULL; + eloop->signals_len = 0; + while ((e = TAILQ_FIRST(&eloop->events))) { TAILQ_REMOVE(&eloop->events, e, next); free(e); @@ -855,11 +689,17 @@ TAILQ_REMOVE(&eloop->free_timeouts, t, next); free(t); } -#if defined(HAVE_KQUEUE) || defined(HAVE_EPOLL) - close(eloop->poll_fd); -#elif defined(HAVE_POLL) + free(eloop->fds); -#endif + eloop->fds = NULL; + eloop->nfds = 0; +} + +void +eloop_free(struct eloop *eloop) +{ + + eloop_clear(eloop); free(eloop); } @@ -869,139 +709,78 @@ int n; struct eloop_event *e; struct eloop_timeout *t; - struct timespec now, ts, *tsp; - void (*t0)(void *); -#if defined(HAVE_KQUEUE) - struct kevent ke; - UNUSED(signals); -#elif defined(HAVE_EPOLL) - struct epoll_event epe; -#endif -#ifndef HAVE_KQUEUE - int timeout; -#endif + struct timespec ts, *tsp; assert(eloop != NULL); - eloop->exitnow = 0; for (;;) { if (eloop->exitnow) break; - /* Run all timeouts first. */ - if (eloop->timeout0) { - t0 = eloop->timeout0; - eloop->timeout0 = NULL; - t0(eloop->timeout0_arg); + if (_eloop_nsig != 0) { + n = _eloop_sig[--_eloop_nsig]; + if (eloop->signal_cb != NULL) + eloop->signal_cb(n, eloop->signal_cb_ctx); continue; } - if ((t = TAILQ_FIRST(&eloop->timeouts))) { - clock_gettime(CLOCK_MONOTONIC, &now); - if (timespeccmp(&now, &t->when, >)) { - TAILQ_REMOVE(&eloop->timeouts, t, next); - t->callback(t->arg); - TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); - continue; + + t = TAILQ_FIRST(&eloop->timeouts); + if (t == NULL && eloop->nevents == 0) + break; + + if (t != NULL) + eloop_reduce_timers(eloop); + + if (t != NULL && t->seconds == 0 && t->nseconds == 0) { + TAILQ_REMOVE(&eloop->timeouts, t, next); + t->callback(t->arg); + TAILQ_INSERT_TAIL(&eloop->free_timeouts, t, next); + continue; + } + + if (t != NULL) { + if (t->seconds > INT_MAX) { + ts.tv_sec = (time_t)INT_MAX; + ts.tv_nsec = 0; + } else { + ts.tv_sec = (time_t)t->seconds; + ts.tv_nsec = (long)t->nseconds; } - timespecsub(&t->when, &now, &ts); tsp = &ts; } else - /* No timeouts, so wait forever. */ tsp = NULL; - if (tsp == NULL && eloop->events_len == 0) - break; - -#ifndef HAVE_KQUEUE - if (tsp == NULL) - timeout = -1; - else if (tsp->tv_sec > INT_MAX / 1000 || - (tsp->tv_sec == INT_MAX / 1000 && - (tsp->tv_nsec + 999999) / 1000000 > INT_MAX % 1000000)) - timeout = INT_MAX; - else - timeout = (int)(tsp->tv_sec * 1000 + - (tsp->tv_nsec + 999999) / 1000000); -#endif + if (eloop->events_need_setup) + eloop_event_setup_fds(eloop); -#if defined(HAVE_KQUEUE) - n = kevent(eloop->poll_fd, NULL, 0, &ke, 1, tsp); -#elif defined(HAVE_EPOLL) - if (signals) - n = epoll_pwait(eloop->poll_fd, &epe, 1, - timeout, signals); - else - n = epoll_wait(eloop->poll_fd, &epe, 1, timeout); -#elif defined(HAVE_POLL) - if (signals) - n = POLLTS(eloop->fds, (nfds_t)eloop->events_len, - tsp, signals); - else - n = poll(eloop->fds, (nfds_t)eloop->events_len, - timeout); -#endif + n = ppoll(eloop->fds, (nfds_t)eloop->nevents, tsp, signals); if (n == -1) { if (errno == EINTR) continue; return -errno; } + if (n == 0) + continue; - /* Process any triggered events. - * We go back to the start after calling each callback incase - * the current event or next event is removed. */ -#if defined(HAVE_KQUEUE) - if (n) { - if (ke.filter == EVFILT_SIGNAL) { - eloop->signal_cb((int)ke.ident, - eloop->signal_cb_ctx); - continue; - } - e = (struct eloop_event *)ke.udata; - if (ke.filter == EVFILT_WRITE) { - e->write_cb(e->write_cb_arg); - continue; - } else if (ke.filter == EVFILT_READ) { - e->read_cb(e->read_cb_arg); + TAILQ_FOREACH(e, &eloop->events, next) { + /* Skip freshly added events */ + if (e->pollfd == NULL) continue; + if (e->pollfd->revents) + n--; + if (e->fd != -1 && e->pollfd->revents & POLLOUT) { + if (e->write_cb != NULL) + e->write_cb(e->write_cb_arg); } - } -#elif defined(HAVE_EPOLL) - if (n) { - e = (struct eloop_event *)epe.data.ptr; - if (epe.events & EPOLLOUT && e->write_cb != NULL) { - e->write_cb(e->write_cb_arg); - continue; - } - if (epe.events & - (EPOLLIN | EPOLLERR | EPOLLHUP) && - e->read_cb != NULL) + if (e->fd != -1 && + e->pollfd != NULL && e->pollfd->revents) { - e->read_cb(e->read_cb_arg); - continue; + if (e->read_cb != NULL) + e->read_cb(e->read_cb_arg); } + if (n == 0) + break; } -#elif defined(HAVE_POLL) - if (n > 0) { - size_t i; - - for (i = 0; i < eloop->events_len; i++) { - if (eloop->fds[i].revents & POLLOUT) { - e = eloop->event_fds[eloop->fds[i].fd]; - if (e->write_cb != NULL) { - e->write_cb(e->write_cb_arg); - break; - } - } - if (eloop->fds[i].revents) { - e = eloop->event_fds[eloop->fds[i].fd]; - if (e->read_cb != NULL) { - e->read_cb(e->read_cb_arg); - break; - } - } - } - } -#endif } return eloop->exitcode; Index: contrib/dhcpcd/src/if-bsd.c =================================================================== --- contrib/dhcpcd/src/if-bsd.c +++ contrib/dhcpcd/src/if-bsd.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * BSD interface driver for dhcpcd - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -58,8 +58,6 @@ #endif #ifdef __DragonFly__ # include -#elif __APPLE__ - /* FIXME: Add apple includes so we can work out SSID */ #else # include # include @@ -92,6 +90,7 @@ #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" +#include "privsep.h" #include "route.h" #include "sa.h" @@ -101,19 +100,18 @@ #define RT_ADVANCE(x, n) (x += RT_ROUNDUP((n)->sa_len)) #endif -/* Ignore these interface names which look like ethernet but are virtual. */ +/* Ignore these interface names which look like ethernet but are virtual or + * just won't work without explicit configuration. */ static const char * const ifnames_ignore[] = { "bridge", "fwe", /* Firewire */ + "fwip", /* Firewire */ "tap", + "vether", + "xvif", /* XEN DOM0 -> guest interface */ NULL }; -#ifdef INET6 -static void ifa_setscope(struct sockaddr_in6 *, unsigned int); -static unsigned int ifa_getscope(const struct sockaddr_in6 *); -#endif - struct priv { int pf_inet6_fd; }; @@ -124,6 +122,12 @@ char buffer[sizeof(struct sockaddr_storage) * RTAX_MAX]; }; +int +os_init(void) +{ + return 0; +} + int if_init(__unused struct interface *iface) { @@ -166,6 +170,10 @@ #ifdef INET6 priv->pf_inet6_fd = xsocket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); +#ifdef PRIVSEP_RIGHTS + if (IN_PRIVSEP(ctx)) + ps_rights_limit_ioctl(priv->pf_inet6_fd); +#endif /* Don't return an error so we at least work on kernels witout INET6 * even though we expect INET6 support. * We will fail noisily elsewhere anyway. */ @@ -173,12 +181,16 @@ priv->pf_inet6_fd = -1; #endif -#define SOCK_FLAGS (SOCK_CLOEXEC | SOCK_NONBLOCK) - ctx->link_fd = xsocket(PF_ROUTE, SOCK_RAW | SOCK_FLAGS, AF_UNSPEC); -#undef SOCK_FLAGS + ctx->link_fd = xsocket(PF_ROUTE, SOCK_RAW | SOCK_CXNB, AF_UNSPEC); if (ctx->link_fd == -1) return -1; +#ifdef SO_RERROR + n = 1; + if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RERROR, &n,sizeof(n)) == -1) + logerr("%s: SO_RERROR", __func__); +#endif + /* Ignore our own route(4) messages. * Sadly there is no way of doing this for route(4) messages * generated from addresses we add/delete. */ @@ -203,6 +215,13 @@ #warning kernel does not support route message filtering #endif +#ifdef PRIVSEP_RIGHTS + /* We need to getsockopt for SO_RCVBUF and + * setsockopt for RO_MISSFILTER. */ + if (IN_PRIVSEP(ctx)) + ps_rights_limit_fd_sockopt(ctx->link_fd); +#endif + return 0; } @@ -214,6 +233,70 @@ priv = (struct priv *)ctx->priv; if (priv->pf_inet6_fd != -1) close(priv->pf_inet6_fd); + free(priv); + ctx->priv = NULL; + free(ctx->rt_missfilter); +} + +#if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */ +static int +if_ioctllink(struct dhcpcd_ctx *ctx, unsigned long req, void *data, size_t len) +{ + int s; + int retval; + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) + return (int)ps_root_ioctllink(ctx, req, data, len); +#else + UNUSED(ctx); +#endif + + s = socket(PF_LINK, SOCK_DGRAM, 0); + if (s == -1) + return -1; + retval = ioctl(s, req, data, len); + close(s); + return retval; +} +#endif + +int +if_setmac(struct interface *ifp, void *mac, uint8_t maclen) +{ + + if (ifp->hwlen != maclen) { + errno = EINVAL; + return -1; + } + +#if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */ + struct if_laddrreq iflr = { .flags = IFLR_ACTIVE }; + struct sockaddr_dl *sdl = satosdl(&iflr.addr); + int retval; + + strlcpy(iflr.iflr_name, ifp->name, sizeof(iflr.iflr_name)); + sdl->sdl_family = AF_LINK; + sdl->sdl_len = sizeof(*sdl); + sdl->sdl_alen = maclen; + memcpy(LLADDR(sdl), mac, maclen); + retval = if_ioctllink(ifp->ctx, SIOCALIFADDR, &iflr, sizeof(iflr)); + + /* Try and remove the old address */ + memcpy(LLADDR(sdl), ifp->hwaddr, ifp->hwlen); + if_ioctllink(ifp->ctx, SIOCDLIFADDR, &iflr, sizeof(iflr)); + + return retval; +#else + struct ifreq ifr = { + .ifr_addr.sa_family = AF_LINK, + .ifr_addr.sa_len = maclen, + }; + + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + memcpy(ifr.ifr_addr.sa_data, mac, maclen); + return if_ioctl(ifp->ctx, SIOCSIFLLADDR, &ifr, sizeof(ifr)); +#endif } static bool @@ -228,18 +311,10 @@ return false; } -bool -if_ignore(struct dhcpcd_ctx *ctx, const char *ifname) -{ - struct if_spec spec; - - if (if_nametospec(ifname, &spec) != 0) - return false; - - if (if_ignore1(spec.drvname)) - return true; - #ifdef SIOCGIFGROUP +int +if_ignoregroup(int s, const char *ifname) +{ struct ifgroupreq ifgr = { .ifgr_len = 0 }; struct ifg_req *ifg; size_t ifg_len; @@ -249,12 +324,12 @@ * will be very unlikely.... */ strlcpy(ifgr.ifgr_name, ifname, sizeof(ifgr.ifgr_name)); - if (ioctl(ctx->pf_inet_fd, SIOCGIFGROUP, &ifgr) == -1 || + if (ioctl(s, SIOCGIFGROUP, &ifgr) == -1 || (ifgr.ifgr_groups = malloc(ifgr.ifgr_len)) == NULL || - ioctl(ctx->pf_inet_fd, SIOCGIFGROUP, &ifgr) == -1) + ioctl(s, SIOCGIFGROUP, &ifgr) == -1) { logerr(__func__); - return false; + return -1; } for (ifg = ifgr.ifgr_groups, ifg_len = ifgr.ifgr_len; @@ -262,26 +337,93 @@ ifg++, ifg_len -= sizeof(*ifg)) { if (if_ignore1(ifg->ifgrq_group)) - return true; + return 1; } + return 0; +} +#endif + +bool +if_ignore(struct dhcpcd_ctx *ctx, const char *ifname) +{ + struct if_spec spec; + + if (if_nametospec(ifname, &spec) != 0) + return false; + + if (if_ignore1(spec.drvname)) + return true; + +#ifdef SIOCGIFGROUP +#if defined(PRIVSEP) && defined(HAVE_PLEDGE) + if (IN_PRIVSEP(ctx)) + return ps_root_ifignoregroup(ctx, ifname) == 1 ? true : false; +#endif + else + return if_ignoregroup(ctx->pf_inet_fd, ifname) == 1 ? + true : false; #else UNUSED(ctx); + return false; #endif +} - return false; +static int if_indirect_ioctl(struct dhcpcd_ctx *ctx, + const char *ifname, unsigned long cmd, void *data, size_t len) +{ + struct ifreq ifr = { .ifr_flags = 0 }; + +#if defined(PRIVSEP) && (defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE)) + if (IN_PRIVSEP(ctx)) + return (int)ps_root_indirectioctl(ctx, cmd, ifname, data, len); +#else + UNUSED(len); +#endif + + strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_data = data; + return ioctl(ctx->pf_inet_fd, cmd, &ifr); } int -if_carrier(struct interface *ifp) +if_carrier(struct interface *ifp, const void *ifadata) { - struct ifmediareq ifmr = { .ifm_status = 0 }; + const struct if_data *ifi = ifadata; + + /* + * Every BSD returns this and it is the sole source of truth. + * Not all BSD's support SIOCGIFDATA and not all interfaces + * support SIOCGIFMEDIA. + */ + assert(ifadata != NULL); - strlcpy(ifmr.ifm_name, ifp->name, sizeof(ifmr.ifm_name)); - if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFMEDIA, &ifmr) == -1 || - !(ifmr.ifm_status & IFM_AVALID)) + if (ifi->ifi_link_state >= LINK_STATE_UP) + return LINK_UP; + if (ifi->ifi_link_state == LINK_STATE_UNKNOWN) { + /* + * Work around net80211 issues in some BSDs. + * Wireless MUST support link state change. + */ + if (ifp->wireless) + return LINK_DOWN; return LINK_UNKNOWN; + } + return LINK_DOWN; +} + +bool +if_roaming(struct interface *ifp) +{ - return (ifmr.ifm_status & IFM_ACTIVE) ? LINK_UP : LINK_DOWN; +/* Check for NetBSD as a safety measure. + * If other BSD's gain IN_IFF_TENTATIVE check they re-do DAD + * when the carrier comes up again. */ +#if defined(IN_IFF_TENTATIVE) && defined(__NetBSD__) + return ifp->flags & IFF_UP && ifp->carrier == LINK_DOWN; +#else + UNUSED(ifp); + return false; +#endif } static void @@ -295,28 +437,8 @@ sdl->sdl_index = (unsigned short)ifp->index; } -#if defined(SIOCG80211NWID) || defined(SIOCGETVLAN) -static int if_direct_ioctl(int s, const char *ifname, - unsigned long cmd, void *data) -{ - - strlcpy(data, ifname, IFNAMSIZ); - return ioctl(s, cmd, data); -} - -static int if_indirect_ioctl(int s, const char *ifname, - unsigned long cmd, void *data) -{ - struct ifreq ifr; - - memset(&ifr, 0, sizeof(ifr)); - ifr.ifr_data = data; - return if_direct_ioctl(s, ifname, cmd, &ifr); -} -#endif - static int -if_getssid1(int s, const char *ifname, void *ssid) +if_getssid1(struct dhcpcd_ctx *ctx, const char *ifname, void *ssid) { int retval = -1; #if defined(SIOCG80211NWID) @@ -328,7 +450,9 @@ #if defined(SIOCG80211NWID) /* NetBSD */ memset(&nwid, 0, sizeof(nwid)); - if (if_indirect_ioctl(s, ifname, SIOCG80211NWID, &nwid) == 0) { + if (if_indirect_ioctl(ctx, ifname, SIOCG80211NWID, + &nwid, sizeof(nwid)) == 0) + { if (ssid == NULL) retval = nwid.i_len; else if (nwid.i_len > IF_SSIDLEN) @@ -345,7 +469,7 @@ ireq.i_val = -1; memset(nwid, 0, sizeof(nwid)); ireq.i_data = &nwid; - if (ioctl(s, SIOCG80211, &ireq) == 0) { + if (ioctl(ctx->pf_inet_fd, SIOCG80211, &ireq) == 0) { if (ssid == NULL) retval = ireq.i_len; else if (ireq.i_len > IF_SSIDLEN) @@ -367,7 +491,7 @@ { int r; - r = if_getssid1(ifp->ctx->pf_inet_fd, ifp->name, ifp->ssid); + r = if_getssid1(ifp->ctx, ifp->name, ifp->ssid); if (r != -1) ifp->ssid_len = (unsigned int)r; else @@ -384,12 +508,11 @@ * returning the SSID gives an error. */ int -if_vimaster(const struct dhcpcd_ctx *ctx, const char *ifname) +if_vimaster(struct dhcpcd_ctx *ctx, const char *ifname) { int r; - struct ifmediareq ifmr; + struct ifmediareq ifmr = { .ifm_active = 0 }; - memset(&ifmr, 0, sizeof(ifmr)); strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name)); r = ioctl(ctx->pf_inet_fd, SIOCGIFMEDIA, &ifmr); if (r == -1) @@ -397,7 +520,7 @@ if (ifmr.ifm_status & IFM_AVALID && IFM_TYPE(ifmr.ifm_active) == IFM_IEEE80211) { - if (if_getssid1(ctx->pf_inet_fd, ifname, NULL) == -1) + if (if_getssid1(ctx, ifname, NULL) == -1) return 1; } return 0; @@ -407,17 +530,15 @@ if_vlanid(const struct interface *ifp) { #ifdef SIOCGETVLAN - struct vlanreq vlr; + struct vlanreq vlr = { .vlr_tag = 0 }; - memset(&vlr, 0, sizeof(vlr)); - if (if_indirect_ioctl(ifp->ctx->pf_inet_fd, - ifp->name, SIOCGETVLAN, &vlr) != 0) + if (if_indirect_ioctl(ifp->ctx, ifp->name, SIOCGETVLAN, + &vlr, sizeof(vlr)) != 0) return 0; /* 0 means no VLANID */ return vlr.vlr_tag; #elif defined(SIOCGVNETID) - struct ifreq ifr; + struct ifreq ifr = { .ifr_vnetid = 0 }; - memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); if (ioctl(ifp->ctx->pf_inet_fd, SIOCGVNETID, &ifr) != 0) return 0; /* 0 means no VLANID */ @@ -506,6 +627,8 @@ sin = (const void *)sa; if ((ia = ipv4_findmaskaddr(ctx, &sin->sin_addr))) return ia->iface; + if ((ia = ipv4_findmaskbrd(ctx, &sin->sin_addr))) + return ia->iface; break; } #endif @@ -517,7 +640,7 @@ struct ipv6_addr *ia; sin = (const void *)sa; - scope = ifa_getscope(sin); + scope = ipv6_getscope(sin); if (scope != 0) return if_findindex(ctx->ifaces, scope); if ((ia = ipv6_findmaskaddr(ctx, &sin->sin6_addr))) @@ -647,9 +770,7 @@ } else rtm->rtm_flags |= RTF_GATEWAY; - /* Emulate the kernel by marking address generated - * network routes non-static. */ - if (!(rt->rt_dflags & RTDF_IFA_ROUTE)) + if (rt->rt_dflags & RTDF_STATIC) rtm->rtm_flags |= RTF_STATIC; if (rt->rt_mtu != 0) { @@ -674,7 +795,7 @@ if_copysa(&gateway.sa, &rt->rt_gateway); #ifdef INET6 if (gateway.sa.sa_family == AF_INET6) - ifa_setscope(&gateway.sin6, rt->rt_ifp->index); + ipv6_setscope(&gateway.sin6, rt->rt_ifp->index); #endif ADDSA(&gateway.sa); } @@ -692,11 +813,42 @@ #undef ADDSA rtm->rtm_msglen = (unsigned short)(bp - (char *)rtm); + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) { + if (ps_root_route(ctx, rtm, rtm->rtm_msglen) == -1) + return -1; + return 0; + } +#endif if (write(ctx->link_fd, rtm, rtm->rtm_msglen) == -1) return -1; return 0; } +static bool +if_realroute(const struct rt_msghdr *rtm) +{ + +#ifdef RTF_CLONED + if (rtm->rtm_flags & RTF_CLONED) + return false; +#endif +#ifdef RTF_WASCLONED + if (rtm->rtm_flags & RTF_WASCLONED) + return false; +#endif +#ifdef RTF_LOCAL + if (rtm->rtm_flags & RTF_LOCAL) + return false; +#endif +#ifdef RTF_BROADCAST + if (rtm->rtm_flags & RTF_BROADCAST) + return false; +#endif + return true; +} + static int if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, const struct rt_msghdr *rtm) { @@ -710,30 +862,6 @@ errno = EINVAL; return -1; } -#ifdef RTF_CLONED - if (rtm->rtm_flags & RTF_CLONED) { - errno = ENOTSUP; - return -1; - } -#endif -#ifdef RTF_WASCLONED - if (rtm->rtm_flags & RTF_WASCLONED) { - errno = ENOTSUP; - return -1; - } -#endif -#ifdef RTF_LOCAL - if (rtm->rtm_flags & RTF_LOCAL) { - errno = ENOTSUP; - return -1; - } -#endif -#ifdef RTF_BROADCAST - if (rtm->rtm_flags & RTF_BROADCAST) { - errno = ENOTSUP; - return -1; - } -#endif if (get_addrs(rtm->rtm_addrs, (const char *)rtm + sizeof(*rtm), rtm->rtm_msglen - sizeof(*rtm), rti_info) == -1) @@ -820,6 +948,8 @@ errno = EINVAL; break; } + if (!if_realroute(rtm)) + continue; if (if_copyrt(ctx, &rt, rtm) != 0) continue; if ((rtn = rt_new(rt.rt_ifp)) == NULL) { @@ -840,6 +970,7 @@ { int r; struct in_aliasreq ifra; + struct dhcpcd_ctx *ctx = ia->iface->ctx; memset(&ifra, 0, sizeof(ifra)); strlcpy(ifra.ifra_name, ia->iface->name, sizeof(ifra.ifra_name)); @@ -855,13 +986,11 @@ ADDADDR(&ifra.ifra_broadaddr, &ia->brd); #undef ADDADDR - r = ioctl(ia->iface->ctx->pf_inet_fd, - cmd == RTM_DELADDR ? SIOCDIFADDR : SIOCAIFADDR, &ifra); + r = if_ioctl(ctx, + cmd == RTM_DELADDR ? SIOCDIFADDR : SIOCAIFADDR, &ifra,sizeof(ifra)); return r; } - - #if !(defined(HAVE_IFADDRS_ADDRFLAGS) && defined(HAVE_IFAM_ADDRFLAGS)) int if_addrflags(const struct interface *ifp, const struct in_addr *addr, @@ -889,72 +1018,41 @@ #endif /* INET */ #ifdef INET6 -static void -ifa_setscope(struct sockaddr_in6 *sin, unsigned int ifindex) +static int +if_ioctl6(struct dhcpcd_ctx *ctx, unsigned long req, void *data, size_t len) { + struct priv *priv; -#ifdef __KAME__ - /* KAME based systems want to store the scope inside the sin6_addr - * for link local addresses */ - if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) { - uint16_t scope = htons((uint16_t)ifindex); - memcpy(&sin->sin6_addr.s6_addr[2], &scope, - sizeof(scope)); - } - sin->sin6_scope_id = 0; -#else - if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) - sin->sin6_scope_id = ifindex; - else - sin->sin6_scope_id = 0; -#endif -} - -static unsigned int -ifa_getscope(const struct sockaddr_in6 *sin) -{ -#ifdef __KAME__ - uint16_t scope; +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) + return (int)ps_root_ioctl6(ctx, req, data, len); #endif - if (!IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) - return 0; -#ifdef __KAME__ - memcpy(&scope, &sin->sin6_addr.s6_addr[2], sizeof(scope)); - return (unsigned int)ntohs(scope); -#else - return (unsigned int)sin->sin6_scope_id; -#endif + priv = ctx->priv; + return ioctl(priv->pf_inet6_fd, req, data, len); } int if_address6(unsigned char cmd, const struct ipv6_addr *ia) { - struct in6_aliasreq ifa; + struct in6_aliasreq ifa = { .ifra_flags = 0 }; struct in6_addr mask; - struct priv *priv; - - priv = (struct priv *)ia->iface->ctx->priv; + struct dhcpcd_ctx *ctx = ia->iface->ctx; - memset(&ifa, 0, sizeof(ifa)); strlcpy(ifa.ifra_name, ia->iface->name, sizeof(ifa.ifra_name)); - /* - * We should not set IN6_IFF_TENTATIVE as the kernel should be - * able to work out if it's a new address or not. - * - * We should set IN6_IFF_AUTOCONF, but the kernel won't let us. - * This is probably a safety measure, but still it's not entirely right - * either. - */ -#if 0 - if (ia->autoconf) - ifa.ifra_flags |= IN6_IFF_AUTOCONF; -#endif #if defined(__FreeBSD__) || defined(__DragonFly__) + /* This is a bug - the kernel should work this out. */ if (ia->addr_flags & IN6_IFF_TENTATIVE) ifa.ifra_flags |= IN6_IFF_TENTATIVE; #endif -#ifdef IPV6_MANGETEMPADDR +#if (defined(__NetBSD__) || defined(__OpenBSD__)) && \ + (defined(IPV6CTL_ACCEPT_RTADV) || defined(ND6_IFF_ACCEPT_RTADV)) + /* These kernels don't accept userland setting IN6_IFF_AUTOCONF */ +#else + if (ia->flags & IPV6_AF_AUTOCONF) + ifa.ifra_flags |= IN6_IFF_AUTOCONF; +#endif +#ifdef IPV6_MANAGETEMPADDR if (ia->flags & IPV6_AF_TEMPORARY) ifa.ifra_flags |= IN6_IFF_TEMPORARY; #endif @@ -966,7 +1064,7 @@ } ADDADDR(&ifa.ifra_addr, &ia->addr); - ifa_setscope(&ifa.ifra_addr, ia->iface->index); + ipv6_setscope(&ifa.ifra_addr, ia->iface->index); ipv6_mask(&mask, ia->prefix_len); ADDADDR(&ifa.ifra_prefixmask, &mask); @@ -1006,7 +1104,7 @@ if (cmd == RTM_NEWADDR && !(ia->flags & IPV6_AF_ADDED)) { ifa.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; ifa.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; - (void)ioctl(priv->pf_inet6_fd, SIOCAIFADDR_IN6, &ifa); + (void)if_ioctl6(ctx, SIOCAIFADDR_IN6, &ifa, sizeof(ifa)); } #endif @@ -1027,8 +1125,9 @@ ifa.ifra_lifetime.ia6t_pltime = ia->prefix_pltime; #endif - return ioctl(priv->pf_inet6_fd, - cmd == RTM_DELADDR ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6, &ifa); + return if_ioctl6(ctx, + cmd == RTM_DELADDR ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6, + &ifa, sizeof(ifa)); } int @@ -1043,7 +1142,7 @@ strlcpy(ifr6.ifr_name, ifp->name, sizeof(ifr6.ifr_name)); ifr6.ifr_addr.sin6_family = AF_INET6; ifr6.ifr_addr.sin6_addr = *addr; - ifa_setscope(&ifr6.ifr_addr, ifp->index); + ipv6_setscope(&ifr6.ifr_addr, ifp->index); priv = (struct priv *)ifp->ctx->priv; if (ioctl(priv->pf_inet6_fd, SIOCGIFAFLAG_IN6, &ifr6) != -1) flags = ifr6.ifr_ifru.ifru_flags6; @@ -1064,7 +1163,7 @@ strlcpy(ifr6.ifr_name, ia->iface->name, sizeof(ifr6.ifr_name)); ifr6.ifr_addr.sin6_family = AF_INET6; ifr6.ifr_addr.sin6_addr = ia->addr; - ifa_setscope(&ifr6.ifr_addr, ia->iface->index); + ipv6_setscope(&ifr6.ifr_addr, ia->iface->index); priv = (struct priv *)ia->iface->ctx->priv; if (ioctl(priv->pf_inet6_fd, SIOCGIFALIFETIME_IN6, &ifr6) == -1) return -1; @@ -1126,20 +1225,8 @@ if ((ifp = if_findindex(ctx->ifaces, ifm->ifm_index)) == NULL) return 0; - switch (ifm->ifm_data.ifi_link_state) { - case LINK_STATE_UNKNOWN: - link_state = LINK_UNKNOWN; - break; - case LINK_STATE_UP: - link_state = LINK_UP; - break; - default: - link_state = LINK_DOWN; - break; - } - - dhcpcd_handlecarrier(ctx, link_state, - (unsigned int)ifm->ifm_flags, ifp->name); + link_state = if_carrier(ifp, &ifm->ifm_data); + dhcpcd_handlecarrier(ifp, link_state, (unsigned int)ifm->ifm_flags); return 0; } @@ -1157,6 +1244,14 @@ if (rtm->rtm_errno != 0) return 0; + /* Ignore messages from ourself. */ +#ifdef PRIVSEP + if (ctx->ps_root_pid != 0) { + if (rtm->rtm_pid == ctx->ps_root_pid) + return 0; + } +#endif + if (if_copyrt(ctx, &rt, rtm) == -1) return errno == ENOTSUP ? 0 : -1; @@ -1180,7 +1275,7 @@ } #endif - if (rtm->rtm_type != RTM_MISS) + if (rtm->rtm_type != RTM_MISS && if_realroute(rtm)) rt_recvrt(rtm->rtm_type, &rt, rtm->rtm_pid); return 0; } @@ -1190,13 +1285,35 @@ { struct interface *ifp; const struct sockaddr *rti_info[RTAX_MAX]; - int addrflags; + int flags; pid_t pid; if (ifam->ifam_msglen < sizeof(*ifam)) { errno = EINVAL; return -1; } + +#ifdef HAVE_IFAM_PID + /* Ignore address deletions from ourself. + * We need to process address flag changes though. */ + if (ifam->ifam_type == RTM_DELADDR) { +#ifdef PRIVSEP + if (ctx->ps_root_pid != 0) { + if (ifam->ifam_pid == ctx->ps_root_pid) + return 0; + } else +#endif + /* address management is done via ioctl, + * so SO_USELOOPBACK has no effect, + * so we do need to check the pid. */ + if (ifam->ifam_pid == getpid()) + return 0; + } + pid = ifam->ifam_pid; +#else + pid = 0; +#endif + if (~ifam->ifam_addrs & RTA_IFA) return 0; if ((ifp = if_findindex(ctx->ifaces, ifam->ifam_index)) == NULL) @@ -1206,15 +1323,6 @@ ifam->ifam_msglen - sizeof(*ifam), rti_info) == -1) return -1; -#ifdef HAVE_IFAM_PID - pid = ifam->ifam_pid; -#else - pid = 0; -#endif - -#ifdef HAVE_IFAM_ADDRFLAGS - addrflags = ifam->ifam_addrflags; -#endif switch (rti_info[RTAX_IFA]->sa_family) { case AF_LINK: { @@ -1228,7 +1336,8 @@ break; #endif memcpy(&sdl, rti_info[RTAX_IFA], rti_info[RTAX_IFA]->sa_len); - dhcpcd_handlehwaddr(ctx, ifp->name, CLLADDR(&sdl),sdl.sdl_alen); + dhcpcd_handlehwaddr(ifp, ifp->hwtype, + CLLADDR(&sdl), sdl.sdl_alen); break; } #ifdef INET @@ -1248,78 +1357,86 @@ bcast.s_addr = sin != NULL && sin->sin_family == AF_INET ? sin->sin_addr.s_addr : INADDR_ANY; -#if defined(__NetBSD_Version__) && __NetBSD_Version__ < 800000000 /* * NetBSD-7 and older send an invalid broadcast address. * So we need to query the actual address to get * the right one. + * We can also use this to test if the address + * has really been added or deleted. */ - { -#else - /* - * If the address was deleted, lets check if it's - * a late message and it still exists (maybe modified). - * If so, ignore it as deleting an address causes - * dhcpcd to drop any lease to which it belongs. - */ - if (ifam->ifam_type == RTM_DELADDR) { -#endif #ifdef SIOCGIFALIAS - struct in_aliasreq ifra; - - memset(&ifra, 0, sizeof(ifra)); - strlcpy(ifra.ifra_name, ifp->name, - sizeof(ifra.ifra_name)); - ifra.ifra_addr.sin_family = AF_INET; - ifra.ifra_addr.sin_len = sizeof(ifra.ifra_addr); - ifra.ifra_addr.sin_addr = addr; - if (ioctl(ctx->pf_inet_fd, SIOCGIFALIAS, &ifra) == -1) { - if (errno != ENXIO && errno != EADDRNOTAVAIL) - logerr("%s: SIOCGIFALIAS", __func__); - if (ifam->ifam_type != RTM_DELADDR) - break; - } + struct in_aliasreq ifra; + + memset(&ifra, 0, sizeof(ifra)); + strlcpy(ifra.ifra_name, ifp->name, sizeof(ifra.ifra_name)); + ifra.ifra_addr.sin_family = AF_INET; + ifra.ifra_addr.sin_len = sizeof(ifra.ifra_addr); + ifra.ifra_addr.sin_addr = addr; + if (ioctl(ctx->pf_inet_fd, SIOCGIFALIAS, &ifra) == -1) { + if (errno != ENXIO && errno != EADDRNOTAVAIL) + logerr("%s: SIOCGIFALIAS", __func__); + if (ifam->ifam_type != RTM_DELADDR) + break; + } else { + if (ifam->ifam_type == RTM_DELADDR) + break; #if defined(__NetBSD_Version__) && __NetBSD_Version__ < 800000000 - else - bcast = ifra.ifra_broadaddr.sin_addr; + bcast = ifra.ifra_broadaddr.sin_addr; #endif + } #else #warning No SIOCGIFALIAS support - /* - * No SIOCGIFALIAS? That sucks! - * This makes this call very heavy weight, but we - * really need to know if the message is late or not. - */ - const struct sockaddr *sa; - struct ifaddrs *ifaddrs = NULL, *ifa; - - sa = rti_info[RTAX_IFA]; - getifaddrs(&ifaddrs); - for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == NULL) - continue; - if (sa_cmp(ifa->ifa_addr, sa) == 0 && - strcmp(ifa->ifa_name, ifp->name) == 0) - break; + /* + * No SIOCGIFALIAS? That sucks! + * This makes this call very heavy weight, but we + * really need to know if the message is late or not. + */ + const struct sockaddr *sa; + struct ifaddrs *ifaddrs = NULL, *ifa; + + sa = rti_info[RTAX_IFA]; +#ifdef PRIVSEP_GETIFADDRS + if (IN_PRIVSEP(ctx)) { + if (ps_root_getifaddrs(ctx, &ifaddrs) == -1) { + logerr("ps_root_getifaddrs"); + break; } - freeifaddrs(ifaddrs); - if (ifa != NULL) - return 0; + } else #endif - } - -#ifndef HAVE_IFAM_ADDRFLAGS - if (ifam->ifam_type == RTM_DELADDR) - addrflags = 0 ; - else if ((addrflags = if_addrflags(ifp, &addr, NULL)) == -1) { - if (errno != EADDRNOTAVAIL) - logerr("%s: if_addrflags", __func__); + if (getifaddrs(&ifaddrs) == -1) { + logerr("getifaddrs"); break; } + for (ifa = ifaddrs; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if (sa_cmp(ifa->ifa_addr, sa) == 0 && + strcmp(ifa->ifa_name, ifp->name) == 0) + break; + } +#ifdef PRIVSEP_GETIFADDRS + if (IN_PRIVSEP(ctx)) + free(ifaddrs); + else +#endif + freeifaddrs(ifaddrs); + if (ifam->ifam_type == RTM_DELADDR) { + if (ifa != NULL) + break; + } else { + if (ifa == NULL) + break; + } +#endif + +#ifdef HAVE_IFAM_ADDRFLAGS + flags = ifam->ifam_addrflags; +#else + flags = 0; #endif ipv4_handleifa(ctx, ifam->ifam_type, NULL, ifp->name, - &addr, &mask, &bcast, addrflags, pid); + &addr, &mask, &bcast, flags, pid); break; } #endif @@ -1328,7 +1445,6 @@ { struct in6_addr addr6, mask6; const struct sockaddr_in6 *sin6; - int flags; sin6 = (const void *)rti_info[RTAX_IFA]; addr6 = sin6->sin6_addr; @@ -1340,20 +1456,17 @@ * a late message and it still exists (maybe modified). * If so, ignore it as deleting an address causes * dhcpcd to drop any lease to which it belongs. + * Also check an added address was really added. */ - if (ifam->ifam_type == RTM_DELADDR) { - flags = if_addrflags6(ifp, &addr6, NULL); - if (flags != -1) - break; - addrflags = 0; - } -#ifndef HAVE_IFAM_ADDRFLAGS - else if ((addrflags = if_addrflags6(ifp, &addr6, NULL)) == -1) { - if (errno != EADDRNOTAVAIL) + flags = if_addrflags6(ifp, &addr6, NULL); + if (flags == -1) { + if (errno != ENXIO && errno != EADDRNOTAVAIL) logerr("%s: if_addrflags6", __func__); + if (ifam->ifam_type != RTM_DELADDR) + break; + flags = 0; + } else if (ifam->ifam_type == RTM_DELADDR) break; - } -#endif #ifdef __KAME__ if (IN6_IS_ADDR_LINKLOCAL(&addr6)) @@ -1362,7 +1475,7 @@ #endif ipv6_handleifa(ctx, ifam->ifam_type, NULL, - ifp->name, &addr6, ipv6_prefixlen(&mask6), addrflags, pid); + ifp->name, &addr6, ipv6_prefixlen(&mask6), flags, pid); break; } #endif @@ -1407,6 +1520,75 @@ return 0; } +static int +if_missfilter0(struct dhcpcd_ctx *ctx, struct interface *ifp, + struct sockaddr *sa) +{ + size_t salen = (size_t)RT_ROUNDUP(sa->sa_len); + size_t newlen = ctx->rt_missfilterlen + salen; + size_t diff = salen - (sa->sa_len); + uint8_t *cp; + + if (ctx->rt_missfiltersize < newlen) { + void *n = realloc(ctx->rt_missfilter, newlen); + if (n == NULL) + return -1; + ctx->rt_missfilter = n; + ctx->rt_missfiltersize = newlen; + } + +#ifdef INET6 + if (sa->sa_family == AF_INET6) + ipv6_setscope(satosin6(sa), ifp->index); +#else + UNUSED(ifp); +#endif + + cp = ctx->rt_missfilter + ctx->rt_missfilterlen; + memcpy(cp, sa, sa->sa_len); + if (diff != 0) + memset(cp + sa->sa_len, 0, diff); + ctx->rt_missfilterlen += salen; + +#ifdef INET6 + if (sa->sa_family == AF_INET6) + ipv6_setscope(satosin6(sa), 0); +#endif + + return 0; +} + +int +if_missfilter(struct interface *ifp, struct sockaddr *sa) +{ + + return if_missfilter0(ifp->ctx, ifp, sa); +} + +int +if_missfilter_apply(struct dhcpcd_ctx *ctx) +{ +#ifdef RO_MISSFILTER + if (ctx->rt_missfilterlen == 0) { + struct sockaddr sa = { + .sa_family = AF_UNSPEC, + .sa_len = sizeof(sa), + }; + + if (if_missfilter0(ctx, NULL, &sa) == -1) + return -1; + } + + return setsockopt(ctx->link_fd, PF_ROUTE, RO_MISSFILTER, + ctx->rt_missfilter, (socklen_t)ctx->rt_missfilterlen); +#else +#warning kernel does not support RTM_MISS DST filtering + UNUSED(ctx); + errno = ENOTSUP; + return -1; +#endif +} + __CTASSERT(offsetof(struct rt_msghdr, rtm_msglen) == 0); int if_handlelink(struct dhcpcd_ctx *ctx) @@ -1438,7 +1620,7 @@ } #ifndef SYS_NMLN /* OSX */ -# define SYS_NMLN 256 +# define SYS_NMLN __SYS_NAMELEN #endif #ifndef HW_MACHINE_ARCH # ifdef HW_MODEL /* OpenBSD */ @@ -1449,18 +1631,12 @@ if_machinearch(char *str, size_t len) { int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; - char march[SYS_NMLN]; - size_t marchlen = sizeof(march); - if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), - march, &marchlen, NULL, 0) != 0) - return -1; - return snprintf(str, len, ":%s", march); + return sysctl(mib, sizeof(mib) / sizeof(mib[0]), str, &len, NULL, 0); } #ifdef INET6 #if (defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV)) || \ - defined(IPV6CTL_USETEMPADDR) || defined(IPV6CTL_TEMPVLTIME) || \ defined(IPV6CTL_FORWARDING) #define get_inet6_sysctl(code) inet6_sysctl(code, 0, 0) #define set_inet6_sysctl(code, val) inet6_sysctl(code, val, 1) @@ -1484,8 +1660,57 @@ } #endif -#ifdef IPV6_MANAGETEMPADDR -#ifndef IPV6CTL_TEMPVLTIME +int +if_applyra(const struct ra *rap) +{ +#ifdef SIOCSIFINFO_IN6 + struct in6_ndireq nd = { .ndi.chlim = 0 }; + struct dhcpcd_ctx *ctx = rap->iface->ctx; + int error; + + strlcpy(nd.ifname, rap->iface->name, sizeof(nd.ifname)); + +#ifdef IPV6CTL_ACCEPT_RTADV + struct priv *priv = ctx->priv; + + /* + * NetBSD changed SIOCSIFINFO_IN6 to NOT set flags when kernel + * RA was removed, however both FreeBSD and DragonFlyBSD still do. + * linkmtu was also removed. + * Hopefully this guard will still work if either remove kernel RA. + */ + if (ioctl(priv->pf_inet6_fd, SIOCGIFINFO_IN6, &nd, sizeof(nd)) == -1) + return -1; + + nd.ndi.linkmtu = rap->mtu; +#endif + + nd.ndi.chlim = rap->hoplimit; + nd.ndi.retrans = rap->retrans; + nd.ndi.basereachable = rap->reachable; + error = if_ioctl6(ctx, SIOCSIFINFO_IN6, &nd, sizeof(nd)); +#ifdef IPV6CTL_ACCEPT_RTADV + if (error == -1 && errno == EINVAL) { + /* + * Very likely that this is caused by a dodgy MTU + * setting specific to the interface. + * Let's set it to "unspecified" and try again. + * Doesn't really matter as we fix the MTU against the + * routes we add as not all OS support SIOCSIFINFO_IN6. + */ + nd.ndi.linkmtu = 0; + error = if_ioctl6(ctx, SIOCSIFINFO_IN6, &nd, sizeof(nd)); + } +#endif + return error; +#else +#warning OS does not allow setting of RA bits hoplimit, retrans or reachable + UNUSED(rap); + return 0; +#endif +} + +#ifndef IPV6CTL_FORWARDING #define get_inet6_sysctlbyname(code) inet6_sysctlbyname(code, 0, 0) #define set_inet6_sysctlbyname(code, val) inet6_sysctlbyname(code, val, 1) static int @@ -1505,46 +1730,6 @@ } #endif -int -ip6_use_tempaddr(__unused const char *ifname) -{ - int val; - -#ifdef IPV6CTL_USETEMPADDR - val = get_inet6_sysctl(IPV6CTL_USETEMPADDR); -#else - val = get_inet6_sysctlbyname("net.inet6.ip6.use_tempaddr"); -#endif - return val == -1 ? 0 : val; -} - -int -ip6_temp_preferred_lifetime(__unused const char *ifname) -{ - int val; - -#ifdef IPV6CTL_TEMPPLTIME - val = get_inet6_sysctl(IPV6CTL_TEMPPLTIME); -#else - val = get_inet6_sysctlbyname("net.inet6.ip6.temppltime"); -#endif - return val < 0 ? TEMP_PREFERRED_LIFETIME : val; -} - -int -ip6_temp_valid_lifetime(__unused const char *ifname) -{ - int val; - -#ifdef IPV6CTL_TEMPVLTIME - val = get_inet6_sysctl(IPV6CTL_TEMPVLTIME); -#else - val = get_inet6_sysctlbyname("net.inet6.ip6.tempvltime"); -#endif - return val < 0 ? TEMP_VALID_LIFETIME : val; -} -#endif - int ip6_forwarding(__unused const char *ifname) { @@ -1560,25 +1745,26 @@ #ifdef SIOCIFAFATTACH static int -af_attach(int s, const struct interface *ifp, int af) +if_af_attach(const struct interface *ifp, int af) { struct if_afreq ifar; strlcpy(ifar.ifar_name, ifp->name, sizeof(ifar.ifar_name)); ifar.ifar_af = af; - return ioctl(s, SIOCIFAFATTACH, (void *)&ifar); + return if_ioctl6(ifp->ctx, SIOCIFAFATTACH, &ifar, sizeof(ifar)); } #endif #ifdef SIOCGIFXFLAGS static int -set_ifxflags(int s, const struct interface *ifp) +if_set_ifxflags(const struct interface *ifp) { struct ifreq ifr; int flags; + struct priv *priv = ifp->ctx->priv; strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); - if (ioctl(s, SIOCGIFXFLAGS, (void *)&ifr) == -1) + if (ioctl(priv->pf_inet6_fd, SIOCGIFXFLAGS, &ifr) == -1) return -1; flags = ifr.ifr_flags; #ifdef IFXF_NOINET6 @@ -1605,7 +1791,7 @@ if (ifr.ifr_flags == flags) return 0; ifr.ifr_flags = flags; - return ioctl(s, SIOCSIFXFLAGS, (void *)&ifr); + return if_ioctl6(ifp->ctx, SIOCSIFXFLAGS, &ifr, sizeof(ifr)); } #endif @@ -1619,6 +1805,22 @@ #define ND6_NDI_FLAGS #endif +void +if_disable_rtadv(void) +{ +#if defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV) + int ra = get_inet6_sysctl(IPV6CTL_ACCEPT_RTADV); + + if (ra == -1) { + if (errno != ENOENT) + logerr("IPV6CTL_ACCEPT_RTADV"); + else if (ra != 0) + if (set_inet6_sysctl(IPV6CTL_ACCEPT_RTADV, 0) == -1) + logerr("IPV6CTL_ACCEPT_RTADV"); + } +#endif +} + void if_setup_inet6(const struct interface *ifp) { @@ -1641,8 +1843,7 @@ #endif #ifdef ND6_IFF_AUTO_LINKLOCAL - /* Unlike the kernel, - * dhcpcd make make a stable private address. */ + /* Unlike the kernel, dhcpcd make make a stable private address. */ flags &= ~ND6_IFF_AUTO_LINKLOCAL; #endif @@ -1672,7 +1873,8 @@ #ifdef ND6_NDI_FLAGS if (nd.ndi.flags != (uint32_t)flags) { nd.ndi.flags = (uint32_t)flags; - if (ioctl(s, SIOCSIFINFO_FLAGS, &nd) == -1) + if (if_ioctl6(ifp->ctx, SIOCSIFINFO_FLAGS, + &nd, sizeof(nd)) == -1) logerr("%s: SIOCSIFINFO_FLAGS", ifp->name); } #endif @@ -1681,31 +1883,16 @@ * last action undertaken to ensure kernel RS and * LLADDR auto configuration are disabled where applicable. */ #ifdef SIOCIFAFATTACH - if (af_attach(s, ifp, AF_INET6) == -1) - logerr("%s: af_attach", ifp->name); + if (if_af_attach(ifp, AF_INET6) == -1) + logerr("%s: if_af_attach", ifp->name); #endif #ifdef SIOCGIFXFLAGS - if (set_ifxflags(s, ifp) == -1) + if (if_set_ifxflags(ifp) == -1) logerr("%s: set_ifxflags", ifp->name); #endif -#if defined(IPV6CTL_ACCEPT_RTADV) && !defined(ND6_IFF_ACCEPT_RTADV) - /* If we cannot control ra per interface, disable it globally. */ - if (ifp->options->options & DHCPCD_IPV6RS) { - int ra = get_inet6_sysctl(IPV6CTL_ACCEPT_RTADV); - - if (ra == -1) { - if (errno != ENOENT) - logerr("IPV6CTL_ACCEPT_RTADV"); - else if (ra != 0) - if (set_inet6_sysctl(IPV6CTL_ACCEPT_RTADV, 0) == -1) - logerr("IPV6CTL_ACCEPT_RTADV"); - } - } -#endif - -#if defined(IPV6CTL_ACCEPT_RTADV) || defined(ND6_IFF_ACCEPT_RTADV) +#ifdef SIOCSRTRFLUSH_IN6 /* Flush the kernel knowledge of advertised routers * and prefixes so the kernel does not expire prefixes * and default routes we are trying to own. */ @@ -1714,12 +1901,16 @@ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); - if (ioctl(s, SIOCSRTRFLUSH_IN6, &ifr) == -1 && - errno != ENOTSUP) - logwarn("SIOCSRTRFLUSH_IN6"); - if (ioctl(s, SIOCSPFXFLUSH_IN6, &ifr) == -1 && - errno != ENOTSUP) + if (if_ioctl6(ifp->ctx, SIOCSRTRFLUSH_IN6, + &ifr, sizeof(ifr)) == -1 && + errno != ENOTSUP && errno != ENOTTY) + logwarn("SIOCSRTRFLUSH_IN6 %d", errno); +#ifdef SIOCSPFXFLUSH_IN6 + if (if_ioctl6(ifp->ctx, SIOCSPFXFLUSH_IN6, + &ifr, sizeof(ifr)) == -1 && + errno != ENOTSUP && errno != ENOTTY) logwarn("SIOCSPFXFLUSH_IN6"); +#endif } #endif } Index: contrib/dhcpcd/src/if-options.h =================================================================== --- contrib/dhcpcd/src/if-options.h +++ contrib/dhcpcd/src/if-options.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -74,11 +74,11 @@ #define DHCPCD_DAEMONISE (1ULL << 14) #define DHCPCD_DAEMONISED (1ULL << 15) #define DHCPCD_TEST (1ULL << 16) -#define DHCPCD_MASTER (1ULL << 17) +#define DHCPCD_MANAGER (1ULL << 17) #define DHCPCD_HOSTNAME (1ULL << 18) #define DHCPCD_CLIENTID (1ULL << 19) #define DHCPCD_LINK (1ULL << 20) -// unused (1ULL << 21) +#define DHCPCD_ANONYMOUS (1ULL << 21) #define DHCPCD_BACKGROUND (1ULL << 22) #define DHCPCD_VENDORRAW (1ULL << 23) #define DHCPCD_NOWAITIP (1ULL << 24) /* To force daemonise */ @@ -90,8 +90,8 @@ #define DHCPCD_DUMPLEASE (1ULL << 30) #define DHCPCD_IPV6RS (1ULL << 31) #define DHCPCD_IPV6RA_REQRDNSS (1ULL << 32) -// unused (1ULL << 33) -// unused (1ULL << 34) +#define DHCPCD_PRIVSEP (1ULL << 33) +#define DHCPCD_CONFIGURE (1ULL << 34) #define DHCPCD_IPV4 (1ULL << 35) #define DHCPCD_FORKED (1ULL << 36) #define DHCPCD_IPV6 (1ULL << 37) @@ -99,7 +99,7 @@ #define DHCPCD_NOALIAS (1ULL << 39) #define DHCPCD_IA_FORCED (1ULL << 40) #define DHCPCD_STOPPING (1ULL << 41) -#define DHCPCD_DEPARTED (1ULL << 42) +#define DHCPCD_LAUNCHER (1ULL << 42) #define DHCPCD_HOSTNAME_SHORT (1ULL << 43) #define DHCPCD_EXITING (1ULL << 44) #define DHCPCD_WAITIP4 (1ULL << 45) @@ -119,6 +119,8 @@ #define DHCPCD_PRINT_PIDFILE (1ULL << 59) #define DHCPCD_ONESHOT (1ULL << 60) #define DHCPCD_INACTIVE (1ULL << 61) +#define DHCPCD_SLAACTEMP (1ULL << 62) +#define DHCPCD_PRIVSEPROOT (1ULL << 63) #define DHCPCD_NODROP (DHCPCD_EXITING | DHCPCD_PERSISTENT) @@ -127,6 +129,61 @@ #define DHCPCD_WARNINGS (DHCPCD_CSR_WARNED | \ DHCPCD_ROUTER_HOST_ROUTE_WARNED) +/* These options only make sense in the config file, so don't use any + valid short options for them */ +#define O_BASE MAX('z', 'Z') + 1 +#define O_ARPING O_BASE + 1 +#define O_FALLBACK O_BASE + 2 +#define O_DESTINATION O_BASE + 3 +#define O_IPV6RS O_BASE + 4 +#define O_NOIPV6RS O_BASE + 5 +#define O_IPV6RA_FORK O_BASE + 6 +#define O_LINK_RCVBUF O_BASE + 7 +#define O_ANONYMOUS O_BASE + 8 +#define O_NOALIAS O_BASE + 9 +#define O_IA_NA O_BASE + 10 +#define O_IA_TA O_BASE + 11 +#define O_IA_PD O_BASE + 12 +#define O_HOSTNAME_SHORT O_BASE + 13 +#define O_DEV O_BASE + 14 +#define O_NODEV O_BASE + 15 +#define O_NOIPV4 O_BASE + 16 +#define O_NOIPV6 O_BASE + 17 +#define O_IAID O_BASE + 18 +#define O_DEFINE O_BASE + 19 +#define O_DEFINE6 O_BASE + 20 +#define O_EMBED O_BASE + 21 +#define O_ENCAP O_BASE + 22 +#define O_VENDOPT O_BASE + 23 +#define O_VENDCLASS O_BASE + 24 +#define O_AUTHPROTOCOL O_BASE + 25 +#define O_AUTHTOKEN O_BASE + 26 +#define O_AUTHNOTREQUIRED O_BASE + 27 +#define O_NODHCP O_BASE + 28 +#define O_NODHCP6 O_BASE + 29 +#define O_DHCP O_BASE + 30 +#define O_DHCP6 O_BASE + 31 +#define O_IPV4 O_BASE + 32 +#define O_IPV6 O_BASE + 33 +#define O_CONTROLGRP O_BASE + 34 +#define O_SLAAC O_BASE + 35 +#define O_GATEWAY O_BASE + 36 +#define O_NOUP O_BASE + 37 +#define O_IPV6RA_AUTOCONF O_BASE + 38 +#define O_IPV6RA_NOAUTOCONF O_BASE + 39 +#define O_REJECT O_BASE + 40 +#define O_BOOTP O_BASE + 42 +#define O_DEFINEND O_BASE + 43 +#define O_NODELAY O_BASE + 44 +#define O_INFORM6 O_BASE + 45 +#define O_LASTLEASE_EXTEND O_BASE + 46 +#define O_INACTIVE O_BASE + 47 +#define O_MUDURL O_BASE + 48 +#define O_MSUSERCLASS O_BASE + 49 +#define O_CONFIGURE O_BASE + 50 +#define O_NOCONFIGURE O_BASE + 51 +#define O_RANDOMISE_HWADDR O_BASE + 52 + extern const struct option cf_options[]; struct if_sla { @@ -134,7 +191,7 @@ uint32_t sla; uint8_t prefix_len; uint64_t suffix; - int8_t sla_set; + bool sla_set; }; struct if_ia { @@ -175,9 +232,10 @@ uint8_t nomask6[(UINT16_MAX + 1) / NBBY]; uint8_t rejectmask6[(UINT16_MAX + 1) / NBBY]; uint32_t leasetime; - time_t timeout; - time_t reboot; + uint32_t timeout; + uint32_t reboot; unsigned long long options; + bool randomise_hwaddr; struct in_addr req_addr; struct in_addr req_mask; @@ -189,7 +247,6 @@ char **config; char **environ; - char *script; char hostname[HOSTNAME_MAX_LEN + 1]; /* We don't store the length */ uint8_t fqdn; Index: contrib/dhcpcd/src/if-options.c =================================================================== --- contrib/dhcpcd/src/if-options.c +++ contrib/dhcpcd/src/if-options.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -27,7 +27,6 @@ */ #include -#include #include #include @@ -50,62 +49,18 @@ #include "dhcp.h" #include "dhcp6.h" #include "dhcpcd-embedded.h" +#include "duid.h" #include "if.h" #include "if-options.h" #include "ipv4.h" #include "logerr.h" #include "sa.h" -/* These options only make sense in the config file, so don't use any - valid short options for them */ -#define O_BASE MAX('z', 'Z') + 1 -#define O_ARPING O_BASE + 1 -#define O_FALLBACK O_BASE + 2 -#define O_DESTINATION O_BASE + 3 -#define O_IPV6RS O_BASE + 4 -#define O_NOIPV6RS O_BASE + 5 -#define O_IPV6RA_FORK O_BASE + 6 -#define O_LINK_RCVBUF O_BASE + 7 -// unused O_BASE + 8 -#define O_NOALIAS O_BASE + 9 -#define O_IA_NA O_BASE + 10 -#define O_IA_TA O_BASE + 11 -#define O_IA_PD O_BASE + 12 -#define O_HOSTNAME_SHORT O_BASE + 13 -#define O_DEV O_BASE + 14 -#define O_NODEV O_BASE + 15 -#define O_NOIPV4 O_BASE + 16 -#define O_NOIPV6 O_BASE + 17 -#define O_IAID O_BASE + 18 -#define O_DEFINE O_BASE + 19 -#define O_DEFINE6 O_BASE + 20 -#define O_EMBED O_BASE + 21 -#define O_ENCAP O_BASE + 22 -#define O_VENDOPT O_BASE + 23 -#define O_VENDCLASS O_BASE + 24 -#define O_AUTHPROTOCOL O_BASE + 25 -#define O_AUTHTOKEN O_BASE + 26 -#define O_AUTHNOTREQUIRED O_BASE + 27 -#define O_NODHCP O_BASE + 28 -#define O_NODHCP6 O_BASE + 29 -#define O_DHCP O_BASE + 30 -#define O_DHCP6 O_BASE + 31 -#define O_IPV4 O_BASE + 32 -#define O_IPV6 O_BASE + 33 -#define O_CONTROLGRP O_BASE + 34 -#define O_SLAAC O_BASE + 35 -#define O_GATEWAY O_BASE + 36 -#define O_NOUP O_BASE + 37 -#define O_IPV6RA_AUTOCONF O_BASE + 38 -#define O_IPV6RA_NOAUTOCONF O_BASE + 39 -#define O_REJECT O_BASE + 40 -#define O_BOOTP O_BASE + 42 -#define O_DEFINEND O_BASE + 43 -#define O_NODELAY O_BASE + 44 -#define O_INFORM6 O_BASE + 45 -#define O_LASTLEASE_EXTEND O_BASE + 46 -#define O_INACTIVE O_BASE + 47 -#define O_MUDURL O_BASE + 48 +#define IN_CONFIG_BLOCK(ifo) ((ifo)->options & DHCPCD_FORKED) +#define SET_CONFIG_BLOCK(ifo) ((ifo)->options |= DHCPCD_FORKED) +#define CLEAR_CONFIG_BLOCK(ifo) ((ifo)->options &= ~DHCPCD_FORKED) + +static unsigned long long default_options; const struct option cf_options[] = { {"background", no_argument, NULL, 'b'}, @@ -129,6 +84,9 @@ {"inform6", optional_argument, NULL, O_INFORM6}, {"timeout", required_argument, NULL, 't'}, {"userclass", required_argument, NULL, 'u'}, +#ifndef SMALL + {"msuserclass", required_argument, NULL, O_MSUSERCLASS}, +#endif {"vendor", required_argument, NULL, 'v'}, {"waitip", optional_argument, NULL, 'w'}, {"exit", no_argument, NULL, 'x'}, @@ -137,7 +95,7 @@ {"noarp", no_argument, NULL, 'A'}, {"nobackground", no_argument, NULL, 'B'}, {"nohook", required_argument, NULL, 'C'}, - {"duid", no_argument, NULL, 'D'}, + {"duid", optional_argument, NULL, 'D'}, {"lastlease", no_argument, NULL, 'E'}, {"fqdn", optional_argument, NULL, 'F'}, {"nogateway", no_argument, NULL, 'G'}, @@ -146,7 +104,7 @@ {"broadcast", no_argument, NULL, 'J'}, {"nolink", no_argument, NULL, 'K'}, {"noipv4ll", no_argument, NULL, 'L'}, - {"master", no_argument, NULL, 'M'}, + {"manager", no_argument, NULL, 'M'}, {"renew", no_argument, NULL, 'N'}, {"nooption", required_argument, NULL, 'O'}, {"printpidfile", no_argument, NULL, 'P'}, @@ -161,6 +119,8 @@ {"oneshot", no_argument, NULL, '1'}, {"ipv4only", no_argument, NULL, '4'}, {"ipv6only", no_argument, NULL, '6'}, + {"anonymous", no_argument, NULL, O_ANONYMOUS}, + {"randomise_hwaddr",no_argument, NULL, O_RANDOMISE_HWADDR}, {"arping", required_argument, NULL, O_ARPING}, {"destination", required_argument, NULL, O_DESTINATION}, {"fallback", required_argument, NULL, O_FALLBACK}, @@ -175,9 +135,9 @@ {"noipv6", no_argument, NULL, O_NOIPV6}, {"noalias", no_argument, NULL, O_NOALIAS}, {"iaid", required_argument, NULL, O_IAID}, - {"ia_na", no_argument, NULL, O_IA_NA}, - {"ia_ta", no_argument, NULL, O_IA_TA}, - {"ia_pd", no_argument, NULL, O_IA_PD}, + {"ia_na", optional_argument, NULL, O_IA_NA}, + {"ia_ta", optional_argument, NULL, O_IA_TA}, + {"ia_pd", optional_argument, NULL, O_IA_PD}, {"hostname_short", no_argument, NULL, O_HOSTNAME_SHORT}, {"dev", required_argument, NULL, O_DEV}, {"nodev", no_argument, NULL, O_NODEV}, @@ -206,11 +166,11 @@ {"inactive", no_argument, NULL, O_INACTIVE}, {"mudurl", required_argument, NULL, O_MUDURL}, {"link_rcvbuf", required_argument, NULL, O_LINK_RCVBUF}, + {"configure", no_argument, NULL, O_CONFIGURE}, + {"noconfigure", no_argument, NULL, O_NOCONFIGURE}, {NULL, 0, NULL, '\0'} }; -static const char *default_script = SCRIPT; - static char * add_environ(char ***array, const char *value, int uniq) { @@ -286,6 +246,7 @@ #define PARSE_STRING_NULL 1 #define PARSE_HWADDR 2 #define parse_string(a, b, c) parse_str((a), (b), (c), PARSE_STRING) +#define parse_nstring(a, b, c) parse_str((a), (b), (c), PARSE_STRING_NULL) #define parse_hwaddr(a, b, c) parse_str((a), (b), (c), PARSE_HWADDR) static ssize_t parse_str(char *sbuf, size_t slen, const char *str, int flags) @@ -360,9 +321,10 @@ break; c[i] = *str++; } - if (c[1] != '\0' && sbuf) { + if (c[1] != '\0') { c[2] = '\0'; - *sbuf++ = (char)strtol(c, NULL, 16); + if (sbuf) + *sbuf++ = (char)strtol(c, NULL, 16); } else l--; break; @@ -374,11 +336,12 @@ break; c[i] = *str++; } - if (c[2] != '\0' && sbuf) { + if (c[2] != '\0') { i = (int)strtol(c, NULL, 8); if (i > 255) i = 255; - *sbuf ++= (char)i; + if (sbuf) + *sbuf++ = (char)i; } else l--; break; @@ -393,8 +356,16 @@ str++; } } - if (flags == PARSE_STRING_NULL && sbuf) - *sbuf = '\0'; + if (flags == PARSE_STRING_NULL) { + l++; + if (sbuf != NULL) { + if (l > slen) { + errno = ENOBUFS; + return -1; + } + *sbuf = '\0'; + } + } return (ssize_t)l; } @@ -494,13 +465,13 @@ if (e != 0 || (net != NULL && inet_cidrtoaddr((int)i, net) != 0)) { - logerrx("`%s' is not a valid CIDR", p); + logerrx("invalid CIDR: %s", p); return -1; } } if (addr != NULL && inet_aton(arg, addr) == 0) { - logerrx("`%s' is not a valid IP address", arg); + logerrx("invalid IP address: %s", arg); return -1; } if (p != NULL) @@ -559,6 +530,8 @@ return; } #endif +#else + UNUSED(arg); #endif #ifdef INET @@ -712,26 +685,32 @@ break; case 'c': ARG_REQUIRED; - if (ifo->script != default_script) - free(ifo->script); - s = parse_str(NULL, 0, arg, PARSE_STRING_NULL); + if (IN_CONFIG_BLOCK(ifo)) { + logerrx("%s: per interface scripts" + " are no longer supported", + ifname); + return -1; + } + if (ctx->script != dhcpcd_default_script) + free(ctx->script); + s = parse_nstring(NULL, 0, arg); if (s == 0) { - ifo->script = NULL; + ctx->script = NULL; break; } dl = (size_t)s; - if (s == -1 || (ifo->script = malloc(dl)) == NULL) { - ifo->script = NULL; + if (s == -1 || (ctx->script = malloc(dl)) == NULL) { + ctx->script = NULL; logerr(__func__); return -1; } - s = parse_str(ifo->script, dl, arg, PARSE_STRING_NULL); + s = parse_nstring(ctx->script, dl, arg); if (s == -1 || - ifo->script[0] == '\0' || - strcmp(ifo->script, "/dev/null") == 0) + ctx->script[0] == '\0' || + strcmp(ctx->script, "/dev/null") == 0) { - free(ifo->script); - ifo->script = NULL; + free(ctx->script); + ctx->script = NULL; } break; case 'd': @@ -746,7 +725,7 @@ ifo->options |= DHCPCD_HOSTNAME; break; } - s = parse_string(ifo->hostname, HOSTNAME_MAX_LEN, arg); + s = parse_nstring(ifo->hostname, sizeof(ifo->hostname), arg); if (s == -1) { logerr("%s: hostname", __func__); return -1; @@ -755,7 +734,6 @@ logerrx("hostname cannot begin with ."); return -1; } - ifo->hostname[s] = '\0'; if (ifo->hostname[0] == '\0') ifo->options &= ~DHCPCD_HOSTNAME; else @@ -777,7 +755,7 @@ ARG_REQUIRED; /* per interface logging is not supported * don't want to overide the commandline */ - if (ifname == NULL && ctx->logfile == NULL) { + if (!IN_CONFIG_BLOCK(ifo) && ctx->logfile == NULL) { logclose(); ctx->logfile = strdup(arg); logopen(ctx->logfile); @@ -788,6 +766,10 @@ break; case 'l': ARG_REQUIRED; + if (strcmp(arg, "-1") == 0) { + ifo->leasetime = DHCP_INFINITE_LIFETIME; + break; + } ifo->leasetime = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); if (e) { @@ -805,25 +787,29 @@ break; case 'o': ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, request, arg, 1) != 0 || make_option_mask(d, dl, od, odl, no, arg, -1) != 0 || make_option_mask(d, dl, od, odl, reject, arg, -1) != 0) { - logerrx("unknown option `%s'", arg); + logerrx("unknown option: %s", arg); return -1; } break; case O_REJECT: ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, reject, arg, 1) != 0 || make_option_mask(d, dl, od, odl, request, arg, -1) != 0 || make_option_mask(d, dl, od, odl, require, arg, -1) != 0) { - logerrx("unknown option `%s'", arg); + logerrx("unknown option: %s", arg); return -1; } break; @@ -866,16 +852,16 @@ break; case 't': ARG_REQUIRED; - ifo->timeout = (time_t)strtoi(arg, NULL, 0, 0, INT32_MAX, &e); + ifo->timeout = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); if (e) { logerrx("failed to convert timeout %s", arg); return -1; } break; case 'u': - s = USERCLASS_MAX_LEN - ifo->userclass[0] - 1; + dl = sizeof(ifo->userclass) - ifo->userclass[0] - 1; s = parse_string((char *)ifo->userclass + - ifo->userclass[0] + 2, (size_t)s, arg); + ifo->userclass[0] + 2, dl, arg); if (s == -1) { logerr("userclass"); return -1; @@ -885,6 +871,19 @@ ifo->userclass[0] = (uint8_t)(ifo->userclass[0] + s +1); } break; +#ifndef SMALL + case O_MSUSERCLASS: + /* Some Microsoft DHCP servers expect userclass to be an + * opaque blob. This is not RFC 3004 compliant. */ + s = parse_string((char *)ifo->userclass + 1, + sizeof(ifo->userclass) - 1, arg); + if (s == -1) { + logerr("msuserclass"); + return -1; + } + ifo->userclass[0] = (uint8_t)s; + break; +#endif case 'v': ARG_REQUIRED; p = strchr(arg, ','); @@ -959,7 +958,7 @@ break; case 'y': ARG_REQUIRED; - ifo->reboot = (time_t)strtoi(arg, NULL, 0, 0, UINT32_MAX, &e); + ifo->reboot = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e); if (e) { logerr("failed to convert reboot %s", arg); return -1; @@ -967,7 +966,7 @@ break; case 'z': ARG_REQUIRED; - if (ifname == NULL) + if (!IN_CONFIG_BLOCK(ifo)) ctx->ifav = splitv(&ctx->ifac, ctx->ifav, arg); break; case 'A': @@ -995,6 +994,28 @@ break; case 'D': ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID; + if (ifname != NULL) /* duid type only a global option */ + break; + if (arg == NULL) + ctx->duid_type = DUID_DEFAULT; + else if (strcmp(arg, "ll") == 0) + ctx->duid_type = DUID_LL; + else if (strcmp(arg, "llt") == 0) + ctx->duid_type = DUID_LLT; + else if (strcmp(arg, "uuid") == 0) + ctx->duid_type = DUID_UUID; + else { + dl = hwaddr_aton(NULL, arg); + if (dl != 0) { + no = realloc(ctx->duid, dl); + if (no == NULL) + logerrx(__func__); + else { + ctx->duid = no; + ctx->duid_len = hwaddr_aton(no, arg); + } + } + } break; case 'E': ifo->options |= DHCPCD_LASTLEASE; @@ -1013,7 +1034,7 @@ else if (strcmp(arg, "disable") == 0) ifo->fqdn = FQDN_DISABLE; else { - logerrx("invalid value `%s' for FQDN", arg); + logerrx("invalid FQDN value: %s", arg); return -1; } break; @@ -1037,6 +1058,7 @@ } ifo->options |= DHCPCD_CLIENTID; ifo->clientid[0] = (uint8_t)s; + ifo->options &= ~DHCPCD_DUID; break; case 'J': ifo->options |= DHCPCD_BROADCAST; @@ -1048,22 +1070,26 @@ ifo->options &= ~DHCPCD_IPV4LL; break; case 'M': - ifo->options |= DHCPCD_MASTER; + ifo->options |= DHCPCD_MANAGER; break; case 'O': ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, request, arg, -1) != 0 || make_option_mask(d, dl, od, odl, require, arg, -1) != 0 || make_option_mask(d, dl, od, odl, no, arg, 1) != 0) { - logerrx("unknown option `%s'", arg); + logerrx("unknown option: %s", arg); return -1; } break; case 'Q': ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, require, arg, 1) != 0 || @@ -1071,7 +1097,7 @@ make_option_mask(d, dl, od, odl, no, arg, -1) != 0 || make_option_mask(d, dl, od, odl, reject, arg, -1) != 0) { - logerrx("unknown option `%s'", arg); + logerrx("unknown option: %s", arg); return -1; } break; @@ -1157,7 +1183,7 @@ np = strchr(p, '/'); if (np) *np++ = '\0'; - if (inet_pton(AF_INET6, p, &ifo->req_addr6) == 1) { + if ((i = inet_pton(AF_INET6, p, &ifo->req_addr6)) == 1) { if (np) { ifo->req_prefix_len = (uint8_t)strtou(np, NULL, 0, 0, 128, &e); @@ -1170,6 +1196,14 @@ } else ifo->req_prefix_len = 128; } + if (np) + *(--np) = '\0'; + if (i != 1) { + logerrx("invalid AF_INET6: %s", p); + memset(&ifo->req_addr6, 0, + sizeof(ifo->req_addr6)); + return -1; + } } else add_environ(&ifo->config, arg, 1); break; @@ -1205,20 +1239,30 @@ break; case 'Z': ARG_REQUIRED; - if (ifname == NULL) + if (!IN_CONFIG_BLOCK(ifo)) ctx->ifdv = splitv(&ctx->ifdc, ctx->ifdv, arg); break; case '1': ifo->options |= DHCPCD_ONESHOT; break; case '4': +#ifdef INET ifo->options &= ~DHCPCD_IPV6; ifo->options |= DHCPCD_IPV4; break; +#else + logerrx("INET has been compiled out"); + return -1; +#endif case '6': +#ifdef INET6 ifo->options &= ~DHCPCD_IPV4; ifo->options |= DHCPCD_IPV6; break; +#else + logerrx("INET6 has been compiled out"); + return -1; +#endif case O_IPV4: ifo->options |= DHCPCD_IPV4; break; @@ -1231,6 +1275,41 @@ case O_NOIPV6: ifo->options &= ~DHCPCD_IPV6; break; + case O_ANONYMOUS: + ifo->options |= DHCPCD_ANONYMOUS; + ifo->options &= ~DHCPCD_HOSTNAME; + ifo->fqdn = FQDN_DISABLE; + + /* Block everything */ + memset(ifo->nomask, 0xff, sizeof(ifo->nomask)); + memset(ifo->nomask6, 0xff, sizeof(ifo->nomask6)); + + /* Allow the bare minimum through */ +#ifdef INET + del_option_mask(ifo->nomask, DHO_SUBNETMASK); + del_option_mask(ifo->nomask, DHO_CSR); + del_option_mask(ifo->nomask, DHO_ROUTER); + del_option_mask(ifo->nomask, DHO_DNSSERVER); + del_option_mask(ifo->nomask, DHO_DNSDOMAIN); + del_option_mask(ifo->nomask, DHO_BROADCAST); + del_option_mask(ifo->nomask, DHO_STATICROUTE); + del_option_mask(ifo->nomask, DHO_SERVERID); + del_option_mask(ifo->nomask, DHO_RENEWALTIME); + del_option_mask(ifo->nomask, DHO_REBINDTIME); + del_option_mask(ifo->nomask, DHO_DNSSEARCH); +#endif + +#ifdef DHCP6 + del_option_mask(ifo->nomask6, D6_OPTION_DNS_SERVERS); + del_option_mask(ifo->nomask6, D6_OPTION_DOMAIN_LIST); + del_option_mask(ifo->nomask6, D6_OPTION_SOL_MAX_RT); + del_option_mask(ifo->nomask6, D6_OPTION_INF_MAX_RT); +#endif + + break; + case O_RANDOMISE_HWADDR: + ifo->randomise_hwaddr = true; + break; #ifdef INET case O_ARPING: while (arg != NULL) { @@ -1252,16 +1331,18 @@ break; case O_DESTINATION: ARG_REQUIRED; + if (ctx->options & DHCPCD_PRINT_PIDFILE) + break; set_option_space(ctx, arg, &d, &dl, &od, &odl, ifo, &request, &require, &no, &reject); if (make_option_mask(d, dl, od, odl, ifo->dstmask, arg, 2) != 0) { if (errno == EINVAL) - logerrx("option `%s' does not take" - " an IPv4 address", arg); + logerrx("option does not take" + " an IPv4 address: %s", arg); else - logerrx("unknown option `%s'", arg); + logerrx("unknown option: %s", arg); return -1; } break; @@ -1277,7 +1358,7 @@ #endif case O_IAID: ARG_REQUIRED; - if (ifname == NULL) { + if (ctx->options & DHCPCD_MANAGER && !IN_CONFIG_BLOCK(ifo)) { logerrx("IAID must belong in an interface block"); return -1; } @@ -1319,7 +1400,9 @@ logwarnx("%s: IA_PD not compiled in", ifname); return -1; #else - if (ifname == NULL) { + if (ctx->options & DHCPCD_MANAGER && + !IN_CONFIG_BLOCK(ifo)) + { logerrx("IA PD must belong in an " "interface block"); return -1; @@ -1327,7 +1410,9 @@ i = D6_OPTION_IA_PD; #endif } - if (ifname == NULL && arg) { + if (ctx->options & DHCPCD_MANAGER && + !IN_CONFIG_BLOCK(ifo) && arg) + { logerrx("IA with IAID must belong in an " "interface block"); return -1; @@ -1390,8 +1475,8 @@ p = strchr(arg, '/'); if (p) *p++ = '\0'; - if (inet_pton(AF_INET6, arg, &ia->addr) == -1) { - logerr("%s", arg); + if (inet_pton(AF_INET6, arg, &ia->addr) != 1) { + logerrx("invalid AF_INET6: %s", arg); memset(&ia->addr, 0, sizeof(ia->addr)); } if (p && ia->ia_type == D6_OPTION_IA_PD) { @@ -1441,7 +1526,7 @@ logerrx("%s: interface name too long", arg); goto err_sla; } - sla->sla_set = 0; + sla->sla_set = false; sla->prefix_len = 0; sla->suffix = 1; p = np; @@ -1452,7 +1537,7 @@ if (*p != '\0') { sla->sla = (uint32_t)strtou(p, NULL, 0, 0, UINT32_MAX, &e); - sla->sla_set = 1; + sla->sla_set = true; if (e) { logerrx("%s: failed to convert " "sla", @@ -1511,7 +1596,7 @@ sla->ifname); goto err_sla; } - if (sla->sla_set == 0 && + if (!sla->sla_set && strcmp(slap->ifname, sla->ifname) == 0) { logwarnx("%s: cannot specify the " @@ -1744,7 +1829,7 @@ return -1; } if (l && !(t & (OT_STRING | OT_BINHEX))) { - logwarnx("ignoring length for type `%s'", arg); + logwarnx("ignoring length for type: %s", arg); l = 0; } if (t & OT_ARRAY && t & (OT_STRING | OT_BINHEX) && @@ -1964,52 +2049,42 @@ return -1; } *fp++ = '\0'; - token = malloc(sizeof(*token)); + token = calloc(1, sizeof(*token)); if (token == NULL) { logerr(__func__); - free(token); return -1; } if (parse_uint32(&token->secretid, arg) == -1) { logerrx("%s: not a number", arg); - free(token); - return -1; + goto invalid_token; } arg = fp; fp = strend(arg); if (fp == NULL) { logerrx("authtoken requies an a key"); - free(token); - return -1; + goto invalid_token; } *fp++ = '\0'; s = parse_string(NULL, 0, arg); if (s == -1) { logerr("realm_len"); - free(token); - return -1; + goto invalid_token; } - if (s) { + if (s != 0) { token->realm_len = (size_t)s; token->realm = malloc(token->realm_len); if (token->realm == NULL) { logerr(__func__); - free(token); - return -1; + goto invalid_token; } parse_string((char *)token->realm, token->realm_len, arg); - } else { - token->realm_len = 0; - token->realm = NULL; } arg = fp; fp = strend(arg); if (fp == NULL) { logerrx("authtoken requies an expiry date"); - free(token->realm); - free(token); - return -1; + goto invalid_token; } *fp++ = '\0'; if (*arg == '"') { @@ -2026,15 +2101,11 @@ memset(&tm, 0, sizeof(tm)); if (strptime(arg, "%Y-%m-%d %H:%M", &tm) == NULL) { logerrx("%s: invalid date time", arg); - free(token->realm); - free(token); - return -1; + goto invalid_token; } if ((token->expire = mktime(&tm)) == (time_t)-1) { logerr("%s: mktime", __func__); - free(token->realm); - free(token); - return -1; + goto invalid_token; } } arg = fp; @@ -2044,19 +2115,25 @@ logerr("token_len"); else logerrx("authtoken needs a key"); - free(token->realm); - free(token); - return -1; + goto invalid_token; } token->key_len = (size_t)s; token->key = malloc(token->key_len); + if (token->key == NULL) { + logerr(__func__); + goto invalid_token; + } parse_string((char *)token->key, token->key_len, arg); TAILQ_INSERT_TAIL(&ifo->auth.tokens, token, next); + break; + +invalid_token: + free(token->realm); + free(token); #else logerrx("no authentication support"); - return -1; #endif - break; + return -1; case O_AUTHNOTREQUIRED: ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE; break; @@ -2074,6 +2151,12 @@ break; case O_CONTROLGRP: ARG_REQUIRED; +#ifdef PRIVSEP + /* Control group is already set by this point. + * We don't need to pledge getpw either with this. */ + if (IN_PRIVSEP(ctx)) + break; +#endif #ifdef _REENTRANT l = sysconf(_SC_GETGR_R_SIZE_MAX); if (l == -1) @@ -2085,7 +2168,7 @@ logerr(__func__); return -1; } - while ((i = getgrnam_r(arg, &grpbuf, p, (size_t)l, &grp)) == + while ((i = getgrnam_r(arg, &grpbuf, p, dl, &grp)) == ERANGE) { size_t nl = dl * 2; @@ -2110,7 +2193,8 @@ return -1; } if (grp == NULL) { - logerrx("controlgroup: %s: not found", arg); + if (!ctx->control_group) + logerrx("controlgroup: %s: not found", arg); free(p); return -1; } @@ -2119,7 +2203,8 @@ #else grp = getgrnam(arg); if (grp == NULL) { - logerrx("controlgroup: %s: not found", arg); + if (!ctx->control_group) + logerrx("controlgroup: %s: not found", arg); return -1; } ctx->control_group = grp->gr_gid; @@ -2133,12 +2218,20 @@ break; case O_SLAAC: ARG_REQUIRED; + np = strwhite(arg); + if (np != NULL) { + *np++ = '\0'; + np = strskipwhite(np); + } if (strcmp(arg, "private") == 0 || strcmp(arg, "stableprivate") == 0 || strcmp(arg, "stable") == 0) ifo->options |= DHCPCD_SLAACPRIVATE; else ifo->options &= ~DHCPCD_SLAACPRIVATE; + if (np != NULL && + (strcmp(np, "temp") == 0 || strcmp(np, "temporary") == 0)) + ifo->options |= DHCPCD_SLAACTEMP; break; case O_BOOTP: ifo->options |= DHCPCD_BOOTP; @@ -2171,6 +2264,12 @@ } #endif break; + case O_CONFIGURE: + ifo->options |= DHCPCD_CONFIGURE; + break; + case O_NOCONFIGURE: + ifo->options &= ~DHCPCD_CONFIGURE; + break; default: return 0; } @@ -2206,7 +2305,8 @@ ldop, edop); } - logerrx("unknown option: %s", opt); + if (!(ctx->options & DHCPCD_PRINT_PIDFILE)) + logerrx("unknown option: %s", opt); return -1; } @@ -2223,45 +2323,21 @@ * guard should suffice */ ifo->options |= DHCPCD_VENDORRAW; } -} -/* Handy routine to read very long lines in text files. - * This means we read the whole line and avoid any nasty buffer overflows. - * We strip leading space and avoid comment lines, making the code that calls - * us smaller. */ -static char * -get_line(char ** __restrict buf, size_t * __restrict buflen, - FILE * __restrict fp) -{ - char *p, *c; - ssize_t bytes; - int quoted; + if (!(ifo->options & DHCPCD_ARP) || + ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) + ifo->options &= ~DHCPCD_IPV4LL; - do { - bytes = getline(buf, buflen, fp); - if (bytes == -1) - return NULL; - for (p = *buf; *p == ' ' || *p == '\t'; p++) - ; - } while (*p == '\0' || *p == '\n' || *p == '#' || *p == ';'); - if ((*buf)[--bytes] == '\n') - (*buf)[bytes] = '\0'; - - /* Strip embedded comments unless in a quoted string or escaped */ - quoted = 0; - for (c = p; *c != '\0'; c++) { - if (*c == '\\') { - c++; /* escaped */ - continue; - } - if (*c == '"') - quoted = !quoted; - else if (*c == '#' && !quoted) { - *c = '\0'; - break; - } - } - return p; + if (!(ifo->options & DHCPCD_IPV4)) + ifo->options &= ~(DHCPCD_DHCP | DHCPCD_IPV4LL | DHCPCD_WAITIP4); + + if (!(ifo->options & DHCPCD_IPV6)) + ifo->options &= + ~(DHCPCD_IPV6RS | DHCPCD_DHCP6 | DHCPCD_WAITIP6); + + if (!(ifo->options & DHCPCD_IPV6RS)) + ifo->options &= + ~(DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS); } struct if_options * @@ -2277,7 +2353,6 @@ ifo->options |= DHCPCD_IF_UP | DHCPCD_LINK | DHCPCD_INITIAL_DELAY; ifo->timeout = DEFAULT_TIMEOUT; ifo->reboot = DEFAULT_REBOOT; - ifo->script = UNCONST(default_script); ifo->metric = -1; ifo->auth.options |= DHCPCD_AUTH_REQUIRE; rb_tree_init(&ifo->routes, &rt_compare_list_ops); @@ -2286,6 +2361,8 @@ #endif /* Inherit some global defaults */ + if (ctx->options & DHCPCD_CONFIGURE) + ifo->options |= DHCPCD_CONFIGURE; if (ctx->options & DHCPCD_PERSISTENT) ifo->options |= DHCPCD_PERSISTENT; if (ctx->options & DHCPCD_SLAACPRIVATE) @@ -2299,16 +2376,11 @@ const char *ifname, const char *ssid, const char *profile) { struct if_options *ifo; - FILE *fp; - struct stat sb; - char *line, *buf, *option, *p; - size_t buflen; - ssize_t vlen; + char buf[UDPLEN_MAX], *bp; /* 64k max config file size */ + char *line, *option, *p; + ssize_t buflen; + size_t vlen; int skip, have_profile, new_block, had_block; -#ifndef EMBEDDED_CONFIG - const char * const *e; - size_t ol; -#endif #if !defined(INET) || !defined(INET6) size_t i; struct dhcp_opt *opt; @@ -2318,25 +2390,37 @@ /* Seed our default options */ if ((ifo = default_config(ctx)) == NULL) return NULL; - ifo->options |= DHCPCD_DAEMONISE | DHCPCD_GATEWAY; -#ifdef PLUGIN_DEV - ifo->options |= DHCPCD_DEV; -#endif + if (default_options == 0) { + default_options |= DHCPCD_CONFIGURE | DHCPCD_DAEMONISE | + DHCPCD_GATEWAY; #ifdef INET - ifo->options |= DHCPCD_IPV4 | DHCPCD_ARP | DHCPCD_DHCP | DHCPCD_IPV4LL; + skip = socket(PF_INET, SOCK_DGRAM, 0); + if (skip != -1) { + close(skip); + default_options |= DHCPCD_IPV4 | DHCPCD_ARP | + DHCPCD_DHCP | DHCPCD_IPV4LL; + } #endif #ifdef INET6 - ifo->options |= DHCPCD_IPV6 | DHCPCD_IPV6RS; - ifo->options |= DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS; - ifo->options |= DHCPCD_DHCP6; + skip = socket(PF_INET6, SOCK_DGRAM, 0); + if (skip != -1) { + close(skip); + default_options |= DHCPCD_IPV6 | DHCPCD_IPV6RS | + DHCPCD_IPV6RA_AUTOCONF | DHCPCD_IPV6RA_REQRDNSS | + DHCPCD_DHCP6; + } +#endif +#ifdef PLUGIN_DEV + default_options |= DHCPCD_DEV; #endif + } + ifo->options |= default_options; - vlen = dhcp_vendor((char *)ifo->vendorclassid + 1, - sizeof(ifo->vendorclassid) - 1); - ifo->vendorclassid[0] = (uint8_t)(vlen == -1 ? 0 : vlen); + CLEAR_CONFIG_BLOCK(ifo); - buf = NULL; - buflen = 0; + vlen = strlcpy((char *)ifo->vendorclassid + 1, ctx->vendor, + sizeof(ifo->vendorclassid) - 1); + ifo->vendorclassid[0] = (uint8_t)(vlen > 255 ? 0 : vlen); /* Reset route order */ ctx->rt_order = 0; @@ -2372,38 +2456,27 @@ /* Now load our embedded config */ #ifdef EMBEDDED_CONFIG - fp = fopen(EMBEDDED_CONFIG, "r"); - if (fp == NULL) - logerr("%s: fopen `%s'", __func__, EMBEDDED_CONFIG); - - while (fp && (line = get_line(&buf, &buflen, fp))) { + buflen = dhcp_readfile(ctx, EMBEDDED_CONFIG, buf, sizeof(buf)); + if (buflen == -1) { + logerr("%s: %s", __func__, EMBEDDED_CONFIG); + return ifo; + } + if (buf[buflen - 1] != '\0') { + if ((size_t)buflen < sizeof(buf) - 1) + buflen++; + buf[buflen - 1] = '\0'; + } #else - buflen = 80; - buf = malloc(buflen); - if (buf == NULL) { - logerr(__func__); - free_options(ctx, ifo); - return NULL; + buflen = (ssize_t)strlcpy(buf, dhcpcd_embedded_conf, + sizeof(buf)); + if ((size_t)buflen >= sizeof(buf)) { + logerrx("%s: embedded config too big", __func__); + return ifo; } - ldop = edop = NULL; - for (e = dhcpcd_embedded_conf; *e; e++) { - ol = strlen(*e) + 1; - if (ol > buflen) { - char *nbuf; - - buflen = ol; - nbuf = realloc(buf, buflen); - if (nbuf == NULL) { - logerr(__func__); - free(buf); - free_options(ctx, ifo); - return NULL; - } - buf = nbuf; - } - memcpy(buf, *e, ol); - line = buf; + /* Our embedded config is NULL terminated */ #endif + bp = buf; + while ((line = get_line(&bp, &buflen)) != NULL) { option = strsep(&line, " \t"); if (line) line = strskipwhite(line); @@ -2417,13 +2490,8 @@ } parse_config_line(ctx, NULL, ifo, option, line, &ldop, &edop); - } -#ifdef EMBEDDED_CONFIG - if (fp) - fclose(fp); -#endif #ifdef INET ctx->dhcp_opts = ifo->dhcp_override; ctx->dhcp_opts_len = ifo->dhcp_override_len; @@ -2468,21 +2536,25 @@ } /* Parse our options file */ - fp = fopen(ctx->cffile, "r"); - if (fp == NULL) { + buflen = dhcp_readfile(ctx, ctx->cffile, buf, sizeof(buf)); + if (buflen == -1) { /* dhcpcd can continue without it, but no DNS options * would be requested ... */ - logwarn("%s: fopen `%s'", __func__, ctx->cffile); - free(buf); + logerr("%s: %s", __func__, ctx->cffile); return ifo; } - if (stat(ctx->cffile, &sb) == 0) - ifo->mtime = sb.st_mtime; + if (buf[buflen - 1] != '\0') { + if ((size_t)buflen < sizeof(buf) - 1) + buflen++; + buf[buflen - 1] = '\0'; + } + dhcp_filemtime(ctx, ctx->cffile, &ifo->mtime); ldop = edop = NULL; skip = have_profile = new_block = 0; had_block = ifname == NULL ? 1 : 0; - while ((line = get_line(&buf, &buflen, fp))) { + bp = buf; + while ((line = get_line(&bp, &buflen)) != NULL) { option = strsep(&line, " \t"); if (line) line = strskipwhite(line); @@ -2498,7 +2570,9 @@ had_block = 1; new_block = 0; ifo->options &= ~DHCPCD_WAITOPTS; + SET_CONFIG_BLOCK(ifo); } + /* Start of an interface block, skip if not ours */ if (strcmp(option, "interface") == 0) { char **n; @@ -2556,10 +2630,9 @@ continue; if (skip) continue; + parse_config_line(ctx, ifname, ifo, option, line, &ldop, &edop); } - fclose(fp); - free(buf); if (profile && !have_profile) { free_options(ctx, ifo); @@ -2569,6 +2642,7 @@ if (!had_block) ifo->options &= ~DHCPCD_WAITOPTS; + CLEAR_CONFIG_BLOCK(ifo); finish_config(ifo); return ifo; } @@ -2650,8 +2724,6 @@ #endif rt_headclear0(ctx, &ifo->routes, AF_UNSPEC); - if (ifo->script != default_script) - free(ifo->script); free(ifo->arping); free(ifo->blacklist); free(ifo->fallback); Index: contrib/dhcpcd/src/if.h =================================================================== --- contrib/dhcpcd/src/if.h +++ contrib/dhcpcd/src/if.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -38,13 +38,9 @@ #include -/* Some systems have in-built IPv4 DAD. - * However, we need them to do DAD at carrier up as well. */ -#ifdef IN_IFF_TENTATIVE -# ifdef __NetBSD__ -# define NOCARRIER_PRESERVE_IP -# endif -#endif +/* If the interface does not support carrier status (ie PPP), + * dhcpcd can poll it for the relevant flags periodically */ +#define IF_POLL_UP 100 /* milliseconds */ /* * Systems which handle 1 address per alias. @@ -62,6 +58,16 @@ #endif #include "config.h" + +/* POSIX defines ioctl request as an int, which Solaris and musl use. + * Everyone else use an unsigned long, which happens to be the bigger one + * so we use that in our on wire API. */ +#ifdef IOCTL_REQUEST_TYPE +typedef IOCTL_REQUEST_TYPE ioctl_request_t; +#else +typedef unsigned long ioctl_request_t; +#endif + #include "dhcpcd.h" #include "ipv4.h" #include "ipv6.h" @@ -80,6 +86,13 @@ # define ARPHRD_INFINIBAND 32 #endif +/* Maximum frame length. + * Support jumbo frames and some extra. */ +#define FRAMEHDRLEN_MAX 14 /* only ethernet support */ +#define FRAMELEN_MAX (FRAMEHDRLEN_MAX + 9216) + +#define UDPLEN_MAX 64 * 1024 + /* Work out if we have a private address or not * 10/8 * 172.16/12 @@ -102,6 +115,11 @@ * but then ignores it. */ #undef RTF_CLONING +/* This interface is busted on DilOS at least. + * It used to work, but lukily Solaris can fall back to + * IP_PKTINFO. */ +#undef IP_RECVIF + /* Solaris getifaddrs is very un-suitable for dhcpcd. * See if-sun.c for details why. */ struct ifaddrs; @@ -110,9 +128,17 @@ int if_getsubnet(struct dhcpcd_ctx *, const char *, int, void *, size_t); #endif -int if_getflags(struct interface *ifp); -int if_setflag(struct interface *ifp, short flag); -#define if_up(ifp) if_setflag((ifp), (IFF_UP | IFF_RUNNING)) +int if_ioctl(struct dhcpcd_ctx *, ioctl_request_t, void *, size_t); +#ifdef HAVE_PLEDGE +#define pioctl(ctx, req, data, len) if_ioctl((ctx), (req), (data), (len)) +#else +#define pioctl(ctx, req, data, len) ioctl((ctx)->pf_inet_fd, (req),(data),(len)) +#endif +int if_getflags(struct interface *); +int if_setflag(struct interface *, short, short); +#define if_up(ifp) if_setflag((ifp), (IFF_UP | IFF_RUNNING), 0) +#define if_down(ifp) if_setflag((ifp), 0, IFF_UP); +bool if_is_link_up(const struct interface *); bool if_valid_hwaddr(const uint8_t *, size_t); struct if_head *if_discover(struct dhcpcd_ctx *, struct ifaddrs **, int, char * const *); @@ -126,7 +152,8 @@ int if_domtu(const struct interface *, short int); #define if_getmtu(ifp) if_domtu((ifp), 0) #define if_setmtu(ifp, mtu) if_domtu((ifp), (mtu)) -int if_carrier(struct interface *); +int if_carrier(struct interface *, const void *); +bool if_roaming(struct interface *); #ifdef ALIAS_ADDR int if_makealias(char *, size_t, const char *, int); @@ -144,22 +171,28 @@ char devname[IF_NAMESIZE]; char drvname[IF_NAMESIZE]; int ppa; + int vlid; int lun; }; int if_nametospec(const char *, struct if_spec *); /* The below functions are provided by if-KERNEL.c */ +int os_init(void); int if_conf(struct interface *); int if_init(struct interface *); int if_getssid(struct interface *); +int if_ignoregroup(int, const char *); bool if_ignore(struct dhcpcd_ctx *, const char *); -int if_vimaster(const struct dhcpcd_ctx *ctx, const char *); +int if_vimaster(struct dhcpcd_ctx *ctx, const char *); unsigned short if_vlanid(const struct interface *); +char * if_getnetworknamespace(char *, size_t); int if_opensockets(struct dhcpcd_ctx *); int if_opensockets_os(struct dhcpcd_ctx *); void if_closesockets(struct dhcpcd_ctx *); void if_closesockets_os(struct dhcpcd_ctx *); int if_handlelink(struct dhcpcd_ctx *); +int if_randomisemac(struct interface *); +int if_setmac(struct interface *ifp, void *, uint8_t); /* dhcpcd uses the same routing flags as BSD. * If the platform doesn't use these flags, @@ -183,10 +216,18 @@ #else # define SOCK_NONBLOCK 0x20000000 #endif +#ifndef SOCK_CXNB +#define SOCK_CXNB SOCK_CLOEXEC | SOCK_NONBLOCK +#endif +int xsocket(int, int, int); +int xsocketpair(int, int, int, int[2]); int if_route(unsigned char, const struct rt *rt); int if_initrt(struct dhcpcd_ctx *, rb_tree_t *, int); +int if_missfilter(struct interface *, struct sockaddr *); +int if_missfilter_apply(struct dhcpcd_ctx *); + #ifdef INET int if_address(unsigned char, const struct ipv4_addr *); int if_addrflags(const struct interface *, const struct in_addr *, @@ -195,16 +236,14 @@ #endif #ifdef INET6 +void if_disable_rtadv(void); void if_setup_inet6(const struct interface *); -#ifdef IPV6_MANAGETEMPADDR -int ip6_use_tempaddr(const char *ifname); -int ip6_temp_preferred_lifetime(const char *ifname); -int ip6_temp_valid_lifetime(const char *ifname); -#else -#define ip6_use_tempaddr(a) (0) -#endif int ip6_forwarding(const char *ifname); +struct ra; +struct ipv6_addr; + +int if_applyra(const struct ra *); int if_address6(unsigned char, const struct ipv6_addr *); int if_addrflags6(const struct interface *, const struct in6_addr *, const char *); @@ -217,5 +256,10 @@ int if_machinearch(char *, size_t); struct interface *if_findifpfromcmsg(struct dhcpcd_ctx *, struct msghdr *, int *); -int xsocket(int, int, int); + +#ifdef __linux__ +int if_linksocket(struct sockaddr_nl *, int, int); +int if_getnetlink(struct dhcpcd_ctx *, struct iovec *, int, int, + int (*)(struct dhcpcd_ctx *, void *, struct nlmsghdr *), void *); +#endif #endif Index: contrib/dhcpcd/src/if.c =================================================================== --- contrib/dhcpcd/src/if.c +++ contrib/dhcpcd/src/if.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,8 @@ #include #include +#include /* Needs to be here for old Linux */ + #include "config.h" #include @@ -59,9 +61,12 @@ #include #include #include +#include #include +#define ELOOP_QUEUE ELOOP_IF #include "common.h" +#include "eloop.h" #include "dev.h" #include "dhcp.h" #include "dhcp6.h" @@ -71,12 +76,7 @@ #include "ipv4ll.h" #include "ipv6nd.h" #include "logerr.h" - -#ifdef __sun -/* It has the ioctl, but the member is missing from the struct? - * No matter, our getifaddrs foo in if-sun.c will DTRT. */ -#undef SIOCGIFHWADDR -#endif +#include "privsep.h" void if_free(struct interface *ifp) @@ -110,6 +110,16 @@ if (if_opensockets_os(ctx) == -1) return -1; +#ifdef IFLR_ACTIVE + ctx->pf_link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (ctx->pf_link_fd == -1) + return -1; +#ifdef HAVE_CAPSICUM + if (ps_rights_limit_ioctl(ctx->pf_link_fd) == -1) + return -1; +#endif +#endif + /* We use this socket for some operations without INET. */ ctx->pf_inet_fd = xsocket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (ctx->pf_inet_fd == -1) @@ -124,6 +134,10 @@ if (ctx->pf_inet_fd != -1) close(ctx->pf_inet_fd); +#ifdef PF_LINK + if (ctx->pf_link_fd != -1) + close(ctx->pf_link_fd); +#endif if (ctx->priv) { if_closesockets_os(ctx); @@ -131,6 +145,17 @@ } } +int +if_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data, size_t len) +{ + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) + return (int)ps_root_ioctl(ctx, req, data, len); +#endif + return ioctl(ctx->pf_inet_fd, req, data, len); +} + int if_getflags(struct interface *ifp) { @@ -144,25 +169,78 @@ } int -if_setflag(struct interface *ifp, short flag) +if_setflag(struct interface *ifp, short setflag, short unsetflag) { struct ifreq ifr = { .ifr_flags = 0 }; - short f; + short oflags; + + strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); + if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == -1) + return -1; - if (if_getflags(ifp) == -1) + oflags = ifr.ifr_flags; + ifr.ifr_flags |= setflag; + ifr.ifr_flags &= (short)~unsetflag; + if (ifr.ifr_flags != oflags && + if_ioctl(ifp->ctx, SIOCSIFFLAGS, &ifr, sizeof(ifr)) == -1) return -1; - f = (short)ifp->flags; - if ((f & flag) == flag) - return 0; + /* + * Do NOT set ifp->flags here. + * We need to listen for flag updates from the kernel as they + * need to sync with carrier. + */ + return 0; +} - strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); - ifr.ifr_flags = f | flag; - if (ioctl(ifp->ctx->pf_inet_fd, SIOCSIFFLAGS, &ifr) == -1) +bool +if_is_link_up(const struct interface *ifp) +{ + + return ifp->flags & IFF_UP && + (ifp->carrier != LINK_DOWN || + (ifp->options != NULL && !(ifp->options->options & DHCPCD_LINK))); +} + +int +if_randomisemac(struct interface *ifp) +{ + uint32_t randnum; + size_t hwlen = ifp->hwlen, rlen = 0; + uint8_t buf[HWADDR_LEN], *bp = buf, *rp = (uint8_t *)&randnum; + char sbuf[HWADDR_LEN * 3]; + int retval; + + if (hwlen == 0) { + errno = ENOTSUP; return -1; + } + if (hwlen > sizeof(buf)) { + errno = ENOBUFS; + return -1; + } - ifp->flags = (unsigned int)ifr.ifr_flags; - return 0; + for (; hwlen != 0; hwlen--) { + if (rlen == 0) { + randnum = arc4random(); + rp = (uint8_t *)&randnum; + rlen = sizeof(randnum); + } + *bp++ = *rp++; + rlen--; + } + + /* Unicast address and locally administered. */ + buf[0] &= 0xFC; + buf[0] |= 0x02; + + logdebugx("%s: hardware address randomised to %s", + ifp->name, + hwaddr_ntoa(buf, ifp->hwlen, sbuf, sizeof(sbuf))); + retval = if_setmac(ifp, buf, ifp->hwlen); + if (retval == 0) + memcpy(ifp->hwaddr, buf, ifp->hwlen); + return retval; } static int @@ -227,8 +305,15 @@ addrflags = if_addrflags(ifp, &addr->sin_addr, ifa->ifa_name); if (addrflags == -1) { - if (errno != EEXIST && errno != EADDRNOTAVAIL) - logerr("%s: if_addrflags", __func__); + if (errno != EEXIST && errno != EADDRNOTAVAIL) { + char dbuf[INET_ADDRSTRLEN]; + const char *dbp; + + dbp = inet_ntop(AF_INET, &addr->sin_addr, + dbuf, sizeof(dbuf)); + logerr("%s: if_addrflags: %s%%%s", + __func__, dbp, ifp->name); + } continue; } #endif @@ -252,8 +337,15 @@ addrflags = if_addrflags6(ifp, &sin6->sin6_addr, ifa->ifa_name); if (addrflags == -1) { - if (errno != EEXIST && errno != EADDRNOTAVAIL) - logerr("%s: if_addrflags6", __func__); + if (errno != EEXIST && errno != EADDRNOTAVAIL) { + char dbuf[INET6_ADDRSTRLEN]; + const char *dbp; + + dbp = inet_ntop(AF_INET6, &sin6->sin6_addr, + dbuf, sizeof(dbuf)); + logerr("%s: if_addrflags6: %s%%%s", + __func__, dbp, ifp->name); + } continue; } #endif @@ -265,7 +357,12 @@ } } - freeifaddrs(*ifaddrs); +#ifdef PRIVSEP_GETIFADDRS + if (IN_PRIVSEP(ctx)) + free(*ifaddrs); + else +#endif + freeifaddrs(*ifaddrs); *ifaddrs = NULL; } @@ -302,6 +399,44 @@ return false; } +#if defined(AF_PACKET) && !defined(AF_LINK) +static unsigned int +if_check_arphrd(struct interface *ifp, unsigned int active, bool if_noconf) +{ + + switch(ifp->hwtype) { + case ARPHRD_ETHER: /* FALLTHROUGH */ + case ARPHRD_IEEE1394: /* FALLTHROUGH */ + case ARPHRD_INFINIBAND: /* FALLTHROUGH */ + case ARPHRD_NONE: /* FALLTHROUGH */ + break; + case ARPHRD_LOOPBACK: + case ARPHRD_PPP: + if (if_noconf && active) { + logdebugx("%s: ignoring due to interface type and" + " no config", + ifp->name); + active = IF_INACTIVE; + } + break; + default: + if (active) { + int i; + + if (if_noconf) + active = IF_INACTIVE; + i = active ? LOG_WARNING : LOG_DEBUG; + logmessage(i, "%s: unsupported" + " interface type 0x%.2x", + ifp->name, ifp->hwtype); + } + break; + } + + return active; +} +#endif + struct if_head * if_discover(struct dhcpcd_ctx *ctx, struct ifaddrs **ifaddrs, int argc, char * const *argv) @@ -317,12 +452,11 @@ const struct sockaddr_dl *sdl; #ifdef IFLR_ACTIVE struct if_laddrreq iflr = { .flags = IFLR_PREFIX }; - int link_fd; #endif -#elif AF_PACKET +#elif defined(AF_PACKET) const struct sockaddr_ll *sll; #endif -#if defined(SIOCGIFPRIORITY) || defined(SIOCGIFHWADDR) +#if defined(SIOCGIFPRIORITY) struct ifreq ifr; #endif @@ -330,28 +464,29 @@ logerr(__func__); return NULL; } - if (getifaddrs(ifaddrs) == -1) { - logerr(__func__); - free(ifs); - return NULL; - } TAILQ_INIT(ifs); -#ifdef IFLR_ACTIVE - link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (link_fd == -1) { - logerr(__func__); +#ifdef PRIVSEP_GETIFADDRS + if (ctx->options & DHCPCD_PRIVSEP) { + if (ps_root_getifaddrs(ctx, ifaddrs) == -1) { + logerr("ps_root_getifaddrs"); + free(ifs); + return NULL; + } + } else +#endif + if (getifaddrs(ifaddrs) == -1) { + logerr("getifaddrs"); free(ifs); return NULL; } -#endif for (ifa = *ifaddrs; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr != NULL) { #ifdef AF_LINK if (ifa->ifa_addr->sa_family != AF_LINK) continue; -#elif AF_PACKET +#elif defined(AF_PACKET) if (ifa->ifa_addr->sa_family != AF_PACKET) continue; #endif @@ -402,13 +537,17 @@ #ifdef PLUGIN_DEV /* Ensure that the interface name has settled */ - if (!dev_initialized(ctx, spec.devname)) + if (!dev_initialised(ctx, spec.devname)) { + logdebugx("%s: waiting for interface to initialise", + spec.devname); continue; + } #endif if (if_vimaster(ctx, spec.devname) == 1) { - logfunc_t *logfunc = argc != 0 ? logerrx : logdebugx; - logfunc("%s: is a Virtual Interface Master, skipping", + int loglevel = argc != 0 ? LOG_ERR : LOG_DEBUG; + logmessage(loglevel, + "%s: is a Virtual Interface Master, skipping", spec.devname); continue; } @@ -416,12 +555,11 @@ if_noconf = ((argc == 0 || argc == -1) && ctx->ifac == 0 && !if_hasconf(ctx, spec.devname)); - /* Don't allow loopback or pointopoint unless explicit. - * Don't allow some reserved interface names unless explicit. */ - if (if_noconf) { - if (ifa->ifa_flags & (IFF_LOOPBACK | IFF_POINTOPOINT) || - if_ignore(ctx, spec.devname)) - active = IF_INACTIVE; + /* Don't allow some reserved interface names unless explicit. */ + if (if_noconf && if_ignore(ctx, spec.devname)) { + logdebugx("%s: ignoring due to interface type and" + " no config", spec.devname); + active = IF_INACTIVE; } ifp = calloc(1, sizeof(*ifp)); @@ -445,7 +583,7 @@ MIN(ifa->ifa_addr->sa_len, sizeof(iflr.addr))); iflr.flags = IFLR_PREFIX; iflr.prefixlen = (unsigned int)sdl->sdl_alen * NBBY; - if (ioctl(link_fd, SIOCGLIFADDR, &iflr) == -1 || + if (ioctl(ctx->pf_link_fd, SIOCGLIFADDR, &iflr) == -1 || !(iflr.flags & IFLR_ACTIVE)) { if_free(ifp); @@ -464,9 +602,10 @@ #ifdef IFT_TUNNEL case IFT_TUNNEL: /* FALLTHROUGH */ #endif + case IFT_LOOP: /* FALLTHROUGH */ case IFT_PPP: /* Don't allow unless explicit */ - if (if_noconf) { + if (if_noconf && active) { logdebugx("%s: ignoring due to" " interface type and" " no config", @@ -482,94 +621,63 @@ case IFT_L3IPVLAN: /* FALLTHROUGH */ #endif case IFT_ETHER: - ifp->family = ARPHRD_ETHER; + ifp->hwtype = ARPHRD_ETHER; break; #ifdef IFT_IEEE1394 case IFT_IEEE1394: - ifp->family = ARPHRD_IEEE1394; + ifp->hwtype = ARPHRD_IEEE1394; break; #endif #ifdef IFT_INFINIBAND case IFT_INFINIBAND: - ifp->family = ARPHRD_INFINIBAND; + ifp->hwtype = ARPHRD_INFINIBAND; break; #endif default: /* Don't allow unless explicit */ - if (if_noconf) - active = IF_INACTIVE; - if (active) - logwarnx("%s: unsupported" + if (active) { + if (if_noconf) + active = IF_INACTIVE; + i = active ? LOG_WARNING : LOG_DEBUG; + logmessage(i, "%s: unsupported" " interface type 0x%.2x", ifp->name, sdl->sdl_type); + } /* Pretend it's ethernet */ - ifp->family = ARPHRD_ETHER; + ifp->hwtype = ARPHRD_ETHER; break; } ifp->hwlen = sdl->sdl_alen; memcpy(ifp->hwaddr, CLLADDR(sdl), ifp->hwlen); -#elif AF_PACKET +#elif defined(AF_PACKET) sll = (const void *)ifa->ifa_addr; ifp->index = (unsigned int)sll->sll_ifindex; - ifp->family = sll->sll_hatype; + ifp->hwtype = sll->sll_hatype; ifp->hwlen = sll->sll_halen; if (ifp->hwlen != 0) memcpy(ifp->hwaddr, sll->sll_addr, ifp->hwlen); + active = if_check_arphrd(ifp, active, if_noconf); #endif } -#ifdef SIOCGIFHWADDR +#ifdef __linux__ else { + struct ifreq ifr = { .ifr_flags = 0 }; + /* This is a huge bug in getifaddrs(3) as there * is no reason why this can't be returned in * ifa_addr. */ - memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name)); if (ioctl(ctx->pf_inet_fd, SIOCGIFHWADDR, &ifr) == -1) logerr("%s: SIOCGIFHWADDR", ifa->ifa_name); - ifp->family = ifr.ifr_hwaddr.sa_family; + ifp->hwtype = ifr.ifr_hwaddr.sa_family; if (ioctl(ctx->pf_inet_fd, SIOCGIFINDEX, &ifr) == -1) logerr("%s: SIOCGIFINDEX", ifa->ifa_name); ifp->index = (unsigned int)ifr.ifr_ifindex; + if_check_arphrd(ifp, active, if_noconf); } #endif - /* Ensure hardware address is valid. */ - if (!if_valid_hwaddr(ifp->hwaddr, ifp->hwlen)) - ifp->hwlen = 0; - - /* We only work on ethernet by default */ - if (ifp->family != ARPHRD_ETHER) { - if ((argc == 0 || argc == -1) && - ctx->ifac == 0 && !if_hasconf(ctx, ifp->name)) - active = IF_INACTIVE; - switch (ifp->family) { - case ARPHRD_IEEE1394: - case ARPHRD_INFINIBAND: -#ifdef ARPHRD_LOOPBACK - case ARPHRD_LOOPBACK: -#endif -#ifdef ARPHRD_PPP - case ARPHRD_PPP: -#endif -#ifdef ARPHRD_NONE - case ARPHRD_NONE: -#endif - /* We don't warn for supported families */ - break; - -/* IFT already checked */ -#ifndef AF_LINK - default: - if (active) - logwarnx("%s: unsupported" - " interface family 0x%.2x", - ifp->name, ifp->family); - break; -#endif - } - } - if (!(ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) { /* Handle any platform init for the interface */ if (active != IF_INACTIVE && if_init(ifp) == -1) { @@ -585,38 +693,38 @@ /* Respect the interface priority */ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); - if (ioctl(ctx->pf_inet_fd, SIOCGIFPRIORITY, &ifr) == 0) + if (pioctl(ctx, SIOCGIFPRIORITY, &ifr, sizeof(ifr)) == 0) ifp->metric = (unsigned int)ifr.ifr_metric; if_getssid(ifp); #else - /* We reserve the 100 range for virtual interfaces, if and when - * we can work them out. */ - ifp->metric = 200 + ifp->index; + /* Leave a low portion for user config */ + ifp->metric = RTMETRIC_BASE + ifp->index; if (if_getssid(ifp) != -1) { ifp->wireless = true; - ifp->metric += 100; + ifp->metric += RTMETRIC_WIRELESS; } #endif ifp->active = active; - if (ifp->active) - ifp->carrier = if_carrier(ifp); - else - ifp->carrier = LINK_UNKNOWN; + ifp->carrier = if_carrier(ifp, ifa->ifa_data); TAILQ_INSERT_TAIL(ifs, ifp, next); } -#ifdef IFLR_ACTIVE - close(link_fd); -#endif return ifs; } -/* Decode bge0:1 as dev = bge, ppa = 0 and lun = 1 */ +/* + * eth0.100:2 OR eth0i100:2 (seems to be NetBSD xvif(4) only) + * + * drvname == eth + * devname == eth0.100 OR eth0i100 + * ppa = 0 + * lun = 2 + */ int if_nametospec(const char *ifname, struct if_spec *spec) { - char *ep; + char *ep, *pp; int e; if (ifname == NULL || *ifname == '\0' || @@ -628,6 +736,8 @@ errno = EINVAL; return -1; } + + /* :N is an alias */ ep = strchr(spec->drvname, ':'); if (ep) { spec->lun = (int)strtoi(ep + 1, NULL, 10, 0, INT_MAX, &e); @@ -635,23 +745,51 @@ errno = e; return -1; } - *ep-- = '\0'; + *ep = '\0'; +#ifdef __sun + ep--; +#endif } else { spec->lun = -1; +#ifdef __sun ep = spec->drvname + strlen(spec->drvname) - 1; +#endif } + strlcpy(spec->devname, spec->drvname, sizeof(spec->devname)); +#ifdef __sun + /* Solaris has numbers in the driver name, such as e1000g */ while (ep > spec->drvname && isdigit((int)*ep)) ep--; if (*ep++ == ':') { errno = EINVAL; return -1; } - spec->ppa = (int)strtoi(ep, NULL, 10, 0, INT_MAX, &e); - if (e != 0) - spec->ppa = -1; +#else + /* BSD and Linux no not have numbers in the driver name */ + for (ep = spec->drvname; *ep != '\0' && !isdigit((int)*ep); ep++) { + if (*ep == ':') { + errno = EINVAL; + return -1; + } + } +#endif + spec->ppa = (int)strtoi(ep, &pp, 10, 0, INT_MAX, &e); *ep = '\0'; +#ifndef __sun + /* + * . is used for VLAN style names + * i is used on NetBSD for xvif interfaces + */ + if (pp != NULL && (*pp == '.' || *pp == 'i')) { + spec->vlid = (int)strtoi(pp + 1, NULL, 10, 0, INT_MAX, &e); + if (e) + spec->vlid = -1; + } else +#endif + spec->vlid = -1; + return 0; } @@ -717,7 +855,11 @@ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name)); ifr.ifr_mtu = mtu; - r = ioctl(ifp->ctx->pf_inet_fd, mtu ? SIOCSIFMTU : SIOCGIFMTU, &ifr); + if (mtu != 0) + r = if_ioctl(ifp->ctx, SIOCSIFMTU, &ifr, sizeof(ifr)); + else + r = pioctl(ifp->ctx, SIOCGIFMTU, &ifr, sizeof(ifr)); + if (r == -1) return -1; return ifr.ifr_mtu; @@ -740,9 +882,13 @@ struct cmsghdr *cm; unsigned int ifindex = 0; struct interface *ifp; -#if defined(INET) && defined(IP_PKTINFO) +#ifdef INET +#ifdef IP_RECVIF + struct sockaddr_dl sdl; +#else struct in_pktinfo ipi; #endif +#endif #ifdef INET6 struct in6_pktinfo ipi6; #else @@ -753,15 +899,27 @@ cm; cm = (struct cmsghdr *)CMSG_NXTHDR(msg, cm)) { -#if defined(INET) && defined(IP_PKTINFO) +#ifdef INET if (cm->cmsg_level == IPPROTO_IP) { switch(cm->cmsg_type) { +#ifdef IP_RECVIF + case IP_RECVIF: + if (cm->cmsg_len < + offsetof(struct sockaddr_dl, sdl_index) + + sizeof(sdl.sdl_index)) + continue; + memcpy(&sdl, CMSG_DATA(cm), + MIN(sizeof(sdl), cm->cmsg_len)); + ifindex = sdl.sdl_index; + break; +#else case IP_PKTINFO: if (cm->cmsg_len != CMSG_LEN(sizeof(ipi))) continue; memcpy(&ipi, CMSG_DATA(cm), sizeof(ipi)); ifindex = (unsigned int)ipi.ipi_ifindex; break; +#endif } } #endif @@ -803,9 +961,6 @@ #if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) int xflags, xtype = type; #endif -#ifdef SO_RERROR - int on; -#endif #ifndef HAVE_SOCK_CLOEXEC if (xtype & SOCK_CLOEXEC) @@ -830,18 +985,58 @@ goto out; #endif -#ifdef SO_RERROR - /* Tell recvmsg(2) to return ENOBUFS if the receiving socket overflows. */ - on = 1; - if (setsockopt(s, SOL_SOCKET, SO_RERROR, &on, sizeof(on)) == -1) - logerr("%s: SO_RERROR", __func__); + return s; + +#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) +out: + close(s); + return -1; +#endif +} + +int +xsocketpair(int domain, int type, int protocol, int fd[2]) +{ + int s; +#if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) + int xflags, xtype = type; +#endif + +#ifndef HAVE_SOCK_CLOEXEC + if (xtype & SOCK_CLOEXEC) + type &= ~SOCK_CLOEXEC; +#endif +#ifndef HAVE_SOCK_NONBLOCK + if (xtype & SOCK_NONBLOCK) + type &= ~SOCK_NONBLOCK; +#endif + + if ((s = socketpair(domain, type, protocol, fd)) == -1) + return -1; + +#ifndef HAVE_SOCK_CLOEXEC + if ((xtype & SOCK_CLOEXEC) && ((xflags = fcntl(fd[0], F_GETFD)) == -1 || + fcntl(fd[0], F_SETFD, xflags | FD_CLOEXEC) == -1)) + goto out; + if ((xtype & SOCK_CLOEXEC) && ((xflags = fcntl(fd[1], F_GETFD)) == -1 || + fcntl(fd[1], F_SETFD, xflags | FD_CLOEXEC) == -1)) + goto out; +#endif +#ifndef HAVE_SOCK_NONBLOCK + if ((xtype & SOCK_NONBLOCK) && ((xflags = fcntl(fd[0], F_GETFL)) == -1 || + fcntl(fd[0], F_SETFL, xflags | O_NONBLOCK) == -1)) + goto out; + if ((xtype & SOCK_NONBLOCK) && ((xflags = fcntl(fd[1], F_GETFL)) == -1 || + fcntl(fd[1], F_SETFL, xflags | O_NONBLOCK) == -1)) + goto out; #endif return s; #if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK) out: - close(s); + close(fd[0]); + close(fd[1]); return -1; #endif } Index: contrib/dhcpcd/src/ipv4.h =================================================================== --- contrib/dhcpcd/src/ipv4.h +++ contrib/dhcpcd/src/ipv4.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -110,10 +110,6 @@ struct ipv4_state { struct ipv4_addrhead addrs; - - /* Buffer for BPF */ - size_t buffer_size, buffer_len, buffer_pos; - char *buffer; }; #define IPV4_STATE(ifp) \ @@ -133,12 +129,13 @@ #define STATE_ADDED 0x01 #define STATE_FAKE 0x02 +#define STATE_EXPIRED 0x04 int ipv4_deladdr(struct ipv4_addr *, int); struct ipv4_addr *ipv4_addaddr(struct interface *, const struct in_addr *, const struct in_addr *, const struct in_addr *, uint32_t, uint32_t); -void ipv4_applyaddr(void *); +struct ipv4_addr *ipv4_applyaddr(void *); struct ipv4_addr *ipv4_iffindaddr(struct interface *, const struct in_addr *, const struct in_addr *); @@ -146,6 +143,8 @@ struct ipv4_addr *ipv4_findaddr(struct dhcpcd_ctx *, const struct in_addr *); struct ipv4_addr *ipv4_findmaskaddr(struct dhcpcd_ctx *, const struct in_addr *); +struct ipv4_addr *ipv4_findmaskbrd(struct dhcpcd_ctx *, + const struct in_addr *); void ipv4_markaddrsstale(struct interface *); void ipv4_deletestaleaddrs(struct interface *); void ipv4_handleifa(struct dhcpcd_ctx *, int, struct if_head *, const char *, Index: contrib/dhcpcd/src/ipv4.c =================================================================== --- contrib/dhcpcd/src/ipv4.c +++ contrib/dhcpcd/src/ipv4.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -49,6 +49,7 @@ #include "common.h" #include "dhcpcd.h" #include "dhcp.h" +#include "eloop.h" #include "if.h" #include "if-options.h" #include "ipv4.h" @@ -169,6 +170,23 @@ return NULL; } +static struct ipv4_addr * +ipv4_iffindmaskbrd(struct interface *ifp, const struct in_addr *addr) +{ + struct ipv4_state *state; + struct ipv4_addr *ap; + + state = IPV4_STATE(ifp); + if (state) { + TAILQ_FOREACH (ap, &state->addrs, next) { + if ((ap->brd.s_addr & ap->mask.s_addr) == + (addr->s_addr & ap->mask.s_addr)) + return ap; + } + } + return NULL; +} + struct ipv4_addr * ipv4_findaddr(struct dhcpcd_ctx *ctx, const struct in_addr *addr) { @@ -197,6 +215,20 @@ return NULL; } +struct ipv4_addr * +ipv4_findmaskbrd(struct dhcpcd_ctx *ctx, const struct in_addr *addr) +{ + struct interface *ifp; + struct ipv4_addr *ap; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + ap = ipv4_iffindmaskbrd(ifp, addr); + if (ap) + return ap; + } + return NULL; +} + int ipv4_hasaddr(const struct interface *ifp) { @@ -429,7 +461,10 @@ in.s_addr = INADDR_ANY; sa_in_init(&rth->rt_gateway, &in); rth->rt_mtu = dhcp_get_mtu(ifp); - sa_in_init(&rth->rt_ifa, &state->addr->addr); + if (state->addr != NULL) + sa_in_init(&rth->rt_ifa, &state->addr->addr); + else + rth->rt_ifa.sa_family = AF_UNSPEC; /* We need to insert the host route just before the router. */ while ((rtp = RB_TREE_MAX(routes)) != NULL) { @@ -561,8 +596,6 @@ return NULL; } TAILQ_INIT(&state->addrs); - state->buffer_size = state->buffer_len = state->buffer_pos = 0; - state->buffer = NULL; } return state; } @@ -654,13 +687,18 @@ #endif ia->flags = IPV4_AF_NEW; } else - ia->flags |= ~IPV4_AF_NEW; + ia->flags &= ~IPV4_AF_NEW; ia->mask = *mask; ia->brd = *bcast; #ifdef IP_LIFETIME - ia->vltime = vltime; - ia->pltime = pltime; + if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { + /* We don't want the kernel to expire the address. */ + ia->vltime = ia->pltime = DHCP_INFINITE_LIFETIME; + } else { + ia->vltime = vltime; + ia->pltime = pltime; + } #else UNUSED(vltime); UNUSED(pltime); @@ -687,7 +725,8 @@ if (errno != EEXIST) logerr("%s: if_addaddress", __func__); - free(ia); + if (ia->flags & IPV4_AF_NEW) + free(ia); return NULL; } @@ -720,7 +759,7 @@ return 0; } -void +struct ipv4_addr * ipv4_applyaddr(void *arg) { struct interface *ifp = arg; @@ -730,7 +769,7 @@ struct ipv4_addr *ia; if (state == NULL) - return; + return NULL; lease = &state->lease; if (state->new == NULL) { @@ -749,7 +788,7 @@ script_runreason(ifp, state->reason); } else rt_build(ifp->ctx, AF_INET); - return; + return NULL; } ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); @@ -775,22 +814,22 @@ #endif #ifndef IP_LIFETIME if (ipv4_daddaddr(ifp, lease) == -1 && errno != EEXIST) - return; + return NULL; #endif } #ifdef IP_LIFETIME if (ipv4_daddaddr(ifp, lease) == -1 && errno != EEXIST) - return; + return NULL; #endif ia = ipv4_iffindaddr(ifp, &lease->addr, NULL); if (ia == NULL) { logerrx("%s: added address vanished", ifp->name); - return; + return NULL; } #if defined(ARP) && defined(IN_IFF_NOTUSEABLE) if (ia->addr_flags & IN_IFF_NOTUSEABLE) - return; + return NULL; #endif /* Delete the old address if different */ @@ -812,6 +851,7 @@ script_runreason(ifp, state->reason); dhcpcd_daemonise(ifp->ctx); } + return ia; } void @@ -941,7 +981,7 @@ #endif } - if (cmd == RTM_DELADDR && ia != NULL) + if (cmd == RTM_DELADDR) free(ia); } @@ -951,15 +991,12 @@ struct ipv4_state *state; struct ipv4_addr *ia; - if (ifp) { - state = IPV4_STATE(ifp); - if (state) { - while ((ia = TAILQ_FIRST(&state->addrs))) { - TAILQ_REMOVE(&state->addrs, ia, next); - free(ia); - } - free(state->buffer); - free(state); - } + if (ifp == NULL || (state = IPV4_STATE(ifp)) == NULL) + return; + + while ((ia = TAILQ_FIRST(&state->addrs))) { + TAILQ_REMOVE(&state->addrs, ia, next); + free(ia); } + free(state); } Index: contrib/dhcpcd/src/ipv4ll.h =================================================================== --- contrib/dhcpcd/src/ipv4ll.h +++ contrib/dhcpcd/src/ipv4ll.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -43,11 +43,13 @@ struct ipv4ll_state { struct in_addr pickedaddr; struct ipv4_addr *addr; - struct arp_state *arp; char randomstate[128]; bool seeded; bool down; size_t conflicts; +#ifndef KERNEL_RFC5227 + struct arp_state *arp; +#endif }; #define IPV4LL_STATE(ifp) \ Index: contrib/dhcpcd/src/ipv4ll.c =================================================================== --- contrib/dhcpcd/src/ipv4ll.c +++ contrib/dhcpcd/src/ipv4ll.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -36,7 +36,7 @@ #include #include -#define ELOOP_QUEUE 6 +#define ELOOP_QUEUE IPV4LL #include "config.h" #include "arp.h" #include "common.h" @@ -57,12 +57,10 @@ .s_addr = HTONL(LINKLOCAL_BCAST) }; -static void ipv4ll_start1(struct interface *, struct arp_state *); - -static in_addr_t +static void ipv4ll_pickaddr(struct interface *ifp) { - struct in_addr addr; + struct in_addr addr = { .s_addr = 0 }; struct ipv4ll_state *state; state = IPV4LL_STATE(ifp); @@ -88,7 +86,7 @@ /* Restore the original random state */ setstate(ifp->ctx->randomstate); - return addr.s_addr; + state->pickedaddr = addr; } int @@ -113,6 +111,7 @@ in.s_addr = INADDR_ANY; sa_in_init(&rt->rt_gateway, &in); sa_in_init(&rt->rt_ifa, &state->addr->addr); + rt->rt_dflags |= RTDF_IPV4LL; return rt_proto_add(routes, rt) ? 1 : 0; } @@ -136,6 +135,10 @@ sa_in_init(&rt->rt_netmask, &in); sa_in_init(&rt->rt_gateway, &in); sa_in_init(&rt->rt_ifa, &state->addr->addr); + rt->rt_dflags |= RTDF_IPV4LL; +#ifdef HAVE_ROUTE_METRIC + rt->rt_metric += RTMETRIC_IPV4LL; +#endif return rt_proto_add(routes, rt) ? 1 : 0; } @@ -181,8 +184,10 @@ #endif } +#ifndef KERNEL_RFC5227 +/* This is the callback by ARP freeing */ static void -ipv4ll_arpfree(struct arp_state *astate) +ipv4ll_free_arp(struct arp_state *astate) { struct ipv4ll_state *state; @@ -191,28 +196,40 @@ state->arp = NULL; } +/* This is us freeing any ARP state */ +static void +ipv4ll_freearp(struct interface *ifp) +{ + struct ipv4ll_state *state; + + state = IPV4LL_STATE(ifp); + if (state == NULL || state->arp == NULL) + return; + + eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp); + arp_free(state->arp); + state->arp = NULL; +} +#else +#define ipv4ll_freearp(ifp) +#endif + static void ipv4ll_not_found(struct interface *ifp) { struct ipv4ll_state *state; struct ipv4_addr *ia; -#ifdef KERNEL_RFC5227 struct arp_state *astate; - bool new_addr; -#endif state = IPV4LL_STATE(ifp); - assert(state != NULL); - ia = ipv4_iffindaddr(ifp, &state->pickedaddr, &inaddr_llmask); -#ifdef KERNEL_RFC5227 - new_addr = ia == NULL; -#endif #ifdef IN_IFF_NOTREADY if (ia == NULL || ia->addr_flags & IN_IFF_NOTREADY) #endif loginfox("%s: using IPv4LL address %s", ifp->name, inet_ntoa(state->pickedaddr)); + if (!(ifp->options->options & DHCPCD_CONFIGURE)) + goto run; if (ia == NULL) { if (ifp->ctx->options & DHCPCD_TEST) goto test; @@ -227,6 +244,7 @@ return; logdebugx("%s: DAD completed for %s", ifp->name, ia->saddr); #endif + test: state->addr = ia; state->down = false; @@ -236,47 +254,28 @@ return; } rt_build(ifp->ctx, AF_INET); -#ifdef KERNEL_RFC5227 - if (!new_addr) { - astate = arp_new(ifp, &ia->addr); - if (astate != NULL) { - astate->announced_cb = ipv4ll_announced_arp; - astate->free_cb = ipv4ll_arpfree; - arp_announce(astate); - } - } -#else - arp_announce(state->arp); -#endif +run: + astate = arp_announceaddr(ifp->ctx, &ia->addr); + if (astate != NULL) + astate->announced_cb = ipv4ll_announced_arp; script_runreason(ifp, "IPV4LL"); dhcpcd_daemonise(ifp->ctx); } -static void -ipv4ll_startifp(void *arg) -{ - struct interface *ifp = arg; - struct ipv4ll_state *state; - - state = IPV4LL_STATE(ifp); - ipv4ll_start1(ifp, state->arp); -} - static void ipv4ll_found(struct interface *ifp) { struct ipv4ll_state *state = IPV4LL_STATE(ifp); - if (state->arp != NULL) - arp_cancel(state->arp); + ipv4ll_freearp(ifp); if (++state->conflicts == MAX_CONFLICTS) logerrx("%s: failed to acquire an IPv4LL address", ifp->name); - state->pickedaddr.s_addr = ipv4ll_pickaddr(ifp); + ipv4ll_pickaddr(ifp); eloop_timeout_add_sec(ifp->ctx->eloop, state->conflicts >= MAX_CONFLICTS ? RATE_LIMIT_INTERVAL : PROBE_WAIT, - ipv4ll_startifp, ifp); + ipv4ll_start, ifp); } static void @@ -284,62 +283,50 @@ { struct ipv4ll_state *state = IPV4LL_STATE(ifp); - if (state->arp != NULL) - arp_cancel(state->arp); - ipv4_deladdr(state->addr, 1); - state->down = true; + ipv4ll_freearp(ifp); + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(state->addr, 1); state->addr = NULL; rt_build(ifp->ctx, AF_INET); script_runreason(ifp, "IPV4LL"); - state->pickedaddr.s_addr = ipv4ll_pickaddr(ifp); - ipv4ll_start1(ifp, state->arp); + ipv4ll_pickaddr(ifp); + ipv4ll_start(ifp); } #ifndef KERNEL_RFC5227 static void ipv4ll_not_found_arp(struct arp_state *astate) { - struct interface *ifp; - struct ipv4ll_state *state; - assert(astate != NULL); - assert(astate->iface != NULL); - - ifp = astate->iface; - state = IPV4LL_STATE(ifp); - assert(state != NULL); - assert(state->arp == astate); - ipv4ll_not_found(ifp); + ipv4ll_not_found(astate->iface); } static void ipv4ll_found_arp(struct arp_state *astate, __unused const struct arp_msg *amsg) { - struct interface *ifp = astate->iface; - struct ipv4ll_state *state = IPV4LL_STATE(ifp); - assert(state->arp == astate); - ipv4ll_found(ifp); + ipv4ll_found(astate->iface); } static void ipv4ll_defend_failed_arp(struct arp_state *astate) { - struct ipv4ll_state *state = IPV4LL_STATE(astate->iface); - assert(state->arp == astate); ipv4ll_defend_failed(astate->iface); } #endif -static void -ipv4ll_start1(struct interface *ifp, struct arp_state *astate) +void +ipv4ll_start(void *arg) { + struct interface *ifp = arg; struct ipv4ll_state *state; struct ipv4_addr *ia; bool repick; +#ifndef KERNEL_RFC5227 + struct arp_state *astate; +#endif - assert(ifp != NULL); if ((state = IPV4LL_STATE(ifp)) == NULL) { ifp->if_data[IF_DATA_IPV4LL] = calloc(1, sizeof(*state)); if ((state = IPV4LL_STATE(ifp)) == NULL) { @@ -375,26 +362,6 @@ state->seeded = true; } -#ifndef KERNEL_RFC5227 - if (astate == NULL) { - if (state->arp != NULL) - return; - if ((astate = arp_new(ifp, NULL)) == NULL) - return; - astate->found_cb = ipv4ll_found_arp; - astate->not_found_cb = ipv4ll_not_found_arp; - astate->announced_cb = ipv4ll_announced_arp; - astate->defend_failed_cb = ipv4ll_defend_failed_arp; - astate->free_cb = ipv4ll_arpfree; - state->arp = astate; - } else - assert(state->arp == astate); -#else - UNUSED(astate); -#endif - - state->down = true; - /* Find the previosuly used address. */ if (state->pickedaddr.s_addr != INADDR_ANY) ia = ipv4_iffindaddr(ifp, &state->pickedaddr, NULL); @@ -410,17 +377,16 @@ if (ia != NULL && ia->addr_flags & IN_IFF_DUPLICATED) { state->pickedaddr = ia->addr; /* So it's not picked again. */ repick = true; - ipv4_deladdr(ia, 0); + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(ia, 0); ia = NULL; } #endif state->addr = ia; + state->down = true; if (ia != NULL) { state->pickedaddr = ia->addr; -#ifndef KERNEL_RFC5227 - astate->addr = ia->addr; -#endif #ifdef IN_IFF_TENTATIVE if (ia->addr_flags & (IN_IFF_TENTATIVE | IN_IFF_DETACHED)) { loginfox("%s: waiting for DAD to complete on %s", @@ -431,41 +397,27 @@ #ifdef IN_IFF_DUPLICATED loginfox("%s: using IPv4LL address %s", ifp->name, ia->saddr); #endif - ipv4ll_not_found(ifp); - return; + } else { + loginfox("%s: probing for an IPv4LL address", ifp->name); + if (repick || state->pickedaddr.s_addr == INADDR_ANY) + ipv4ll_pickaddr(ifp); } - loginfox("%s: probing for an IPv4LL address", ifp->name); - if (repick || state->pickedaddr.s_addr == INADDR_ANY) - state->pickedaddr.s_addr = ipv4ll_pickaddr(ifp); -#ifndef KERNEL_RFC5227 - astate->addr = state->pickedaddr; -#endif -#ifdef IN_IFF_DUPLICATED +#ifdef KERNEL_RFC5227 ipv4ll_not_found(ifp); #else - arp_probe(astate); -#endif -} - -void -ipv4ll_start(void *arg) -{ - - ipv4ll_start1(arg, NULL); -} - -static void -ipv4ll_freearp(struct interface *ifp) -{ - struct ipv4ll_state *state; - - state = IPV4LL_STATE(ifp); - if (state == NULL || state->arp == NULL) + ipv4ll_freearp(ifp); + state->arp = astate = arp_new(ifp, &state->pickedaddr); + if (state->arp == NULL) return; - eloop_timeout_delete(ifp->ctx->eloop, NULL, state->arp); - arp_free(state->arp); + astate->found_cb = ipv4ll_found_arp; + astate->not_found_cb = ipv4ll_not_found_arp; + astate->announced_cb = ipv4ll_announced_arp; + astate->defend_failed_cb = ipv4ll_defend_failed_arp; + astate->free_cb = ipv4ll_free_arp; + arp_probe(astate); +#endif } void @@ -484,7 +436,8 @@ state = IPV4LL_STATE(ifp); if (state && state->addr != NULL) { - ipv4_deladdr(state->addr, 1); + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(state->addr, 1); state->addr = NULL; dropped = true; } @@ -495,7 +448,8 @@ TAILQ_FOREACH_SAFE(ia, &istate->addrs, next, ian) { if (IN_LINKLOCAL(ntohl(ia->addr.s_addr))) { - ipv4_deladdr(ia, 0); + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(ia, 0); dropped = true; } } @@ -514,6 +468,7 @@ if (state == NULL) return; + ipv4ll_freearp(ifp); state->pickedaddr.s_addr = INADDR_ANY; state->seeded = false; } @@ -585,10 +540,9 @@ ipv4ll_not_found(ifp); else if (ia->addr_flags & IN_IFF_DUPLICATED) { logerrx("%s: DAD detected %s", ifp->name, ia->saddr); -#ifdef KERNEL_RFC5227 - arp_freeaddr(ifp, &ia->addr); -#endif - ipv4_deladdr(ia, 1); + ipv4ll_freearp(ifp); + if (ifp->options->options & DHCPCD_CONFIGURE) + ipv4_deladdr(ia, 1); state->addr = NULL; rt_build(ifp->ctx, AF_INET); ipv4ll_found(ifp); Index: contrib/dhcpcd/src/ipv6.h =================================================================== --- contrib/dhcpcd/src/ipv6.h +++ contrib/dhcpcd/src/ipv6.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -59,14 +59,17 @@ #define TEMP_PREFERRED_LIFETIME 86400 /* 1 day */ #define REGEN_ADVANCE 5 /* seconds */ #define MAX_DESYNC_FACTOR 600 /* 10 minutes */ - #define TEMP_IDGEN_RETRIES 3 -#define GEN_TEMPID_RETRY_MAX 5 /* RFC7217 constants */ #define IDGEN_RETRIES 3 #define IDGEN_DELAY 1 /* second */ +/* Interface identifier length. Prefix + this == 128 for autoconf */ +#define ipv6_ifidlen(ifp) 64 +#define IA6_CANAUTOCONF(ia) \ + ((ia)->prefix_len + ipv6_ifidlen((ia)->iface) == 128) + #ifndef IN6_ARE_MASKED_ADDR_EQUAL #define IN6_ARE_MASKED_ADDR_EQUAL(d, a, m) ( \ (((d)->s6_addr32[0] ^ (a)->s6_addr32[0]) & (m)->s6_addr32[0]) == 0 && \ @@ -105,6 +108,11 @@ # undef IPV6_POLLADDRFLAG #endif +/* Of course OpenBSD has their own special name. */ +#if !defined(IN6_IFF_TEMPORARY) && defined(IN6_IFF_PRIVACY) +#define IN6_IFF_TEMPORARY IN6_IFF_PRIVACY +#endif + #ifdef __sun /* Solaris lacks these defines. * While it supports DaD, to seems to only expose IFF_DUPLICATE @@ -208,18 +216,19 @@ #define IPV6_AF_STALE (1U << 2) #define IPV6_AF_ADDED (1U << 3) #define IPV6_AF_AUTOCONF (1U << 4) -#define IPV6_AF_DUPLICATED (1U << 5) -#define IPV6_AF_DADCOMPLETED (1U << 6) -#define IPV6_AF_DELEGATED (1U << 7) -#define IPV6_AF_DELEGATEDPFX (1U << 8) -#define IPV6_AF_NOREJECT (1U << 9) -#define IPV6_AF_REQUEST (1U << 10) -#define IPV6_AF_STATIC (1U << 11) -#define IPV6_AF_DELEGATEDLOG (1U << 12) -#define IPV6_AF_RAPFX (1U << 13) -#define IPV6_AF_EXTENDED (1U << 14) +#define IPV6_AF_DADCOMPLETED (1U << 5) +#define IPV6_AF_DELEGATED (1U << 6) +#define IPV6_AF_DELEGATEDPFX (1U << 7) +#define IPV6_AF_NOREJECT (1U << 8) +#define IPV6_AF_REQUEST (1U << 9) +#define IPV6_AF_STATIC (1U << 10) +#define IPV6_AF_DELEGATEDLOG (1U << 11) +#define IPV6_AF_RAPFX (1U << 12) +#define IPV6_AF_EXTENDED (1U << 13) +#define IPV6_AF_REGEN (1U << 14) +#define IPV6_AF_ROUTER (1U << 15) #ifdef IPV6_MANAGETEMPADDR -#define IPV6_AF_TEMPORARY (1U << 15) +#define IPV6_AF_TEMPORARY (1U << 16) #endif struct ll_callback { @@ -234,10 +243,7 @@ struct ll_callback_head ll_callbacks; #ifdef IPV6_MANAGETEMPADDR - time_t desync_factor; - uint8_t randomseed0[8]; /* upper 64 bits of MD5 digest */ - uint8_t randomseed1[8]; /* lower 64 bits */ - uint8_t randomid[8]; + uint32_t desync_factor; #endif }; @@ -249,11 +255,10 @@ int ipv6_init(struct dhcpcd_ctx *); -int ipv6_makestableprivate(struct in6_addr *addr, - const struct in6_addr *prefix, int prefix_len, - const struct interface *ifp, int *dad_counter); +int ipv6_makestableprivate(struct in6_addr *, + const struct in6_addr *, int, const struct interface *, int *); int ipv6_makeaddr(struct in6_addr *, struct interface *, - const struct in6_addr *, int); + const struct in6_addr *, int, unsigned int); int ipv6_mask(struct in6_addr *, int); uint8_t ipv6_prefixlen(const struct in6_addr *); int ipv6_userprefix( const struct in6_addr *, short prefix_len, @@ -262,6 +267,7 @@ void ipv6_markaddrsstale(struct interface *, unsigned int); void ipv6_deletestaleaddrs(struct interface *); int ipv6_addaddr(struct ipv6_addr *, const struct timespec *); +int ipv6_doaddr(struct ipv6_addr *, struct timespec *); ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs); void ipv6_deleteaddr(struct ipv6_addr *); void ipv6_freedrop_addrs(struct ipv6_addrhead *, int, @@ -282,22 +288,21 @@ const struct in6_addr *); #define ipv6_linklocal(ifp) ipv6_iffindaddr((ifp), NULL, IN6_IFF_NOTUSEABLE) int ipv6_addlinklocalcallback(struct interface *, void (*)(void *), void *); -struct ipv6_addr *ipv6_newaddr(struct interface *, const struct in6_addr *, uint8_t, - unsigned int); +void ipv6_setscope(struct sockaddr_in6 *, unsigned int); +unsigned int ipv6_getscope(const struct sockaddr_in6 *); +struct ipv6_addr *ipv6_newaddr(struct interface *, const struct in6_addr *, + uint8_t, unsigned int); void ipv6_freeaddr(struct ipv6_addr *); void ipv6_freedrop(struct interface *, int); #define ipv6_free(ifp) ipv6_freedrop((ifp), 0) #define ipv6_drop(ifp) ipv6_freedrop((ifp), 2) #ifdef IPV6_MANAGETEMPADDR -void ipv6_gentempifid(struct interface *); struct ipv6_addr *ipv6_createtempaddr(struct ipv6_addr *, const struct timespec *); struct ipv6_addr *ipv6_settemptime(struct ipv6_addr *, int); void ipv6_addtempaddrs(struct interface *, const struct timespec *); -#else -#define ipv6_gentempifid(a) {} -#define ipv6_settempstale(a) {} +void ipv6_regentempaddrs(void *); #endif int ipv6_start(struct interface *); Index: contrib/dhcpcd/src/ipv6.c =================================================================== --- contrib/dhcpcd/src/ipv6.c +++ contrib/dhcpcd/src/ipv6.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -57,9 +57,10 @@ #include #include #include +#include #include -#define ELOOP_QUEUE 7 +#define ELOOP_QUEUE ELOOP_IPV6 #include "common.h" #include "if.h" #include "dhcpcd.h" @@ -68,6 +69,7 @@ #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" +#include "privsep.h" #include "sa.h" #include "script.h" @@ -103,7 +105,7 @@ defined(IFF_NOLINKLOCAL) /* Only add the LL address if we have a carrier, so DaD works. */ #define CAN_ADD_LLADDR(ifp) \ - (!((ifp)->options->options & DHCPCD_LINK) || (ifp)->carrier != LINK_DOWN) + (!((ifp)->options->options & DHCPCD_LINK) || if_is_link_up((ifp))) #ifdef __sun /* Although we can add our own LL address, we cannot drop it * without unplumbing the if which is a lot of code. @@ -120,10 +122,7 @@ #endif #ifdef IPV6_MANAGETEMPADDR -static void ipv6_regentempifid(void *); static void ipv6_regentempaddr(void *); -#else -#define ipv6_regentempifid(a) {} #endif int @@ -141,25 +140,27 @@ #ifndef __sun ctx->nd_fd = -1; #endif - ctx->dhcp6_fd = -1; +#ifdef DHCP6 + ctx->dhcp6_rfd = -1; + ctx->dhcp6_wfd = -1; +#endif return 0; } static ssize_t ipv6_readsecret(struct dhcpcd_ctx *ctx) { - FILE *fp; char line[1024]; unsigned char *p; size_t len; uint32_t r; - int x; - if ((ctx->secret_len = read_hwaddr_aton(&ctx->secret, SECRET)) != 0) + ctx->secret_len = dhcp_read_hwaddr_aton(ctx, &ctx->secret, SECRET); + if (ctx->secret_len != 0) return (ssize_t)ctx->secret_len; if (errno != ENOENT) - logerr("%s: %s", __func__, SECRET); + logerr("%s: cannot read secret", __func__); /* Chaining arc4random should be good enough. * RFC7217 section 5.1 states the key SHOULD be at least 128 bits. @@ -179,27 +180,18 @@ p += sizeof(r); } - /* Ensure that only the dhcpcd user can read the secret. - * Write permission is also denied as chaning it would remove - * it's stability. */ - if ((fp = fopen(SECRET, "w")) == NULL || - chmod(SECRET, S_IRUSR) == -1) - goto eexit; - x = fprintf(fp, "%s\n", - hwaddr_ntoa(ctx->secret, ctx->secret_len, line, sizeof(line))); - if (fclose(fp) == EOF) - x = -1; - fp = NULL; - if (x > 0) - return (ssize_t)ctx->secret_len; - -eexit: - logerr("%s: %s", __func__, SECRET); - if (fp != NULL) - fclose(fp); - unlink(SECRET); - ctx->secret_len = 0; - return -1; + hwaddr_ntoa(ctx->secret, ctx->secret_len, line, sizeof(line)); + len = strlen(line); + if (len < sizeof(line) - 2) { + line[len++] = '\n'; + line[len] = '\0'; + } + if (dhcp_writefile(ctx, SECRET, S_IRUSR, line, len) == -1) { + logerr("%s: cannot write secret", __func__); + ctx->secret_len = 0; + return -1; + } + return (ssize_t)ctx->secret_len; } /* http://www.iana.org/assignments/ipv6-interface-ids/ipv6-interface-ids.xhtml @@ -216,7 +208,7 @@ { 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } } }; -static int +static bool ipv6_reserved(const struct in6_addr *addr) { uint64_t id, low, high; @@ -226,37 +218,42 @@ id = be64dec(addr->s6_addr + sizeof(id)); if (id == 0) /* RFC4291 */ return 1; - for (i = 0; i < sizeof(reslowhigh) / sizeof(reslowhigh[0]); i++) { + for (i = 0; i < __arraycount(reslowhigh); i++) { r = &reslowhigh[i]; low = be64dec(r->low); high = be64dec(r->high); if (id >= low && id <= high) - return 1; + return true; } - return 0; + return false; } /* RFC7217 */ static int -ipv6_makestableprivate1(struct in6_addr *addr, - const struct in6_addr *prefix, int prefix_len, +ipv6_makestableprivate1(struct dhcpcd_ctx *ctx, + struct in6_addr *addr, const struct in6_addr *prefix, int prefix_len, const unsigned char *netiface, size_t netiface_len, const unsigned char *netid, size_t netid_len, unsigned short vlanid, - uint32_t *dad_counter, - const unsigned char *secret, size_t secret_len) + uint32_t *dad_counter) { unsigned char buf[2048], *p, digest[SHA256_DIGEST_LENGTH]; size_t len, l; - SHA256_CTX ctx; + SHA256_CTX sha_ctx; if (prefix_len < 0 || prefix_len > 120) { errno = EINVAL; return -1; } + if (ctx->secret_len == 0) { + if (ipv6_readsecret(ctx) == -1) + return -1; + } + l = (size_t)(ROUNDUP8(prefix_len) / NBBY); - len = l + netiface_len + netid_len + sizeof(*dad_counter) + secret_len; + len = l + netiface_len + netid_len + sizeof(*dad_counter) + + ctx->secret_len; if (vlanid != 0) len += sizeof(vlanid); if (len > sizeof(buf)) { @@ -281,15 +278,15 @@ } memcpy(p, dad_counter, sizeof(*dad_counter)); p += sizeof(*dad_counter); - memcpy(p, secret, secret_len); + memcpy(p, ctx->secret, ctx->secret_len); /* Make an address using the digest of the above. * RFC7217 Section 5.1 states that we shouldn't use MD5. * Pity as we use that for HMAC-MD5 which is still deemed OK. * SHA-256 is recommended */ - SHA256_Init(&ctx); - SHA256_Update(&ctx, buf, len); - SHA256_Final(digest, &ctx); + SHA256_Init(&sha_ctx); + SHA256_Update(&sha_ctx, buf, len); + SHA256_Final(digest, &sha_ctx); p = addr->s6_addr; memcpy(p, prefix, l); @@ -319,29 +316,52 @@ uint32_t dad; int r; - if (ifp->ctx->secret_len == 0) { - if (ipv6_readsecret(ifp->ctx) == -1) - return -1; - } - dad = (uint32_t)*dad_counter; /* For our implementation, we shall set the hardware address * as the interface identifier */ - r = ipv6_makestableprivate1(addr, prefix, prefix_len, + r = ipv6_makestableprivate1(ifp->ctx, addr, prefix, prefix_len, ifp->hwaddr, ifp->hwlen, ifp->ssid, ifp->ssid_len, - ifp->vlanid, &dad, - ifp->ctx->secret, ifp->ctx->secret_len); + ifp->vlanid, &dad); if (r == 0) *dad_counter = (int)dad; return r; } +#ifdef IPV6_AF_TEMPORARY +static int +ipv6_maketemporaryaddress(struct in6_addr *addr, + const struct in6_addr *prefix, int prefix_len, + const struct interface *ifp) +{ + struct in6_addr mask; + struct interface *ifpn; + + if (ipv6_mask(&mask, prefix_len) == -1) + return -1; + *addr = *prefix; + +again: + addr->s6_addr32[2] |= (arc4random() & ~mask.s6_addr32[2]); + addr->s6_addr32[3] |= (arc4random() & ~mask.s6_addr32[3]); + + TAILQ_FOREACH(ifpn, ifp->ctx->ifaces, next) { + if (ipv6_iffindaddr(ifpn, addr, 0) != NULL) + break; + } + if (ifpn != NULL) + goto again; + if (ipv6_reserved(addr)) + goto again; + return 0; +} +#endif + int ipv6_makeaddr(struct in6_addr *addr, struct interface *ifp, - const struct in6_addr *prefix, int prefix_len) + const struct in6_addr *prefix, int prefix_len, unsigned int flags) { const struct ipv6_addr *ap; int dad; @@ -351,6 +371,13 @@ return -1; } +#ifdef IPV6_AF_TEMPORARY + if (flags & IPV6_AF_TEMPORARY) + return ipv6_maketemporaryaddress(addr, prefix, prefix_len, ifp); +#else + UNUSED(flags); +#endif + if (ifp->options->options & DHCPCD_SLAACPRIVATE) { dad = 0; if (ipv6_makestableprivate(addr, @@ -380,25 +407,14 @@ static int ipv6_makeprefix(struct in6_addr *prefix, const struct in6_addr *addr, int len) { - int bytes, bits; + struct in6_addr mask; + size_t i; - if (len < 0 || len > 128) { - errno = EINVAL; + if (ipv6_mask(&mask, len) == -1) return -1; - } - - bytes = len / NBBY; - bits = len % NBBY; - memcpy(&prefix->s6_addr, &addr->s6_addr, (size_t)bytes); - if (bits != 0) { - /* Coverify false positive. - * bytelen cannot be 16 if bitlen is non zero */ - /* coverity[overrun-local] */ - prefix->s6_addr[bytes] = - (uint8_t)(prefix->s6_addr[bytes] >> (NBBY - bits)); - } - memset((char *)prefix->s6_addr + bytes, 0, - sizeof(prefix->s6_addr) - (size_t)bytes); + *prefix = *addr; + for (i = 0; i < sizeof(prefix->s6_addr); i++) + prefix->s6_addr[i] &= mask.s6_addr[i]; return 0; } @@ -568,11 +584,8 @@ &ia->addr, ia->prefix_len, flags, 0); } else { /* Still tentative? Check again in a bit. */ - struct timespec tv; - - ms_to_ts(&tv, RETRANS_TIMER / 2); - eloop_timeout_add_tv(ia->iface->ctx->eloop, &tv, - ipv6_checkaddrflags, ia); + eloop_timeout_add_msec(ia->iface->ctx->eloop, + RETRANS_TIMER / 2, ipv6_checkaddrflags, ia); } } #endif @@ -581,7 +594,11 @@ ipv6_deletedaddr(struct ipv6_addr *ia) { -#ifdef SMALL +#ifdef DHCP6 +#ifdef PRIVSEP + if (!(ia->iface->ctx->options & DHCPCD_MANAGER)) + ps_inet_closedhcp6(ia); +#elif defined(SMALL) UNUSED(ia); #else /* NOREJECT is set if we delegated exactly the prefix to another @@ -591,6 +608,9 @@ if (ia->delegating_prefix != NULL) ia->delegating_prefix->flags &= ~IPV6_AF_NOREJECT; #endif +#else + UNUSED(ia); +#endif } void @@ -627,7 +647,7 @@ { struct interface *ifp; uint32_t pltime, vltime; - __printflike(1, 2) void (*logfunc)(const char *, ...); + int loglevel; #ifdef ND6_ADVERTISE bool vltime_was_zero = ia->prefix_vltime == 0; #endif @@ -659,37 +679,43 @@ /* Adjust plftime and vltime based on acquired time */ pltime = ia->prefix_pltime; vltime = ia->prefix_vltime; + + if (ifp->options->options & DHCPCD_LASTLEASE_EXTEND) { + /* We don't want the kernel to expire the address. + * The saved times will be re-applied to the ia + * before exiting this function. */ + ia->prefix_vltime = ia->prefix_pltime = ND6_INFINITE_LIFETIME; + } + if (timespecisset(&ia->acquired) && (ia->prefix_pltime != ND6_INFINITE_LIFETIME || ia->prefix_vltime != ND6_INFINITE_LIFETIME)) { + uint32_t elapsed; struct timespec n; if (now == NULL) { clock_gettime(CLOCK_MONOTONIC, &n); now = &n; } - timespecsub(now, &ia->acquired, &n); + elapsed = (uint32_t)eloop_timespec_diff(now, &ia->acquired, + NULL); if (ia->prefix_pltime != ND6_INFINITE_LIFETIME) { - ia->prefix_pltime -= (uint32_t)n.tv_sec; - /* This can happen when confirming a - * deprecated but still valid lease. */ - if (ia->prefix_pltime > pltime) + if (elapsed > ia->prefix_pltime) ia->prefix_pltime = 0; + else + ia->prefix_pltime -= elapsed; } if (ia->prefix_vltime != ND6_INFINITE_LIFETIME) { - ia->prefix_vltime -= (uint32_t)n.tv_sec; - /* This should never happen. */ - if (ia->prefix_vltime > vltime) { - logerrx("%s: %s: lifetime overflow", - ifp->name, ia->saddr); - ia->prefix_vltime = ia->prefix_pltime = 0; - } + if (elapsed > ia->prefix_vltime) + ia->prefix_vltime = 0; + else + ia->prefix_vltime -= elapsed; } } - logfunc = ia->flags & IPV6_AF_NEW ? loginfox : logdebugx; - logfunc("%s: adding %saddress %s", ifp->name, + loglevel = ia->flags & IPV6_AF_NEW ? LOG_INFO : LOG_DEBUG; + logmessage(loglevel, "%s: adding %saddress %s", ifp->name, #ifdef IPV6_AF_TEMPORARY ia->flags & IPV6_AF_TEMPORARY ? "temporary " : "", #else @@ -724,9 +750,9 @@ if (ia->flags & IPV6_AF_TEMPORARY && ia->prefix_pltime && ia->prefix_vltime && - ip6_use_tempaddr(ifp->name)) + ifp->options->options & DHCPCD_SLAACTEMP) eloop_timeout_add_sec(ifp->ctx->eloop, - (time_t)ia->prefix_pltime - REGEN_ADVANCE, + ia->prefix_pltime - REGEN_ADVANCE, ipv6_regentempaddr, ia); #endif @@ -745,11 +771,8 @@ eloop_timeout_delete(ifp->ctx->eloop, ipv6_checkaddrflags, ia); if (!(ia->flags & IPV6_AF_DADCOMPLETED)) { - struct timespec tv; - - ms_to_ts(&tv, RETRANS_TIMER / 2); - eloop_timeout_add_tv(ifp->ctx->eloop, - &tv, ipv6_checkaddrflags, ia); + eloop_timeout_add_msec(ifp->ctx->eloop, + RETRANS_TIMER / 2, ipv6_checkaddrflags, ia); } #endif @@ -920,49 +943,61 @@ #endif } +int +ipv6_doaddr(struct ipv6_addr *ia, struct timespec *now) +{ + + /* A delegated prefix is not an address. */ + if (ia->flags & IPV6_AF_DELEGATEDPFX) + return 0; + + if (ia->prefix_vltime == 0) { + if (ia->flags & IPV6_AF_ADDED) + ipv6_deleteaddr(ia); + eloop_q_timeout_delete(ia->iface->ctx->eloop, + ELOOP_QUEUE_ALL, NULL, ia); + if (ia->flags & IPV6_AF_REQUEST) { + ia->flags &= ~IPV6_AF_ADDED; + return 0; + } + return -1; + } + + if (ia->flags & IPV6_AF_STALE || + IN6_IS_ADDR_UNSPECIFIED(&ia->addr)) + return 0; + + if (!timespecisset(now)) + clock_gettime(CLOCK_MONOTONIC, now); + ipv6_addaddr(ia, now); + return ia->flags & IPV6_AF_NEW ? 1 : 0; +} + ssize_t -ipv6_addaddrs(struct ipv6_addrhead *addrs) +ipv6_addaddrs(struct ipv6_addrhead *iaddrs) { - struct ipv6_addr *ap, *apn; - ssize_t i; struct timespec now; + struct ipv6_addr *ia, *ian; + ssize_t i, r; i = 0; timespecclear(&now); - TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { - /* A delegated prefix is not an address. */ - if (ap->flags & IPV6_AF_DELEGATEDPFX) - continue; - if (ap->prefix_vltime == 0) { - if (ap->flags & IPV6_AF_ADDED) { - ipv6_deleteaddr(ap); - i++; - } - eloop_q_timeout_delete(ap->iface->ctx->eloop, - 0, NULL, ap); - if (ap->flags & IPV6_AF_REQUEST) { - ap->flags &= ~IPV6_AF_ADDED; - } else { - TAILQ_REMOVE(addrs, ap, next); - ipv6_freeaddr(ap); - } - } else if (!(ap->flags & IPV6_AF_STALE) && - !IN6_IS_ADDR_UNSPECIFIED(&ap->addr)) - { - if (ap->flags & IPV6_AF_NEW) - i++; - if (!timespecisset(&now)) - clock_gettime(CLOCK_MONOTONIC, &now); - ipv6_addaddr(ap, &now); + TAILQ_FOREACH_SAFE(ia, iaddrs, next, ian) { + r = ipv6_doaddr(ia, &now); + if (r != 0) + i++; + if (r == -1) { + TAILQ_REMOVE(iaddrs, ia, next); + ipv6_freeaddr(ia); } } - return i; } void ipv6_freeaddr(struct ipv6_addr *ia) { + struct eloop *eloop = ia->iface->ctx->eloop; #ifndef SMALL struct ipv6_addr *iad; @@ -978,10 +1013,10 @@ if (ia->dhcp6_fd != -1) { close(ia->dhcp6_fd); - eloop_event_delete(ia->iface->ctx->eloop, ia->dhcp6_fd); + eloop_event_delete(eloop, ia->dhcp6_fd); } - eloop_q_timeout_delete(ia->iface->ctx->eloop, 0, NULL, ia); + eloop_q_timeout_delete(eloop, ELOOP_QUEUE_ALL, NULL, ia); free(ia->na); free(ia); } @@ -1055,11 +1090,6 @@ } TAILQ_INIT(&state->addrs); TAILQ_INIT(&state->ll_callbacks); - - /* Regenerate new ids */ - if (ifp->options && - ip6_use_tempaddr(ifp->name)) - ipv6_regentempifid(ifp); } return state; } @@ -1070,9 +1100,21 @@ struct interface *ifp; struct ipv6_state *state; struct ipv6_addr *ia; + bool forwarding; + + /* BSD forwarding is either on or off. + * Linux forwarding is technically the same as it's + * configured by the "all" interface. + * Per interface only affects IsRouter of NA messages. */ +#if defined(PRIVSEP) && (defined(HAVE_PLEDGE) || defined(__linux__)) + if (IN_PRIVSEP(sifp->ctx)) + forwarding = ps_root_ip6forwarding(sifp->ctx, NULL) != 0; + else +#endif + forwarding = ip6_forwarding(NULL) != 0; TAILQ_FOREACH(ifp, sifp->ctx->ifaces, next) { - if (ifp != sifp && ip6_forwarding(ifp->name) != 1) + if (ifp != sifp && !forwarding) continue; state = IPV6_STATE(ifp); @@ -1196,21 +1238,15 @@ if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) || ia->dadcallback) { #ifdef IPV6_POLLADDRFLAG if (ia->addr_flags & IN6_IFF_TENTATIVE) { - struct timespec tv; - - ms_to_ts(&tv, RETRANS_TIMER / 2); - eloop_timeout_add_tv( + eloop_timeout_add_msec( ia->iface->ctx->eloop, - &tv, ipv6_checkaddrflags, ia); + RETRANS_TIMER / 2, ipv6_checkaddrflags, ia); break; } #endif - if (ia->dadcallback) { + if (ia->dadcallback) ia->dadcallback(ia); - if (ctx->options & DHCPCD_FORKED) - goto out; - } if (IN6_IS_ADDR_LINKLOCAL(&ia->addr) && !(ia->addr_flags & IN6_IFF_NOTUSEABLE)) @@ -1225,8 +1261,6 @@ cb, next); cb->callback(cb->arg); free(cb); - if (ctx->options & DHCPCD_FORKED) - goto out; } } } @@ -1242,7 +1276,6 @@ dhcp6_handleifa(cmd, ia, pid); #endif -out: /* Done with the ia now, so free it. */ if (cmd == RTM_DELADDR) ipv6_freeaddr(ia); @@ -1253,7 +1286,9 @@ * or DHCP6 handlers and the existance of any useable * global address on the interface has changed, * call rt_build to add/remove the default route. */ - if (ifp->active && ifp->options->options & DHCPCD_IPV6 && + if (ifp->active && + ((ifp->options != NULL && ifp->options->options & DHCPCD_IPV6) || + (ifp->options == NULL && ctx->options & DHCPCD_IPV6)) && !(ctx->options & DHCPCD_RTBUILD) && (ipv6_anyglobal(ifp) != NULL) != anyglobal) rt_build(ctx, AF_INET6); @@ -1381,9 +1416,12 @@ struct ipv6_addr *ap, *ap2; int dadcounter; + if (!(ifp->options->options & DHCPCD_CONFIGURE)) + return 0; + /* Check sanity before malloc */ if (!(ifp->options->options & DHCPCD_SLAACPRIVATE)) { - switch (ifp->family) { + switch (ifp->hwtype) { case ARPHRD_ETHER: /* Check for a valid hardware address */ if (ifp->hwlen != 6 && ifp->hwlen != 8) { @@ -1423,7 +1461,7 @@ ap->dadcounter = dadcounter; } else { memcpy(ap->addr.s6_addr, ap->prefix.s6_addr, 8); - switch (ifp->family) { + switch (ifp->hwtype) { case ARPHRD_ETHER: if (ifp->hwlen == 6) { ap->addr.s6_addr[ 8] = ifp->hwaddr[0]; @@ -1496,12 +1534,9 @@ if (ia != NULL) { #ifdef IPV6_POLLADDRFLAG if (ia->addr_flags & IN6_IFF_TENTATIVE) { - struct timespec tv; - - ms_to_ts(&tv, RETRANS_TIMER / 2); - eloop_timeout_add_tv( + eloop_timeout_add_msec( ia->iface->ctx->eloop, - &tv, ipv6_checkaddrflags, ia); + RETRANS_TIMER / 2, ipv6_checkaddrflags, ia); } #endif return 0; @@ -1512,22 +1547,72 @@ return ipv6_addlinklocal(ifp); } +void +ipv6_setscope(struct sockaddr_in6 *sin, unsigned int ifindex) +{ + +#ifdef __KAME__ + /* KAME based systems want to store the scope inside the sin6_addr + * for link local addresses */ + if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) { + uint16_t scope = htons((uint16_t)ifindex); + memcpy(&sin->sin6_addr.s6_addr[2], &scope, + sizeof(scope)); + } + sin->sin6_scope_id = 0; +#else + if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) + sin->sin6_scope_id = ifindex; + else + sin->sin6_scope_id = 0; +#endif +} + +unsigned int +ipv6_getscope(const struct sockaddr_in6 *sin) +{ +#ifdef __KAME__ + uint16_t scope; +#endif + + if (!IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr)) + return 0; +#ifdef __KAME__ + memcpy(&scope, &sin->sin6_addr.s6_addr[2], sizeof(scope)); + return (unsigned int)ntohs(scope); +#else + return (unsigned int)sin->sin6_scope_id; +#endif +} + struct ipv6_addr * ipv6_newaddr(struct interface *ifp, const struct in6_addr *addr, uint8_t prefix_len, unsigned int flags) { - struct ipv6_addr *ia; + struct ipv6_addr *ia, *iaf; char buf[INET6_ADDRSTRLEN]; const char *cbp; bool tempaddr; int addr_flags; +#ifdef IPV6_AF_TEMPORARY + tempaddr = flags & IPV6_AF_TEMPORARY; +#else + tempaddr = false; +#endif + /* If adding a new DHCP / RA derived address, check current flags * from an existing address. */ - ia = ipv6_iffindaddr(ifp, addr, 0); - if (ia != NULL) - addr_flags = ia->addr_flags; + if (tempaddr) + iaf = NULL; + else if (flags & IPV6_AF_AUTOCONF) + iaf = ipv6nd_iffindprefix(ifp, addr, prefix_len); else + iaf = ipv6_iffindaddr(ifp, addr, 0); + if (iaf != NULL) { + addr_flags = iaf->addr_flags; + flags |= IPV6_AF_ADDED; + } else addr_flags = IN6_IFF_TENTATIVE; ia = calloc(1, sizeof(*ia)); @@ -1542,19 +1627,24 @@ ia->prefix_len = prefix_len; ia->dhcp6_fd = -1; -#ifdef IPV6_AF_TEMPORARY - tempaddr = ia->flags & IPV6_AF_TEMPORARY; -#else - tempaddr = false; +#ifndef SMALL + TAILQ_INIT(&ia->pd_pfxs); #endif - if (ia->flags & IPV6_AF_AUTOCONF && !tempaddr) { + if (prefix_len == 128) + goto makepfx; + else if (ia->flags & IPV6_AF_AUTOCONF) { ia->prefix = *addr; - ia->dadcounter = ipv6_makeaddr(&ia->addr, ifp, - &ia->prefix, - ia->prefix_len); - if (ia->dadcounter == -1) - goto err; + if (iaf != NULL) + memcpy(&ia->addr, &iaf->addr, sizeof(ia->addr)); + else { + ia->dadcounter = ipv6_makeaddr(&ia->addr, ifp, + &ia->prefix, + ia->prefix_len, + ia->flags); + if (ia->dadcounter == -1) + goto err; + } } else if (ia->flags & IPV6_AF_RAPFX) { ia->prefix = *addr; #ifdef __sun @@ -1564,13 +1654,12 @@ #else return ia; #endif - } else if (ia->flags & (IPV6_AF_REQUEST | IPV6_AF_DELEGATEDPFX) && - prefix_len != 128) - { + } else if (ia->flags & (IPV6_AF_REQUEST | IPV6_AF_DELEGATEDPFX)) { ia->prefix = *addr; cbp = inet_ntop(AF_INET6, &ia->prefix, buf, sizeof(buf)); goto paddr; } else { +makepfx: ia->addr = *addr; if (ipv6_makeprefix(&ia->prefix, &ia->addr, ia->prefix_len) == -1) @@ -1599,7 +1688,7 @@ wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED); ia->flags |= IPV6_AF_DADCOMPLETED; - if (ia->flags & IPV6_AF_DUPLICATED) + if (ia->addr_flags & IN6_IFF_DUPLICATED) logwarnx("%s: DAD detected %s", ia->iface->name, ia->saddr); else if (!wascompleted) { @@ -1730,12 +1819,6 @@ if (ipv6_tryaddlinklocal(ifp) == -1) return -1; - if (IPV6_CSTATE(ifp)) { - /* Regenerate new ids */ - if (ip6_use_tempaddr(ifp->name)) - ipv6_regentempifid(ifp); - } - return 0; } @@ -1801,23 +1884,20 @@ ia->iface->name, pid, ia->saddr); ia->flags &= ~IPV6_AF_ADDED; } + ipv6_deletedaddr(ia); if (ia->flags & IPV6_AF_DELEGATED) { TAILQ_REMOVE(addrs, ia, next); - ipv6_deletedaddr(ia); ipv6_freeaddr(ia); } break; case RTM_NEWADDR: + ia->addr_flags = addr->addr_flags; /* Safety - ignore tentative announcements */ - if (addr->addr_flags & + if (ia->addr_flags & (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE)) break; if ((ia->flags & IPV6_AF_DADCOMPLETED) == 0) { found++; - if (addr->addr_flags & IN6_IFF_DUPLICATED) - ia->flags |= IPV6_AF_DUPLICATED; - else - ia->flags &= ~IPV6_AF_DUPLICATED; if (ia->dadcallback) ia->dadcallback(ia); /* We need to set this here in-case the @@ -1832,107 +1912,26 @@ } #ifdef IPV6_MANAGETEMPADDR -static const struct ipv6_addr * -ipv6_findaddrid(struct dhcpcd_ctx *ctx, uint8_t *addr) -{ - const struct interface *ifp; - const struct ipv6_state *state; - const struct ipv6_addr *ia; - - TAILQ_FOREACH(ifp, ctx->ifaces, next) { - if ((state = IPV6_CSTATE(ifp))) { - TAILQ_FOREACH(ia, &state->addrs, next) { - if (memcmp(&ia->addr.s6_addr[8], addr, 8) == 0) - return ia; - } - } - } - return NULL; -} - -static const uint8_t nullid[8]; -static const uint8_t anycastid[8] = { - 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80 }; -static const uint8_t isatapid[4] = { 0x00, 0x00, 0x5e, 0xfe }; - static void -ipv6_regen_desync(struct interface *ifp, int force) +ipv6_regen_desync(struct interface *ifp, bool force) { struct ipv6_state *state; - time_t max; + unsigned int max; state = IPV6_STATE(ifp); /* RFC4941 Section 5 states that DESYNC_FACTOR must never be * greater than TEMP_VALID_LIFETIME - REGEN_ADVANCE. - * I believe this is an error and it should be never be greateter than + * I believe this is an error and it should be never be greater than * TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE. */ - max = ip6_temp_preferred_lifetime(ifp->name) - REGEN_ADVANCE; + max = TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE; if (state->desync_factor && !force && state->desync_factor < max) return; if (state->desync_factor == 0) state->desync_factor = - (time_t)arc4random_uniform(MIN(MAX_DESYNC_FACTOR, - (uint32_t)max)); - max = ip6_temp_preferred_lifetime(ifp->name) - - state->desync_factor - REGEN_ADVANCE; - eloop_timeout_add_sec(ifp->ctx->eloop, max, ipv6_regentempifid, ifp); -} - -void -ipv6_gentempifid(struct interface *ifp) -{ - struct ipv6_state *state; - MD5_CTX md5; - uint8_t seed[16], digest[16]; - int retry; - - if ((state = IPV6_STATE(ifp)) == NULL) - return; - - retry = 0; - if (memcmp(nullid, state->randomseed0, sizeof(nullid)) == 0) { - uint32_t r; - - r = arc4random(); - memcpy(seed, &r, sizeof(r)); - r = arc4random(); - memcpy(seed + sizeof(r), &r, sizeof(r)); - } else - memcpy(seed, state->randomseed0, sizeof(state->randomseed0)); - - memcpy(seed + sizeof(state->randomseed0), - state->randomseed1, sizeof(state->randomseed1)); - -again: - MD5Init(&md5); - MD5Update(&md5, seed, sizeof(seed)); - MD5Final(digest, &md5); - - /* RFC4941 Section 3.2.1.1 - * Take the left-most 64bits and set bit 6 to zero */ - memcpy(state->randomid, digest, sizeof(state->randomid)); - state->randomid[0] = (uint8_t)(state->randomid[0] & ~EUI64_UBIT); - - /* RFC4941 Section 3.2.1.4 - * Reject reserved or existing id's */ - if (memcmp(nullid, state->randomid, sizeof(nullid)) == 0 || - (memcmp(anycastid, state->randomid, 7) == 0 && - (anycastid[7] & state->randomid[7]) == anycastid[7]) || - memcmp(isatapid, state->randomid, sizeof(isatapid)) == 0 || - ipv6_findaddrid(ifp->ctx, state->randomid)) - { - if (++retry < GEN_TEMPID_RETRY_MAX) { - memcpy(seed, digest + 8, 8); - goto again; - } - memset(state->randomid, 0, sizeof(state->randomid)); - } - - /* RFC4941 Section 3.2.1.6 - * Save the right-most 64bits of the digest */ - memcpy(state->randomseed0, digest + 8, - sizeof(state->randomseed0)); + arc4random_uniform(MIN(MAX_DESYNC_FACTOR, max)); + max = TEMP_PREFERRED_LIFETIME - state->desync_factor - REGEN_ADVANCE; + eloop_timeout_add_sec(ifp->ctx->eloop, max, ipv6_regentempaddrs, ifp); } /* RFC4941 Section 3.3.7 */ @@ -1941,7 +1940,7 @@ { struct ipv6_addr *ia = arg; - if (ia->flags & IPV6_AF_DUPLICATED) { + if (ia->addr_flags & IN6_IFF_DUPLICATED) { struct ipv6_addr *ia1; struct timespec tv; @@ -1965,76 +1964,25 @@ ipv6_createtempaddr(struct ipv6_addr *ia0, const struct timespec *now) { struct ipv6_state *state; - const struct ipv6_state *cstate; - int genid; - struct in6_addr addr, mask; - uint32_t randid[2]; - const struct interface *ifp; - const struct ipv6_addr *ap; + struct interface *ifp = ia0->iface; struct ipv6_addr *ia; - uint32_t i, trylimit; - - trylimit = TEMP_IDGEN_RETRIES; - state = IPV6_STATE(ia0->iface); - genid = 0; - - addr = ia0->addr; - ipv6_mask(&mask, ia0->prefix_len); - /* clear the old ifid */ - for (i = 0; i < 4; i++) - addr.s6_addr32[i] &= mask.s6_addr32[i]; - -again: - if (memcmp(state->randomid, nullid, sizeof(nullid)) == 0) - genid = 1; - if (genid) { - memcpy(state->randomseed1, &ia0->addr.s6_addr[8], - sizeof(state->randomseed1)); - ipv6_gentempifid(ia0->iface); - if (memcmp(state->randomid, nullid, sizeof(nullid)) == 0) { - errno = EFAULT; - return NULL; - } - } - memcpy(&randid[0], state->randomid, sizeof(randid[0])); - memcpy(&randid[1], state->randomid + sizeof(randid[1]), - sizeof(randid[2])); - addr.s6_addr32[2] |= randid[0] & ~mask.s6_addr32[2]; - addr.s6_addr32[3] |= randid[1] & ~mask.s6_addr32[3]; - - /* Ensure we don't already have it */ - TAILQ_FOREACH(ifp, ia0->iface->ctx->ifaces, next) { - cstate = IPV6_CSTATE(ifp); - if (cstate) { - TAILQ_FOREACH(ap, &cstate->addrs, next) { - if (IN6_ARE_ADDR_EQUAL(&ap->addr, &addr)) { - if (--trylimit == 0) { - errno = EEXIST; - return NULL; - } - genid = 1; - goto again; - } - } - } - } - ia = ipv6_newaddr(ia0->iface, &addr, ia0->prefix_len, + ia = ipv6_newaddr(ifp, &ia0->prefix, ia0->prefix_len, IPV6_AF_AUTOCONF | IPV6_AF_TEMPORARY); - /* Must be made tentative, for our DaD to work */ - ia->addr_flags = IN6_IFF_TENTATIVE; + if (ia == NULL) + return NULL; + ia->dadcallback = ipv6_tempdadcallback; ia->created = ia->acquired = now ? *now : ia0->acquired; /* Ensure desync is still valid */ - ipv6_regen_desync(ia->iface, 0); + ipv6_regen_desync(ifp, false); /* RFC4941 Section 3.3.4 */ - i = (uint32_t)(ip6_temp_preferred_lifetime(ia0->iface->name) - - state->desync_factor); - ia->prefix_pltime = MIN(ia0->prefix_pltime, i); - i = (uint32_t)ip6_temp_valid_lifetime(ia0->iface->name); - ia->prefix_vltime = MIN(ia0->prefix_vltime, i); + state = IPV6_STATE(ia->iface); + ia->prefix_pltime = MIN(ia0->prefix_pltime, + TEMP_PREFERRED_LIFETIME - state->desync_factor); + ia->prefix_vltime = MIN(ia0->prefix_vltime, TEMP_VALID_LIFETIME); if (ia->prefix_pltime <= REGEN_ADVANCE || ia->prefix_pltime > ia0->prefix_vltime) { @@ -2060,7 +2008,7 @@ ap->prefix_pltime && IN6_ARE_ADDR_EQUAL(&ia->prefix, &ap->prefix)) { - time_t max, ext; + unsigned int max, ext; if (flags == 0) { if (ap->prefix_pltime - @@ -2084,15 +2032,16 @@ } /* Ensure desync is still valid */ - ipv6_regen_desync(ap->iface, 0); + ipv6_regen_desync(ap->iface, false); /* RFC4941 Section 3.3.2 * Extend temporary times, but ensure that they * never last beyond the system limit. */ - ext = ia->acquired.tv_sec + (time_t)ia->prefix_pltime; - max = ap->created.tv_sec + - ip6_temp_preferred_lifetime(ap->iface->name) - - state->desync_factor; + ext = (unsigned int)ia->acquired.tv_sec + + ia->prefix_pltime; + max = (unsigned int)(ap->created.tv_sec + + TEMP_PREFERRED_LIFETIME - + state->desync_factor); if (ext < max) ap->prefix_pltime = ia->prefix_pltime; else @@ -2100,9 +2049,10 @@ (uint32_t)(max - ia->acquired.tv_sec); valid: - ext = ia->acquired.tv_sec + (time_t)ia->prefix_vltime; - max = ap->created.tv_sec + - ip6_temp_valid_lifetime(ap->iface->name); + ext = (unsigned int)ia->acquired.tv_sec + + ia->prefix_vltime; + max = (unsigned int)(ap->created.tv_sec + + TEMP_VALID_LIFETIME); if (ext < max) ap->prefix_vltime = ia->prefix_vltime; else @@ -2140,31 +2090,60 @@ } static void -ipv6_regentempaddr(void *arg) +ipv6_regentempaddr0(struct ipv6_addr *ia, struct timespec *tv) { - struct ipv6_addr *ia = arg, *ia1; - struct timespec tv; + struct ipv6_addr *ia1; logdebugx("%s: regen temp addr %s", ia->iface->name, ia->saddr); - clock_gettime(CLOCK_MONOTONIC, &tv); - ia1 = ipv6_createtempaddr(ia, &tv); + ia1 = ipv6_createtempaddr(ia, tv); if (ia1) - ipv6_addaddr(ia1, &tv); + ipv6_addaddr(ia1, tv); else logerr(__func__); } static void -ipv6_regentempifid(void *arg) +ipv6_regentempaddr(void *arg) +{ + struct timespec tv; + + clock_gettime(CLOCK_MONOTONIC, &tv); + ipv6_regentempaddr0(arg, &tv); +} + +void +ipv6_regentempaddrs(void *arg) { struct interface *ifp = arg; + struct timespec tv; struct ipv6_state *state; + struct ipv6_addr *ia; state = IPV6_STATE(ifp); - if (memcmp(state->randomid, nullid, sizeof(state->randomid))) - ipv6_gentempifid(ifp); + if (state == NULL) + return; + + ipv6_regen_desync(ifp, true); + + clock_gettime(CLOCK_MONOTONIC, &tv); + + /* Mark addresses for regen so we don't infinite loop. */ + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_TEMPORARY && + ia->flags & IPV6_AF_ADDED && + !(ia->flags & IPV6_AF_STALE)) + ia->flags |= IPV6_AF_REGEN; + else + ia->flags &= ~IPV6_AF_REGEN; + } - ipv6_regen_desync(ifp, 1); + /* Now regen temp addrs */ + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_REGEN) { + ipv6_regentempaddr0(ia, &tv); + ia->flags &= ~IPV6_AF_REGEN; + } + } } #endif /* IPV6_MANAGETEMPADDR */ @@ -2324,6 +2303,9 @@ struct ra *rap; const struct ipv6_addr *addr; + if (ctx->ra_routers == NULL) + return 0; + TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (rap->expired) continue; @@ -2333,6 +2315,9 @@ rt = inet6_makeprefix(rap->iface, rap, addr); if (rt) { rt->rt_dflags |= RTDF_RA; +#ifdef HAVE_ROUTE_PREF + rt->rt_pref = ipv6nd_rtpref(rap); +#endif rt_proto_add(routes, rt); } } @@ -2344,6 +2329,9 @@ if (rt == NULL) continue; rt->rt_dflags |= RTDF_RA; +#ifdef HAVE_ROUTE_PREF + rt->rt_pref = ipv6nd_rtpref(rap); +#endif rt_proto_add(routes, rt); } return 0; Index: contrib/dhcpcd/src/ipv6nd.h =================================================================== --- contrib/dhcpcd/src/ipv6nd.h +++ contrib/dhcpcd/src/ipv6nd.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - IPv6 ND handling - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -50,9 +50,12 @@ uint32_t reachable; uint32_t retrans; uint32_t mtu; + uint8_t hoplimit; struct ipv6_addrhead addrs; bool hasdns; bool expired; + bool willexpire; + bool doexpire; bool isreachable; }; @@ -78,6 +81,10 @@ #define RTR_SOLICITATION_INTERVAL 4 /* seconds */ #define MAX_RTR_SOLICITATIONS 3 /* times */ #define MAX_NEIGHBOR_ADVERTISEMENT 3 /* 3 transmissions */ + +#ifndef IPV6_DEFHLIM +#define IPV6_DEFHLIM 64 +#endif #endif /* On carrier up, expire known routers after RTR_CARRIER_EXPIRE seconds. */ @@ -91,6 +98,12 @@ #define RETRANS_TIMER 1000 /* milliseconds */ #define DELAY_FIRST_PROBE_TIME 5 /* seconds */ +int ipv6nd_open(bool); +#ifdef __sun +int ipv6nd_openif(struct interface *); +#endif +void ipv6nd_recvmsg(struct dhcpcd_ctx *, struct msghdr *); +int ipv6nd_rtpref(struct ra *); void ipv6nd_printoptions(const struct dhcpcd_ctx *, const struct dhcp_opt *, size_t); void ipv6nd_startrs(struct interface *); @@ -99,11 +112,13 @@ const struct in6_addr *addr, unsigned int flags); struct ipv6_addr *ipv6nd_findaddr(struct dhcpcd_ctx *, const struct in6_addr *, unsigned int); +struct ipv6_addr *ipv6nd_iffindprefix(struct interface *, + const struct in6_addr *, uint8_t); ssize_t ipv6nd_free(struct interface *); void ipv6nd_expirera(void *arg); bool ipv6nd_hasralifetime(const struct interface *, bool); #define ipv6nd_hasra(i) ipv6nd_hasralifetime((i), false) -bool ipv6nd_hasradhcp(const struct interface *); +bool ipv6nd_hasradhcp(const struct interface *, bool); void ipv6nd_handleifa(int, struct ipv6_addr *, pid_t); int ipv6nd_dadcompleted(const struct interface *); void ipv6nd_advertise(struct ipv6_addr *); Index: contrib/dhcpcd/src/ipv6nd.c =================================================================== --- contrib/dhcpcd/src/ipv6nd.c +++ contrib/dhcpcd/src/ipv6nd.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - IPv6 ND handling - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -41,9 +41,10 @@ #include #include #include +#include #include -#define ELOOP_QUEUE 3 +#define ELOOP_QUEUE ELOOP_IPV6ND #include "common.h" #include "dhcpcd.h" #include "dhcp-common.h" @@ -53,12 +54,23 @@ #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" +#include "privsep.h" #include "route.h" #include "script.h" /* Debugging Router Solicitations is a lot of spam, so disable it */ //#define DEBUG_RS +#ifndef ND_RA_FLAG_HOME_AGENT +#define ND_RA_FLAG_HOME_AGENT 0x20 /* Home Agent flag in RA */ +#endif +#ifndef ND_RA_FLAG_PROXY +#define ND_RA_FLAG_PROXY 0x04 /* Proxy */ +#endif +#ifndef ND_OPT_PI_FLAG_ROUTER +#define ND_OPT_PI_FLAG_ROUTER 0x20 /* Router flag in PI */ +#endif + #ifndef ND_OPT_RDNSS #define ND_OPT_RDNSS 25 struct nd_opt_rdnss { /* RDNSS option RFC 6106 */ @@ -99,13 +111,6 @@ #define ND_RA_FLAG_RTPREF_RSV 0x10 #endif -/* RTPREF_MEDIUM has to be 0! */ -#define RTPREF_HIGH 1 -#define RTPREF_MEDIUM 0 -#define RTPREF_LOW (-1) -#define RTPREF_RESERVED (-2) -#define RTPREF_INVALID (-3) /* internal */ - #define EXPIRED_MAX 5 /* Remember 5 expired routers to avoid logspam. */ @@ -193,47 +198,61 @@ } } -static int -ipv6nd_open0(void) +int +ipv6nd_open(bool recv) { - int s, on; + int fd, on; struct icmp6_filter filt; -#define SOCK_FLAGS SOCK_CLOEXEC | SOCK_NONBLOCK - s = xsocket(PF_INET6, SOCK_RAW | SOCK_FLAGS, IPPROTO_ICMPV6); -#undef SOCK_FLAGS - if (s == -1) + fd = xsocket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_ICMPV6); + if (fd == -1) return -1; + ICMP6_FILTER_SETBLOCKALL(&filt); + /* RFC4861 4.1 */ on = 255; - if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &on, sizeof(on)) == -1) goto eexit; - on = 1; - if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, - &on, sizeof(on)) == -1) - goto eexit; + if (recv) { + on = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, + &on, sizeof(on)) == -1) + goto eexit; - ICMP6_FILTER_SETBLOCKALL(&filt); - ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); - if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, + on = 1; + if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, + &on, sizeof(on)) == -1) + goto eexit; + + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); + +#ifdef SO_RERROR + on = 1; + if (setsockopt(fd, SOL_SOCKET, SO_RERROR, + &on, sizeof(on)) == -1) + goto eexit; +#endif + } + + if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)) == -1) goto eexit; - return s; + return fd; eexit: - close(s); + close(fd); return -1; } #ifdef __sun -static int -ipv6nd_open(struct interface *ifp) +int +ipv6nd_openif(struct interface *ifp) { - int s; + int fd; struct ipv6_mreq mreq = { .ipv6mr_multiaddr = IN6ADDR_LINKLOCAL_ALLNODES_INIT, .ipv6mr_interface = ifp->index @@ -244,52 +263,27 @@ if (state->nd_fd != -1) return state->nd_fd; - s = ipv6nd_open0(); - if (s == -1) + fd = ipv6nd_open(true); + if (fd == -1) return -1; - if (setsockopt(s, IPPROTO_IPV6, IPV6_BOUND_IF, + if (setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, &ifindex, sizeof(ifindex)) == -1) { - close(s); + close(fd); return -1; } - if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, + if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) { - close(s); - return -1; - } - - state->nd_fd = s; - eloop_event_add(ifp->ctx->eloop, s, ipv6nd_handledata, ifp); - return s; -} -#else -static int -ipv6nd_open(struct dhcpcd_ctx *ctx) -{ - int s, on; - - if (ctx->nd_fd != -1) - return ctx->nd_fd; - - s = ipv6nd_open0(); - if (s == -1) - return -1; - - on = 1; - if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, - &on, sizeof(on)) == -1) - { - close(s); + close(fd); return -1; } - ctx->nd_fd = s; - eloop_event_add(ctx->eloop, s, ipv6nd_handledata, ctx); - return s; + state->nd_fd = fd; + eloop_event_add(ifp->ctx->eloop, fd, ipv6nd_handledata, ifp); + return fd; } #endif @@ -335,15 +329,21 @@ .sin6_scope_id = ifp->index, }; struct iovec iov = { .iov_base = state->rs, .iov_len = state->rslen }; - unsigned char ctl[CMSG_SPACE(sizeof(struct in6_pktinfo))] = { 0 }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf = { .buf = { 0 } }; struct msghdr msg = { .msg_name = &dst, .msg_namelen = sizeof(dst), .msg_iov = &iov, .msg_iovlen = 1, - .msg_control = ctl, .msg_controllen = sizeof(ctl), + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), }; struct cmsghdr *cm; struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; int s; +#ifndef __sun + struct dhcpcd_ctx *ctx = ifp->ctx; +#endif if (ipv6_linklocal(ifp) == NULL) { logdebugx("%s: delaying Router Solicitation for LL address", @@ -366,9 +366,30 @@ memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); logdebugx("%s: sending Router Solicitation", ifp->name); +#ifdef PRIVSEP + if (IN_PRIVSEP(ifp->ctx)) { + if (ps_inet_sendnd(ifp, &msg) == -1) + logerr(__func__); + goto sent; + } +#endif #ifdef __sun + if (state->nd_fd == -1) { + if (ipv6nd_openif(ifp) == -1) { + logerr(__func__); + return; + } + } s = state->nd_fd; #else + if (ctx->nd_fd == -1) { + ctx->nd_fd = ipv6nd_open(true); + if (ctx->nd_fd == -1) { + logerr(__func__); + return; + } + eloop_event_add(ctx->eloop, ctx->nd_fd, ipv6nd_handledata, ctx); + } s = ifp->ctx->nd_fd; #endif if (sendmsg(s, &msg, 0) == -1) { @@ -379,13 +400,14 @@ * associate with an access point. */ } +#ifdef PRIVSEP +sent: +#endif if (state->rsprobes++ < MAX_RTR_SOLICITATIONS) eloop_timeout_add_sec(ifp->ctx->eloop, RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp); - else { + else logwarnx("%s: no IPv6 Routers available", ifp->name); - ipv6nd_drop(ifp); - } } #ifdef ND6_ADVERTISE @@ -401,18 +423,21 @@ .sin6_scope_id = ifp->index, }; struct iovec iov = { .iov_base = ia->na, .iov_len = ia->na_len }; - unsigned char ctl[CMSG_SPACE(sizeof(struct in6_pktinfo))] = { 0 }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } cmsgbuf = { .buf = { 0 } }; struct msghdr msg = { .msg_name = &dst, .msg_namelen = sizeof(dst), .msg_iov = &iov, .msg_iovlen = 1, - .msg_control = ctl, .msg_controllen = sizeof(ctl), + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), }; struct cmsghdr *cm; struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; const struct rs_state *state = RS_CSTATE(ifp); int s; - if (state == NULL || ifp->carrier <= LINK_DOWN) + if (state == NULL || !if_is_link_up(ifp)) goto freeit; #ifdef SIN6_LEN @@ -427,6 +452,14 @@ cm->cmsg_len = CMSG_LEN(sizeof(pi)); memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); logdebugx("%s: sending NA for %s", ifp->name, ia->saddr); + +#ifdef PRIVSEP + if (IN_PRIVSEP(ifp->ctx)) { + if (ps_inet_sendnd(ifp, &msg) == -1) + logerr(__func__); + goto sent; + } +#endif #ifdef __sun s = state->nd_fd; #else @@ -435,6 +468,9 @@ if (sendmsg(s, &msg, 0) == -1) logerr(__func__); +#ifdef PRIVSEP +sent: +#endif if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) { eloop_timeout_add_sec(ctx->eloop, state->retrans / 1000, ipv6nd_sendadvertisement, ia); @@ -469,7 +505,7 @@ iaf = NULL; TAILQ_FOREACH(ifp, ctx->ifaces, next) { state = IPV6_STATE(ifp); - if (state == NULL || ifp->carrier <= LINK_DOWN) + if (state == NULL || !if_is_link_up(ifp)) continue; TAILQ_FOREACH(iap, &state->addrs, next) { @@ -506,7 +542,13 @@ na->nd_na_type = ND_NEIGHBOR_ADVERT; na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE; - if (ip6_forwarding(ifp->name) == 1) +#if defined(PRIVSEP) && (defined(__linux__) || defined(HAVE_PLEDGE)) + if (IN_PRIVSEP(ctx)) { + if (ps_root_ip6forwarding(ctx, ifp->name) != 0) + na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; + } else +#endif + if (ip6_forwarding(ifp->name) != 0) na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; na->nd_na_target = ia->addr; @@ -534,29 +576,111 @@ { struct interface *ifp = arg; struct ra *rap; - struct ipv6_addr *ia; - struct timespec now = { .tv_sec = 1 }; if (ifp->ctx->ra_routers == NULL) return; TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { - if (rap->iface == ifp) - continue; - rap->acquired = now; - TAILQ_FOREACH(ia, &rap->addrs, next) { - ia->acquired = now; - } + if (rap->iface == ifp && rap->willexpire) + rap->doexpire = true; } ipv6nd_expirera(ifp); } void ipv6nd_startexpire(struct interface *ifp) +{ + struct ra *rap; + + if (ifp->ctx->ra_routers == NULL) + return; + + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface == ifp) + rap->willexpire = true; + } + eloop_q_timeout_add_sec(ifp->ctx->eloop, ELOOP_IPV6RA_EXPIRE, + RTR_CARRIER_EXPIRE, ipv6nd_expire, ifp); +} + +int +ipv6nd_rtpref(struct ra *rap) { - eloop_timeout_add_sec(ifp->ctx->eloop, RTR_CARRIER_EXPIRE, - ipv6nd_expire, ifp); + switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) { + case ND_RA_FLAG_RTPREF_HIGH: + return RTPREF_HIGH; + case ND_RA_FLAG_RTPREF_MEDIUM: + case ND_RA_FLAG_RTPREF_RSV: + return RTPREF_MEDIUM; + case ND_RA_FLAG_RTPREF_LOW: + return RTPREF_LOW; + default: + logerrx("%s: impossible RA flag %x", __func__, rap->flags); + return RTPREF_INVALID; + } + /* NOTREACHED */ +} + +static void +ipv6nd_sortrouters(struct dhcpcd_ctx *ctx) +{ + struct ra_head sorted_routers = TAILQ_HEAD_INITIALIZER(sorted_routers); + struct ra *ra1, *ra2; + + while ((ra1 = TAILQ_FIRST(ctx->ra_routers)) != NULL) { + TAILQ_REMOVE(ctx->ra_routers, ra1, next); + TAILQ_FOREACH(ra2, &sorted_routers, next) { + if (ra1->iface->metric > ra2->iface->metric) + continue; + if (ra1->expired && !ra2->expired) + continue; + if (ra1->willexpire && !ra2->willexpire) + continue; + if (ra1->lifetime == 0 && ra2->lifetime != 0) + continue; + if (!ra1->isreachable && ra2->reachable) + continue; + if (ipv6nd_rtpref(ra1) <= ipv6nd_rtpref(ra2)) + continue; + /* All things being equal, prefer older routers. */ + /* We don't need to check time, becase newer + * routers are always added to the tail and then + * sorted. */ + TAILQ_INSERT_BEFORE(ra2, ra1, next); + break; + } + if (ra2 == NULL) + TAILQ_INSERT_TAIL(&sorted_routers, ra1, next); + } + + TAILQ_CONCAT(ctx->ra_routers, &sorted_routers, next); +} + +static void +ipv6nd_applyra(struct interface *ifp) +{ + struct ra *rap; + struct rs_state *state = RS_STATE(ifp); + struct ra defra = { + .iface = ifp, + .hoplimit = IPV6_DEFHLIM , + .reachable = REACHABLE_TIME, + .retrans = RETRANS_TIMER, + }; + + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface == ifp) + break; + } + + /* If we have no Router Advertisement, then set default values. */ + if (rap == NULL || rap->expired || rap->willexpire) + rap = &defra; + + state->retrans = rap->retrans; + if (if_applyra(rap) == -1 && errno != ENOENT) + logerr(__func__); } /* @@ -585,23 +709,20 @@ break; } - if (rap == NULL || rap->expired) + if (rap == NULL || rap->expired || rap->isreachable == reachable) return; - if (reachable) { - if (rap->isreachable) - return; - loginfox("%s: %s is reachable again", - rap->iface->name, rap->sfrom); - rap->isreachable = true; + rap->isreachable = reachable; + loginfox("%s: %s is %s", rap->iface->name, rap->sfrom, + reachable ? "reachable again" : "unreachable"); + + /* See if we can install a reachable default router. */ + ipv6nd_sortrouters(ctx); + ipv6nd_applyra(rap->iface); + rt_build(ctx, AF_INET6); + + if (reachable) return; - } else { - if (!rap->isreachable) - return; - logwarnx("%s: %s is unreachable", - rap->iface->name, rap->sfrom); - rap->isreachable = false; - } /* If we have no reachable default routers, try and solicit one. */ TAILQ_FOREACH(rapr, ctx->ra_routers, next) { @@ -655,6 +776,40 @@ return NULL; } +static struct ipv6_addr * +ipv6nd_rapfindprefix(struct ra *rap, + const struct in6_addr *pfx, uint8_t pfxlen) +{ + struct ipv6_addr *ia; + + TAILQ_FOREACH(ia, &rap->addrs, next) { + if (ia->prefix_vltime == 0) + continue; + if (ia->prefix_len == pfxlen && + IN6_ARE_ADDR_EQUAL(&ia->prefix, pfx)) + break; + } + return ia; +} + +struct ipv6_addr * +ipv6nd_iffindprefix(struct interface *ifp, + const struct in6_addr *pfx, uint8_t pfxlen) +{ + struct ra *rap; + struct ipv6_addr *ia; + + ia = NULL; + TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { + if (rap->iface != ifp) + continue; + ia = ipv6nd_rapfindprefix(rap, pfx, pfxlen); + if (ia != NULL) + break; + } + return ia; +} + static void ipv6nd_removefreedrop_ra(struct ra *rap, int remove_ra, int drop_ra) { @@ -722,46 +877,10 @@ return n; } -static int -rtpref(struct ra *rap) -{ - - switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) { - case ND_RA_FLAG_RTPREF_HIGH: - return (RTPREF_HIGH); - case ND_RA_FLAG_RTPREF_MEDIUM: - case ND_RA_FLAG_RTPREF_RSV: - return (RTPREF_MEDIUM); - case ND_RA_FLAG_RTPREF_LOW: - return (RTPREF_LOW); - default: - logerrx("rtpref: impossible RA flag %x", rap->flags); - return (RTPREF_INVALID); - } - /* NOTREACHED */ -} - static void -add_router(struct dhcpcd_ctx *ctx, struct ra *router) -{ - struct ra *rap; - - TAILQ_FOREACH(rap, ctx->ra_routers, next) { - if (router->iface->metric < rap->iface->metric || - (router->iface->metric == rap->iface->metric && - rtpref(router) > rtpref(rap))) - { - TAILQ_INSERT_BEFORE(rap, router, next); - return; - } - } - TAILQ_INSERT_TAIL(ctx->ra_routers, router, next); -} - -static int ipv6nd_scriptrun(struct ra *rap) { - int hasdns, hasaddress, pid; + int hasdns, hasaddress; struct ipv6_addr *ap; hasaddress = 0; @@ -779,7 +898,7 @@ logdebugx("%s: waiting for Router Advertisement" " DAD to complete", rap->iface->name); - return 0; + return; } } } @@ -792,19 +911,16 @@ } script_runreason(rap->iface, "ROUTERADVERT"); - pid = 0; if (hasdns && (hasaddress || !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)))) - pid = dhcpcd_daemonise(rap->iface->ctx); + dhcpcd_daemonise(rap->iface->ctx); #if 0 else if (options & DHCPCD_DAEMONISE && !(options & DHCPCD_DAEMONISED) && new_data) logwarnx("%s: did not fork due to an absent" " RDNSS option in the RA", ifp->name); -} #endif - return pid; } static void @@ -841,7 +957,6 @@ struct interface *ifp; struct ra *rap; int wascompleted, found; - struct timespec tv; char buf[INET6_ADDRSTRLEN]; const char *p; int dadcounter; @@ -849,7 +964,7 @@ ifp = ia->iface; wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED); ia->flags |= IPV6_AF_DADCOMPLETED; - if (ia->flags & IPV6_AF_DUPLICATED) { + if (ia->addr_flags & IN6_IFF_DUPLICATED) { ia->dadcounter++; logwarnx("%s: DAD detected %s", ifp->name, ia->saddr); @@ -857,7 +972,11 @@ * Because ap->dadcounter is always increamented, * a different address is generated. */ /* XXX Cache DAD counter per prefix/id/ssid? */ - if (ifp->options->options & DHCPCD_SLAACPRIVATE) { + if (ifp->options->options & DHCPCD_SLAACPRIVATE && + IA6_CANAUTOCONF(ia)) + { + unsigned int delay; + if (ia->dadcounter >= IDGEN_RETRIES) { logerrx("%s: unable to obtain a" " stable private address", @@ -888,11 +1007,8 @@ p, ia->prefix_len); else ia->saddr[0] = '\0'; - tv.tv_sec = 0; - tv.tv_nsec = (suseconds_t) - arc4random_uniform(IDGEN_DELAY * NSEC_PER_SEC); - timespecnorm(&tv); - eloop_timeout_add_tv(ifp->ctx->eloop, &tv, + delay = arc4random_uniform(IDGEN_DELAY * MSEC_PER_SEC); + eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_addaddr, ia); return; } @@ -921,8 +1037,7 @@ logdebugx("%s: Router Advertisement DAD " "completed", rap->iface->name); - if (ipv6nd_scriptrun(rap)) - return; + ipv6nd_scriptrun(rap); } } #ifdef ND6_ADVERTISE @@ -931,6 +1046,30 @@ } } +static struct ipv6_addr * +ipv6nd_findmarkstale(struct ra *rap, struct ipv6_addr *ia, bool mark) +{ + struct dhcpcd_ctx *ctx = ia->iface->ctx; + struct ra *rap2; + struct ipv6_addr *ia2; + + TAILQ_FOREACH(rap2, ctx->ra_routers, next) { + if (rap2 == rap || + rap2->iface != rap->iface || + rap2->expired) + continue; + TAILQ_FOREACH(ia2, &rap2->addrs, next) { + if (!IN6_ARE_ADDR_EQUAL(&ia->prefix, &ia2->prefix)) + continue; + if (!(ia2->flags & IPV6_AF_STALE)) + return ia2; + if (mark) + ia2->prefix_pltime = 0; + } + } + return NULL; +} + #ifndef DHCP6 /* If DHCPv6 is compiled out, supply a shim to provide an error message * if IPv6RA requests DHCPv6. */ @@ -961,16 +1100,18 @@ uint8_t *p; struct ra *rap; struct in6_addr pi_prefix; - struct ipv6_addr *ap; + struct ipv6_addr *ia; struct dhcp_opt *dho; bool new_rap, new_data, has_address; uint32_t old_lifetime; - __printflike(1, 2) void (*logfunc)(const char *, ...); + int ifmtu; + int loglevel; + unsigned int flags; #ifdef IPV6_MANAGETEMPADDR - uint8_t new_ap; + bool new_ia; #endif - if (ifp == NULL) { + if (ifp == NULL || RS_STATE(ifp) == NULL) { #ifdef DEBUG_RS logdebugx("RA for unexpected interface from %s", sfrom); #endif @@ -1014,6 +1155,19 @@ return; } + /* + * Because we preserve RA's and expire them quickly after + * carrier up, it's important to reset the kernels notion of + * reachable timers back to default values before applying + * new RA values. + */ + TAILQ_FOREACH(rap, ctx->ra_routers, next) { + if (ifp == rap->iface) + break; + } + if (rap != NULL && rap->willexpire) + ipv6nd_applyra(ifp); + TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (ifp == rap->iface && IN6_ARE_ADDR_EQUAL(&rap->from, &from->sin6_addr)) @@ -1064,8 +1218,12 @@ * routers like to decrease the advertised valid and preferred times * in accordance with the own prefix times which would result in too * much needless log spam. */ - logfunc = new_data || !rap->isreachable ? loginfox : logdebugx, - logfunc("%s: Router Advertisement from %s", ifp->name, rap->sfrom); + if (rap->willexpire) + new_data = true; + loglevel = new_rap || rap->willexpire || !rap->isreachable ? + LOG_INFO : LOG_DEBUG; + logmessage(loglevel, "%s: Router Advertisement from %s", + ifp->name, rap->sfrom); clock_gettime(CLOCK_MONOTONIC, &rap->acquired); rap->flags = nd_ra->nd_ra_flags_reserved; @@ -1074,26 +1232,31 @@ if (!new_rap && rap->lifetime == 0 && old_lifetime != 0) logwarnx("%s: %s: no longer a default router", ifp->name, rap->sfrom); - if (nd_ra->nd_ra_reachable) { + if (nd_ra->nd_ra_curhoplimit != 0) + rap->hoplimit = nd_ra->nd_ra_curhoplimit; + else + rap->hoplimit = IPV6_DEFHLIM; + if (nd_ra->nd_ra_reachable != 0) { rap->reachable = ntohl(nd_ra->nd_ra_reachable); if (rap->reachable > MAX_REACHABLE_TIME) rap->reachable = 0; - } - if (nd_ra->nd_ra_retransmit) { - struct rs_state *state = RS_STATE(ifp); - - state->retrans = rap->retrans = ntohl(nd_ra->nd_ra_retransmit); - } - rap->expired = false; + } else + rap->reachable = REACHABLE_TIME; + if (nd_ra->nd_ra_retransmit != 0) + rap->retrans = ntohl(nd_ra->nd_ra_retransmit); + else + rap->retrans = RETRANS_TIMER; + rap->expired = rap->willexpire = rap->doexpire = false; rap->hasdns = false; rap->isreachable = true; has_address = false; + rap->mtu = 0; #ifdef IPV6_AF_TEMPORARY ipv6_markaddrsstale(ifp, IPV6_AF_TEMPORARY); #endif - TAILQ_FOREACH(ap, &rap->addrs, next) { - ap->flags |= IPV6_AF_STALE; + TAILQ_FOREACH(ia, &rap->addrs, next) { + ia->flags |= IPV6_AF_STALE; } len -= sizeof(struct nd_router_advert); @@ -1143,15 +1306,17 @@ switch (ndo.nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: - logfunc = new_data ? logerrx : logdebugx; + loglevel = new_data ? LOG_ERR : LOG_DEBUG; if (ndo.nd_opt_len != 4) { - logfunc("%s: invalid option len for prefix", + logmessage(loglevel, + "%s: invalid option len for prefix", ifp->name); continue; } memcpy(&pi, p, sizeof(pi)); if (pi.nd_opt_pi_prefix_len > 128) { - logfunc("%s: invalid prefix len", ifp->name); + logmessage(loglevel, "%s: invalid prefix len", + ifp->name); continue; } /* nd_opt_pi_prefix is not aligned. */ @@ -1160,38 +1325,42 @@ if (IN6_IS_ADDR_MULTICAST(&pi_prefix) || IN6_IS_ADDR_LINKLOCAL(&pi_prefix)) { - logfunc("%s: invalid prefix in RA", ifp->name); + logmessage(loglevel, "%s: invalid prefix in RA", + ifp->name); continue; } if (ntohl(pi.nd_opt_pi_preferred_time) > ntohl(pi.nd_opt_pi_valid_time)) { - logfunc("%s: pltime > vltime", ifp->name); + logmessage(loglevel, "%s: pltime > vltime", + ifp->name); continue; } - TAILQ_FOREACH(ap, &rap->addrs, next) - if (ap->prefix_len ==pi.nd_opt_pi_prefix_len && - IN6_ARE_ADDR_EQUAL(&ap->prefix, &pi_prefix)) - break; - if (ap == NULL) { - unsigned int flags; - - flags = IPV6_AF_RAPFX; - if (pi.nd_opt_pi_flags_reserved & - ND_OPT_PI_FLAG_AUTO && - rap->iface->options->options & - DHCPCD_IPV6RA_AUTOCONF) - flags |= IPV6_AF_AUTOCONF; - ap = ipv6_newaddr(rap->iface, + flags = IPV6_AF_RAPFX; + /* If no flags are set, that means the prefix is + * available via the router. */ + if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) + flags |= IPV6_AF_ONLINK; + if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO && + rap->iface->options->options & + DHCPCD_IPV6RA_AUTOCONF) + flags |= IPV6_AF_AUTOCONF; + if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ROUTER) + flags |= IPV6_AF_ROUTER; + + ia = ipv6nd_rapfindprefix(rap, + &pi_prefix, pi.nd_opt_pi_prefix_len); + if (ia == NULL) { + ia = ipv6_newaddr(rap->iface, &pi_prefix, pi.nd_opt_pi_prefix_len, flags); - if (ap == NULL) + if (ia == NULL) break; - ap->prefix = pi_prefix; + ia->prefix = pi_prefix; if (flags & IPV6_AF_AUTOCONF) - ap->dadcallback = ipv6nd_dadcallback; - ap->created = ap->acquired = rap->acquired; - TAILQ_INSERT_TAIL(&rap->addrs, ap, next); + ia->dadcallback = ipv6nd_dadcallback; + ia->created = ia->acquired = rap->acquired; + TAILQ_INSERT_TAIL(&rap->addrs, ia, next); #ifdef IPV6_MANAGETEMPADDR /* New address to dhcpcd RA handling. @@ -1200,43 +1369,42 @@ * extend the existing one rather than * create a new one */ if (flags & IPV6_AF_AUTOCONF && - ipv6_iffindaddr(ifp, &ap->addr, + ipv6_iffindaddr(ifp, &ia->addr, IN6_IFF_NOTUSEABLE) && - ipv6_settemptime(ap, 0)) - new_ap = 0; + ipv6_settemptime(ia, 0)) + new_ia = false; else - new_ap = 1; + new_ia = true; #endif } else { #ifdef IPV6_MANAGETEMPADDR - new_ap = 0; + new_ia = false; #endif - ap->flags &= ~IPV6_AF_STALE; - ap->acquired = rap->acquired; + ia->flags |= flags; + ia->flags &= ~IPV6_AF_STALE; + ia->acquired = rap->acquired; } - if (pi.nd_opt_pi_flags_reserved & - ND_OPT_PI_FLAG_ONLINK) - ap->flags |= IPV6_AF_ONLINK; - ap->prefix_vltime = + ia->prefix_vltime = ntohl(pi.nd_opt_pi_valid_time); - ap->prefix_pltime = + ia->prefix_pltime = ntohl(pi.nd_opt_pi_preferred_time); - if (ap->prefix_vltime != 0 && - ap->flags & IPV6_AF_AUTOCONF) + if (ia->prefix_vltime != 0 && + ia->flags & IPV6_AF_AUTOCONF) has_address = true; #ifdef IPV6_MANAGETEMPADDR /* RFC4941 Section 3.3.3 */ - if (ap->flags & IPV6_AF_AUTOCONF && - ip6_use_tempaddr(ap->iface->name)) + if (ia->flags & IPV6_AF_AUTOCONF && + ia->iface->options->options & DHCPCD_SLAACTEMP && + IA6_CANAUTOCONF(ia)) { - if (!new_ap) { - if (ipv6_settemptime(ap, 1) == NULL) - new_ap = 1; + if (!new_ia) { + if (ipv6_settemptime(ia, 1) == NULL) + new_ia = true; } - if (new_ap && ap->prefix_pltime) { - if (ipv6_createtempaddr(ap, - &ap->acquired) == NULL) + if (new_ia && ia->prefix_pltime) { + if (ipv6_createtempaddr(ia, + &ia->acquired) == NULL) logerr("ipv6_createtempaddr"); } } @@ -1245,22 +1413,30 @@ case ND_OPT_MTU: if (len < sizeof(mtu)) { - logerrx("%s: short MTU option", ifp->name); + logmessage(loglevel, "%s: short MTU option", ifp->name); break; } memcpy(&mtu, p, sizeof(mtu)); mtu.nd_opt_mtu_mtu = ntohl(mtu.nd_opt_mtu_mtu); if (mtu.nd_opt_mtu_mtu < IPV6_MMTU) { - logerrx("%s: invalid MTU %d", + logmessage(loglevel, "%s: invalid MTU %d", ifp->name, mtu.nd_opt_mtu_mtu); break; } - rap->mtu = mtu.nd_opt_mtu_mtu; + ifmtu = if_getmtu(ifp); + if (ifmtu == -1) + logerr("if_getmtu"); + else if (mtu.nd_opt_mtu_mtu > (uint32_t)ifmtu) { + logmessage(loglevel, "%s: advertised MTU %d" + " is greater than link MTU %d", + ifp->name, mtu.nd_opt_mtu_mtu, ifmtu); + rap->mtu = (uint32_t)ifmtu; + } else + rap->mtu = mtu.nd_opt_mtu_mtu; break; - case ND_OPT_RDNSS: if (len < sizeof(rdnss)) { - logerrx("%s: short RDNSS option", ifp->name); + logmessage(loglevel, "%s: short RDNSS option", ifp->name); break; } memcpy(&rdnss, p, sizeof(rdnss)); @@ -1290,25 +1466,45 @@ } } + TAILQ_FOREACH(ia, &rap->addrs, next) { + if (!(ia->flags & IPV6_AF_STALE) || ia->prefix_pltime == 0) + continue; + if (ipv6nd_findmarkstale(rap, ia, false) != NULL) + continue; + ipv6nd_findmarkstale(rap, ia, true); + logdebugx("%s: %s: became stale", ifp->name, ia->saddr); + /* Technically this violates RFC 4861 6.3.4, + * but we need a mechanism to tell the kernel to + * try and prefer other addresses. */ + ia->prefix_pltime = 0; + } + if (new_data && !has_address && rap->lifetime && !ipv6_anyglobal(ifp)) logwarnx("%s: no global addresses for default route", ifp->name); if (new_rap) - add_router(ifp->ctx, rap); + TAILQ_INSERT_TAIL(ctx->ra_routers, rap, next); + if (new_data) + ipv6nd_sortrouters(ifp->ctx); if (ifp->ctx->options & DHCPCD_TEST) { script_runreason(ifp, "TEST"); goto handle_flag; } + + if (!(ifp->options->options & DHCPCD_CONFIGURE)) + goto run; + + ipv6nd_applyra(ifp); ipv6_addaddrs(&rap->addrs); #ifdef IPV6_MANAGETEMPADDR ipv6_addtempaddrs(ifp, &rap->acquired); #endif - rt_build(ifp->ctx, AF_INET6); - if (ipv6nd_scriptrun(rap)) - return; + +run: + ipv6nd_scriptrun(rap); eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); /* reachable timer */ @@ -1351,7 +1547,8 @@ if (ifp->ctx->ra_routers) { TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) - if (rap->iface == ifp && !rap->expired && + if (rap->iface == ifp && + !rap->expired && (!lifetime ||rap->lifetime)) return true; } @@ -1359,15 +1556,16 @@ } bool -ipv6nd_hasradhcp(const struct interface *ifp) +ipv6nd_hasradhcp(const struct interface *ifp, bool managed) { const struct ra *rap; if (ifp->ctx->ra_routers) { TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface == ifp && - !rap->expired && - (rap->flags &(ND_RA_FLAG_MANAGED|ND_RA_FLAG_OTHER))) + !rap->expired && !rap->willexpire && + ((managed && rap->flags & ND_RA_FLAG_MANAGED) || + (!managed && rap->flags & ND_RA_FLAG_OTHER))) return true; } } @@ -1424,6 +1622,7 @@ struct nd_opt_hdr ndo; struct ipv6_addr *ia; struct timespec now; + int pref; clock_gettime(CLOCK_MONOTONIC, &now); i = n = 0; @@ -1440,6 +1639,18 @@ if (efprintf(fp, "%s_now=%lld", ndprefix, (long long)now.tv_sec) == -1) return -1; + if (efprintf(fp, "%s_hoplimit=%u", ndprefix, rap->hoplimit) == -1) + return -1; + pref = ipv6nd_rtpref(rap); + if (efprintf(fp, "%s_flags=%s%s%s%s%s", ndprefix, + rap->flags & ND_RA_FLAG_MANAGED ? "M" : "", + rap->flags & ND_RA_FLAG_OTHER ? "O" : "", + rap->flags & ND_RA_FLAG_HOME_AGENT ? "H" : "", + pref == RTPREF_HIGH ? "h" : pref == RTPREF_LOW ? "l" : "", + rap->flags & ND_RA_FLAG_PROXY ? "P" : "") == -1) + return -1; + if (efprintf(fp, "%s_lifetime=%u", ndprefix, rap->lifetime) == -1) + return -1; /* Zero our indexes */ for (j = 0, opt = rap->iface->ctx->nd_opts; @@ -1502,7 +1713,7 @@ ia->prefix_vltime == 0) continue; if (efprintf(fp, "%s_addr%zu=%s", - ndprefix, j++, ia->saddr) == -1) + ndprefix, ++j, ia->saddr) == -1) return -1; } } @@ -1531,7 +1742,8 @@ { struct interface *ifp; struct ra *rap, *ran; - struct timespec now, lt, expire, next; + struct timespec now; + uint32_t elapsed; bool expired, valid; struct ipv6_addr *ia; size_t len, olen; @@ -1542,23 +1754,21 @@ #endif struct nd_opt_dnssl dnssl; struct nd_opt_rdnss rdnss; - uint32_t ltime; + unsigned int next = 0, ltime; size_t nexpired = 0; ifp = arg; clock_gettime(CLOCK_MONOTONIC, &now); expired = false; - timespecclear(&next); TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { if (rap->iface != ifp || rap->expired) continue; valid = false; if (rap->lifetime) { - lt.tv_sec = (time_t)rap->lifetime; - lt.tv_nsec = 0; - timespecadd(&rap->acquired, <, &expire); - if (timespeccmp(&now, &expire, >)) { + elapsed = (uint32_t)eloop_timespec_diff(&now, + &rap->acquired, NULL); + if (elapsed >= rap->lifetime || rap->doexpire) { if (!rap->expired) { logwarnx("%s: %s: router expired", ifp->name, rap->sfrom); @@ -1567,10 +1777,9 @@ } } else { valid = true; - timespecsub(&expire, &now, <); - if (!timespecisset(&next) || - timespeccmp(&next, <, >)) - next = lt; + ltime = rap->lifetime - elapsed; + if (next == 0 || ltime < next) + next = ltime; } } @@ -1580,17 +1789,21 @@ TAILQ_FOREACH(ia, &rap->addrs, next) { if (ia->prefix_vltime == 0) continue; - if (ia->prefix_vltime == ND6_INFINITE_LIFETIME) { + if (ia->prefix_vltime == ND6_INFINITE_LIFETIME && + !rap->doexpire) + { valid = true; continue; } - lt.tv_sec = (time_t)ia->prefix_vltime; - lt.tv_nsec = 0; - timespecadd(&ia->acquired, <, &expire); - if (timespeccmp(&now, &expire, >)) { + elapsed = (uint32_t)eloop_timespec_diff(&now, + &ia->acquired, NULL); + if (elapsed >= ia->prefix_vltime || rap->doexpire) { if (ia->flags & IPV6_AF_ADDED) { - logwarnx("%s: expired address %s", - ia->iface->name, ia->saddr); + logwarnx("%s: expired %s %s", + ia->iface->name, + ia->flags & IPV6_AF_AUTOCONF ? + "address" : "prefix", + ia->saddr); if (if_address6(RTM_DELADDR, ia)== -1 && errno != EADDRNOTAVAIL && errno != ENXIO) @@ -1601,15 +1814,16 @@ ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); expired = true; } else { - timespecsub(&expire, &now, <); - if (!timespecisset(&next) || - timespeccmp(&next, <, >)) - next = lt; valid = true; + ltime = ia->prefix_vltime - elapsed; + if (next == 0 || ltime < next) + next = ltime; } } /* Work out expiry for ND options */ + elapsed = (uint32_t)eloop_timespec_diff(&now, + &rap->acquired, NULL); len = rap->data_len - sizeof(struct nd_router_advert); for (p = rap->data + sizeof(struct nd_router_advert); len >= sizeof(ndo); @@ -1654,26 +1868,25 @@ if (ltime == 0) continue; + if (rap->doexpire) { + expired = true; + continue; + } if (ltime == ND6_INFINITE_LIFETIME) { valid = true; continue; } - lt.tv_sec = (time_t)ntohl(ltime); - lt.tv_nsec = 0; - timespecadd(&rap->acquired, <, &expire); - if (timespeccmp(&now, &expire, >)) { + ltime = ntohl(ltime); + if (elapsed >= ltime) { expired = true; continue; } - timespecsub(&expire, &now, <); - if (!timespecisset(&next) || - timespeccmp(&next, <, >)) - { - next = lt; - valid = true; - } + valid = true; + ltime -= elapsed; + if (next == 0 || ltime < next) + next = ltime; } if (valid) @@ -1685,11 +1898,14 @@ ipv6nd_free_ra(rap); } - if (timespecisset(&next)) - eloop_timeout_add_tv(ifp->ctx->eloop, - &next, ipv6nd_expirera, ifp); + if (next != 0) + eloop_timeout_add_sec(ifp->ctx->eloop, + next, ipv6nd_expirera, ifp); if (expired) { - logwarnx("%s: part of Router Advertisement expired", ifp->name); + logwarnx("%s: part of a Router Advertisement expired", + ifp->name); + ipv6nd_sortrouters(ifp->ctx); + ipv6nd_applyra(ifp); rt_build(ifp->ctx, AF_INET6); script_runreason(ifp, "ROUTERADVERT"); } @@ -1712,77 +1928,45 @@ } } if (expired) { + ipv6nd_applyra(ifp); rt_build(ifp->ctx, AF_INET6); if ((ifp->options->options & DHCPCD_NODROP) != DHCPCD_NODROP) script_runreason(ifp, "ROUTERADVERT"); } } -static void -ipv6nd_handledata(void *arg) +void +ipv6nd_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg) { - struct dhcpcd_ctx *ctx; - int s; - struct sockaddr_in6 from; - unsigned char buf[64 * 1024]; /* Maximum ICMPv6 size */ - struct iovec iov = { - .iov_base = buf, - .iov_len = sizeof(buf), - }; - unsigned char ctl[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))] = { 0 }; - struct msghdr msg = { - .msg_name = &from, .msg_namelen = sizeof(from), - .msg_iov = &iov, .msg_iovlen = 1, - .msg_control = ctl, .msg_controllen = sizeof(ctl), - }; - ssize_t len; + struct sockaddr_in6 *from = (struct sockaddr_in6 *)msg->msg_name; char sfrom[INET6_ADDRSTRLEN]; int hoplimit = 0; struct icmp6_hdr *icp; struct interface *ifp; + size_t len = msg->msg_iov[0].iov_len; -#ifdef __sun - struct rs_state *state; - - ifp = arg; - state = RS_STATE(ifp); - ctx = ifp->ctx; - s = state->nd_fd; -#else - ctx = arg; - s = ctx->nd_fd; -#endif - len = recvmsg(s, &msg, 0); - if (len == -1) { - logerr(__func__); - return; - } - inet_ntop(AF_INET6, &from.sin6_addr, sfrom, sizeof(sfrom)); + inet_ntop(AF_INET6, &from->sin6_addr, sfrom, sizeof(sfrom)); if ((size_t)len < sizeof(struct icmp6_hdr)) { logerrx("IPv6 ICMP packet too short from %s", sfrom); return; } -#ifdef __sun - if_findifpfromcmsg(ctx, &msg, &hoplimit); -#else - ifp = if_findifpfromcmsg(ctx, &msg, &hoplimit); + ifp = if_findifpfromcmsg(ctx, msg, &hoplimit); if (ifp == NULL) { logerr(__func__); return; } -#endif /* Don't do anything if the user hasn't configured it. */ if (ifp->active != IF_ACTIVE_USER || !(ifp->options->options & DHCPCD_IPV6)) return; - icp = (struct icmp6_hdr *)buf; + icp = (struct icmp6_hdr *)msg->msg_iov[0].iov_base; if (icp->icmp6_code == 0) { switch(icp->icmp6_type) { case ND_ROUTER_ADVERT: - ipv6nd_handlera(ctx, &from, sfrom, + ipv6nd_handlera(ctx, from, sfrom, ifp, icp, (size_t)len, hoplimit); return; } @@ -1792,6 +1976,53 @@ icp->icmp6_type, icp->icmp6_code, sfrom); } +static void +ipv6nd_handledata(void *arg) +{ + struct dhcpcd_ctx *ctx; + int fd; + struct sockaddr_in6 from; + union { + struct icmp6_hdr hdr; + uint8_t buf[64 * 1024]; /* Maximum ICMPv6 size */ + } iovbuf; + struct iovec iov = { + .iov_base = iovbuf.buf, .iov_len = sizeof(iovbuf.buf), + }; + union { + struct cmsghdr hdr; + uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo)) + + CMSG_SPACE(sizeof(int))]; + } cmsgbuf = { .buf = { 0 } }; + struct msghdr msg = { + .msg_name = &from, .msg_namelen = sizeof(from), + .msg_iov = &iov, .msg_iovlen = 1, + .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), + }; + ssize_t len; + +#ifdef __sun + struct interface *ifp; + struct rs_state *state; + + ifp = arg; + state = RS_STATE(ifp); + ctx = ifp->ctx; + fd = state->nd_fd; +#else + ctx = arg; + fd = ctx->nd_fd; +#endif + len = recvmsg(fd, &msg, 0); + if (len == -1) { + logerr(__func__); + return; + } + + iov.iov_len = (size_t)len; + ipv6nd_recvmsg(ctx, &msg); +} + static void ipv6nd_startrs1(void *arg) { @@ -1812,18 +2043,6 @@ #endif } -#ifdef __sun - if (ipv6nd_open(ifp) == -1) { - logerr(__func__); - return; - } -#else - if (ipv6nd_open(ifp->ctx) == -1) { - logerr(__func__); - return; - } -#endif - /* Always make a new probe as the underlying hardware * address could have changed. */ ipv6nd_makersprobe(ifp); @@ -1840,7 +2059,7 @@ void ipv6nd_startrs(struct interface *ifp) { - struct timespec tv; + unsigned int delay; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) { @@ -1848,12 +2067,9 @@ return; } - tv.tv_sec = 0; - tv.tv_nsec = (suseconds_t)arc4random_uniform( - MAX_RTR_SOLICITATION_DELAY * NSEC_PER_SEC); - timespecnorm(&tv); + delay = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY * MSEC_PER_SEC); logdebugx("%s: delaying IPv6 router solicitation for %0.1f seconds", - ifp->name, timespec_to_double(&tv)); - eloop_timeout_add_tv(ifp->ctx->eloop, &tv, ipv6nd_startrs1, ifp); + ifp->name, (float)delay / MSEC_PER_SEC); + eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs1, ifp); return; } Index: contrib/dhcpcd/src/logerr.h =================================================================== --- contrib/dhcpcd/src/logerr.h +++ contrib/dhcpcd/src/logerr.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * logerr: errx with logging - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -39,18 +39,49 @@ #endif #endif /* !__printflike */ -__printflike(1, 2) typedef void logfunc_t(const char *, ...); - -__printflike(1, 2) void logdebug(const char *, ...); -__printflike(1, 2) void logdebugx(const char *, ...); -__printflike(1, 2) void loginfo(const char *, ...); -__printflike(1, 2) void loginfox(const char *, ...); -__printflike(1, 2) void logwarn(const char *, ...); -__printflike(1, 2) void logwarnx(const char *, ...); -__printflike(1, 2) void logerr(const char *, ...); +/* Please do not call log_* functions directly, use macros below */ +__printflike(1, 2) void log_debug(const char *, ...); +__printflike(1, 2) void log_debugx(const char *, ...); +__printflike(1, 2) void log_info(const char *, ...); +__printflike(1, 2) void log_infox(const char *, ...); +__printflike(1, 2) void log_warn(const char *, ...); +__printflike(1, 2) void log_warnx(const char *, ...); +__printflike(1, 2) void log_err(const char *, ...); +__printflike(1, 2) void log_errx(const char *, ...); #define LOGERROR logerr("%s: %d", __FILE__, __LINE__) -__printflike(1, 2) void logerrx(const char *, ...); +__printflike(2, 3) void logmessage(int pri, const char *fmt, ...); +__printflike(2, 3) void logerrmessage(int pri, const char *fmt, ...); + +/* + * These are macros to prevent taking address of them so + * __FILE__, __LINE__, etc can easily be added. + * + * We should be using + * #define loginfox(fmt, __VA_OPT__(,) __VA_ARGS__) + * but that requires gcc-8 or clang-6 and we still have a need to support + * old OS's without modern compilers. + * + * Likewise, ##__VA_ARGS__ can't be used as that's a gcc only extension. + * + * The solution is to put fmt into __VA_ARGS__. + * It's not pretty but it's 100% portable. + */ +#define logdebug(...) log_debug(__VA_ARGS__) +#define logdebugx(...) log_debugx(__VA_ARGS__) +#define loginfo(...) log_info(__VA_ARGS__) +#define loginfox(...) log_infox(__VA_ARGS__) +#define logwarn(...) log_warn(__VA_ARGS__) +#define logwarnx(...) log_warnx(__VA_ARGS__) +#define logerr(...) log_err(__VA_ARGS__) +#define logerrx(...) log_errx(__VA_ARGS__) + +/* For logging in a chroot */ +int loggetfd(void); +void logsetfd(int); +int logreadfd(int); + +unsigned int loggetopts(void); void logsetopts(unsigned int); #define LOGERR_DEBUG (1U << 6) #define LOGERR_QUIET (1U << 7) @@ -71,8 +102,10 @@ void logsettag(const char *); #endif +/* Can be called more than once. */ int logopen(const char *); + +/* Should only be called at program exit. */ void logclose(void); -int logreopen(void); #endif Index: contrib/dhcpcd/src/logerr.c =================================================================== --- contrib/dhcpcd/src/logerr.c +++ contrib/dhcpcd/src/logerr.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * logerr: errx with logging - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -47,10 +47,16 @@ #undef LOGERR_TAG #endif +/* syslog protocol is 1k message max, RFC 3164 section 4.1 */ +#define LOGERR_SYSLOGBUF 1024 + sizeof(int) + sizeof(pid_t) + #define UNUSED(a) (void)(a) struct logctx { + char log_buf[BUFSIZ]; unsigned int log_opts; + int log_fd; + pid_t log_pid; #ifndef SMALL FILE *log_file; #ifdef LOGERR_TAG @@ -62,9 +68,11 @@ static struct logctx _logctx = { /* syslog style, but without the hostname or tag. */ .log_opts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID, + .log_fd = -1, + .log_pid = 0, }; -#if defined(LOGERR_TAG) && defined(__linux__) +#if defined(__linux__) /* Poor man's getprogname(3). */ static char *_logprog; static const char * @@ -78,9 +86,12 @@ * so zero the buffer. */ if ((_logprog = calloc(1, PATH_MAX + 1)) == NULL) return NULL; + if (readlink("/proc/self/exe", _logprog, PATH_MAX + 1) == -1) { + free(_logprog); + _logprog = NULL; + return NULL; + } } - if (readlink("/proc/self/exe", _logprog, PATH_MAX + 1) == -1) - return NULL; if (_logprog[0] == '[') return NULL; p = strrchr(_logprog, '/'); @@ -104,7 +115,6 @@ return -1; now = tv.tv_sec; - tzset(); if (localtime_r(&now, &tmnow) == NULL) return -1; if (strftime(buf, sizeof(buf), "%b %d %T ", &tmnow) == 0) @@ -147,7 +157,13 @@ log_pid = ((stream == stderr && ctx->log_opts & LOGERR_ERR_PID) || (stream != stderr && ctx->log_opts & LOGERR_LOG_PID)); if (log_pid) { - if ((e = fprintf(stream, "[%d]", getpid())) == -1) + pid_t pid; + + if (ctx->log_pid == 0) + pid = getpid(); + else + pid = ctx->log_pid; + if ((e = fprintf(stream, "[%d]", pid)) == -1) return -1; len += e; } @@ -198,33 +214,44 @@ struct logctx *ctx = &_logctx; int len = 0; + if (ctx->log_fd != -1) { + char buf[LOGERR_SYSLOGBUF]; + pid_t pid; + + memcpy(buf, &pri, sizeof(pri)); + pid = getpid(); + memcpy(buf + sizeof(pri), &pid, sizeof(pid)); + len = vsnprintf(buf + sizeof(pri) + sizeof(pid), + sizeof(buf) - sizeof(pri) - sizeof(pid), + fmt, args); + if (len != -1) + len = (int)write(ctx->log_fd, buf, + ((size_t)++len) + sizeof(pri) + sizeof(pid)); + return len; + } + if (ctx->log_opts & LOGERR_ERR && (pri <= LOG_ERR || (!(ctx->log_opts & LOGERR_QUIET) && pri <= LOG_INFO) || (ctx->log_opts & LOGERR_DEBUG && pri <= LOG_DEBUG))) len = vlogprintf_r(ctx, stderr, fmt, args); - if (!(ctx->log_opts & LOGERR_LOG)) - return len; +#ifndef SMALL + if (ctx->log_file != NULL && + (pri != LOG_DEBUG || (ctx->log_opts & LOGERR_DEBUG))) + len = vlogprintf_r(ctx, ctx->log_file, fmt, args); +#endif -#ifdef SMALL - vsyslog(pri, fmt, args); - return len; -#else - if (ctx->log_file == NULL) { + if (ctx->log_opts & LOGERR_LOG) vsyslog(pri, fmt, args); - return len; - } - if (pri == LOG_DEBUG && !(ctx->log_opts & LOGERR_DEBUG)) - return len; - return vlogprintf_r(ctx, ctx->log_file, fmt, args); -#endif + + return len; } #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5)) #pragma GCC diagnostic pop #endif -__printflike(2, 3) static void +__printflike(2, 3) void logmessage(int pri, const char *fmt, ...) { va_list args; @@ -242,10 +269,21 @@ vsnprintf(buf, sizeof(buf), fmt, args); logmessage(pri, "%s: %s", buf, strerror(_errno)); + errno = _errno; +} + +__printflike(2, 3) void +logerrmessage(int pri, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogerrmessage(pri, fmt, args); + va_end(args); } void -logdebug(const char *fmt, ...) +log_debug(const char *fmt, ...) { va_list args; @@ -255,7 +293,7 @@ } void -logdebugx(const char *fmt, ...) +log_debugx(const char *fmt, ...) { va_list args; @@ -265,7 +303,7 @@ } void -loginfo(const char *fmt, ...) +log_info(const char *fmt, ...) { va_list args; @@ -275,7 +313,7 @@ } void -loginfox(const char *fmt, ...) +log_infox(const char *fmt, ...) { va_list args; @@ -285,7 +323,7 @@ } void -logwarn(const char *fmt, ...) +log_warn(const char *fmt, ...) { va_list args; @@ -295,7 +333,7 @@ } void -logwarnx(const char *fmt, ...) +log_warnx(const char *fmt, ...) { va_list args; @@ -305,7 +343,7 @@ } void -logerr(const char *fmt, ...) +log_err(const char *fmt, ...) { va_list args; @@ -315,7 +353,7 @@ } void -logerrx(const char *fmt, ...) +log_errx(const char *fmt, ...) { va_list args; @@ -324,6 +362,62 @@ va_end(args); } +int +loggetfd(void) +{ + struct logctx *ctx = &_logctx; + + return ctx->log_fd; +} + +void +logsetfd(int fd) +{ + struct logctx *ctx = &_logctx; + + ctx->log_fd = fd; +#ifndef SMALL + if (fd != -1 && ctx->log_file != NULL) { + fclose(ctx->log_file); + ctx->log_file = NULL; + } +#endif +} + +int +logreadfd(int fd) +{ + struct logctx *ctx = &_logctx; + char buf[LOGERR_SYSLOGBUF]; + int len, pri; + + len = (int)read(fd, buf, sizeof(buf)); + if (len == -1) + return -1; + + /* Ensure we have pri, pid and a terminator */ + if (len < (int)(sizeof(pri) + sizeof(pid_t) + 1) || + buf[len - 1] != '\0') + { + errno = EINVAL; + return -1; + } + + memcpy(&pri, buf, sizeof(pri)); + memcpy(&ctx->log_pid, buf + sizeof(pri), sizeof(ctx->log_pid)); + logmessage(pri, "%s", buf + sizeof(pri) + sizeof(ctx->log_pid)); + ctx->log_pid = 0; + return len; +} + +unsigned int +loggetopts(void) +{ + struct logctx *ctx = &_logctx; + + return ctx->log_opts; +} + void logsetopts(unsigned int opts) { @@ -351,18 +445,28 @@ logopen(const char *path) { struct logctx *ctx = &_logctx; + int opts = 0; + + /* Cache timezone */ + tzset(); - if (path == NULL) { - int opts = 0; + (void)setvbuf(stderr, ctx->log_buf, _IOLBF, sizeof(ctx->log_buf)); - if (ctx->log_opts & LOGERR_LOG_PID) - opts |= LOG_PID; - openlog(NULL, opts, LOGERR_SYSLOG_FACILITY); - return 1; +#ifndef SMALL + if (ctx->log_file != NULL) { + fclose(ctx->log_file); + ctx->log_file = NULL; } +#endif + + if (ctx->log_opts & LOGERR_LOG_PID) + opts |= LOG_PID; + openlog(getprogname(), opts, LOGERR_SYSLOG_FACILITY); + if (path == NULL) + return 1; #ifndef SMALL - if ((ctx->log_file = fopen(path, "a")) == NULL) + if ((ctx->log_file = fopen(path, "ae")) == NULL) return -1; setlinebuf(ctx->log_file); return fileno(ctx->log_file); @@ -380,13 +484,14 @@ #endif closelog(); +#if defined(__linux__) + free(_logprog); + _logprog = NULL; +#endif #ifndef SMALL if (ctx->log_file == NULL) return; fclose(ctx->log_file); ctx->log_file = NULL; #endif -#if defined(LOGERR_TAG) && defined(__linux__) - free(_logprog); -#endif } Index: contrib/dhcpcd/src/privsep-bpf.h =================================================================== --- contrib/dhcpcd/src/privsep-bpf.h +++ contrib/dhcpcd/src/privsep-bpf.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* - * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Privilege Separation for dhcpcd + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -26,14 +26,25 @@ * SUCH DAMAGE. */ -#ifndef SCRIPT_H -#define SCRIPT_H +#ifndef PRIVSEP_BPF_H +#define PRIVSEP_BPF_H -#include "control.h" +ssize_t ps_bpf_cmd(struct dhcpcd_ctx *, + struct ps_msghdr *, struct msghdr *); +ssize_t ps_bpf_dispatch(struct dhcpcd_ctx *, + struct ps_msghdr *, struct msghdr *); -__printflike(2, 3) int efprintf(FILE *, const char *, ...); -void if_printoptions(void); -int send_interface(struct fd_list *, const struct interface *); -int script_runreason(const struct interface *, const char *); +#ifdef ARP +ssize_t ps_bpf_openarp(const struct interface *, const struct in_addr *); +ssize_t ps_bpf_closearp(const struct interface *, const struct in_addr *); +ssize_t ps_bpf_sendarp(const struct interface *, const struct in_addr *, + const void *, size_t); +#endif +ssize_t ps_bpf_openbootp(const struct interface *); +ssize_t ps_bpf_closebootp(const struct interface *); +ssize_t ps_bpf_sendbootp(const struct interface *, const void *, size_t); +ssize_t ps_bpf_openbootpudp(const struct interface *); +ssize_t ps_bpf_closebootpudp(const struct interface *); +ssize_t ps_bpf_sendbootpudp(const struct interface *, const void *, size_t); #endif Index: contrib/dhcpcd/src/privsep-bpf.c =================================================================== --- /dev/null +++ contrib/dhcpcd/src/privsep-bpf.c @@ -0,0 +1,372 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation BPF Initiator + * 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 +#include + +/* Need these headers just for if_ether on some OS. */ +#ifndef __NetBSD__ +#include +#include +#include +#endif +#include + +#include +#include +#include +#include +#include +#include + +#include "arp.h" +#include "bpf.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "eloop.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" + +static void +ps_bpf_recvbpf(void *arg) +{ + struct ps_process *psp = arg; + struct bpf *bpf = psp->psp_bpf; + uint8_t buf[FRAMELEN_MAX]; + ssize_t len; + struct ps_msghdr psm = { + .ps_id = psp->psp_id, + .ps_cmd = psp->psp_id.psi_cmd, + }; + + bpf->bpf_flags &= ~BPF_EOF; + /* A BPF read can read more than one filtered packet at time. + * This mechanism allows us to read each packet from the buffer. */ + while (!(bpf->bpf_flags & BPF_EOF)) { + len = bpf_read(bpf, buf, sizeof(buf)); + if (len == -1) { + int error = errno; + + if (errno != ENETDOWN) + logerr("%s: %s", psp->psp_ifname, __func__); + if (error != ENXIO) + break; + /* If the interface has departed, close the BPF + * socket. This stops log spam if RTM_IFANNOUNCE is + * delayed in announcing the departing interface. */ + eloop_event_delete(psp->psp_ctx->eloop, bpf->bpf_fd); + bpf_close(bpf); + psp->psp_bpf = NULL; + break; + } + if (len == 0) + break; + psm.ps_flags = bpf->bpf_flags; + len = ps_sendpsmdata(psp->psp_ctx, psp->psp_ctx->ps_data_fd, + &psm, buf, (size_t)len); + if (len == -1) + logerr(__func__); + if (len == -1 || len == 0) + break; + } +} + +static ssize_t +ps_bpf_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct ps_process *psp = arg; + struct iovec *iov = msg->msg_iov; + +#ifdef PRIVSEP_DEBUG + logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); +#endif + + switch(psm->ps_cmd) { +#ifdef ARP + case PS_BPF_ARP: /* FALLTHROUGH */ +#endif + case PS_BPF_BOOTP: + break; + default: + /* IPC failure, we should not be processing any commands + * at this point!/ */ + errno = EINVAL; + return -1; + } + + /* We might have had an earlier ENXIO error. */ + if (psp->psp_bpf == NULL) { + errno = ENXIO; + return -1; + } + + return bpf_send(psp->psp_bpf, psp->psp_proto, + iov->iov_base, iov->iov_len); +} + +static void +ps_bpf_recvmsg(void *arg) +{ + struct ps_process *psp = arg; + + if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, + ps_bpf_recvmsgcb, arg) == -1) + logerr(__func__); +} + +static int +ps_bpf_start_bpf(void *arg) +{ + struct ps_process *psp = arg; + struct dhcpcd_ctx *ctx = psp->psp_ctx; + char *addr; + struct in_addr *ia = &psp->psp_id.psi_addr.psa_in_addr; + + if (ia->s_addr == INADDR_ANY) { + ia = NULL; + addr = NULL; + } else + addr = inet_ntoa(*ia); + setproctitle("[BPF %s] %s%s%s", psp->psp_protostr, psp->psp_ifname, + addr != NULL ? " " : "", addr != NULL ? addr : ""); + ps_freeprocesses(ctx, psp); + + psp->psp_bpf = bpf_open(&psp->psp_ifp, psp->psp_filter, ia); + if (psp->psp_bpf == NULL) + logerr("%s: bpf_open",__func__); +#ifdef PRIVSEP_RIGHTS + else if (ps_rights_limit_fd(psp->psp_bpf->bpf_fd) == -1) + logerr("%s: ps_rights_limit_fd", __func__); +#endif + else if (eloop_event_add(ctx->eloop, + psp->psp_bpf->bpf_fd, ps_bpf_recvbpf, psp) == -1) + logerr("%s: eloop_event_add", __func__); + else { + psp->psp_work_fd = psp->psp_bpf->bpf_fd; + return 0; + } + + eloop_exit(ctx->eloop, EXIT_FAILURE); + return -1; +} + +ssize_t +ps_bpf_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) +{ + uint16_t cmd; + struct ps_process *psp; + pid_t start; + struct iovec *iov = msg->msg_iov; + struct interface *ifp; + + cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP)); + psp = ps_findprocess(ctx, &psm->ps_id); + +#ifdef PRIVSEP_DEBUG + logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); +#endif + + switch (cmd) { +#ifdef ARP + case PS_BPF_ARP: /* FALLTHROUGH */ +#endif + case PS_BPF_BOOTP: + break; + default: + logerrx("%s: unknown command %x", __func__, psm->ps_cmd); + errno = ENOTSUP; + return -1; + } + + if (!(psm->ps_cmd & PS_START)) { + errno = EINVAL; + return -1; + } + + if (psp != NULL) + return 1; + + psp = ps_newprocess(ctx, &psm->ps_id); + if (psp == NULL) + return -1; + + ifp = &psp->psp_ifp; + assert(msg->msg_iovlen == 1); + assert(iov->iov_len == sizeof(*ifp)); + memcpy(ifp, iov->iov_base, sizeof(*ifp)); + ifp->ctx = psp->psp_ctx; + ifp->options = NULL; + memset(ifp->if_data, 0, sizeof(ifp->if_data)); + + memcpy(psp->psp_ifname, ifp->name, sizeof(psp->psp_ifname)); + + switch (cmd) { +#ifdef ARP + case PS_BPF_ARP: + psp->psp_proto = ETHERTYPE_ARP; + psp->psp_protostr = "ARP"; + psp->psp_filter = bpf_arp; + break; +#endif + case PS_BPF_BOOTP: + psp->psp_proto = ETHERTYPE_IP; + psp->psp_protostr = "BOOTP"; + psp->psp_filter = bpf_bootp; + break; + } + + start = ps_dostart(ctx, + &psp->psp_pid, &psp->psp_fd, + ps_bpf_recvmsg, NULL, psp, + ps_bpf_start_bpf, NULL, + PSF_DROPPRIVS); + switch (start) { + case -1: + ps_freeprocess(psp); + return -1; + case 0: + ps_entersandbox("stdio", NULL); + break; + default: + logdebugx("%s: spawned BPF %s on PID %d", + psp->psp_ifname, psp->psp_protostr, start); + break; + } + return start; +} + +ssize_t +ps_bpf_dispatch(struct dhcpcd_ctx *ctx, + struct ps_msghdr *psm, struct msghdr *msg) +{ + struct iovec *iov = msg->msg_iov; + struct interface *ifp; + uint8_t *bpf; + size_t bpf_len; + + switch (psm->ps_cmd) { +#ifdef ARP + case PS_BPF_ARP: +#endif + case PS_BPF_BOOTP: + break; + default: + errno = ENOTSUP; + return -1; + } + + ifp = if_findindex(ctx->ifaces, psm->ps_id.psi_ifindex); + /* interface may have departed .... */ + if (ifp == NULL) + return -1; + + bpf = iov->iov_base; + bpf_len = iov->iov_len; + + switch (psm->ps_cmd) { +#ifdef ARP + case PS_BPF_ARP: + arp_packet(ifp, bpf, bpf_len, (unsigned int)psm->ps_flags); + break; +#endif + case PS_BPF_BOOTP: + dhcp_packet(ifp, bpf, bpf_len, (unsigned int)psm->ps_flags); + break; + } + + return 1; +} + +static ssize_t +ps_bpf_send(const struct interface *ifp, const struct in_addr *ia, + uint16_t cmd, const void *data, size_t len) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_id = { + .psi_ifindex = ifp->index, + .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), + }, + }; + + if (ia != NULL) + psm.ps_id.psi_addr.psa_in_addr = *ia; + + return ps_sendpsmdata(ctx, ctx->ps_root_fd, &psm, data, len); +} + +#ifdef ARP +ssize_t +ps_bpf_openarp(const struct interface *ifp, const struct in_addr *ia) +{ + + assert(ia != NULL); + return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_START, + ifp, sizeof(*ifp)); +} + +ssize_t +ps_bpf_closearp(const struct interface *ifp, const struct in_addr *ia) +{ + + return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_STOP, NULL, 0); +} + +ssize_t +ps_bpf_sendarp(const struct interface *ifp, const struct in_addr *ia, + const void *data, size_t len) +{ + + assert(ia != NULL); + return ps_bpf_send(ifp, ia, PS_BPF_ARP, data, len); +} +#endif + +ssize_t +ps_bpf_openbootp(const struct interface *ifp) +{ + + return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_START, + ifp, sizeof(*ifp)); +} + +ssize_t +ps_bpf_closebootp(const struct interface *ifp) +{ + + return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_STOP, NULL, 0); +} + +ssize_t +ps_bpf_sendbootp(const struct interface *ifp, const void *data, size_t len) +{ + + return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP, data, len); +} Index: contrib/dhcpcd/src/privsep-bsd.c =================================================================== --- /dev/null +++ contrib/dhcpcd/src/privsep-bsd.c @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, BSD driver + * 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 + +/* Need these for filtering the ioctls */ +#include +#include +#include +#include +#include +#include +#ifdef __NetBSD__ +#include +#include /* Needs netinet/if_ether.h */ +#elif defined(__DragonFly__) +#include +#else +#include +#endif +#ifdef __DragonFly__ +# include +#else +# include +# include +#endif + +#include +#include +#include + +#include "dhcpcd.h" +#include "logerr.h" +#include "privsep.h" + +static ssize_t +ps_root_doioctldom(int domain, unsigned long req, void *data, size_t len) +{ + int s, err; + + /* Only allow these ioctls */ + switch(req) { +#ifdef SIOCGIFDATA + case SIOCGIFDATA: /* FALLTHROUGH */ +#endif +#ifdef SIOCG80211NWID + case SIOCG80211NWID: /* FALLTHROUGH */ +#endif +#ifdef SIOCGETVLAN + case SIOCGETVLAN: /* FALLTHROUGH */ +#endif +#ifdef SIOCIFAFATTACH + case SIOCIFAFATTACH: /* FALLTHROUGH */ +#endif +#ifdef SIOCSIFXFLAGS + case SIOCSIFXFLAGS: /* FALLTHROUGH */ +#endif +#ifdef SIOCSIFINFO_FLAGS + case SIOCSIFINFO_FLAGS: /* FALLTHROUGH */ +#endif +#ifdef SIOCSRTRFLUSH_IN6 + case SIOCSRTRFLUSH_IN6: /* FALLTHROUGH */ + case SIOCSPFXFLUSH_IN6: /* FALLTHROUGH */ +#endif +#if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) + case SIOCALIFADDR: /* FALLTHROUGH */ + case SIOCDLIFADDR: /* FALLTHROUGH */ +#else + case SIOCSIFLLADDR: /* FALLTHROUGH */ +#endif +#ifdef SIOCSIFINFO_IN6 + case SIOCSIFINFO_IN6: /* FALLTHROUGH */ +#endif + case SIOCAIFADDR_IN6: /* FALLTHROUGH */ + case SIOCDIFADDR_IN6: + break; + default: + errno = EPERM; + return -1; + } + + s = socket(domain, SOCK_DGRAM, 0); + if (s == -1) + return -1; + err = ioctl(s, req, data, len); + close(s); + return err; +} + +static ssize_t +ps_root_doroute(void *data, size_t len) +{ + int s; + ssize_t err; + + s = socket(PF_ROUTE, SOCK_RAW, 0); + if (s != -1) + err = write(s, data, len); + else + err = -1; + if (s != -1) + close(s); + return err; +} + +#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) +static ssize_t +ps_root_doindirectioctl(unsigned long req, void *data, size_t len) +{ + char *p = data; + struct ifreq ifr = { .ifr_flags = 0 }; + + /* ioctl filtering is done in ps_root_doioctldom */ + + if (len < IFNAMSIZ + 1) { + errno = EINVAL; + return -1; + } + + strlcpy(ifr.ifr_name, p, IFNAMSIZ); + len -= IFNAMSIZ; + memmove(data, p + IFNAMSIZ, len); + ifr.ifr_data = data; + + return ps_root_doioctldom(PF_INET, req, &ifr, sizeof(ifr)); +} +#endif + +#ifdef HAVE_PLEDGE +static ssize_t +ps_root_doifignoregroup(void *data, size_t len) +{ + int s, err; + + if (len == 0 || ((const char *)data)[len - 1] != '\0') { + errno = EINVAL; + return -1; + } + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s == -1) + return -1; + err = if_ignoregroup(s, data); + close(s); + return err; +} +#endif + +ssize_t +ps_root_os(struct ps_msghdr *psm, struct msghdr *msg, + void **rdata, size_t *rlen) +{ + struct iovec *iov = msg->msg_iov; + void *data = iov->iov_base; + size_t len = iov->iov_len; + ssize_t err; + + switch (psm->ps_cmd) { + case PS_IOCTLLINK: + err = ps_root_doioctldom(PF_LINK, psm->ps_flags, data, len); + break; + case PS_IOCTL6: + err = ps_root_doioctldom(PF_INET6, psm->ps_flags, data, len); + break; + case PS_ROUTE: + return ps_root_doroute(data, len); +#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) + case PS_IOCTLINDIRECT: + err = ps_root_doindirectioctl(psm->ps_flags, data, len); + break; +#endif +#ifdef HAVE_PLEDGE + case PS_IFIGNOREGRP: + return ps_root_doifignoregroup(data, len); +#endif + default: + errno = ENOTSUP; + return -1; + } + + if (err != -1) { + *rdata = data; + *rlen = len; + } + return err; +} + +static ssize_t +ps_root_ioctldom(struct dhcpcd_ctx *ctx, uint16_t domain, unsigned long request, + void *data, size_t len) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, domain, + request, data, len) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} + +ssize_t +ps_root_ioctllink(struct dhcpcd_ctx *ctx, unsigned long request, + void *data, size_t len) +{ + + return ps_root_ioctldom(ctx, PS_IOCTLLINK, request, data, len); +} + +ssize_t +ps_root_ioctl6(struct dhcpcd_ctx *ctx, unsigned long request, + void *data, size_t len) +{ + + return ps_root_ioctldom(ctx, PS_IOCTL6, request, data, len); +} + +ssize_t +ps_root_route(struct dhcpcd_ctx *ctx, void *data, size_t len) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_ROUTE, 0, data, len) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} + +#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) +ssize_t +ps_root_indirectioctl(struct dhcpcd_ctx *ctx, unsigned long request, + const char *ifname, void *data, size_t len) +{ + char buf[PS_BUFLEN]; + + if (IFNAMSIZ + len > sizeof(buf)) { + errno = ENOBUFS; + return -1; + } + + strlcpy(buf, ifname, IFNAMSIZ); + memcpy(buf + IFNAMSIZ, data, len); + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTLINDIRECT, + request, buf, IFNAMSIZ + len) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} + +ssize_t +ps_root_ifignoregroup(struct dhcpcd_ctx *ctx, const char *ifname) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IFIGNOREGRP, 0, + ifname, strlen(ifname) + 1) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} +#endif Index: contrib/dhcpcd/src/privsep-control.h =================================================================== --- contrib/dhcpcd/src/privsep-control.h +++ contrib/dhcpcd/src/privsep-control.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* - * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2015 Roy Marples + * Privilege Separation for dhcpcd + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -26,11 +26,16 @@ * SUCH DAMAGE. */ -#ifndef DUID_H -#define DUID_H +#ifndef PRIVSEP_CTL_H +#define PRIVSEP_CTL_H -#define DUID_LEN 128 + 2 +#define IN_PRIVSEP_CONTROLLER(ctx) \ + (IN_PRIVSEP((ctx)) && (ctx)->ps_control_pid == getpid()) -size_t duid_init(const struct interface *); +pid_t ps_ctl_start(struct dhcpcd_ctx *); +int ps_ctl_stop(struct dhcpcd_ctx *); +ssize_t ps_ctl_handleargs(struct fd_list *, char *, size_t); +ssize_t ps_ctl_sendargs(struct fd_list *, void *, size_t); +ssize_t ps_ctl_sendeof(struct fd_list *fd); #endif Index: contrib/dhcpcd/src/privsep-control.c =================================================================== --- /dev/null +++ contrib/dhcpcd/src/privsep-control.c @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, control proxy + * 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 +#include +#include + +#include "dhcpcd.h" +#include "control.h" +#include "eloop.h" +#include "logerr.h" +#include "privsep.h" + +static int +ps_ctl_startcb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + sa_family_t af; + + if (ctx->options & DHCPCD_MANAGER) { + setproctitle("[control proxy]"); + af = AF_UNSPEC; + } else { + setproctitle("[control proxy] %s%s%s", + ctx->ifv[0], + ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", + ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); + if ((ctx->options & + (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV4) + af = AF_INET; + else if ((ctx->options & + (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV6) + af = AF_INET6; + else + af = AF_UNSPEC; + } + + ctx->ps_control_pid = getpid(); + + return control_start(ctx, + ctx->options & DHCPCD_MANAGER ? NULL : *ctx->ifv, af); +} + +static ssize_t +ps_ctl_recvmsgcb(void *arg, struct ps_msghdr *psm, __unused struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (psm->ps_cmd != PS_CTL_EOF) { + errno = ENOTSUP; + return -1; + } + + if (ctx->ps_control_client != NULL) + ctx->ps_control_client = NULL; + return 0; +} + +static void +ps_ctl_recvmsg(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_recvmsgcb, ctx) == -1) + logerr(__func__); +} + +ssize_t +ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len) +{ + + /* Make any change here in dhcpcd.c as well. */ + if (strncmp(data, "--version", + MIN(strlen("--version"), len)) == 0) { + return control_queue(fd, UNCONST(VERSION), + strlen(VERSION) + 1); + } else if (strncmp(data, "--getconfigfile", + MIN(strlen("--getconfigfile"), len)) == 0) { + return control_queue(fd, UNCONST(fd->ctx->cffile), + strlen(fd->ctx->cffile) + 1); + } else if (strncmp(data, "--listen", + MIN(strlen("--listen"), len)) == 0) { + fd->flags |= FD_LISTEN; + return 0; + } + + if (fd->ctx->ps_control_client != NULL && + fd->ctx->ps_control_client != fd) + { + logerrx("%s: cannot handle another client", __func__); + return 0; + } + return 1; +} + +static ssize_t +ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + struct iovec *iov = msg->msg_iov; + struct fd_list *fd; + unsigned int fd_flags = FD_SENDLEN; + + switch (psm->ps_flags) { + case PS_CTL_PRIV: + break; + case PS_CTL_UNPRIV: + fd_flags |= FD_UNPRIV; + break; + } + + switch (psm->ps_cmd) { + case PS_CTL: + if (msg->msg_iovlen != 1) { + errno = EINVAL; + return -1; + } + if (ctx->ps_control_client != NULL) { + logerrx("%s: cannot handle another client", __func__); + return 0; + } + fd = control_new(ctx, ctx->ps_control_data_fd, fd_flags); + if (fd == NULL) + return -1; + ctx->ps_control_client = fd; + control_recvdata(fd, iov->iov_base, iov->iov_len); + break; + case PS_CTL_EOF: + control_free(ctx->ps_control_client); + break; + default: + errno = ENOTSUP; + return -1; + } + return 0; +} + +static void +ps_ctl_dodispatch(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_dispatch, ctx) == -1) + logerr(__func__); +} + +static void +ps_ctl_recv(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + char buf[BUFSIZ]; + ssize_t len; + + errno = 0; + len = read(ctx->ps_control_data_fd, buf, sizeof(buf)); + if (len == -1 || len == 0) { + logerr("%s: read", __func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + if (ctx->ps_control_client == NULL) /* client disconnected */ + return; + errno = 0; + if (control_queue(ctx->ps_control_client, buf, (size_t)len) == -1) + logerr("%s: control_queue", __func__); +} + +static void +ps_ctl_listen(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + char buf[BUFSIZ]; + ssize_t len; + struct fd_list *fd; + + errno = 0; + len = read(ctx->ps_control->fd, buf, sizeof(buf)); + if (len == -1 || len == 0) { + logerr("%s: read", __func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + + /* Send to our listeners */ + TAILQ_FOREACH(fd, &ctx->control_fds, next) { + if (!(fd->flags & FD_LISTEN)) + continue; + if (control_queue(fd, buf, (size_t)len)== -1) + logerr("%s: control_queue", __func__); + } +} + +pid_t +ps_ctl_start(struct dhcpcd_ctx *ctx) +{ + int data_fd[2], listen_fd[2]; + pid_t pid; + + if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, data_fd) == -1 || + xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1) + return -1; +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fdpair(data_fd) == -1 || + ps_rights_limit_fdpair(listen_fd) == -1) + return -1; +#endif + + pid = ps_dostart(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd, + ps_ctl_recvmsg, ps_ctl_dodispatch, ctx, + ps_ctl_startcb, NULL, + PSF_DROPPRIVS); + + if (pid == -1) + return -1; + else if (pid != 0) { + ctx->ps_control_data_fd = data_fd[1]; + close(data_fd[0]); + ctx->ps_control = control_new(ctx, + listen_fd[1], FD_SENDLEN | FD_LISTEN); + if (ctx->ps_control == NULL) + return -1; + close(listen_fd[0]); + return pid; + } + + ctx->ps_control_data_fd = data_fd[0]; + close(data_fd[1]); + if (eloop_event_add(ctx->eloop, ctx->ps_control_data_fd, + ps_ctl_recv, ctx) == -1) + return -1; + + ctx->ps_control = control_new(ctx, + listen_fd[0], 0); + close(listen_fd[1]); + if (ctx->ps_control == NULL) + return -1; + if (eloop_event_add(ctx->eloop, ctx->ps_control->fd, + ps_ctl_listen, ctx) == -1) + return -1; + + ps_entersandbox("stdio inet", NULL); + return 0; +} + +int +ps_ctl_stop(struct dhcpcd_ctx *ctx) +{ + + return ps_dostop(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd); +} + +ssize_t +ps_ctl_sendargs(struct fd_list *fd, void *data, size_t len) +{ + struct dhcpcd_ctx *ctx = fd->ctx; + + if (ctx->ps_control_client != NULL && ctx->ps_control_client != fd) + logerrx("%s: cannot deal with another client", __func__); + ctx->ps_control_client = fd; + return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL, + fd->flags & FD_UNPRIV ? PS_CTL_UNPRIV : PS_CTL_PRIV, + data, len); +} + +ssize_t +ps_ctl_sendeof(struct fd_list *fd) +{ + struct dhcpcd_ctx *ctx = fd->ctx; + + return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL_EOF, 0, NULL, 0); +} Index: contrib/dhcpcd/src/privsep-inet.h =================================================================== --- contrib/dhcpcd/src/privsep-inet.h +++ contrib/dhcpcd/src/privsep-inet.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* - * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Privilege Separation for dhcpcd + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -26,51 +26,33 @@ * SUCH DAMAGE. */ -#ifndef CONTROL_H -#define CONTROL_H +#ifndef PRIVSEP_INET_H +#define PRIVSEP_INET_H -#include +bool ps_inet_canstart(const struct dhcpcd_ctx *); +pid_t ps_inet_start(struct dhcpcd_ctx *); +int ps_inet_stop(struct dhcpcd_ctx *); +ssize_t ps_inet_cmd(struct dhcpcd_ctx *, struct ps_msghdr *, struct msghdr *); +ssize_t ps_inet_dispatch(void *, struct ps_msghdr *, struct msghdr *); -#include "dhcpcd.h" - -#if !defined(CTL_FREE_LIST) -#define CTL_FREE_LIST 1 -#elif CTL_FREE_LIST == 0 -#undef CTL_FREE_LIST +#ifdef INET +struct ipv4_addr; +ssize_t ps_inet_openbootp(struct ipv4_addr *); +ssize_t ps_inet_closebootp(struct ipv4_addr *); +ssize_t ps_inet_sendbootp(struct interface *, const struct msghdr *); #endif -/* Limit queue size per fd */ -#define CONTROL_QUEUE_MAX 100 - -struct fd_data { - TAILQ_ENTRY(fd_data) next; - void *data; - size_t data_size; - size_t data_len; -}; -TAILQ_HEAD(fd_data_head, fd_data); - -struct fd_list { - TAILQ_ENTRY(fd_list) next; - struct dhcpcd_ctx *ctx; - int fd; - unsigned int flags; - struct fd_data_head queue; - size_t queue_len; -#ifdef CTL_FREE_LIST - struct fd_data_head free_queue; +#ifdef INET6 +struct ipv6_addr; +#ifdef __sun +ssize_t ps_inet_opennd(struct interface *); +ssize_t ps_inet_closend(struct interface *); #endif -}; -TAILQ_HEAD(fd_list_head, fd_list); - -#define FD_LISTEN (1<<0) -#define FD_UNPRIV (1<<1) - -int control_start(struct dhcpcd_ctx *, const char *); -int control_stop(struct dhcpcd_ctx *); -int control_open(const char *); -ssize_t control_send(struct dhcpcd_ctx *, int, char * const *); -int control_queue(struct fd_list *, void *, size_t, bool); -void control_close(struct dhcpcd_ctx *ctx); - +ssize_t ps_inet_sendnd(struct interface *, const struct msghdr *); +#ifdef DHCP6 +ssize_t ps_inet_opendhcp6(struct ipv6_addr *); +ssize_t ps_inet_closedhcp6(struct ipv6_addr *); +ssize_t ps_inet_senddhcp6(struct interface *, const struct msghdr *); +#endif /* DHCP6 */ +#endif /* INET6 */ #endif Index: contrib/dhcpcd/src/privsep-inet.c =================================================================== --- /dev/null +++ contrib/dhcpcd/src/privsep-inet.c @@ -0,0 +1,717 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, network proxy + * 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 +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "arp.h" +#include "bpf.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "eloop.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" + +#ifdef INET +static void +ps_inet_recvbootp(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvmsg(ctx, ctx->udp_rfd, PS_BOOTP, ctx->ps_inet_fd) == -1) + logerr(__func__); +} +#endif + +#ifdef INET6 +static void +ps_inet_recvra(void *arg) +{ +#ifdef __sun + struct interface *ifp = arg; + struct rs_state *state = RS_STATE(ifp); + struct dhcpcd_ctx *ctx = ifp->ctx; + + if (ps_recvmsg(ctx, state->nd_fd, PS_ND, ctx->ps_inet_fd) == -1) + logerr(__func__); +#else + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvmsg(ctx, ctx->nd_fd, PS_ND, ctx->ps_inet_fd) == -1) + logerr(__func__); +#endif +} +#endif + +#ifdef DHCP6 +static void +ps_inet_recvdhcp6(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvmsg(ctx, ctx->dhcp6_rfd, PS_DHCP6, ctx->ps_inet_fd) == -1) + logerr(__func__); +} +#endif + +bool +ps_inet_canstart(const struct dhcpcd_ctx *ctx) +{ + +#ifdef INET + if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_MANAGER)) == + (DHCPCD_IPV4 | DHCPCD_MANAGER)) + return true; +#endif +#if defined(INET6) && !defined(__sun) + if (ctx->options & DHCPCD_IPV6) + return true; +#endif +#ifdef DHCP6 + if ((ctx->options & (DHCPCD_IPV6 | DHCPCD_MANAGER)) == + (DHCPCD_IPV6 | DHCPCD_MANAGER)) + return true; +#endif + + return false; +} + +static int +ps_inet_startcb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + int ret = 0; + + if (ctx->options & DHCPCD_MANAGER) + setproctitle("[network proxy]"); + else + setproctitle("[network proxy] %s%s%s", + ctx->ifv[0], + ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", + ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); + + /* This end is the main engine, so it's useless for us. */ + close(ctx->ps_data_fd); + ctx->ps_data_fd = -1; + + errno = 0; + +#ifdef INET + if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_MANAGER)) == + (DHCPCD_IPV4 | DHCPCD_MANAGER)) + { + ctx->udp_rfd = dhcp_openudp(NULL); + if (ctx->udp_rfd == -1) + logerr("%s: dhcp_open", __func__); +#ifdef PRIVSEP_RIGHTS + else if (ps_rights_limit_fd_rdonly(ctx->udp_rfd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + close(ctx->udp_rfd); + ctx->udp_rfd = -1; + } +#endif + else if (eloop_event_add(ctx->eloop, ctx->udp_rfd, + ps_inet_recvbootp, ctx) == -1) + { + logerr("%s: eloop_event_add DHCP", __func__); + close(ctx->udp_rfd); + ctx->udp_rfd = -1; + } else + ret++; + } +#endif +#if defined(INET6) && !defined(__sun) + if (ctx->options & DHCPCD_IPV6) { + ctx->nd_fd = ipv6nd_open(true); + if (ctx->nd_fd == -1) + logerr("%s: ipv6nd_open", __func__); +#ifdef PRIVSEP_RIGHTS + else if (ps_rights_limit_fd_rdonly(ctx->nd_fd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + close(ctx->nd_fd); + ctx->nd_fd = -1; + } +#endif + else if (eloop_event_add(ctx->eloop, ctx->nd_fd, + ps_inet_recvra, ctx) == -1) + { + logerr("%s: eloop_event_add RA", __func__); + close(ctx->nd_fd); + ctx->nd_fd = -1; + } else + ret++; + } +#endif +#ifdef DHCP6 + if ((ctx->options & (DHCPCD_IPV6 | DHCPCD_MANAGER)) == + (DHCPCD_IPV6 | DHCPCD_MANAGER)) + { + ctx->dhcp6_rfd = dhcp6_openudp(0, NULL); + if (ctx->dhcp6_rfd == -1) + logerr("%s: dhcp6_open", __func__); +#ifdef PRIVSEP_RIGHTS + else if (ps_rights_limit_fd_rdonly(ctx->dhcp6_rfd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + close(ctx->dhcp6_rfd); + ctx->dhcp6_rfd = -1; + } +#endif + else if (eloop_event_add(ctx->eloop, ctx->dhcp6_rfd, + ps_inet_recvdhcp6, ctx) == -1) + { + logerr("%s: eloop_event_add DHCP6", __func__); + close(ctx->dhcp6_rfd); + ctx->dhcp6_rfd = -1; + } else + ret++; + } +#endif + + if (ret == 0 && errno == 0) { + errno = ENXIO; + return -1; + } + return ret; +} + +static bool +ps_inet_validudp(struct msghdr *msg, uint16_t sport, uint16_t dport) +{ + struct udphdr udp; + struct iovec *iov = msg->msg_iov; + + if (msg->msg_iovlen == 0 || iov->iov_len < sizeof(udp)) { + errno = EINVAL; + return false; + } + + memcpy(&udp, iov->iov_base, sizeof(udp)); + if (udp.uh_sport != htons(sport) || udp.uh_dport != htons(dport)) { + errno = EPERM; + return false; + } + return true; +} + +#ifdef INET6 +static bool +ps_inet_validnd(struct msghdr *msg) +{ + struct icmp6_hdr icmp6; + struct iovec *iov = msg->msg_iov; + + if (msg->msg_iovlen == 0 || iov->iov_len < sizeof(icmp6)) { + errno = EINVAL; + return false; + } + + memcpy(&icmp6, iov->iov_base, sizeof(icmp6)); + switch(icmp6.icmp6_type) { + case ND_ROUTER_SOLICIT: + case ND_NEIGHBOR_ADVERT: + break; + default: + errno = EPERM; + return false; + } + + return true; +} +#endif + +static ssize_t +ps_inet_sendmsg(struct dhcpcd_ctx *ctx, + struct ps_msghdr *psm, struct msghdr *msg) +{ + struct ps_process *psp; + int s; + + psp = ps_findprocess(ctx, &psm->ps_id); + if (psp != NULL) { + s = psp->psp_work_fd; + goto dosend; + } + + switch (psm->ps_cmd) { +#ifdef INET + case PS_BOOTP: + if (!ps_inet_validudp(msg, BOOTPC, BOOTPS)) + return -1; + s = ctx->udp_wfd; + break; +#endif +#if defined(INET6) && !defined(__sun) + case PS_ND: + if (!ps_inet_validnd(msg)) + return -1; + s = ctx->nd_fd; + break; +#endif +#ifdef DHCP6 + case PS_DHCP6: + if (!ps_inet_validudp(msg, DHCP6_CLIENT_PORT,DHCP6_SERVER_PORT)) + return -1; + s = ctx->dhcp6_wfd; + break; +#endif + default: + errno = EINVAL; + return -1; + } + +dosend: + return sendmsg(s, msg, 0); +} + +static void +ps_inet_recvmsg(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + /* Receive shutdown */ + if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, NULL, NULL) == -1) + logerr(__func__); +} + +ssize_t +ps_inet_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + + switch (psm->ps_cmd) { +#ifdef INET + case PS_BOOTP: + dhcp_recvmsg(ctx, msg); + break; +#endif +#ifdef INET6 + case PS_ND: + ipv6nd_recvmsg(ctx, msg); + break; +#endif +#ifdef DHCP6 + case PS_DHCP6: + dhcp6_recvmsg(ctx, msg, NULL); + break; +#endif + default: + errno = ENOTSUP; + return -1; + } + return 1; +} + +static void +ps_inet_dodispatch(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_inet_fd, ps_inet_dispatch, ctx) == -1) + logerr(__func__); +} + +pid_t +ps_inet_start(struct dhcpcd_ctx *ctx) +{ + pid_t pid; + + pid = ps_dostart(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd, + ps_inet_recvmsg, ps_inet_dodispatch, ctx, + ps_inet_startcb, NULL, + PSF_DROPPRIVS); + + if (pid == 0) + ps_entersandbox("stdio", NULL); + + return pid; +} + +int +ps_inet_stop(struct dhcpcd_ctx *ctx) +{ + + return ps_dostop(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd); +} + +#ifdef INET +static void +ps_inet_recvinbootp(void *arg) +{ + struct ps_process *psp = arg; + + if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd, + PS_BOOTP, psp->psp_ctx->ps_data_fd) == -1) + logerr(__func__); +} + +static int +ps_inet_listenin(void *arg) +{ + struct ps_process *psp = arg; + struct in_addr *ia = &psp->psp_id.psi_addr.psa_in_addr; + char buf[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, ia, buf, sizeof(buf)); + setproctitle("[network proxy] %s", buf); + + psp->psp_work_fd = dhcp_openudp(ia); + if (psp->psp_work_fd == -1) { + logerr(__func__); + return -1; + } + +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + return -1; + } +#endif + + if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd, + ps_inet_recvinbootp, psp) == -1) + { + logerr("%s: eloop_event_add DHCP", __func__); + return -1; + } + + logdebugx("spawned listener %s on PID %d", buf, getpid()); + return 0; +} +#endif + +#if defined(INET6) && defined(__sun) +static void +ps_inet_recvin6nd(void *arg) +{ + struct ps_process *psp = arg; + + if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd, + PS_ND, psp->psp_ctx->ps_data_fd) == -1) + logerr(__func__); +} + +static int +ps_inet_listennd(void *arg) +{ + struct ps_process *psp = arg; + + setproctitle("[ND network proxy]"); + + psp->psp_work_fd = ipv6nd_open(&psp->psp_ifp); + if (psp->psp_work_fd == -1) { + logerr(__func__); + return -1; + } + +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + return -1; + } +#endif + + if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd, + ps_inet_recvin6nd, psp) == -1) + { + logerr(__func__); + return -1; + } + + logdebugx("spawned ND listener on PID %d", getpid()); + return 0; +} +#endif + +#ifdef DHCP6 +static void +ps_inet_recvin6dhcp6(void *arg) +{ + struct ps_process *psp = arg; + + if (ps_recvmsg(psp->psp_ctx, psp->psp_work_fd, + PS_DHCP6, psp->psp_ctx->ps_data_fd) == -1) + logerr(__func__); +} + +static int +ps_inet_listenin6(void *arg) +{ + struct ps_process *psp = arg; + struct in6_addr *ia = &psp->psp_id.psi_addr.psa_in6_addr; + char buf[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, ia, buf, sizeof(buf)); + setproctitle("[network proxy] %s", buf); + + psp->psp_work_fd = dhcp6_openudp(psp->psp_id.psi_ifindex, ia); + if (psp->psp_work_fd == -1) { + logerr(__func__); + return -1; + } + +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fd_rdonly(psp->psp_work_fd) == -1) { + logerr("%s: ps_rights_limit_fd_rdonly", __func__); + return -1; + } +#endif + + if (eloop_event_add(psp->psp_ctx->eloop, psp->psp_work_fd, + ps_inet_recvin6dhcp6, psp) == -1) + { + logerr("%s: eloop_event_add DHCP", __func__); + return -1; + } + + logdebugx("spawned listener %s on PID %d", buf, getpid()); + return 0; +} +#endif + +static void +ps_inet_recvmsgpsp(void *arg) +{ + struct ps_process *psp = arg; + + /* Receive shutdown. */ + if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, NULL, NULL) == -1) + logerr(__func__); +} + +ssize_t +ps_inet_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) +{ + uint16_t cmd; + struct ps_process *psp; + int (*start_func)(void *); + pid_t start; + + cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP)); + if (cmd == psm->ps_cmd) + return ps_inet_sendmsg(ctx, psm, msg); + + psp = ps_findprocess(ctx, &psm->ps_id); + +#ifdef PRIVSEP_DEBUG + logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); +#endif + + if (psm->ps_cmd & PS_STOP) { + assert(psp == NULL); + return 0; + } + + switch (cmd) { +#ifdef INET + case PS_BOOTP: + start_func = ps_inet_listenin; + break; +#endif +#ifdef INET6 +#ifdef __sun + case PS_ND: + start_func = ps_inet_listennd; + break; +#endif +#ifdef DHCP6 + case PS_DHCP6: + start_func = ps_inet_listenin6; + break; +#endif +#endif + default: + logerrx("%s: unknown command %x", __func__, psm->ps_cmd); + errno = ENOTSUP; + return -1; + } + + if (!(psm->ps_cmd & PS_START)) { + errno = EINVAL; + return -1; + } + + if (psp != NULL) + return 1; + + psp = ps_newprocess(ctx, &psm->ps_id); + if (psp == NULL) + return -1; + + start = ps_dostart(ctx, + &psp->psp_pid, &psp->psp_fd, + ps_inet_recvmsgpsp, NULL, psp, + start_func, NULL, + PSF_DROPPRIVS); + switch (start) { + case -1: + ps_freeprocess(psp); + return -1; + case 0: + ps_entersandbox("stdio", NULL); + break; + default: + break; + } + return start; +} + +#ifdef INET +static ssize_t +ps_inet_in_docmd(struct ipv4_addr *ia, uint16_t cmd, const struct msghdr *msg) +{ + assert(ia != NULL); + struct dhcpcd_ctx *ctx = ia->iface->ctx; + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_id = { + .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), + .psi_ifindex = ia->iface->index, + .psi_addr.psa_in_addr = ia->addr, + }, + }; + + return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg); +} + +ssize_t +ps_inet_openbootp(struct ipv4_addr *ia) +{ + + return ps_inet_in_docmd(ia, PS_START | PS_BOOTP, NULL); +} + +ssize_t +ps_inet_closebootp(struct ipv4_addr *ia) +{ + + return ps_inet_in_docmd(ia, PS_STOP | PS_BOOTP, NULL); +} + +ssize_t +ps_inet_sendbootp(struct interface *ifp, const struct msghdr *msg) +{ + + return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_BOOTP, 0, msg); +} +#endif /* INET */ + +#ifdef INET6 +#ifdef __sun +static ssize_t +ps_inet_ifp_docmd(struct interface *ifp, uint16_t cmd, const struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_id = { + .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), + .psi_ifindex = ifp->index, + }, + }; + + return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg); +} + +ssize_t +ps_inet_opennd(struct interface *ifp) +{ + + return ps_inet_ifp_docmd(ifp, PS_ND | PS_START, NULL); +} + +ssize_t +ps_inet_closend(struct interface *ifp) +{ + + return ps_inet_ifp_docmd(ifp, PS_ND | PS_STOP, NULL); +} + +ssize_t +ps_inet_sendnd(struct interface *ifp, const struct msghdr *msg) +{ + + return ps_inet_ifp_docmd(ifp, PS_ND, msg); +} +#else +ssize_t +ps_inet_sendnd(struct interface *ifp, const struct msghdr *msg) +{ + + return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_ND, 0, msg); +} +#endif + +#ifdef DHCP6 +static ssize_t +ps_inet_in6_docmd(struct ipv6_addr *ia, uint16_t cmd, const struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = ia->iface->ctx; + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_id = { + .psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), + .psi_ifindex = ia->iface->index, + .psi_addr.psa_in6_addr = ia->addr, + }, + }; + + return ps_sendpsmmsg(ctx, ctx->ps_root_fd, &psm, msg); +} + +ssize_t +ps_inet_opendhcp6(struct ipv6_addr *ia) +{ + + return ps_inet_in6_docmd(ia, PS_DHCP6 | PS_START, NULL); +} + +ssize_t +ps_inet_closedhcp6(struct ipv6_addr *ia) +{ + + return ps_inet_in6_docmd(ia, PS_DHCP6 | PS_STOP, NULL); +} + +ssize_t +ps_inet_senddhcp6(struct interface *ifp, const struct msghdr *msg) +{ + + return ps_sendmsg(ifp->ctx, ifp->ctx->ps_root_fd, PS_DHCP6, 0, msg); +} +#endif /* DHCP6 */ +#endif /* INET6 */ Index: contrib/dhcpcd/src/privsep-root.h =================================================================== --- /dev/null +++ contrib/dhcpcd/src/privsep-root.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation 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. + */ + +#ifndef PRIVSEP_ROOT_H +#define PRIVSEP_ROOT_H + +#include "if.h" + +#if defined(PRIVSEP) && (defined(HAVE_CAPSICUM) || defined(__linux__)) +#define PRIVSEP_GETIFADDRS +#endif + +pid_t ps_root_start(struct dhcpcd_ctx *ctx); +int ps_root_stop(struct dhcpcd_ctx *ctx); + +ssize_t ps_root_readerror(struct dhcpcd_ctx *, void *, size_t); +ssize_t ps_root_mreaderror(struct dhcpcd_ctx *, void **, size_t *); +ssize_t ps_root_ioctl(struct dhcpcd_ctx *, ioctl_request_t, void *, size_t); +ssize_t ps_root_ip6forwarding(struct dhcpcd_ctx *, const char *); +ssize_t ps_root_unlink(struct dhcpcd_ctx *, const char *); +ssize_t ps_root_filemtime(struct dhcpcd_ctx *, const char *, time_t *); +ssize_t ps_root_readfile(struct dhcpcd_ctx *, const char *, void *, size_t); +ssize_t ps_root_writefile(struct dhcpcd_ctx *, const char *, mode_t, + const void *, size_t); +ssize_t ps_root_logreopen(struct dhcpcd_ctx *); +ssize_t ps_root_script(struct dhcpcd_ctx *, const void *, size_t); +int ps_root_getauthrdm(struct dhcpcd_ctx *, uint64_t *); +#ifdef PRIVSEP_GETIFADDRS +int ps_root_getifaddrs(struct dhcpcd_ctx *, struct ifaddrs **); +#endif + +ssize_t ps_root_os(struct ps_msghdr *, struct msghdr *, void **, size_t *); +#if defined(BSD) || defined(__sun) +ssize_t ps_root_route(struct dhcpcd_ctx *, void *, size_t); +ssize_t ps_root_ioctllink(struct dhcpcd_ctx *, unsigned long, void *, size_t); +ssize_t ps_root_ioctl6(struct dhcpcd_ctx *, unsigned long, void *, size_t); +ssize_t ps_root_indirectioctl(struct dhcpcd_ctx *, unsigned long, const char *, + void *, size_t); +ssize_t ps_root_ifignoregroup(struct dhcpcd_ctx *, const char *); +#endif +#ifdef __linux__ +ssize_t ps_root_sendnetlink(struct dhcpcd_ctx *, int, struct msghdr *); +#endif + +#ifdef PLUGIN_DEV +int ps_root_dev_initialised(struct dhcpcd_ctx *, const char *); +int ps_root_dev_listening(struct dhcpcd_ctx *); +#endif + +#endif Index: contrib/dhcpcd/src/privsep-root.c =================================================================== --- /dev/null +++ contrib/dhcpcd/src/privsep-root.c @@ -0,0 +1,1069 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, privileged proxy + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "auth.h" +#include "common.h" +#include "dev.h" +#include "dhcpcd.h" +#include "dhcp6.h" +#include "eloop.h" +#include "if.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" +#include "sa.h" +#include "script.h" + +__CTASSERT(sizeof(ioctl_request_t) <= sizeof(unsigned long)); + +struct psr_error +{ + ssize_t psr_result; + int psr_errno; + char psr_pad[sizeof(ssize_t) - sizeof(int)]; + size_t psr_datalen; +}; + +struct psr_ctx { + struct dhcpcd_ctx *psr_ctx; + struct psr_error psr_error; + size_t psr_datalen; + void *psr_data; +}; + +static void +ps_root_readerrorcb(void *arg) +{ + struct psr_ctx *psr_ctx = arg; + struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx; + struct psr_error *psr_error = &psr_ctx->psr_error; + struct iovec iov[] = { + { .iov_base = psr_error, .iov_len = sizeof(*psr_error) }, + { .iov_base = psr_ctx->psr_data, + .iov_len = psr_ctx->psr_datalen }, + }; + ssize_t len; + int exit_code = EXIT_FAILURE; + +#define PSR_ERROR(e) \ + do { \ + psr_error->psr_result = -1; \ + psr_error->psr_errno = (e); \ + goto out; \ + } while (0 /* CONSTCOND */) + + len = readv(ctx->ps_root_fd, iov, __arraycount(iov)); + if (len == -1) + PSR_ERROR(errno); + else if ((size_t)len < sizeof(*psr_error)) + PSR_ERROR(EINVAL); + exit_code = EXIT_SUCCESS; + +out: + eloop_exit(ctx->ps_eloop, exit_code); +} + +ssize_t +ps_root_readerror(struct dhcpcd_ctx *ctx, void *data, size_t len) +{ + struct psr_ctx psr_ctx = { + .psr_ctx = ctx, + .psr_data = data, .psr_datalen = len, + }; + + if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd, + ps_root_readerrorcb, &psr_ctx) == -1) + return -1; + + eloop_enter(ctx->ps_eloop); + eloop_start(ctx->ps_eloop, &ctx->sigset); + + errno = psr_ctx.psr_error.psr_errno; + return psr_ctx.psr_error.psr_result; +} + +#ifdef PRIVSEP_GETIFADDRS +static void +ps_root_mreaderrorcb(void *arg) +{ + struct psr_ctx *psr_ctx = arg; + struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx; + struct psr_error *psr_error = &psr_ctx->psr_error; + struct iovec iov[] = { + { .iov_base = psr_error, .iov_len = sizeof(*psr_error) }, + { .iov_base = NULL, .iov_len = 0 }, + }; + ssize_t len; + int exit_code = EXIT_FAILURE; + + len = recv(ctx->ps_root_fd, psr_error, sizeof(*psr_error), MSG_PEEK); + if (len == -1) + PSR_ERROR(errno); + else if ((size_t)len < sizeof(*psr_error)) + PSR_ERROR(EINVAL); + + if (psr_error->psr_datalen > SSIZE_MAX) + PSR_ERROR(ENOBUFS); + else if (psr_error->psr_datalen != 0) { + psr_ctx->psr_data = malloc(psr_error->psr_datalen); + if (psr_ctx->psr_data == NULL) + PSR_ERROR(errno); + psr_ctx->psr_datalen = psr_error->psr_datalen; + iov[1].iov_base = psr_ctx->psr_data; + iov[1].iov_len = psr_ctx->psr_datalen; + } + + len = readv(ctx->ps_root_fd, iov, __arraycount(iov)); + if (len == -1) + PSR_ERROR(errno); + else if ((size_t)len != sizeof(*psr_error) + psr_ctx->psr_datalen) + PSR_ERROR(EINVAL); + exit_code = EXIT_SUCCESS; + +out: + eloop_exit(ctx->ps_eloop, exit_code); +} + +ssize_t +ps_root_mreaderror(struct dhcpcd_ctx *ctx, void **data, size_t *len) +{ + struct psr_ctx psr_ctx = { + .psr_ctx = ctx, + }; + + if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd, + ps_root_mreaderrorcb, &psr_ctx) == -1) + return -1; + + eloop_enter(ctx->ps_eloop); + eloop_start(ctx->ps_eloop, &ctx->sigset); + + errno = psr_ctx.psr_error.psr_errno; + *data = psr_ctx.psr_data; + *len = psr_ctx.psr_datalen; + return psr_ctx.psr_error.psr_result; +} +#endif + +static ssize_t +ps_root_writeerror(struct dhcpcd_ctx *ctx, ssize_t result, + void *data, size_t len) +{ + struct psr_error psr = { + .psr_result = result, + .psr_errno = errno, + .psr_datalen = len, + }; + struct iovec iov[] = { + { .iov_base = &psr, .iov_len = sizeof(psr) }, + { .iov_base = data, .iov_len = len }, + }; + +#ifdef PRIVSEP_DEBUG + logdebugx("%s: result %zd errno %d", __func__, result, errno); +#endif + + return writev(ctx->ps_root_fd, iov, __arraycount(iov)); +} + +static ssize_t +ps_root_doioctl(unsigned long req, void *data, size_t len) +{ + int s, err; + + /* Only allow these ioctls */ + switch(req) { +#ifdef SIOCAIFADDR + case SIOCAIFADDR: /* FALLTHROUGH */ + case SIOCDIFADDR: /* FALLTHROUGH */ +#endif +#ifdef SIOCSIFHWADDR + case SIOCSIFHWADDR: /* FALLTHROUGH */ +#endif +#ifdef SIOCGIFPRIORITY + case SIOCGIFPRIORITY: /* FALLTHROUGH */ +#endif + case SIOCSIFFLAGS: /* FALLTHROUGH */ + case SIOCGIFMTU: /* FALLTHROUGH */ + case SIOCSIFMTU: + break; + default: + errno = EPERM; + return -1; + } + + s = socket(PF_INET, SOCK_DGRAM, 0); + if (s != -1) +#ifdef IOCTL_REQUEST_TYPE + { + ioctl_request_t reqt; + + memcpy(&reqt, &req, sizeof(reqt)); + err = ioctl(s, reqt, data, len); + } +#else + err = ioctl(s, req, data, len); +#endif + else + err = -1; + if (s != -1) + close(s); + return err; +} + +static ssize_t +ps_root_run_script(struct dhcpcd_ctx *ctx, const void *data, size_t len) +{ + const char *envbuf = data; + char * const argv[] = { ctx->script, NULL }; + pid_t pid; + int status; + + if (len == 0) + return 0; + + if (script_buftoenv(ctx, UNCONST(envbuf), len) == NULL) + return -1; + + pid = script_exec(argv, ctx->script_env); + if (pid == -1) + return -1; + /* Wait for the script to finish */ + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + logerr(__func__); + status = 0; + break; + } + } + return status; +} + +static bool +ps_root_validpath(const struct dhcpcd_ctx *ctx, uint16_t cmd, const char *path) +{ + + /* Avoid a previous directory attack to avoid /proc/../ + * dhcpcd should never use a path with double dots. */ + if (strstr(path, "..") != NULL) + return false; + + if (cmd == PS_READFILE) { +#ifdef EMBEDDED_CONFIG + if (strcmp(ctx->cffile, EMBEDDED_CONFIG) == 0) + return true; +#endif + if (strcmp(ctx->cffile, path) == 0) + return true; + } + if (strncmp(DBDIR, path, strlen(DBDIR)) == 0) + return true; + if (strncmp(RUNDIR, path, strlen(RUNDIR)) == 0) + return true; + +#ifdef __linux__ + if (strncmp("/proc/net/", path, strlen("/proc/net/")) == 0 || + strncmp("/proc/sys/net/", path, strlen("/proc/sys/net/")) == 0 || + strncmp("/sys/class/net/", path, strlen("/sys/class/net/")) == 0) + return true; +#endif + + errno = EPERM; + return false; +} + +static ssize_t +ps_root_dowritefile(const struct dhcpcd_ctx *ctx, + mode_t mode, void *data, size_t len) +{ + char *file = data, *nc; + + nc = memchr(file, '\0', len); + if (nc == NULL) { + errno = EINVAL; + return -1; + } + + if (!ps_root_validpath(ctx, PS_WRITEFILE, file)) + return -1; + nc++; + return writefile(file, mode, nc, len - (size_t)(nc - file)); +} + +#ifdef AUTH +static ssize_t +ps_root_monordm(uint64_t *rdm, size_t len) +{ + + if (len != sizeof(*rdm)) { + errno = EINVAL; + return -1; + } + return auth_get_rdm_monotonic(rdm); +} +#endif + +#ifdef PRIVSEP_GETIFADDRS +#define IFA_NADDRS 4 +static ssize_t +ps_root_dogetifaddrs(void **rdata, size_t *rlen) +{ + struct ifaddrs *ifaddrs, *ifa; + size_t len; + uint8_t *buf, *sap; + socklen_t salen; + + if (getifaddrs(&ifaddrs) == -1) + return -1; + if (ifaddrs == NULL) { + *rdata = NULL; + *rlen = 0; + return 0; + } + + /* Work out the buffer length required. + * Ensure everything is aligned correctly, which does + * create a larger buffer than what is needed to send, + * but makes creating the same structure in the client + * much easier. */ + len = 0; + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + len += ALIGN(sizeof(*ifa)); + len += ALIGN(IFNAMSIZ); + len += ALIGN(sizeof(salen) * IFA_NADDRS); + if (ifa->ifa_addr != NULL) + len += ALIGN(sa_len(ifa->ifa_addr)); + if (ifa->ifa_netmask != NULL) + len += ALIGN(sa_len(ifa->ifa_netmask)); + if (ifa->ifa_broadaddr != NULL) + len += ALIGN(sa_len(ifa->ifa_broadaddr)); +#ifdef BSD + /* + * On BSD we need to carry ifa_data so we can access + * if_data->ifi_link_state + */ + if (ifa->ifa_addr != NULL && + ifa->ifa_addr->sa_family == AF_LINK) + len += ALIGN(sizeof(struct if_data)); +#endif + } + + /* Use calloc to set everything to zero. + * This satisfies memory sanitizers because don't write + * where we don't need to. */ + buf = calloc(1, len); + if (buf == NULL) { + freeifaddrs(ifaddrs); + return -1; + } + *rdata = buf; + *rlen = len; + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + memcpy(buf, ifa, sizeof(*ifa)); + buf += ALIGN(sizeof(*ifa)); + + strlcpy((char *)buf, ifa->ifa_name, IFNAMSIZ); + buf += ALIGN(IFNAMSIZ); + sap = buf; + buf += ALIGN(sizeof(salen) * IFA_NADDRS); + +#define COPYINSA(addr) \ + do { \ + if ((addr) != NULL) \ + salen = sa_len((addr)); \ + else \ + salen = 0; \ + if (salen != 0) { \ + memcpy(sap, &salen, sizeof(salen)); \ + memcpy(buf, (addr), salen); \ + buf += ALIGN(salen); \ + } \ + sap += sizeof(salen); \ + } while (0 /*CONSTCOND */) + + COPYINSA(ifa->ifa_addr); + COPYINSA(ifa->ifa_netmask); + COPYINSA(ifa->ifa_broadaddr); + +#ifdef BSD + if (ifa->ifa_addr != NULL && + ifa->ifa_addr->sa_family == AF_LINK) + { + salen = (socklen_t)sizeof(struct if_data); + memcpy(buf, ifa->ifa_data, salen); + buf += ALIGN(salen); + } else +#endif + salen = 0; + memcpy(sap, &salen, sizeof(salen)); + } + + freeifaddrs(ifaddrs); + return 0; +} +#endif + +static ssize_t +ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + uint16_t cmd; + struct ps_process *psp; + struct iovec *iov = msg->msg_iov; + void *data = iov->iov_base, *rdata = NULL; + size_t len = iov->iov_len, rlen = 0; + uint8_t buf[PS_BUFLEN]; + time_t mtime; + ssize_t err; + bool free_rdata = false; + + cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP)); + psp = ps_findprocess(ctx, &psm->ps_id); + +#ifdef PRIVSEP_DEBUG + logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); +#endif + + if (psp != NULL) { + if (psm->ps_cmd & PS_STOP) { + int ret = ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd); + + ps_freeprocess(psp); + return ret; + } else if (psm->ps_cmd & PS_START) { + /* Process has already started .... */ + return 0; + } + + err = ps_sendpsmmsg(ctx, psp->psp_fd, psm, msg); + if (err == -1) { + logerr("%s: failed to send message to pid %d", + __func__, psp->psp_pid); + shutdown(psp->psp_fd, SHUT_RDWR); + close(psp->psp_fd); + psp->psp_fd = -1; + ps_freeprocess(psp); + } + return 0; + } + + if (psm->ps_cmd & PS_STOP && psp == NULL) + return 0; + + switch (cmd) { +#ifdef INET +#ifdef ARP + case PS_BPF_ARP: /* FALLTHROUGH */ +#endif + case PS_BPF_BOOTP: + return ps_bpf_cmd(ctx, psm, msg); +#endif +#ifdef INET + case PS_BOOTP: + return ps_inet_cmd(ctx, psm, msg); +#endif +#ifdef INET6 +#ifdef DHCP6 + case PS_DHCP6: /* FALLTHROUGH */ +#endif + case PS_ND: + return ps_inet_cmd(ctx, psm, msg); +#endif + default: + break; + } + + assert(msg->msg_iovlen == 0 || msg->msg_iovlen == 1); + + /* Reset errno */ + errno = 0; + + switch (psm->ps_cmd) { + case PS_IOCTL: + err = ps_root_doioctl(psm->ps_flags, data, len); + if (err != -1) { + rdata = data; + rlen = len; + } + break; + case PS_SCRIPT: + err = ps_root_run_script(ctx, data, len); + break; + case PS_UNLINK: + if (!ps_root_validpath(ctx, psm->ps_cmd, data)) { + err = -1; + break; + } + err = unlink(data); + break; + case PS_READFILE: + if (!ps_root_validpath(ctx, psm->ps_cmd, data)) { + err = -1; + break; + } + err = readfile(data, buf, sizeof(buf)); + if (err != -1) { + rdata = buf; + rlen = (size_t)err; + } + break; + case PS_WRITEFILE: + err = ps_root_dowritefile(ctx, (mode_t)psm->ps_flags, + data, len); + break; + case PS_FILEMTIME: + err = filemtime(data, &mtime); + if (err != -1) { + rdata = &mtime; + rlen = sizeof(mtime); + } + break; + case PS_LOGREOPEN: + err = logopen(ctx->logfile); + break; +#ifdef AUTH + case PS_AUTH_MONORDM: + err = ps_root_monordm(data, len); + if (err != -1) { + rdata = data; + rlen = len; + } + break; +#endif +#ifdef PRIVSEP_GETIFADDRS + case PS_GETIFADDRS: + err = ps_root_dogetifaddrs(&rdata, &rlen); + free_rdata = true; + break; +#endif +#if defined(INET6) && (defined(__linux__) || defined(HAVE_PLEDGE)) + case PS_IP6FORWARDING: + err = ip6_forwarding(data); + break; +#endif +#ifdef PLUGIN_DEV + case PS_DEV_INITTED: + err = dev_initialised(ctx, data); + break; + case PS_DEV_LISTENING: + err = dev_listening(ctx); + break; +#endif + default: + err = ps_root_os(psm, msg, &rdata, &rlen); + break; + } + + err = ps_root_writeerror(ctx, err, rlen != 0 ? rdata : 0, rlen); + if (free_rdata) + free(rdata); + return err; +} + +/* Receive from state engine, do an action. */ +static void +ps_root_recvmsg(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_root_fd, ps_root_recvmsgcb, ctx) == -1) + logerr(__func__); +} + +#ifdef PLUGIN_DEV +static int +ps_root_handleinterface(void *arg, int action, const char *ifname) +{ + struct dhcpcd_ctx *ctx = arg; + unsigned long flag; + + if (action == 1) + flag = PS_DEV_IFADDED; + else if (action == -1) + flag = PS_DEV_IFREMOVED; + else if (action == 0) + flag = PS_DEV_IFUPDATED; + else { + errno = EINVAL; + return -1; + } + + return (int)ps_sendcmd(ctx, ctx->ps_data_fd, PS_DEV_IFCMD, flag, + ifname, strlen(ifname) + 1); +} +#endif + +static int +ps_root_startcb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ctx->options & DHCPCD_MANAGER) + setproctitle("[privileged proxy]"); + else + setproctitle("[privileged proxy] %s%s%s", + ctx->ifv[0], + ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", + ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); + ctx->ps_root_pid = getpid(); + ctx->options |= DHCPCD_PRIVSEPROOT; + + /* Open network sockets for sending. + * This is a small bit wasteful for non sandboxed OS's + * but makes life very easy for unicasting DHCPv6 in non manager + * mode as we no longer care about address selection. + * We can't call shutdown SHUT_RD on the socket because it's + * not connectd. All we can do is try and set a zero sized + * receive buffer and just let it overflow. + * Reading from it just to drain it is a waste of CPU time. */ +#ifdef INET + if (ctx->options & DHCPCD_IPV4) { + int buflen = 1; + + ctx->udp_wfd = xsocket(PF_INET, + SOCK_RAW | SOCK_CXNB, IPPROTO_UDP); + if (ctx->udp_wfd == -1) + logerr("%s: dhcp_openraw", __func__); + else if (setsockopt(ctx->udp_wfd, SOL_SOCKET, SO_RCVBUF, + &buflen, sizeof(buflen)) == -1) + logerr("%s: setsockopt SO_RCVBUF DHCP", __func__); + } +#endif +#ifdef INET6 + if (ctx->options & DHCPCD_IPV6) { + int buflen = 1; + + ctx->nd_fd = ipv6nd_open(false); + if (ctx->nd_fd == -1) + logerr("%s: ipv6nd_open", __func__); + else if (setsockopt(ctx->nd_fd, SOL_SOCKET, SO_RCVBUF, + &buflen, sizeof(buflen)) == -1) + logerr("%s: setsockopt SO_RCVBUF ND", __func__); + } +#endif +#ifdef DHCP6 + if (ctx->options & DHCPCD_IPV6) { + int buflen = 1; + + ctx->dhcp6_wfd = dhcp6_openraw(); + if (ctx->dhcp6_wfd == -1) + logerr("%s: dhcp6_openraw", __func__); + else if (setsockopt(ctx->dhcp6_wfd, SOL_SOCKET, SO_RCVBUF, + &buflen, sizeof(buflen)) == -1) + logerr("%s: setsockopt SO_RCVBUF DHCP6", __func__); + } +#endif + +#ifdef PLUGIN_DEV + /* Start any dev listening plugin which may want to + * change the interface name provided by the kernel */ + if ((ctx->options & (DHCPCD_MANAGER | DHCPCD_DEV)) == + (DHCPCD_MANAGER | DHCPCD_DEV)) + dev_start(ctx, ps_root_handleinterface); +#endif + + return 0; +} + +static void +ps_root_signalcb(int sig, __unused void *arg) +{ + + if (sig == SIGCHLD) { + while (waitpid(-1, NULL, WNOHANG) > 0) + ; + return; + } +} + +int (*handle_interface)(void *, int, const char *); + +#ifdef PLUGIN_DEV +static ssize_t +ps_root_devcb(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) +{ + int action; + struct iovec *iov = msg->msg_iov; + + if (msg->msg_iovlen != 1) { + errno = EINVAL; + return -1; + } + + switch(psm->ps_flags) { + case PS_DEV_IFADDED: + action = 1; + break; + case PS_DEV_IFREMOVED: + action = -1; + break; + case PS_DEV_IFUPDATED: + action = 0; + break; + default: + errno = EINVAL; + return -1; + } + + return dhcpcd_handleinterface(ctx, action, iov->iov_base); +} +#endif + +static ssize_t +ps_root_dispatchcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t err; + + switch(psm->ps_cmd) { +#ifdef PLUGIN_DEV + case PS_DEV_IFCMD: + err = ps_root_devcb(ctx, psm, msg); + break; +#endif + default: +#ifdef INET + err = ps_bpf_dispatch(ctx, psm, msg); + if (err == -1 && errno == ENOTSUP) +#endif + err = ps_inet_dispatch(ctx, psm, msg); + } + return err; +} + +static void +ps_root_dispatch(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_data_fd, ps_root_dispatchcb, ctx) == -1) + logerr(__func__); +} + +static void +ps_root_log(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (logreadfd(ctx->ps_log_fd) == -1) + logerr(__func__); +} + +pid_t +ps_root_start(struct dhcpcd_ctx *ctx) +{ + int logfd[2], datafd[2]; + pid_t pid; + + if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, logfd) == -1) + return -1; +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fdpair(logfd) == -1) + return -1; +#endif + + if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, datafd) == -1) + return -1; + if (ps_setbuf_fdpair(datafd) == -1) + return -1; +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fdpair(datafd) == -1) + return -1; +#endif + + pid = ps_dostart(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd, + ps_root_recvmsg, NULL, ctx, + ps_root_startcb, ps_root_signalcb, 0); + + if (pid == 0) { + ctx->ps_log_fd = logfd[1]; + if (eloop_event_add(ctx->eloop, ctx->ps_log_fd, + ps_root_log, ctx) == -1) + return -1; + close(logfd[0]); + ctx->ps_data_fd = datafd[1]; + close(datafd[0]); + return 0; + } else if (pid == -1) + return -1; + + logsetfd(logfd[0]); + close(logfd[1]); + + ctx->ps_data_fd = datafd[0]; + close(datafd[1]); + if (eloop_event_add(ctx->eloop, ctx->ps_data_fd, + ps_root_dispatch, ctx) == -1) + return -1; + + if ((ctx->ps_eloop = eloop_new()) == NULL) + return -1; + + eloop_signal_set_cb(ctx->ps_eloop, + dhcpcd_signals, dhcpcd_signals_len, + ps_root_signalcb, ctx); + + return pid; +} + +int +ps_root_stop(struct dhcpcd_ctx *ctx) +{ + + return ps_dostop(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd); +} + +ssize_t +ps_root_script(struct dhcpcd_ctx *ctx, const void *data, size_t len) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_SCRIPT, 0, data, len) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} + +ssize_t +ps_root_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data, + size_t len) +{ +#ifdef IOCTL_REQUEST_TYPE + unsigned long ulreq = 0; + + memcpy(&ulreq, &req, sizeof(req)); + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, ulreq, data, len) == -1) + return -1; +#else + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, req, data, len) == -1) + return -1; +#endif + return ps_root_readerror(ctx, data, len); +} + +ssize_t +ps_root_unlink(struct dhcpcd_ctx *ctx, const char *file) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_UNLINK, 0, + file, strlen(file) + 1) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} + +ssize_t +ps_root_readfile(struct dhcpcd_ctx *ctx, const char *file, + void *data, size_t len) +{ + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_READFILE, 0, + file, strlen(file) + 1) == -1) + return -1; + return ps_root_readerror(ctx, data, len); +} + +ssize_t +ps_root_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode, + const void *data, size_t len) +{ + char buf[PS_BUFLEN]; + size_t flen; + + flen = strlcpy(buf, file, sizeof(buf)); + flen += 1; + if (flen > sizeof(buf) || flen + len > sizeof(buf)) { + errno = ENOBUFS; + return -1; + } + memcpy(buf + flen, data, len); + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_WRITEFILE, mode, + buf, flen + len) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} + +ssize_t +ps_root_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_FILEMTIME, 0, + file, strlen(file) + 1) == -1) + return -1; + return ps_root_readerror(ctx, time, sizeof(*time)); +} + +ssize_t +ps_root_logreopen(struct dhcpcd_ctx *ctx) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_LOGREOPEN, 0, NULL, 0) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} + +#ifdef PRIVSEP_GETIFADDRS +int +ps_root_getifaddrs(struct dhcpcd_ctx *ctx, struct ifaddrs **ifahead) +{ + struct ifaddrs *ifa; + void *buf = NULL; + char *bp, *sap; + socklen_t salen; + size_t len; + ssize_t err; + + if (ps_sendcmd(ctx, ctx->ps_root_fd, + PS_GETIFADDRS, 0, NULL, 0) == -1) + return -1; + err = ps_root_mreaderror(ctx, &buf, &len); + + if (err == -1) + return -1; + + /* Should be impossible - lo0 will always exist. */ + if (len == 0) { + *ifahead = NULL; + return 0; + } + + bp = buf; + *ifahead = (struct ifaddrs *)(void *)bp; + for (ifa = *ifahead; ifa != NULL; ifa = ifa->ifa_next) { + if (len < ALIGN(sizeof(*ifa)) + + ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS)) + goto err; + bp += ALIGN(sizeof(*ifa)); + ifa->ifa_name = bp; + bp += ALIGN(IFNAMSIZ); + sap = bp; + bp += ALIGN(sizeof(salen) * IFA_NADDRS); + len -= ALIGN(sizeof(*ifa)) + + ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS); + +#define COPYOUTSA(addr) \ + do { \ + memcpy(&salen, sap, sizeof(salen)); \ + if (len < salen) \ + goto err; \ + if (salen != 0) { \ + (addr) = (struct sockaddr *)bp; \ + bp += ALIGN(salen); \ + len -= ALIGN(salen); \ + } \ + sap += sizeof(salen); \ + } while (0 /* CONSTCOND */) + + COPYOUTSA(ifa->ifa_addr); + COPYOUTSA(ifa->ifa_netmask); + COPYOUTSA(ifa->ifa_broadaddr); + + memcpy(&salen, sap, sizeof(salen)); + if (len < salen) + goto err; + if (salen != 0) { + ifa->ifa_data = bp; + bp += ALIGN(salen); + len -= ALIGN(salen); + } else + ifa->ifa_data = NULL; + + if (len != 0) + ifa->ifa_next = (struct ifaddrs *)(void *)bp; + else + ifa->ifa_next = NULL; + } + return 0; + +err: + free(buf); + *ifahead = NULL; + errno = EINVAL; + return -1; +} +#endif + +#if defined(__linux__) || defined(HAVE_PLEDGE) +ssize_t +ps_root_ip6forwarding(struct dhcpcd_ctx *ctx, const char *ifname) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IP6FORWARDING, 0, + ifname, ifname != NULL ? strlen(ifname) + 1 : 0) == -1) + return -1; + return ps_root_readerror(ctx, NULL, 0); +} +#endif + +#ifdef AUTH +int +ps_root_getauthrdm(struct dhcpcd_ctx *ctx, uint64_t *rdm) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_AUTH_MONORDM, 0, + rdm, sizeof(*rdm))== -1) + return -1; + return (int)ps_root_readerror(ctx, rdm, sizeof(*rdm)); +} +#endif + +#ifdef PLUGIN_DEV +int +ps_root_dev_initialised(struct dhcpcd_ctx *ctx, const char *ifname) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_INITTED, 0, + ifname, strlen(ifname) + 1)== -1) + return -1; + return (int)ps_root_readerror(ctx, NULL, 0); +} + +int +ps_root_dev_listening(struct dhcpcd_ctx * ctx) +{ + + if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_LISTENING, 0, NULL, 0)== -1) + return -1; + return (int)ps_root_readerror(ctx, NULL, 0); +} +#endif Index: contrib/dhcpcd/src/privsep.h =================================================================== --- /dev/null +++ contrib/dhcpcd/src/privsep.h @@ -0,0 +1,221 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation 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. + */ + +#ifndef PRIVSEP_H +#define PRIVSEP_H + +//#define PRIVSEP_DEBUG + +/* Start flags */ +#define PSF_DROPPRIVS 0x01 + +/* Protocols */ +#define PS_BOOTP 0x0001 +#define PS_ND 0x0002 +#define PS_DHCP6 0x0003 +#define PS_BPF_BOOTP 0x0004 +#define PS_BPF_ARP 0x0005 + +/* Generic commands */ +#define PS_IOCTL 0x0010 +#define PS_ROUTE 0x0011 /* Also used for NETLINK */ +#define PS_SCRIPT 0x0012 +#define PS_UNLINK 0x0013 +#define PS_READFILE 0x0014 +#define PS_WRITEFILE 0x0015 +#define PS_FILEMTIME 0x0016 +#define PS_AUTH_MONORDM 0x0017 +#define PS_CTL 0x0018 +#define PS_CTL_EOF 0x0019 +#define PS_LOGREOPEN 0x0020 + +/* BSD Commands */ +#define PS_IOCTLLINK 0x0101 +#define PS_IOCTL6 0x0102 +#define PS_IOCTLINDIRECT 0x0103 +#define PS_IP6FORWARDING 0x0104 +#define PS_GETIFADDRS 0x0105 +#define PS_IFIGNOREGRP 0x0106 + +/* Dev Commands */ +#define PS_DEV_LISTENING 0x1001 +#define PS_DEV_INITTED 0x1002 +#define PS_DEV_IFCMD 0x1003 + +/* Dev Interface Commands (via flags) */ +#define PS_DEV_IFADDED 0x0001 +#define PS_DEV_IFREMOVED 0x0002 +#define PS_DEV_IFUPDATED 0x0003 + +/* Control Type (via flags) */ +#define PS_CTL_PRIV 0x0004 +#define PS_CTL_UNPRIV 0x0005 + +/* Process commands */ +#define PS_START 0x4000 +#define PS_STOP 0x8000 + +/* Max INET message size + meta data for IPC */ +#define PS_BUFLEN ((64 * 1024) + \ + sizeof(struct ps_msghdr) + \ + sizeof(struct msghdr) + \ + CMSG_SPACE(sizeof(struct in6_pktinfo) + \ + sizeof(int))) + +/* Handy macro to work out if in the privsep engine or not. */ +#define IN_PRIVSEP(ctx) \ + ((ctx)->options & DHCPCD_PRIVSEP) +#define IN_PRIVSEP_SE(ctx) \ + (((ctx)->options & (DHCPCD_PRIVSEP | DHCPCD_FORKED)) == DHCPCD_PRIVSEP) + +#if defined(PRIVSEP) && defined(HAVE_CAPSICUM) +#define PRIVSEP_RIGHTS +#endif + +#ifdef __linux__ +# include +# if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 5, 0) +# define HAVE_SECCOMP +# endif +#endif + +#include "config.h" +#include "arp.h" +#include "dhcp.h" +#include "dhcpcd.h" + +struct ps_addr { + sa_family_t psa_family; + uint8_t psa_pad[4 - sizeof(sa_family_t)]; + union { + struct in_addr psau_in_addr; + struct in6_addr psau_in6_addr; + } psa_u; +#define psa_in_addr psa_u.psau_in_addr +#define psa_in6_addr psa_u.psau_in6_addr +}; + +/* Uniquely identify a process */ +struct ps_id { + struct ps_addr psi_addr; + unsigned int psi_ifindex; + uint16_t psi_cmd; + uint8_t psi_pad[2]; +}; + +struct ps_msghdr { + uint16_t ps_cmd; + uint8_t ps_pad[sizeof(unsigned long) - sizeof(uint16_t)]; + unsigned long ps_flags; + struct ps_id ps_id; + socklen_t ps_namelen; + socklen_t ps_controllen; + uint8_t ps_pad2[sizeof(size_t) - sizeof(socklen_t)]; + size_t ps_datalen; +}; + +struct ps_msg { + struct ps_msghdr psm_hdr; + uint8_t psm_data[PS_BUFLEN]; +}; + +struct bpf; +struct ps_process { + TAILQ_ENTRY(ps_process) next; + struct dhcpcd_ctx *psp_ctx; + struct ps_id psp_id; + pid_t psp_pid; + int psp_fd; + int psp_work_fd; + unsigned int psp_ifindex; + char psp_ifname[IF_NAMESIZE]; + uint16_t psp_proto; + const char *psp_protostr; + +#ifdef INET + int (*psp_filter)(const struct bpf *, const struct in_addr *); + struct interface psp_ifp; /* Move BPF gubbins elsewhere */ + struct bpf *psp_bpf; +#endif +}; +TAILQ_HEAD(ps_process_head, ps_process); + +#include "privsep-control.h" +#include "privsep-inet.h" +#include "privsep-root.h" +#ifdef INET +#include "privsep-bpf.h" +#endif + +int ps_init(struct dhcpcd_ctx *); +int ps_start(struct dhcpcd_ctx *); +int ps_stop(struct dhcpcd_ctx *); +int ps_entersandbox(const char *, const char **); +int ps_managersandbox(struct dhcpcd_ctx *, const char *); + +int ps_unrollmsg(struct msghdr *, struct ps_msghdr *, const void *, size_t); +ssize_t ps_sendpsmmsg(struct dhcpcd_ctx *, int, + struct ps_msghdr *, const struct msghdr *); +ssize_t ps_sendpsmdata(struct dhcpcd_ctx *, int, + struct ps_msghdr *, const void *, size_t); +ssize_t ps_sendmsg(struct dhcpcd_ctx *, int, uint16_t, unsigned long, + const struct msghdr *); +ssize_t ps_sendcmd(struct dhcpcd_ctx *, int, uint16_t, unsigned long, + const void *data, size_t len); +ssize_t ps_recvmsg(struct dhcpcd_ctx *, int, uint16_t, int); +ssize_t ps_recvpsmsg(struct dhcpcd_ctx *, int, + ssize_t (*callback)(void *, struct ps_msghdr *, struct msghdr *), void *); + +/* Internal privsep functions. */ +int ps_setbuf_fdpair(int []); + +#ifdef PRIVSEP_RIGHTS +int ps_rights_limit_ioctl(int); +int ps_rights_limit_fd_fctnl(int); +int ps_rights_limit_fd_rdonly(int); +int ps_rights_limit_fd_sockopt(int); +int ps_rights_limit_fd(int); +int ps_rights_limit_fdpair(int []); +#endif + +#ifdef HAVE_SECCOMP +int ps_seccomp_enter(void); +#endif + +pid_t ps_dostart(struct dhcpcd_ctx * ctx, + pid_t *priv_pid, int *priv_fd, + void (*recv_msg)(void *), void (*recv_unpriv_msg), + void *recv_ctx, int (*callback)(void *), void (*)(int, void *), + unsigned int); +int ps_dostop(struct dhcpcd_ctx *ctx, pid_t *pid, int *fd); + +struct ps_process *ps_findprocess(struct dhcpcd_ctx *, struct ps_id *); +struct ps_process *ps_newprocess(struct dhcpcd_ctx *, struct ps_id *); +void ps_freeprocess(struct ps_process *); +void ps_freeprocesses(struct dhcpcd_ctx *, struct ps_process *); +#endif Index: contrib/dhcpcd/src/privsep.c =================================================================== --- /dev/null +++ contrib/dhcpcd/src/privsep.c @@ -0,0 +1,1029 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation 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. + */ + +/* + * The current design is this: + * Spawn a priv process to carry out privileged actions and + * spawning unpriv process to initate network connections such as BPF + * or address specific listener. + * Spawn an unpriv process to send/receive common network data. + * Then drop all privs and start running. + * Every process aside from the privileged proxy is chrooted. + * All privsep processes ignore signals - only the manager process accepts them. + * + * dhcpcd will maintain the config file in the chroot, no need to handle + * this in a script or something. + */ + +#include +#include +#include +#include +#include + +#ifdef AF_LINK +#include +#endif + +#include +#include +#include +#include +#include +#include +#include /* For offsetof, struct padding debug */ +#include +#include +#include +#include + +#include "arp.h" +#include "common.h" +#include "control.h" +#include "dev.h" +#include "dhcp.h" +#include "dhcp6.h" +#include "eloop.h" +#include "ipv6nd.h" +#include "logerr.h" +#include "privsep.h" + +#ifdef HAVE_CAPSICUM +#include +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif + +/* CMSG_ALIGN is a Linux extension */ +#ifndef CMSG_ALIGN +#define CMSG_ALIGN(n) (CMSG_SPACE((n)) - CMSG_SPACE(0)) +#endif + +/* Calculate number of padding bytes to achieve 'struct cmsghdr' alignment */ +#define CALC_CMSG_PADLEN(has_cmsg, pos) \ + ((has_cmsg) ? (socklen_t)(CMSG_ALIGN((pos)) - (pos)) : 0) + +int +ps_init(struct dhcpcd_ctx *ctx) +{ + struct passwd *pw; + struct stat st; + + errno = 0; + if ((ctx->ps_user = pw = getpwnam(PRIVSEP_USER)) == NULL) { + ctx->options &= ~DHCPCD_PRIVSEP; + if (errno == 0) { + logerrx("no such user %s", PRIVSEP_USER); + /* Just incase logerrx caused an error... */ + errno = 0; + } else + logerr("getpwnam"); + return -1; + } + + if (stat(pw->pw_dir, &st) == -1 || !S_ISDIR(st.st_mode)) { + ctx->options &= ~DHCPCD_PRIVSEP; + logerrx("refusing chroot: %s: %s", + PRIVSEP_USER, pw->pw_dir); + errno = 0; + return -1; + } + + ctx->options |= DHCPCD_PRIVSEP; + return 0; +} + +static int +ps_dropprivs(struct dhcpcd_ctx *ctx) +{ + struct passwd *pw = ctx->ps_user; + + if (ctx->options & DHCPCD_LAUNCHER) + logdebugx("chrooting as %s to %s", pw->pw_name, pw->pw_dir); + if (chroot(pw->pw_dir) == -1 && + (errno != EPERM || ctx->options & DHCPCD_FORKED)) + logerr("%s: chroot: %s", __func__, pw->pw_dir); + if (chdir("/") == -1) + logerr("%s: chdir: /", __func__); + + if ((setgroups(1, &pw->pw_gid) == -1 || + setgid(pw->pw_gid) == -1 || + setuid(pw->pw_uid) == -1) && + (errno != EPERM || ctx->options & DHCPCD_FORKED)) + { + logerr("failed to drop privileges"); + return -1; + } + + struct rlimit rzero = { .rlim_cur = 0, .rlim_max = 0 }; + + if (ctx->ps_control_pid != getpid()) { + /* Prohibit new files, sockets, etc */ +#if defined(__linux__) || defined(__sun) || defined(__OpenBSD__) + /* + * If poll(2) is called with nfds > RLIMIT_NOFILE + * then it returns EINVAL. + * This blows. + * Do the best we can and limit to what we need. + * An attacker could potentially close a file and + * open a new one still, but that cannot be helped. + */ + unsigned long maxfd; + maxfd = (unsigned long)eloop_event_count(ctx->eloop); + if (IN_PRIVSEP_SE(ctx)) + maxfd++; /* XXX why? */ + + struct rlimit rmaxfd = { + .rlim_cur = maxfd, + .rlim_max = maxfd + }; + if (setrlimit(RLIMIT_NOFILE, &rmaxfd) == -1) + logerr("setrlimit RLIMIT_NOFILE"); +#else + if (setrlimit(RLIMIT_NOFILE, &rzero) == -1) + logerr("setrlimit RLIMIT_NOFILE"); +#endif + } + +#define DHC_NOCHKIO (DHCPCD_STARTED | DHCPCD_DAEMONISE) + /* Prohibit writing to files. + * Obviously this won't work if we are using a logfile + * or redirecting stderr to a file. */ + if ((ctx->options & DHC_NOCHKIO) == DHC_NOCHKIO || + (ctx->logfile == NULL && + (!ctx->stderr_valid || isatty(STDERR_FILENO) == 1))) + { + if (setrlimit(RLIMIT_FSIZE, &rzero) == -1) + logerr("setrlimit RLIMIT_FSIZE"); + } + +#ifdef RLIMIT_NPROC + /* Prohibit forks */ + if (setrlimit(RLIMIT_NPROC, &rzero) == -1) + logerr("setrlimit RLIMIT_NPROC"); +#endif + + return 0; +} + +static int +ps_setbuf0(int fd, int ctl, int minlen) +{ + int len; + socklen_t slen; + + slen = sizeof(len); + if (getsockopt(fd, SOL_SOCKET, ctl, &len, &slen) == -1) + return -1; + +#ifdef __linux__ + len /= 2; +#endif + if (len >= minlen) + return 0; + + return setsockopt(fd, SOL_SOCKET, ctl, &minlen, sizeof(minlen)); +} + +static int +ps_setbuf(int fd) +{ + /* Ensure we can receive a fully sized privsep message. + * Double the send buffer. */ + int minlen = (int)sizeof(struct ps_msg); + + if (ps_setbuf0(fd, SO_RCVBUF, minlen) == -1 || + ps_setbuf0(fd, SO_SNDBUF, minlen * 2) == -1) + { + logerr(__func__); + return -1; + } + return 0; +} + +int +ps_setbuf_fdpair(int fd[]) +{ + + if (ps_setbuf(fd[0]) == -1 || ps_setbuf(fd[1]) == -1) + return -1; + return 0; +} + +#ifdef PRIVSEP_RIGHTS +int +ps_rights_limit_ioctl(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_IOCTL); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fd_fctnl(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, + CAP_ACCEPT, CAP_FCNTL); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fd(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, CAP_SHUTDOWN); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fd_sockopt(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, + CAP_GETSOCKOPT, CAP_SETSOCKOPT); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fd_rdonly(int fd) +{ + cap_rights_t rights; + + cap_rights_init(&rights, CAP_READ, CAP_EVENT); + if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS) + return -1; + return 0; +} + +int +ps_rights_limit_fdpair(int fd[]) +{ + + if (ps_rights_limit_fd(fd[0]) == -1 || ps_rights_limit_fd(fd[1]) == -1) + return -1; + return 0; +} + +static int +ps_rights_limit_stdio(struct dhcpcd_ctx *ctx) +{ + const int iebadf = CAPH_IGNORE_EBADF; + int error = 0; + + if (ctx->stdin_valid && + caph_limit_stream(STDIN_FILENO, CAPH_READ | iebadf) == -1) + error = -1; + if (ctx->stdout_valid && + caph_limit_stream(STDOUT_FILENO, CAPH_WRITE | iebadf) == -1) + error = -1; + if (ctx->stderr_valid && + caph_limit_stream(STDERR_FILENO, CAPH_WRITE | iebadf) == -1) + error = -1; + + return error; +} +#endif + +pid_t +ps_dostart(struct dhcpcd_ctx *ctx, + pid_t *priv_pid, int *priv_fd, + void (*recv_msg)(void *), void (*recv_unpriv_msg), + void *recv_ctx, int (*callback)(void *), void (*signal_cb)(int, void *), + unsigned int flags) +{ + int fd[2]; + pid_t pid; + + if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fd) == -1) { + logerr("%s: socketpair", __func__); + return -1; + } + if (ps_setbuf_fdpair(fd) == -1) { + logerr("%s: ps_setbuf_fdpair", __func__); + return -1; + } +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fdpair(fd) == -1) { + logerr("%s: ps_rights_limit_fdpair", __func__); + return -1; + } +#endif + + switch (pid = fork()) { + case -1: + logerr("fork"); + return -1; + case 0: + *priv_fd = fd[1]; + close(fd[0]); + break; + default: + *priv_pid = pid; + *priv_fd = fd[0]; + close(fd[1]); + if (recv_unpriv_msg == NULL) + ; + else if (eloop_event_add(ctx->eloop, *priv_fd, + recv_unpriv_msg, recv_ctx) == -1) + { + logerr("%s: eloop_event_add", __func__); + return -1; + } + return pid; + } + + ctx->options |= DHCPCD_FORKED; + if (ctx->fork_fd != -1) { + close(ctx->fork_fd); + ctx->fork_fd = -1; + } + pidfile_clean(); + + eloop_clear(ctx->eloop); + eloop_signal_set_cb(ctx->eloop, + dhcpcd_signals, dhcpcd_signals_len, signal_cb, ctx); + /* ctx->sigset aready has the initial sigmask set in main() */ + if (eloop_signal_mask(ctx->eloop, NULL) == -1) { + logerr("%s: eloop_signal_mask", __func__); + goto errexit; + } + + /* We are not root */ + if (priv_fd != &ctx->ps_root_fd) { + ps_freeprocesses(ctx, recv_ctx); + if (ctx->ps_root_fd != -1) { + close(ctx->ps_root_fd); + ctx->ps_root_fd = -1; + } + +#ifdef PRIVSEP_RIGHTS + /* We cannot limit the root process in any way. */ + if (ps_rights_limit_stdio(ctx) == -1) { + logerr("ps_rights_limit_stdio"); + goto errexit; + } +#endif + } + + if (priv_fd != &ctx->ps_inet_fd && ctx->ps_inet_fd != -1) { + close(ctx->ps_inet_fd); + ctx->ps_inet_fd = -1; + } + + if (eloop_event_add(ctx->eloop, *priv_fd, recv_msg, recv_ctx) == -1) + { + logerr("%s: eloop_event_add", __func__); + goto errexit; + } + + if (callback(recv_ctx) == -1) + goto errexit; + + if (flags & PSF_DROPPRIVS) + ps_dropprivs(ctx); + + return 0; + +errexit: + /* Failure to start root or inet processes is fatal. */ + if (priv_fd == &ctx->ps_root_fd || priv_fd == &ctx->ps_inet_fd) + (void)ps_sendcmd(ctx, *priv_fd, PS_STOP, 0, NULL, 0); + shutdown(*priv_fd, SHUT_RDWR); + *priv_fd = -1; + eloop_exit(ctx->eloop, EXIT_FAILURE); + return -1; +} + +int +ps_dostop(struct dhcpcd_ctx *ctx, pid_t *pid, int *fd) +{ + int err = 0; + +#ifdef PRIVSEP_DEBUG + logdebugx("%s: pid=%d fd=%d", __func__, *pid, *fd); +#endif + + if (*fd != -1) { + eloop_event_delete(ctx->eloop, *fd); + if (ps_sendcmd(ctx, *fd, PS_STOP, 0, NULL, 0) == -1) { + logerr(__func__); + err = -1; + } + (void)shutdown(*fd, SHUT_RDWR); + close(*fd); + *fd = -1; + } + + /* Don't wait for the process as it may not respond to the shutdown + * request. We'll reap the process on receipt of SIGCHLD. */ + *pid = 0; + return err; +} + +int +ps_start(struct dhcpcd_ctx *ctx) +{ + pid_t pid; + + TAILQ_INIT(&ctx->ps_processes); + + switch (pid = ps_root_start(ctx)) { + case -1: + logerr("ps_root_start"); + return -1; + case 0: + return 0; + default: + logdebugx("spawned privileged proxy on PID %d", pid); + } + + /* No point in spawning the generic network listener if we're + * not going to use it. */ + if (!ps_inet_canstart(ctx)) + goto started_net; + + switch (pid = ps_inet_start(ctx)) { + case -1: + return -1; + case 0: + return 0; + default: + logdebugx("spawned network proxy on PID %d", pid); + } + +started_net: + if (!(ctx->options & DHCPCD_TEST)) { + switch (pid = ps_ctl_start(ctx)) { + case -1: + return -1; + case 0: + return 0; + default: + logdebugx("spawned controller proxy on PID %d", pid); + } + } + +#ifdef ARC4RANDOM_H + /* Seed the random number generator early incase it needs /dev/urandom + * which won't be available in the chroot. */ + arc4random(); +#endif + + return 1; +} + +int +ps_entersandbox(const char *_pledge, const char **sandbox) +{ + +#if !defined(HAVE_PLEDGE) + UNUSED(_pledge); +#endif + +#if defined(HAVE_CAPSICUM) + if (sandbox != NULL) + *sandbox = "capsicum"; + return cap_enter(); +#elif defined(HAVE_PLEDGE) + if (sandbox != NULL) + *sandbox = "pledge"; + return pledge(_pledge, NULL); +#elif defined(HAVE_SECCOMP) + if (sandbox != NULL) + *sandbox = "seccomp"; + return ps_seccomp_enter(); +#else + if (sandbox != NULL) + *sandbox = "posix resource limited"; + return 0; +#endif +} + +int +ps_managersandbox(struct dhcpcd_ctx *ctx, const char *_pledge) +{ + const char *sandbox = NULL; + bool forked; + int dropped; + + forked = ctx->options & DHCPCD_FORKED; + ctx->options &= ~DHCPCD_FORKED; + dropped = ps_dropprivs(ctx); + if (forked) + ctx->options |= DHCPCD_FORKED; + + /* + * If we don't have a root process, we cannot use syslog. + * If it cannot be opened before chrooting then syslog(3) will fail. + * openlog(3) does not return an error which doubly sucks. + */ + if (ctx->ps_root_fd == -1) { + unsigned int logopts = loggetopts(); + + logopts &= ~LOGERR_LOG; + logsetopts(logopts); + } + + if (dropped == -1) { + logerr("%s: ps_dropprivs", __func__); + return -1; + } + +#ifdef PRIVSEP_RIGHTS + if ((ctx->pf_inet_fd != -1 && + ps_rights_limit_ioctl(ctx->pf_inet_fd) == -1) || + ps_rights_limit_stdio(ctx) == -1) + { + logerr("%s: cap_rights_limit", __func__); + return -1; + } +#endif + + if (_pledge == NULL) + _pledge = "stdio"; + if (ps_entersandbox(_pledge, &sandbox) == -1) { + if (errno == ENOSYS) { + if (sandbox != NULL) + logwarnx("sandbox unavailable: %s", sandbox); + return 0; + } + logerr("%s: %s", __func__, sandbox); + return -1; + } else if (ctx->options & DHCPCD_LAUNCHER || + ((!(ctx->options & DHCPCD_DAEMONISE)) && + ctx->options & DHCPCD_MANAGER)) + logdebugx("sandbox: %s", sandbox); + return 0; +} + +int +ps_stop(struct dhcpcd_ctx *ctx) +{ + int r, ret = 0; + + if (!(ctx->options & DHCPCD_PRIVSEP) || + ctx->options & DHCPCD_FORKED || + ctx->eloop == NULL) + return 0; + + r = ps_ctl_stop(ctx); + if (r != 0) + ret = r; + + r = ps_inet_stop(ctx); + if (r != 0) + ret = r; + + /* We've been chrooted, so we need to tell the + * privileged proxy to remove the pidfile. */ + ps_root_unlink(ctx, ctx->pidfile); + + r = ps_root_stop(ctx); + if (r != 0) + ret = r; + + ctx->options &= ~DHCPCD_PRIVSEP; + return ret; +} + +void +ps_freeprocess(struct ps_process *psp) +{ + + TAILQ_REMOVE(&psp->psp_ctx->ps_processes, psp, next); + if (psp->psp_fd != -1) { + eloop_event_delete(psp->psp_ctx->eloop, psp->psp_fd); + close(psp->psp_fd); + } + if (psp->psp_work_fd != -1) { + eloop_event_delete(psp->psp_ctx->eloop, psp->psp_work_fd); + close(psp->psp_work_fd); + } +#ifdef INET + if (psp->psp_bpf != NULL) + bpf_close(psp->psp_bpf); +#endif + free(psp); +} + +static void +ps_free(struct dhcpcd_ctx *ctx) +{ + struct ps_process *psp; + bool stop = ctx->ps_root_pid == getpid(); + + while ((psp = TAILQ_FIRST(&ctx->ps_processes)) != NULL) { + if (stop) + ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd); + ps_freeprocess(psp); + } +} + +int +ps_unrollmsg(struct msghdr *msg, struct ps_msghdr *psm, + const void *data, size_t len) +{ + uint8_t *datap, *namep, *controlp; + socklen_t cmsg_padlen = + CALC_CMSG_PADLEN(psm->ps_controllen, psm->ps_namelen); + + namep = UNCONST(data); + controlp = namep + psm->ps_namelen + cmsg_padlen; + datap = controlp + psm->ps_controllen; + + if (psm->ps_namelen != 0) { + if (psm->ps_namelen > len) { + errno = EINVAL; + return -1; + } + msg->msg_name = namep; + len -= psm->ps_namelen; + } else + msg->msg_name = NULL; + msg->msg_namelen = psm->ps_namelen; + + if (psm->ps_controllen != 0) { + if (psm->ps_controllen > len) { + errno = EINVAL; + return -1; + } + msg->msg_control = controlp; + len -= psm->ps_controllen + cmsg_padlen; + } else + msg->msg_control = NULL; + msg->msg_controllen = psm->ps_controllen; + + if (len != 0) { + msg->msg_iovlen = 1; + msg->msg_iov[0].iov_base = datap; + msg->msg_iov[0].iov_len = len; + } else { + msg->msg_iovlen = 0; + msg->msg_iov[0].iov_base = NULL; + msg->msg_iov[0].iov_len = 0; + } + return 0; +} + +ssize_t +ps_sendpsmmsg(struct dhcpcd_ctx *ctx, int fd, + struct ps_msghdr *psm, const struct msghdr *msg) +{ + long padding[1] = { 0 }; + struct iovec iov[] = { + { .iov_base = UNCONST(psm), .iov_len = sizeof(*psm) }, + { .iov_base = NULL, }, /* name */ + { .iov_base = NULL, }, /* control padding */ + { .iov_base = NULL, }, /* control */ + { .iov_base = NULL, }, /* payload 1 */ + { .iov_base = NULL, }, /* payload 2 */ + { .iov_base = NULL, }, /* payload 3 */ + }; + int iovlen; + ssize_t len; + + if (msg != NULL) { + struct iovec *iovp = &iov[1]; + int i; + socklen_t cmsg_padlen; + + psm->ps_namelen = msg->msg_namelen; + psm->ps_controllen = (socklen_t)msg->msg_controllen; + + iovp->iov_base = msg->msg_name; + iovp->iov_len = msg->msg_namelen; + iovp++; + + cmsg_padlen = + CALC_CMSG_PADLEN(msg->msg_controllen, msg->msg_namelen); + assert(cmsg_padlen <= sizeof(padding)); + iovp->iov_len = cmsg_padlen; + iovp->iov_base = cmsg_padlen != 0 ? padding : NULL; + iovp++; + + iovp->iov_base = msg->msg_control; + iovp->iov_len = msg->msg_controllen; + iovlen = 4; + + for (i = 0; i < (int)msg->msg_iovlen; i++) { + if ((size_t)(iovlen + i) > __arraycount(iov)) { + errno = ENOBUFS; + return -1; + } + iovp++; + iovp->iov_base = msg->msg_iov[i].iov_base; + iovp->iov_len = msg->msg_iov[i].iov_len; + } + iovlen += i; + } else + iovlen = 1; + + len = writev(fd, iov, iovlen); + if (len == -1) { + logerr(__func__); + if (ctx->options & DHCPCD_FORKED && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + eloop_exit(ctx->eloop, EXIT_FAILURE); + } + return len; +} + +ssize_t +ps_sendpsmdata(struct dhcpcd_ctx *ctx, int fd, + struct ps_msghdr *psm, const void *data, size_t len) +{ + struct iovec iov[] = { + { .iov_base = UNCONST(data), .iov_len = len }, + }; + struct msghdr msg = { + .msg_iov = iov, .msg_iovlen = 1, + }; + + return ps_sendpsmmsg(ctx, fd, psm, &msg); +} + + +ssize_t +ps_sendmsg(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags, + const struct msghdr *msg) +{ + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_flags = flags, + .ps_namelen = msg->msg_namelen, + .ps_controllen = (socklen_t)msg->msg_controllen, + }; + size_t i; + + for (i = 0; i < (size_t)msg->msg_iovlen; i++) + psm.ps_datalen += msg->msg_iov[i].iov_len; + +#if 0 /* For debugging structure padding. */ + logerrx("psa.family %lu %zu", offsetof(struct ps_addr, psa_family), sizeof(psm.ps_id.psi_addr.psa_family)); + logerrx("psa.pad %lu %zu", offsetof(struct ps_addr, psa_pad), sizeof(psm.ps_id.psi_addr.psa_pad)); + logerrx("psa.psa_u %lu %zu", offsetof(struct ps_addr, psa_u), sizeof(psm.ps_id.psi_addr.psa_u)); + logerrx("psa %zu", sizeof(psm.ps_id.psi_addr)); + + logerrx("psi.addr %lu %zu", offsetof(struct ps_id, psi_addr), sizeof(psm.ps_id.psi_addr)); + logerrx("psi.index %lu %zu", offsetof(struct ps_id, psi_ifindex), sizeof(psm.ps_id.psi_ifindex)); + logerrx("psi.cmd %lu %zu", offsetof(struct ps_id, psi_cmd), sizeof(psm.ps_id.psi_cmd)); + logerrx("psi.pad %lu %zu", offsetof(struct ps_id, psi_pad), sizeof(psm.ps_id.psi_pad)); + logerrx("psi %zu", sizeof(struct ps_id)); + + logerrx("ps_cmd %lu", offsetof(struct ps_msghdr, ps_cmd)); + logerrx("ps_pad %lu %zu", offsetof(struct ps_msghdr, ps_pad), sizeof(psm.ps_pad)); + logerrx("ps_flags %lu %zu", offsetof(struct ps_msghdr, ps_flags), sizeof(psm.ps_flags)); + + logerrx("ps_id %lu %zu", offsetof(struct ps_msghdr, ps_id), sizeof(psm.ps_id)); + + logerrx("ps_namelen %lu %zu", offsetof(struct ps_msghdr, ps_namelen), sizeof(psm.ps_namelen)); + logerrx("ps_controllen %lu %zu", offsetof(struct ps_msghdr, ps_controllen), sizeof(psm.ps_controllen)); + logerrx("ps_pad2 %lu %zu", offsetof(struct ps_msghdr, ps_pad2), sizeof(psm.ps_pad2)); + logerrx("ps_datalen %lu %zu", offsetof(struct ps_msghdr, ps_datalen), sizeof(psm.ps_datalen)); + logerrx("psm %zu", sizeof(psm)); +#endif + + return ps_sendpsmmsg(ctx, fd, &psm, msg); +} + +ssize_t +ps_sendcmd(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags, + const void *data, size_t len) +{ + struct ps_msghdr psm = { + .ps_cmd = cmd, + .ps_flags = flags, + }; + struct iovec iov[] = { + { .iov_base = UNCONST(data), .iov_len = len } + }; + struct msghdr msg = { + .msg_iov = iov, .msg_iovlen = 1, + }; + + return ps_sendpsmmsg(ctx, fd, &psm, &msg); +} + +static ssize_t +ps_sendcmdmsg(int fd, uint16_t cmd, const struct msghdr *msg) +{ + struct ps_msghdr psm = { .ps_cmd = cmd }; + uint8_t data[PS_BUFLEN], *p = data; + struct iovec iov[] = { + { .iov_base = &psm, .iov_len = sizeof(psm) }, + { .iov_base = data, .iov_len = 0 }, + }; + size_t dl = sizeof(data); + socklen_t cmsg_padlen = + CALC_CMSG_PADLEN(msg->msg_controllen, msg->msg_namelen); + + if (msg->msg_namelen != 0) { + if (msg->msg_namelen > dl) + goto nobufs; + psm.ps_namelen = msg->msg_namelen; + memcpy(p, msg->msg_name, msg->msg_namelen); + p += msg->msg_namelen; + dl -= msg->msg_namelen; + } + + if (msg->msg_controllen != 0) { + if (msg->msg_controllen + cmsg_padlen > dl) + goto nobufs; + if (cmsg_padlen != 0) { + memset(p, 0, cmsg_padlen); + p += cmsg_padlen; + dl -= cmsg_padlen; + } + psm.ps_controllen = (socklen_t)msg->msg_controllen; + memcpy(p, msg->msg_control, msg->msg_controllen); + p += msg->msg_controllen; + dl -= msg->msg_controllen; + } + + psm.ps_datalen = msg->msg_iov[0].iov_len; + if (psm.ps_datalen > dl) + goto nobufs; + + iov[1].iov_len = + psm.ps_namelen + psm.ps_controllen + psm.ps_datalen + cmsg_padlen; + if (psm.ps_datalen != 0) + memcpy(p, msg->msg_iov[0].iov_base, psm.ps_datalen); + return writev(fd, iov, __arraycount(iov)); + +nobufs: + errno = ENOBUFS; + return -1; +} + +ssize_t +ps_recvmsg(struct dhcpcd_ctx *ctx, int rfd, uint16_t cmd, int wfd) +{ + struct sockaddr_storage ss = { .ss_family = AF_UNSPEC }; + uint8_t controlbuf[sizeof(struct sockaddr_storage)] = { 0 }; + uint8_t databuf[64 * 1024]; + struct iovec iov[] = { + { .iov_base = databuf, .iov_len = sizeof(databuf) } + }; + struct msghdr msg = { + .msg_name = &ss, .msg_namelen = sizeof(ss), + .msg_control = controlbuf, .msg_controllen = sizeof(controlbuf), + .msg_iov = iov, .msg_iovlen = 1, + }; + + ssize_t len = recvmsg(rfd, &msg, 0); + + if (len == -1) + logerr("%s: recvmsg", __func__); + if (len == -1 || len == 0) { + if (ctx->options & DHCPCD_FORKED && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + eloop_exit(ctx->eloop, + len == 0 ? EXIT_SUCCESS : EXIT_FAILURE); + return len; + } + + iov[0].iov_len = (size_t)len; + len = ps_sendcmdmsg(wfd, cmd, &msg); + if (len == -1) { + logerr("ps_sendcmdmsg"); + if (ctx->options & DHCPCD_FORKED && + !(ctx->options & DHCPCD_PRIVSEPROOT)) + eloop_exit(ctx->eloop, EXIT_FAILURE); + } + return len; +} + +ssize_t +ps_recvpsmsg(struct dhcpcd_ctx *ctx, int fd, + ssize_t (*callback)(void *, struct ps_msghdr *, struct msghdr *), + void *cbctx) +{ + struct ps_msg psm; + ssize_t len; + size_t dlen; + struct iovec iov[1]; + struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1 }; + bool stop = false; + + len = read(fd, &psm, sizeof(psm)); +#ifdef PRIVSEP_DEBUG + logdebugx("%s: %zd", __func__, len); +#endif + + if (len == -1 || len == 0) + stop = true; + else { + dlen = (size_t)len; + if (dlen < sizeof(psm.psm_hdr)) { + errno = EINVAL; + return -1; + } + + if (psm.psm_hdr.ps_cmd == PS_STOP) { + stop = true; + len = 0; + } + } + + if (stop) { +#ifdef PRIVSEP_DEBUG + logdebugx("process %d stopping", getpid()); +#endif + ps_free(ctx); +#ifdef PLUGIN_DEV + dev_stop(ctx); +#endif + eloop_exit(ctx->eloop, len != -1 ? EXIT_SUCCESS : EXIT_FAILURE); + return len; + } + dlen -= sizeof(psm.psm_hdr); + + if (ps_unrollmsg(&msg, &psm.psm_hdr, psm.psm_data, dlen) == -1) + return -1; + + if (callback == NULL) + return 0; + + errno = 0; + return callback(cbctx, &psm.psm_hdr, &msg); +} + +struct ps_process * +ps_findprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid) +{ + struct ps_process *psp; + + TAILQ_FOREACH(psp, &ctx->ps_processes, next) { + if (memcmp(&psp->psp_id, psid, sizeof(psp->psp_id)) == 0) + return psp; + } + errno = ESRCH; + return NULL; +} + +struct ps_process * +ps_newprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid) +{ + struct ps_process *psp; + + psp = calloc(1, sizeof(*psp)); + if (psp == NULL) + return NULL; + psp->psp_ctx = ctx; + memcpy(&psp->psp_id, psid, sizeof(psp->psp_id)); + psp->psp_work_fd = -1; + TAILQ_INSERT_TAIL(&ctx->ps_processes, psp, next); + return psp; +} + +void +ps_freeprocesses(struct dhcpcd_ctx *ctx, struct ps_process *notthis) +{ + struct ps_process *psp, *psn; + + TAILQ_FOREACH_SAFE(psp, &ctx->ps_processes, next, psn) { + if (psp == notthis) + continue; + ps_freeprocess(psp); + } +} Index: contrib/dhcpcd/src/route.h =================================================================== --- contrib/dhcpcd/src/route.h +++ contrib/dhcpcd/src/route.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - route management - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * rEDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS, WITH OR WITHOUT @@ -60,6 +60,13 @@ # endif #endif +#ifdef __linux__ +# include /* RTA_PREF is only an enum.... */ +# if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) +# define HAVE_ROUTE_PREF +# endif +#endif + #if defined(__OpenBSD__) || defined (__sun) # define ROUTE_PER_GATEWAY /* XXX dhcpcd doesn't really support this yet. @@ -86,9 +93,27 @@ #ifdef HAVE_ROUTE_METRIC unsigned int rt_metric; #endif +/* Maximum interface index is generally USHORT_MAX or 65535. + * Add some padding for other stuff and we get offsets for the + * below that should work automatically. + * This is only an issue if the user defines higher metrics in + * their configuration, but then they might wish to override also. */ +#define RTMETRIC_BASE 1000U +#define RTMETRIC_WIRELESS 2000U +#define RTMETRIC_IPV4LL 1000000U +#define RTMETRIC_ROAM 2000000U +#ifdef HAVE_ROUTE_PREF + int rt_pref; +#endif +#define RTPREF_HIGH 1 +#define RTPREF_MEDIUM 0 /* has to be zero */ +#define RTPREF_LOW (-1) +#define RTPREF_RESERVED (-2) +#define RTPREF_INVALID (-3) /* internal */ unsigned int rt_dflags; -#define RTDF_IFA_ROUTE 0x02 /* Address generated route */ -#define RTDF_FAKE 0x04 /* Maybe us on lease reboot */ +#define RTDF_IFA_ROUTE 0x01 /* Address generated route */ +#define RTDF_FAKE 0x02 /* Maybe us on lease reboot */ +#define RTDF_IPV4LL 0x04 /* IPv4LL route */ #define RTDF_RA 0x08 /* Router Advertisement */ #define RTDF_DHCP 0x10 /* DHCP route */ #define RTDF_STATIC 0x20 /* Configured in dhcpcd */ Index: contrib/dhcpcd/src/route.c =================================================================== --- contrib/dhcpcd/src/route.c +++ contrib/dhcpcd/src/route.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - route management - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -96,17 +96,6 @@ memset(dstp, 0, (size_t)(addre - dstp)); } -int -rt_cmp_dest(const struct rt *rt1, const struct rt *rt2) -{ - union sa_ss ma1 = { .sa.sa_family = AF_UNSPEC }; - union sa_ss ma2 = { .sa.sa_family = AF_UNSPEC }; - - rt_maskedaddr(&ma1.sa, &rt1->rt_dest, &rt1->rt_netmask); - rt_maskedaddr(&ma2.sa, &rt2->rt_dest, &rt2->rt_netmask); - return sa_cmp(&ma1.sa, &ma2.sa); -} - /* * On some systems, host routes have no need for a netmask. * However DHCP specifies host routes using an all-ones netmask. @@ -122,6 +111,22 @@ return sa_cmp(&rt1->rt_netmask, &rt2->rt_netmask); } +int +rt_cmp_dest(const struct rt *rt1, const struct rt *rt2) +{ + union sa_ss ma1 = { .sa.sa_family = AF_UNSPEC }; + union sa_ss ma2 = { .sa.sa_family = AF_UNSPEC }; + int c; + + rt_maskedaddr(&ma1.sa, &rt1->rt_dest, &rt1->rt_netmask); + rt_maskedaddr(&ma2.sa, &rt2->rt_dest, &rt2->rt_netmask); + c = sa_cmp(&ma1.sa, &ma2.sa); + if (c != 0) + return c; + + return rt_cmp_netmask(rt1, rt2); +} + static int rt_compare_os(__unused void *context, const void *node1, const void *node2) { @@ -168,6 +173,23 @@ if (c != 0) return -c; + /* Prefer roaming over non roaming if both carriers are down. */ + if (ifp1->carrier == LINK_DOWN && ifp2->carrier == LINK_DOWN) { + bool roam1 = if_roaming(ifp1); + bool roam2 = if_roaming(ifp2); + + if (roam1 != roam2) + return roam1 ? 1 : -1; + } + +#ifdef INET + /* IPv4LL routes always come last */ + if (rt1->rt_dflags & RTDF_IPV4LL && !(rt2->rt_dflags & RTDF_IPV4LL)) + return -1; + else if (!(rt1->rt_dflags & RTDF_IPV4LL) && rt2->rt_dflags & RTDF_IPV4LL) + return 1; +#endif + /* Lower metric interfaces come first. */ c = (int)(ifp1->metric - ifp2->metric); if (c != 0) @@ -366,6 +388,8 @@ rt->rt_ifp = ifp; #ifdef HAVE_ROUTE_METRIC rt->rt_metric = ifp->metric; + if (if_roaming(ifp)) + rt->rt_metric += RTMETRIC_ROAM; #endif } @@ -390,6 +414,7 @@ return rt; rt_free(rt); + errno = EEXIST; return NULL; } @@ -690,22 +715,31 @@ ctx->rt_order = 0; ctx->options |= DHCPCD_RTBUILD; - switch (af) { #ifdef INET - case AF_INET: - if (!inet_getroutes(ctx, &routes)) - goto getfail; - break; + if (!inet_getroutes(ctx, &routes)) + goto getfail; #endif #ifdef INET6 - case AF_INET6: - if (!inet6_getroutes(ctx, &routes)) - goto getfail; - break; + if (!inet6_getroutes(ctx, &routes)) + goto getfail; +#endif + +#ifdef BSD + /* Rewind the miss filter */ + ctx->rt_missfilterlen = 0; #endif - } RB_TREE_FOREACH_SAFE(rt, &routes, rtn) { + if (rt->rt_ifp->active) { + if (!(rt->rt_ifp->options->options & DHCPCD_CONFIGURE)) + continue; + } else if (!(ctx->options & DHCPCD_CONFIGURE)) + continue; +#ifdef BSD + if (rt_is_default(rt) && + if_missfilter(rt->rt_ifp, &rt->rt_gateway) == -1) + logerr("if_missfilter"); +#endif if ((rt->rt_dest.sa_family != af && rt->rt_dest.sa_family != AF_UNSPEC) || (rt->rt_gateway.sa_family != af && @@ -724,6 +758,11 @@ } } +#ifdef BSD + if (if_missfilter_apply(ctx) == -1 && errno != ENOTSUP) + logerr("if_missfilter_apply"); +#endif + /* Remove old routes we used to manage. */ RB_TREE_FOREACH_REVERSE_SAFE(rt, &ctx->routes, rtn) { if ((rt->rt_dest.sa_family != af && @@ -754,7 +793,6 @@ } } - getfail: rt_headclear(&routes, AF_UNSPEC); rt_headclear(&kroutes, AF_UNSPEC); Index: contrib/dhcpcd/src/sa.h =================================================================== --- contrib/dhcpcd/src/sa.h +++ contrib/dhcpcd/src/sa.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * Socket Address handling for dhcpcd - * Copyright (c) 2015-2019 Roy Marples + * Copyright (c) 2015-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without Index: contrib/dhcpcd/src/sa.c =================================================================== --- contrib/dhcpcd/src/sa.c +++ contrib/dhcpcd/src/sa.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * Socket Address handling for dhcpcd - * Copyright (c) 2015-2019 Roy Marples + * Copyright (c) 2015-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ #include #ifdef AF_LINK #include -#elif AF_PACKET +#elif defined(AF_PACKET) #include #endif @@ -288,7 +288,7 @@ #ifndef NDEBUG /* Ensure the calculation is correct */ if (!sa_inprefix) { - union sa_ss ss = { .sa.sa_family = sa->sa_family }; + union sa_ss ss = { .sa = { .sa_family = sa->sa_family } }; sa_inprefix = true; sa_fromprefix(&ss.sa, prefix); @@ -385,7 +385,7 @@ } return hwaddr_ntoa(CLLADDR(sdl), sdl->sdl_alen, buf, len); } -#elif AF_PACKET +#elif defined(AF_PACKET) if (sa->sa_family == AF_PACKET) { const struct sockaddr_ll *sll; Index: contrib/dhcpcd/src/script.h =================================================================== --- contrib/dhcpcd/src/script.h +++ contrib/dhcpcd/src/script.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -33,7 +33,9 @@ __printflike(2, 3) int efprintf(FILE *, const char *, ...); void if_printoptions(void); -int send_interface(struct fd_list *, const struct interface *); +char ** script_buftoenv(struct dhcpcd_ctx *, char *, size_t); +pid_t script_exec(char *const *, char *const *); +int send_interface(struct fd_list *, const struct interface *, int); +int script_dump(const char *, size_t); int script_runreason(const struct interface *, const char *); - #endif Index: contrib/dhcpcd/src/script.c =================================================================== --- contrib/dhcpcd/src/script.c +++ contrib/dhcpcd/src/script.c @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon - * Copyright (c) 2006-2019 Roy Marples + * Copyright (c) 2006-2021 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -47,18 +48,15 @@ #include "common.h" #include "dhcp.h" #include "dhcp6.h" +#include "eloop.h" #include "if.h" #include "if-options.h" #include "ipv4ll.h" #include "ipv6nd.h" #include "logerr.h" +#include "privsep.h" #include "script.h" -/* Allow the OS to define another script env var name */ -#ifndef RC_SVCNAME -#define RC_SVCNAME "RC_SVCNAME" -#endif - #define DEFAULT_PATH "/usr/bin:/usr/sbin:/bin:/sbin" static const char * const if_params[] = { @@ -76,6 +74,9 @@ NULL }; +static const char * true_str = "true"; +static const char * false_str = "false"; + void if_printoptions(void) { @@ -85,10 +86,10 @@ printf(" - %s\n", *p); } -static int -exec_script(const struct dhcpcd_ctx *ctx, char *const *argv, char *const *env) +pid_t +script_exec(char *const *argv, char *const *env) { - pid_t pid; + pid_t pid = 0; posix_spawnattr_t attr; int r; #ifdef USE_SIGNALS @@ -107,10 +108,12 @@ flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF; posix_spawnattr_setflags(&attr, flags); sigemptyset(&defsigs); + posix_spawnattr_setsigmask(&attr, &defsigs); for (i = 0; i < dhcpcd_signals_len; i++) sigaddset(&defsigs, dhcpcd_signals[i]); + for (i = 0; i < dhcpcd_signals_ignore_len; i++) + sigaddset(&defsigs, dhcpcd_signals_ignore[i]); posix_spawnattr_setsigdefault(&attr, &defsigs); - posix_spawnattr_setsigmask(&attr, &ctx->sigset); #endif errno = 0; r = posix_spawn(&pid, argv[0], NULL, &attr, argv, env); @@ -173,18 +176,65 @@ return r; } +char ** +script_buftoenv(struct dhcpcd_ctx *ctx, char *buf, size_t len) +{ + char **env, **envp, *bufp, *endp; + size_t nenv; + + /* Count the terminated env strings. + * Assert that the terminations are correct. */ + nenv = 0; + endp = buf + len; + for (bufp = buf; bufp < endp; bufp++) { + if (*bufp == '\0') { +#ifndef NDEBUG + if (bufp + 1 < endp) + assert(*(bufp + 1) != '\0'); +#endif + nenv++; + } + } + assert(*(bufp - 1) == '\0'); + if (nenv == 0) + return NULL; + + if (ctx->script_envlen < nenv) { + env = reallocarray(ctx->script_env, nenv + 1, sizeof(*env)); + if (env == NULL) + return NULL; + ctx->script_env = env; + ctx->script_envlen = nenv; + } + + bufp = buf; + envp = ctx->script_env; + *envp++ = bufp++; + endp--; /* Avoid setting the last \0 to an invalid pointer */ + for (; bufp < endp; bufp++) { + if (*bufp == '\0') + *envp++ = bufp + 1; + } + *envp = NULL; + + return ctx->script_env; +} + static long -make_env(const struct interface *ifp, const char *reason) +make_env(struct dhcpcd_ctx *ctx, const struct interface *ifp, + const char *reason) { - struct dhcpcd_ctx *ctx = ifp->ctx; FILE *fp; - char **env, **envp, *bufp, *endp, *path; - size_t nenv; long buf_pos, i; + char *path; int protocol = PROTO_LINK; - const struct if_options *ifo = ifp->options; + const struct if_options *ifo; const struct interface *ifp2; int af; + bool is_stdin = ifp->name[0] == '\0'; + const char *if_up, *if_down; + rb_tree_t ifaces; + struct rt *rt; #ifdef INET const struct dhcp_state *state; #ifdef IPV4LL @@ -211,8 +261,10 @@ fp = NULL; tmpfd = mkstemp(tmpfile); - if (tmpfd == -1) - goto eexit; + if (tmpfd == -1) { + logerr("%s: mkstemp", __func__); + return -1; + } unlink(tmpfile); fp = fdopen(tmpfd, "w+"); if (fp == NULL) { @@ -221,6 +273,22 @@ } #endif + if (!(ifp->ctx->options & DHCPCD_DUMPLEASE)) { + /* Needed for scripts */ + path = getenv("PATH"); + if (efprintf(fp, "PATH=%s", + path == NULL ? DEFAULT_PATH : path) == -1) + goto eexit; + if (efprintf(fp, "pid=%d", getpid()) == -1) + goto eexit; + } + + if (!is_stdin) { + if (efprintf(fp, "reason=%s", reason) == -1) + goto eexit; + } + + ifo = ifp->options; #ifdef INET state = D_STATE(ifp); #ifdef IPV4LL @@ -231,7 +299,10 @@ d6_state = D6_CSTATE(ifp); #endif if (strcmp(reason, "TEST") == 0) { - if (1 == 2) {} + if (1 == 2) { + /* This space left intentionally blank + * as all the below statements are optional. */ + } #ifdef INET6 #ifdef DHCP6 else if (d6_state && d6_state->new) @@ -262,6 +333,7 @@ else if (strcmp(reason, "PREINIT") == 0 || strcmp(reason, "CARRIER") == 0 || strcmp(reason, "NOCARRIER") == 0 || + strcmp(reason, "NOCARRIER_ROAMING") == 0 || strcmp(reason, "UNKNOWN") == 0 || strcmp(reason, "DEPARTED") == 0 || strcmp(reason, "STOPPED") == 0) @@ -275,18 +347,19 @@ protocol = PROTO_DHCP; #endif - /* Needed for scripts */ - path = getenv("PATH"); - if (efprintf(fp, "PATH=%s", path == NULL ? DEFAULT_PATH:path) == -1) - goto eexit; - - if (efprintf(fp, "interface=%s", ifp->name) == -1) - goto eexit; - if (efprintf(fp, "reason=%s", reason) == -1) - goto eexit; - if (ifp->ctx->options & DHCPCD_DUMPLEASE) + if (!is_stdin) { + if (efprintf(fp, "interface=%s", ifp->name) == -1) + goto eexit; + if (protocols[protocol] != NULL) { + if (efprintf(fp, "protocol=%s", + protocols[protocol]) == -1) + goto eexit; + } + } + if (ifp->ctx->options & DHCPCD_DUMPLEASE && protocol != PROTO_LINK) goto dumplease; - if (efprintf(fp, "pid=%d", getpid()) == -1) + if (efprintf(fp, "if_configured=%s", + ifo->options & DHCPCD_CONFIGURE ? "true" : "false") == -1) goto eexit; if (efprintf(fp, "ifcarrier=%s", ifp->carrier == LINK_UNKNOWN ? "unknown" : @@ -300,35 +373,63 @@ goto eexit; if (efprintf(fp, "ifmtu=%d", if_getmtu(ifp)) == -1) goto eexit; + if (ifp->wireless) { + char pssid[IF_SSIDLEN * 4]; + + if (print_string(pssid, sizeof(pssid), OT_ESCSTRING, + ifp->ssid, ifp->ssid_len) != -1) + { + if (efprintf(fp, "ifssid=%s", pssid) == -1) + goto eexit; + } + } + if (*ifp->profile != '\0') { + if (efprintf(fp, "profile=%s", ifp->profile) == -1) + goto eexit; + } + if (ifp->ctx->options & DHCPCD_DUMPLEASE) + goto dumplease; + ifp->ctx->rt_order = 0; + rb_tree_init(&ifaces, &rt_compare_proto_ops); + TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { + if (!ifp2->active) + continue; + rt = rt_new(UNCONST(ifp2)); + if (rt == NULL) + goto eexit; + if (rt_proto_add(&ifaces, rt) != rt) + goto eexit; + } if (fprintf(fp, "interface_order=") == -1) goto eexit; - TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { - if (ifp2 != TAILQ_FIRST(ifp->ctx->ifaces)) { - if (fputc(' ', fp) == EOF) - return -1; - } - if (fprintf(fp, "%s", ifp2->name) == -1) - return -1; + RB_TREE_FOREACH(rt, &ifaces) { + if (rt != RB_TREE_MIN(&ifaces) && + fprintf(fp, "%s", " ") == -1) + goto eexit; + if (fprintf(fp, "%s", rt->rt_ifp->name) == -1) + goto eexit; } + rt_headclear(&ifaces, AF_UNSPEC); if (fputc('\0', fp) == EOF) - return -1; + goto eexit; if (strcmp(reason, "STOPPED") == 0) { - if (efprintf(fp, "if_up=false") == -1) - goto eexit; - if (efprintf(fp, "if_down=%s", - ifo->options & DHCPCD_RELEASE ? "true" : "false") == -1) - goto eexit; + if_up = false_str; + if_down = ifo->options & DHCPCD_RELEASE ? true_str : false_str; } else if (strcmp(reason, "TEST") == 0 || strcmp(reason, "PREINIT") == 0 || strcmp(reason, "CARRIER") == 0 || strcmp(reason, "UNKNOWN") == 0) { - if (efprintf(fp, "if_up=false") == -1) - goto eexit; - if (efprintf(fp, "if_down=false") == -1) - goto eexit; + if_up = false_str; + if_down = false_str; + } else if (strcmp(reason, "NOCARRIER") == 0) { + if_up = false_str; + if_down = true_str; + } else if (strcmp(reason, "NOCARRIER_ROAMING") == 0) { + if_up = true_str; + if_down = false_str; } else if (1 == 2 /* appease ifdefs */ #ifdef INET || (protocol == PROTO_DHCP && state && state->new) @@ -345,20 +446,17 @@ #endif ) { - if (efprintf(fp, "if_up=true") == -1) - goto eexit; - if (efprintf(fp, "if_down=false") == -1) - goto eexit; + if_up = true_str; + if_down = false_str; } else { - if (efprintf(fp, "if_up=false") == -1) - goto eexit; - if (efprintf(fp, "if_down=true") == -1) - goto eexit; - } - if (protocols[protocol] != NULL) { - if (efprintf(fp, "protocol=%s", protocols[protocol]) == -1) - goto eexit; + if_up = false_str; + if_down = true_str; } + if (efprintf(fp, "if_up=%s", if_up) == -1) + goto eexit; + if (efprintf(fp, "if_down=%s", if_down) == -1) + goto eexit; + if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) { if (efprintf(fp, "if_afwaiting=%d", af) == -1) goto eexit; @@ -377,20 +475,6 @@ if (efprintf(fp, "syslog_debug=true") == -1) goto eexit; } - if (*ifp->profile) { - if (efprintf(fp, "profile=%s", ifp->profile) == -1) - goto eexit; - } - if (ifp->wireless) { - char pssid[IF_SSIDLEN * 4]; - - if (print_string(pssid, sizeof(pssid), OT_ESCSTRING, - ifp->ssid, ifp->ssid_len) != -1) - { - if (efprintf(fp, "ifssid=%s", pssid) == -1) - goto eexit; - } - } #ifdef INET if (protocol == PROTO_DHCP && state && state->old) { if (dhcp_env(fp, "old", ifp, @@ -412,7 +496,7 @@ dumplease: #ifdef INET #ifdef IPV4LL - if (protocol == PROTO_IPV4LL) { + if (protocol == PROTO_IPV4LL && istate) { if (ipv4ll_env(fp, istate->down ? "old" : "new", ifp) == -1) goto eexit; } @@ -476,40 +560,13 @@ fp = NULL; #endif - /* Count the terminated env strings. - * Assert that the terminations are correct. */ - nenv = 0; - endp = ctx->script_buf + buf_pos; - for (bufp = ctx->script_buf; bufp < endp; bufp++) { - if (*bufp == '\0') { -#ifndef NDEBUG - if (bufp + 1 < endp) - assert(*(bufp + 1) != '\0'); -#endif - nenv++; - } - } - assert(*(bufp - 1) == '\0'); - - if (ctx->script_envlen < nenv) { - env = reallocarray(ctx->script_env, nenv + 1, sizeof(*env)); - if (env == NULL) - goto eexit; - ctx->script_env = env; - ctx->script_envlen = nenv; - } + if (is_stdin) + return buf_pos; - bufp = ctx->script_buf; - envp = ctx->script_env; - *envp++ = bufp++; - endp--; /* Avoid setting the last \0 to an invalid pointer */ - for (; bufp < endp; bufp++) { - if (*bufp == '\0') - *envp++ = bufp + 1; - } - *envp = NULL; + if (script_buftoenv(ctx, ctx->script_buf, (size_t)buf_pos) == NULL) + goto eexit; - return buf_pos - 1; + return buf_pos; eexit: logerr(__func__); @@ -527,16 +584,15 @@ struct dhcpcd_ctx *ctx = ifp->ctx; long len; - len = make_env(ifp, reason); + len = make_env(ifp->ctx, ifp, reason); if (len == -1) return -1; - return control_queue(fd, ctx->script_buf, (size_t)len, 1); + return control_queue(fd, ctx->script_buf, (size_t)len); } int -send_interface(struct fd_list *fd, const struct interface *ifp) +send_interface(struct fd_list *fd, const struct interface *ifp, int af) { - const char *reason; int retval = 0; #ifdef INET const struct dhcp_state *d; @@ -545,82 +601,93 @@ const struct dhcp6_state *d6; #endif - switch (ifp->carrier) { - case LINK_UP: - reason = "CARRIER"; - break; - case LINK_DOWN: - case LINK_DOWN_IFFUP: - reason = "NOCARRIER"; - break; - default: - reason = "UNKNOWN"; - break; - } - if (send_interface1(fd, ifp, reason) == -1) - retval = -1; -#ifdef INET - if (D_STATE_RUNNING(ifp)) { - d = D_CSTATE(ifp); - if (send_interface1(fd, ifp, d->reason) == -1) - retval = -1; +#ifndef AF_LINK +#define AF_LINK AF_PACKET +#endif + + if (af == AF_UNSPEC || af == AF_LINK) { + const char *reason; + + switch (ifp->carrier) { + case LINK_UP: + reason = "CARRIER"; + break; + case LINK_DOWN: + reason = "NOCARRIER"; + break; + default: + reason = "UNKNOWN"; + break; + } + if (fd != NULL) { + if (send_interface1(fd, ifp, reason) == -1) + retval = -1; + } else + retval++; } + +#ifdef INET + if (af == AF_UNSPEC || af == AF_INET) { + if (D_STATE_RUNNING(ifp)) { + d = D_CSTATE(ifp); + if (fd != NULL) { + if (send_interface1(fd, ifp, d->reason) == -1) + retval = -1; + } else + retval++; + } #ifdef IPV4LL - if (IPV4LL_STATE_RUNNING(ifp)) { - if (send_interface1(fd, ifp, "IPV4LL") == -1) - retval = -1; - } + if (IPV4LL_STATE_RUNNING(ifp)) { + if (fd != NULL) { + if (send_interface1(fd, ifp, "IPV4LL") == -1) + retval = -1; + } else + retval++; + } #endif + } #endif #ifdef INET6 - if (IPV6_STATE_RUNNING(ifp)) { - if (send_interface1(fd, ifp, "STATIC6") == -1) - retval = -1; - } - if (RS_STATE_RUNNING(ifp)) { - if (send_interface1(fd, ifp, "ROUTERADVERT") == -1) - retval = -1; - } + if (af == AF_UNSPEC || af == AF_INET6) { + if (IPV6_STATE_RUNNING(ifp)) { + if (fd != NULL) { + if (send_interface1(fd, ifp, "STATIC6") == -1) + retval = -1; + } else + retval++; + } + if (RS_STATE_RUNNING(ifp)) { + if (fd != NULL) { + if (send_interface1(fd, ifp, + "ROUTERADVERT") == -1) + retval = -1; + } else + retval++; + } #ifdef DHCP6 - if (D6_STATE_RUNNING(ifp)) { - d6 = D6_CSTATE(ifp); - if (send_interface1(fd, ifp, d6->reason) == -1) - retval = -1; - } + if (D6_STATE_RUNNING(ifp)) { + d6 = D6_CSTATE(ifp); + if (fd != NULL) { + if (send_interface1(fd, ifp, d6->reason) == -1) + retval = -1; + } else + retval++; + } #endif + } #endif return retval; } -int -script_runreason(const struct interface *ifp, const char *reason) +static int +script_run(struct dhcpcd_ctx *ctx, char **argv) { - struct dhcpcd_ctx *ctx = ifp->ctx; - char *argv[2]; pid_t pid; int status = 0; - struct fd_list *fd; - - if (ifp->options->script == NULL && - TAILQ_FIRST(&ifp->ctx->control_fds) == NULL) - return 0; - - /* Make our env */ - if (make_env(ifp, reason) == -1) { - logerr(__func__); - return -1; - } - - if (ifp->options->script == NULL) - goto send_listeners; - - argv[0] = ifp->options->script; - argv[1] = NULL; - logdebugx("%s: executing `%s' %s", ifp->name, argv[0], reason); - pid = exec_script(ctx, argv, ctx->script_env); + pid = script_exec(argv, ctx->script_env); if (pid == -1) logerr("%s: %s", __func__, argv[0]); else if (pid != 0) { @@ -641,18 +708,81 @@ __func__, argv[0], strsignal(WTERMSIG(status))); } + return WEXITSTATUS(status); +} + +int +script_dump(const char *env, size_t len) +{ + const char *ep = env + len; + + if (len == 0) + return 0; + + if (*(ep - 1) != '\0') { + errno = EINVAL; + return -1; + } + + for (; env < ep; env += strlen(env) + 1) { + if (strncmp(env, "new_", 4) == 0) + env += 4; + printf("%s\n", env); + } + return 0; +} + +int +script_runreason(const struct interface *ifp, const char *reason) +{ + struct dhcpcd_ctx *ctx = ifp->ctx; + char *argv[2]; + int status = 0; + struct fd_list *fd; + long buflen; + + if (ctx->script == NULL && + TAILQ_FIRST(&ifp->ctx->control_fds) == NULL) + return 0; + + /* Make our env */ + if ((buflen = make_env(ifp->ctx, ifp, reason)) == -1) { + logerr(__func__); + return -1; + } + + if (strncmp(reason, "DUMP", 4) == 0) + return script_dump(ctx->script_buf, (size_t)buflen); + + if (ctx->script == NULL) + goto send_listeners; + + argv[0] = ctx->script; + argv[1] = NULL; + logdebugx("%s: executing: %s %s", ifp->name, argv[0], reason); + +#ifdef PRIVSEP + if (ctx->options & DHCPCD_PRIVSEP) { + if (ps_root_script(ctx, + ctx->script_buf, ctx->script_buflen) == -1) + logerr(__func__); + goto send_listeners; + } +#endif + + script_run(ctx, argv); + send_listeners: /* Send to our listeners */ status = 0; TAILQ_FOREACH(fd, &ctx->control_fds, next) { if (!(fd->flags & FD_LISTEN)) continue; - if (control_queue(fd, ctx->script_buf, ctx->script_buflen, - true) == -1) + if (control_queue(fd, ctx->script_buf, ctx->script_buflen)== -1) logerr("%s: control_queue", __func__); else status = 1; } - return WEXITSTATUS(status); + return status; } Index: sbin/dhcpcd/config.h =================================================================== --- sbin/dhcpcd/config.h +++ sbin/dhcpcd/config.h @@ -5,10 +5,14 @@ #define LIBDIR "/lib" #define LIBEXECDIR "/libexec" #define DBDIR "/var/db/dhcpcd" -#define RUNDIR "/var/run" +#define RUNDIR "/var/run/dhcpcd" #endif #include #include +#ifndef PRIVSEP_USER +#define PRIVSEP_USER "_dhcp" +#endif +#define HAVE_CAPSICUM #define HAVE_OPEN_MEMSTREAM #include "compat/pidfile.h" #include "compat/strtoi.h" @@ -17,7 +21,7 @@ #define RBTEST #include "compat/rbtree.h" #define HAVE_REALLOCARRAY -#define HAVE_KQUEUE +#define HAVE_PPOLL #define HAVE_MD5_H #define SHA2_H #include "compat/crypt/hmac.h"