/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2019 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. */ const char dhcpcd_copyright[] = "Copyright (c) 2006-2019 Roy Marples"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "arp.h" #include "common.h" #include "control.h" #include "dev.h" #include "dhcp-common.h" #include "dhcpcd.h" #include "dhcp.h" #include "dhcp6.h" #include "duid.h" #include "eloop.h" #include "if.h" #include "if-options.h" #include "ipv4.h" #include "ipv4ll.h" #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" #include "script.h" #ifdef HAVE_UTIL_H #include #endif #ifdef USE_SIGNALS const int dhcpcd_signals[] = { SIGTERM, SIGINT, SIGALRM, SIGHUP, SIGUSR1, SIGUSR2, SIGPIPE }; const size_t dhcpcd_signals_len = __arraycount(dhcpcd_signals); #endif #define IF_UPANDRUNNING(a) \ (((a)->flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) static void usage(void) { printf("usage: "PACKAGE"\t[-146ABbDdEGgHJKLMNPpqTV]\n" "\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[-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" "\t\t[-S, --static value]\n" "\t\t[-s, --inform address[/cidr[/broadcast_address]]]\n [--inform6]" "\t\t[-t, --timeout seconds] [-u, --userclass class]\n" "\t\t[-v, --vendor code, value] [-W, --whitelist address[/cidr]] [-w]\n" "\t\t[--waitip [4 | 6]] [-y, --reboot seconds]\n" "\t\t[-X, --blacklist address[/cidr]] [-Z, --denyinterfaces pattern]\n" "\t\t[-z, --allowinterfaces pattern] [--inactive] [interface] [...]\n" " "PACKAGE"\t-n, --rebind [interface]\n" " "PACKAGE"\t-k, --release [interface]\n" " "PACKAGE"\t-U, --dumplease interface\n" " "PACKAGE"\t--version\n" " "PACKAGE"\t-x, --exit [interface]\n"); } static void free_globals(struct dhcpcd_ctx *ctx) { struct dhcp_opt *opt; if (ctx->ifac) { for (; ctx->ifac > 0; ctx->ifac--) free(ctx->ifav[ctx->ifac - 1]); free(ctx->ifav); ctx->ifav = NULL; } if (ctx->ifdc) { for (; ctx->ifdc > 0; ctx->ifdc--) free(ctx->ifdv[ctx->ifdc - 1]); free(ctx->ifdv); ctx->ifdv = NULL; } if (ctx->ifcc) { for (; ctx->ifcc > 0; ctx->ifcc--) free(ctx->ifcv[ctx->ifcc - 1]); free(ctx->ifcv); ctx->ifcv = NULL; } #ifdef INET if (ctx->dhcp_opts) { for (opt = ctx->dhcp_opts; ctx->dhcp_opts_len > 0; opt++, ctx->dhcp_opts_len--) free_dhcp_opt_embenc(opt); free(ctx->dhcp_opts); ctx->dhcp_opts = NULL; } #endif #ifdef INET6 if (ctx->nd_opts) { for (opt = ctx->nd_opts; ctx->nd_opts_len > 0; opt++, ctx->nd_opts_len--) free_dhcp_opt_embenc(opt); free(ctx->nd_opts); ctx->nd_opts = NULL; } #ifdef DHCP6 if (ctx->dhcp6_opts) { for (opt = ctx->dhcp6_opts; ctx->dhcp6_opts_len > 0; opt++, ctx->dhcp6_opts_len--) free_dhcp_opt_embenc(opt); free(ctx->dhcp6_opts); ctx->dhcp6_opts = NULL; } #endif #endif if (ctx->vivso) { for (opt = ctx->vivso; ctx->vivso_len > 0; opt++, ctx->vivso_len--) free_dhcp_opt_embenc(opt); free(ctx->vivso); ctx->vivso = NULL; } } static void handle_exit_timeout(void *arg) { struct dhcpcd_ctx *ctx; ctx = arg; logerrx("timed out"); if (!(ctx->options & DHCPCD_MASTER)) { 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; } ctx->options |= DHCPCD_NOWAITIP; dhcpcd_daemonise(ctx); } static const char * dhcpcd_af(int af) { switch (af) { case AF_UNSPEC: return "IP"; case AF_INET: return "IPv4"; case AF_INET6: return "IPv6"; default: return NULL; } } int dhcpcd_ifafwaiting(const struct interface *ifp) { unsigned long long opts; bool foundany = false; if (ifp->active != IF_ACTIVE_USER) return AF_MAX; #define DHCPCD_WAITALL (DHCPCD_WAITIP4 | DHCPCD_WAITIP6) opts = ifp->options->options; #ifdef INET if (opts & DHCPCD_WAITIP4 || (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL))) { bool foundaddr = ipv4_hasaddr(ifp); if (opts & DHCPCD_WAITIP4 && !foundaddr) return AF_INET; if (foundaddr) foundany = true; } #endif #ifdef INET6 if (opts & DHCPCD_WAITIP6 || (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL))) { bool foundaddr = ipv6_hasaddr(ifp); if (opts & DHCPCD_WAITIP6 && !foundaddr) return AF_INET; if (foundaddr) foundany = true; } #endif if (opts & DHCPCD_WAITIP && !(opts & DHCPCD_WAITALL) && !foundany) return AF_UNSPEC; return AF_MAX; } int dhcpcd_afwaiting(const struct dhcpcd_ctx *ctx) { unsigned long long opts; const struct interface *ifp; int af; if (!(ctx->options & DHCPCD_WAITOPTS)) return AF_MAX; opts = ctx->options; TAILQ_FOREACH(ifp, ctx->ifaces, next) { #ifdef INET if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP4) && ipv4_hasaddr(ifp)) opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP4); #endif #ifdef INET6 if (opts & (DHCPCD_WAITIP | DHCPCD_WAITIP6) && ipv6_hasaddr(ifp)) opts &= ~(DHCPCD_WAITIP | DHCPCD_WAITIP6); #endif if (!(opts & DHCPCD_WAITOPTS)) break; } if (opts & DHCPCD_WAITIP) af = AF_UNSPEC; else if (opts & DHCPCD_WAITIP4) af = AF_INET; else if (opts & DHCPCD_WAITIP6) af = AF_INET6; else return AF_MAX; return af; } static int dhcpcd_ipwaited(struct dhcpcd_ctx *ctx) { struct interface *ifp; int af; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) { logdebugx("%s: waiting for an %s address", ifp->name, dhcpcd_af(af)); return 0; } } if ((af = dhcpcd_afwaiting(ctx)) != AF_MAX) { logdebugx("waiting for an %s address", dhcpcd_af(af)); return 0; } return 1; } /* Returns the pid of the child, otherwise 0. */ pid_t dhcpcd_daemonise(struct dhcpcd_ctx *ctx) { #ifdef THERE_IS_NO_FORK eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); errno = ENOSYS; return 0; #else pid_t pid, lpid; char buf = '\0'; int sidpipe[2], fd; if (ctx->options & DHCPCD_DAEMONISE && !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP))) { if (!dhcpcd_ipwaited(ctx)) return 0; } if (ctx->options & DHCPCD_ONESHOT) { loginfox("exiting due to oneshot"); eloop_exit(ctx->eloop, EXIT_SUCCESS); return 0; } 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; } 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; } #endif } static void dhcpcd_drop(struct interface *ifp, int stop) { #ifdef DHCP6 dhcp6_drop(ifp, stop ? NULL : "EXPIRE6"); #endif #ifdef INET6 ipv6nd_drop(ifp); ipv6_drop(ifp); #endif #ifdef IPV4LL ipv4ll_drop(ifp); #endif #ifdef INET dhcp_drop(ifp, stop ? "STOP" : "EXPIRE"); #endif #ifdef ARP arp_drop(ifp); #endif #if !defined(DHCP6) && !defined(DHCP) UNUSED(stop); #endif } static void stop_interface(struct interface *ifp) { struct dhcpcd_ctx *ctx; ctx = ifp->ctx; loginfox("%s: removing interface", ifp->name); ifp->options->options |= DHCPCD_STOPPING; dhcpcd_drop(ifp, 1); if (ifp->options->options & DHCPCD_DEPARTED) script_runreason(ifp, "DEPARTED"); else script_runreason(ifp, "STOPPED"); /* Delete all timeouts for the interfaces */ eloop_q_timeout_delete(ctx->eloop, 0, 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))) eloop_exit(ctx->eloop, EXIT_FAILURE); } static void configure_interface1(struct interface *ifp) { struct if_options *ifo = ifp->options; /* Do any platform specific configuration */ if_conf(ifp); /* If we want to release a lease, we can't really persist the * address either. */ if (ifo->options & DHCPCD_RELEASE) ifo->options &= ~DHCPCD_PERSISTENT; if (ifp->flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) { ifo->options &= ~DHCPCD_ARP; if (!(ifp->flags & IFF_MULTICAST)) ifo->options &= ~DHCPCD_IPV6RS; 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 && !(ifp->ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) { /* If not doing any DHCP, disable the RDNSS requirement. */ if (!(ifo->options & (DHCPCD_DHCP | DHCPCD_DHCP6))) ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; if_setup_inet6(ifp); } #endif if (!(ifo->options & DHCPCD_IAID)) { /* * An IAID is for identifying a unqiue interface within * the client. It is 4 bytes long. Working out a default * value is problematic. * * Interface name and number are not stable * between different OS's. Some OS's also cannot make * up their mind what the interface should be called * (yes, udev, I'm looking at you). * Also, the name could be longer than 4 bytes. * Also, with pluggable interfaces the name and index * could easily get swapped per actual interface. * * The MAC address is 6 bytes long, the final 3 * being unique to the manufacturer and the initial 3 * being unique to the organisation which makes it. * We could use the last 4 bytes of the MAC address * as the IAID as it's the most stable part given the * above, but equally it's not guaranteed to be * unique. * * Given the above, and our need to reliably work * between reboots without persitent storage, * generating the IAID from the MAC address is the only * logical default. * Saying that, if a VLANID has been specified then we * can use that. It's possible that different interfaces * can have the same VLANID, but this is no worse than * generating the IAID from the duplicate MAC address. * * dhclient uses the last 4 bytes of the MAC address. * dibbler uses an increamenting counter. * wide-dhcpv6 uses 0 or a configured value. * odhcp6c uses 1. * Windows 7 uses the first 3 bytes of the MAC address * and an unknown byte. * dhcpcd-6.1.0 and earlier used the interface name, * falling back to interface index if name > 4. */ if (ifp->vlanid != 0) { uint32_t vlanid; /* Maximal VLANID is 4095, so prefix with 0xff * 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)) { memcpy(ifo->iaid, ifp->hwaddr + ifp->hwlen - sizeof(ifo->iaid), sizeof(ifo->iaid)); } else { uint32_t len; len = (uint32_t)strlen(ifp->name); if (len <= sizeof(ifo->iaid)) { memcpy(ifo->iaid, ifp->name, len); if (len < sizeof(ifo->iaid)) memset(ifo->iaid + len, 0, sizeof(ifo->iaid) - len); } else { /* IAID is the same size as a uint32_t */ len = htonl(ifp->index); memcpy(ifo->iaid, &len, sizeof(ifo->iaid)); } } ifo->options |= DHCPCD_IAID; } #ifdef DHCP6 if (ifo->ia_len == 0 && ifo->options & DHCPCD_IPV6 && ifp->name[0] != '\0') { ifo->ia = malloc(sizeof(*ifo->ia)); if (ifo->ia == NULL) logerr(__func__); else { ifo->ia_len = 1; ifo->ia->ia_type = D6_OPTION_IA_NA; memcpy(ifo->ia->iaid, ifo->iaid, sizeof(ifo->iaid)); memset(&ifo->ia->addr, 0, sizeof(ifo->ia->addr)); #ifndef SMALL ifo->ia->sla = NULL; ifo->ia->sla_len = 0; #endif } } else { size_t i; for (i = 0; i < ifo->ia_len; i++) { if (!ifo->ia[i].iaid_set) { memcpy(&ifo->ia[i].iaid, ifo->iaid, sizeof(ifo->ia[i].iaid)); ifo->ia[i].iaid_set = 1; } } } #endif /* If root is network mounted, we don't want to kill the connection * if the DHCP server goes the way of the dodo OR dhcpcd is rebooting * and the lease file has expired. */ if (is_root_local() == 0) ifo->options |= DHCPCD_LASTLEASE_EXTEND; } int dhcpcd_selectprofile(struct interface *ifp, const char *profile) { struct if_options *ifo; char pssid[PROFILE_LEN]; if (ifp->ssid_len) { ssize_t r; r = print_string(pssid, sizeof(pssid), OT_ESCSTRING, ifp->ssid, ifp->ssid_len); if (r == -1) { logerr(__func__); pssid[0] = '\0'; } } else pssid[0] = '\0'; ifo = read_config(ifp->ctx, ifp->name, pssid, profile); if (ifo == NULL) { logdebugx("%s: no profile %s", ifp->name, profile); return -1; } if (profile != NULL) { strlcpy(ifp->profile, profile, sizeof(ifp->profile)); loginfox("%s: selected profile %s", ifp->name, profile); } else *ifp->profile = '\0'; free_options(ifp->ctx, ifp->options); ifp->options = ifo; if (profile) { add_options(ifp->ctx, ifp->name, ifp->options, ifp->ctx->argc, ifp->ctx->argv); configure_interface1(ifp); } return 1; } static void configure_interface(struct interface *ifp, int argc, char **argv, unsigned long long options) { time_t old; old = ifp->options ? ifp->options->mtime : 0; dhcpcd_selectprofile(ifp, NULL); if (ifp->options == NULL) { /* dhcpcd cannot continue with this interface. */ ifp->active = IF_INACTIVE; return; } add_options(ifp->ctx, ifp->name, ifp->options, argc, argv); ifp->options->options |= options; configure_interface1(ifp); /* If the mtime has changed drop any old lease */ if (old != 0 && ifp->options->mtime != old) { logwarnx("%s: confile file changed, expiring leases", ifp->name); dhcpcd_drop(ifp, 0); } } static void dhcpcd_initstate2(struct interface *ifp, unsigned long long options) { struct if_options *ifo; if (options) { if ((ifo = default_config(ifp->ctx)) == NULL) { logerr(__func__); return; } ifo->options |= options; free(ifp->options); ifp->options = ifo; } else ifo = ifp->options; #ifdef INET6 if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == -1) { logerr(__func__); ifo->options &= ~DHCPCD_IPV6; } #endif } static void dhcpcd_initstate1(struct interface *ifp, int argc, char **argv, unsigned long long options) { configure_interface(ifp, argc, argv, options); if (ifp->active) dhcpcd_initstate2(ifp, 0); } static void dhcpcd_initstate(struct interface *ifp, unsigned long long options) { dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options); } static void dhcpcd_reportssid(struct interface *ifp) { char pssid[IF_SSIDLEN * 4]; 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); } void dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags, const char *ifname) { struct interface *ifp; ifp = if_find(ctx->ifaces, ifname); if (ifp == NULL || ifp->options == NULL || !(ifp->options->options & DHCPCD_LINK) || !ifp->active) return; 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); #endif #ifdef INET dhcp_abort(ifp); #endif #ifdef DHCP6 dhcp6_abort(ifp); #endif } else #endif dhcpcd_drop(ifp, 0); } } else if (carrier == LINK_UP && ifp->flags & IFF_UP) { if (ifp->carrier != LINK_UP) { 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)) { dhcpcd_reportssid(ifp); #ifdef NOCARRIER_PRESERVE_IP dhcpcd_drop(ifp, 0); #endif #ifdef IPV4LL ipv4ll_reset(ifp); #endif } } 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); #endif /* RFC4941 Section 3.5 */ ipv6_gentempifid(ifp); #endif dhcpcd_startinterface(ifp); } } } static void warn_iaid_conflict(struct interface *ifp, uint16_t ia_type, uint8_t *iaid) { struct interface *ifn; #ifdef INET6 size_t i; struct if_ia *ia; #endif TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { if (ifn == ifp || !ifn->active) continue; if (ia_type == 0 && memcmp(ifn->options->iaid, iaid, sizeof(ifn->options->iaid)) == 0) break; #ifdef INET6 for (i = 0; i < ifn->options->ia_len; i++) { ia = &ifn->options->ia[i]; if (ia->ia_type == ia_type && memcmp(ia->iaid, iaid, sizeof(ia->iaid)) == 0) break; } #endif } /* This is only a problem if the interfaces are on the same network. */ if (ifn) logerrx("%s: IAID conflicts with one assigned to %s", ifp->name, ifn->name); } 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_DUID | DHCPCD_IPV6)) { #ifdef INET6 size_t i; struct if_ia *ia; #endif /* 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]; if (memcmp(ifo->iaid, ia->iaid, sizeof(ifo->iaid))) { loginfox("%s: IA type %u IAID %s", ifp->name, ia->ia_type, hwaddr_ntoa(ia->iaid, sizeof(ia->iaid), buf, sizeof(buf))); warn_iaid_conflict(ifp, ia->ia_type, ia->iaid); } } #endif } #ifdef INET6 if (ifo->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) { logerr("%s: ipv6_start", ifp->name); ifo->options &= ~DHCPCD_IPV6; } if (ifo->options & DHCPCD_IPV6) { if (ifp->active == IF_ACTIVE_USER) { ipv6_startstatic(ifp); if (ifo->options & DHCPCD_IPV6RS) ipv6nd_startrs(ifp); } #ifdef DHCP6 if (ifo->options & DHCPCD_DHCP6) { dhcp6_find_delegates(ifp); if (ifp->active == IF_ACTIVE_USER) { enum DH6S d6_state; if (ifo->options & DHCPCD_IA_FORCED) d6_state = DH6S_INIT; else if (ifo->options & DHCPCD_INFORM6) d6_state = DH6S_INFORM; else d6_state = DH6S_CONFIRM; if (dhcp6_start(ifp, d6_state) == -1) logerr("%s: dhcp6_start", ifp->name); } } #endif } #endif #ifdef INET if (ifo->options & DHCPCD_IPV4 && ifp->active == IF_ACTIVE_USER) { /* Ensure we have an IPv4 state before starting DHCP */ if (ipv4_getstate(ifp) != NULL) dhcp_start(ifp); } #endif } static void dhcpcd_prestartinterface(void *arg) { struct interface *ifp = arg; if ((!(ifp->ctx->options & DHCPCD_MASTER) || ifp->options->options & DHCPCD_IF_UP) && if_up(ifp) == -1) logerr("%s: %s", __func__, ifp->name); dhcpcd_startinterface(ifp); } static void run_preinit(struct interface *ifp) { if (ifp->ctx->options & DHCPCD_TEST) return; script_runreason(ifp, "PREINIT"); if (ifp->wireless && ifp->carrier == LINK_UP) dhcpcd_reportssid(ifp); if (ifp->options->options & DHCPCD_LINK && ifp->carrier != LINK_UNKNOWN) script_runreason(ifp, ifp->carrier == LINK_UP ? "CARRIER" : "NOCARRIER"); } void 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); } } } int dhcpcd_handleinterface(void *arg, int action, const char *ifname) { struct dhcpcd_ctx *ctx; 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) { errno = ESRCH; return -1; } if (ifp->active) { logdebugx("%s: interface departed", ifp->name); ifp->options->options |= DHCPCD_DEPARTED; stop_interface(ifp); } TAILQ_REMOVE(ctx->ifaces, ifp, next); if_free(ifp); return 0; } ifs = if_discover(ctx, &ifaddrs, -1, UNCONST(argv)); if (ifs == NULL) { logerr(__func__); return -1; } ifp = if_find(ifs, ifname); if (ifp == NULL) { /* This can happen if an interface is quickly added * and then removed. */ errno = ENOENT; e = -1; goto out; } e = 1; /* Check if we already have the interface */ iff = if_find(ctx->ifaces, ifp->name); if (iff != NULL) { if (iff->active) logdebugx("%s: interface updated", iff->name); /* The flags and hwaddr could have changed */ iff->flags = ifp->flags; iff->hwlen = ifp->hwlen; if (ifp->hwlen != 0) memcpy(iff->hwaddr, ifp->hwaddr, iff->hwlen); } else { TAILQ_REMOVE(ifs, ifp, next); TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); if (ifp->active) { logdebugx("%s: interface added", ifp->name); dhcpcd_initstate(ifp, 0); run_preinit(ifp); } iff = ifp; } if (action > 0) { if_learnaddrs(ctx, ifs, &ifaddrs); if (iff->active) dhcpcd_prestartinterface(iff); } out: /* Free our discovered list */ while ((ifp = TAILQ_FIRST(ifs))) { TAILQ_REMOVE(ifs, ifp, next); if_free(ifp); } free(ifs); return e; } static void dhcpcd_handlelink(void *arg) { struct dhcpcd_ctx *ctx = arg; if (if_handlelink(ctx) == -1) { if (errno == ENOBUFS || errno == ENOMEM) { dhcpcd_linkoverflow(ctx); return; } if (errno != ENOTSUP) logerr(__func__); } } static void dhcpcd_checkcarrier(void *arg) { struct interface *ifp = arg; int carrier; /* 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); } #ifndef SMALL static void dhcpcd_setlinkrcvbuf(struct dhcpcd_ctx *ctx) { socklen_t socklen; if (ctx->link_rcvbuf == 0) return; socklen = sizeof(ctx->link_rcvbuf); if (setsockopt(ctx->link_fd, SOL_SOCKET, SO_RCVBUF, &ctx->link_rcvbuf, socklen) == -1) logerr(__func__); } #endif void dhcpcd_linkoverflow(struct dhcpcd_ctx *ctx) { 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; } #ifndef SMALL dhcpcd_setlinkrcvbuf(ctx); #endif eloop_event_add(ctx->eloop, ctx->link_fd, dhcpcd_handlelink, ctx); /* Work out the current interfaces. */ ifaces = if_discover(ctx, &ifaddrs, ctx->ifc, ctx->ifv); if (ifaces == NULL) { logerr(__func__); return; } /* Punt departed interfaces */ TAILQ_FOREACH_SAFE(ifp, ctx->ifaces, next, ifn) { if (if_find(ifaces, ifp->name) != NULL) continue; dhcpcd_handleinterface(ctx, -1, ifp->name); } /* Add new interfaces */ while ((ifp = TAILQ_FIRST(ifaces)) != NULL ) { TAILQ_REMOVE(ifaces, ifp, next); ifp1 = if_find(ctx->ifaces, ifp->name); if (ifp1 != NULL) { /* If the interface already exists, * check carrier state. */ eloop_timeout_add_sec(ctx->eloop, 0, dhcpcd_checkcarrier, ifp1); if_free(ifp); continue; } TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); if (ifp->active) eloop_timeout_add_sec(ctx->eloop, 0, dhcpcd_prestartinterface, ifp); } free(ifaces); /* Update address state. */ if_markaddrsstale(ctx->ifaces); if_learnaddrs(ctx, ctx->ifaces, &ifaddrs); if_deletestaleaddrs(ctx->ifaces); } void dhcpcd_handlehwaddr(struct dhcpcd_ctx *ctx, const char *ifname, 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)) hwlen = 0; if (hwlen > sizeof(ifp->hwaddr)) { errno = ENOBUFS; logerr("%s: %s", __func__, ifp->name); return; } if (ifp->hwlen == hwlen && memcmp(ifp->hwaddr, hwaddr, hwlen) == 0) return; loginfox("%s: new hardware address: %s", ifp->name, hwaddr_ntoa(hwaddr, hwlen, buf, sizeof(buf))); ifp->hwlen = hwlen; memcpy(ifp->hwaddr, hwaddr, hwlen); } static void if_reboot(struct interface *ifp, int argc, char **argv) { #ifdef INET unsigned long long oldopts; oldopts = ifp->options->options; #endif script_runreason(ifp, "RECONFIGURE"); dhcpcd_initstate1(ifp, argc, argv, 0); #ifdef INET dhcp_reboot_newopts(ifp, oldopts); #endif #ifdef DHCP6 dhcp6_reboot(ifp); #endif dhcpcd_prestartinterface(ifp); } static void reload_config(struct dhcpcd_ctx *ctx) { struct if_options *ifo; free_globals(ctx); 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; if (ctx->options & DHCPCD_DAEMONISED) ifo->options |= DHCPCD_DAEMONISED; ctx->options = ifo->options; free_options(ctx, ifo); } static void reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi) { int i; struct interface *ifp; TAILQ_FOREACH(ifp, ctx->ifaces, next) { for (i = oi; i < argc; i++) { if (strcmp(ifp->name, argv[i]) == 0) break; } if (oi != argc && i == argc) continue; if (ifp->active == IF_ACTIVE_USER) { if (action) if_reboot(ifp, argc, argv); #ifdef INET else ipv4_applyaddr(ifp); #endif } else if (i != argc) { ifp->active = IF_ACTIVE_USER; dhcpcd_initstate1(ifp, argc, argv, 0); run_preinit(ifp); dhcpcd_prestartinterface(ifp); } } } static void stop_all_interfaces(struct dhcpcd_ctx *ctx, unsigned long long opts) { struct interface *ifp; ctx->options |= DHCPCD_EXITING; /* 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); } } } static void dhcpcd_ifrenew(struct interface *ifp) { if (!ifp->active) return; if (ifp->options->options & DHCPCD_LINK && ifp->carrier == LINK_DOWN) return; #ifdef INET dhcp_renew(ifp); #endif #ifdef INET6 #define DHCPCD_RARENEW (DHCPCD_IPV6 | DHCPCD_IPV6RS) if ((ifp->options->options & DHCPCD_RARENEW) == DHCPCD_RARENEW) ipv6nd_startrs(ifp); #endif #ifdef DHCP6 dhcp6_renew(ifp); #endif } static void dhcpcd_renew(struct dhcpcd_ctx *ctx) { struct interface *ifp; TAILQ_FOREACH(ifp, ctx->ifaces, next) { dhcpcd_ifrenew(ifp); } } #ifdef USE_SIGNALS #define sigmsg "received %s, %s" static void signal_cb(int sig, void *arg) { struct dhcpcd_ctx *ctx = arg; unsigned long long opts; int exit_code; opts = 0; exit_code = EXIT_FAILURE; switch (sig) { case SIGINT: loginfox(sigmsg, "SIGINT", "stopping"); break; case SIGTERM: loginfox(sigmsg, "SIGTERM", "stopping"); exit_code = EXIT_SUCCESS; break; case SIGALRM: loginfox(sigmsg, "SIGALRM", "releasing"); opts |= DHCPCD_RELEASE; exit_code = EXIT_SUCCESS; break; case SIGHUP: loginfox(sigmsg, "SIGHUP", "rebinding"); reload_config(ctx); /* Preserve any options passed on the commandline * when we were started. */ reconf_reboot(ctx, 1, ctx->argc, ctx->argv, ctx->argc - ctx->ifc); return; case SIGUSR1: loginfox(sigmsg, "SIGUSR1", "renewing"); dhcpcd_renew(ctx); return; case SIGUSR2: loginfox(sigmsg, "SIGUSR2", "reopening log"); logclose(); if (logopen(ctx->logfile) == -1) logerr(__func__); return; case SIGPIPE: logwarnx("received SIGPIPE"); return; default: logerrx("received signal %d but don't know what to do with it", sig); return; } if (!(ctx->options & DHCPCD_TEST)) stop_all_interfaces(ctx, opts); eloop_exit(ctx->eloop, exit_code); } #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; 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 */ if (strcmp(*argv, "--version") == 0) { return control_queue(fd, UNCONST(VERSION), strlen(VERSION) + 1, false); } else if (strcmp(*argv, "--getconfigfile") == 0) { return control_queue(fd, UNCONST(fd->ctx->cffile), strlen(fd->ctx->cffile) + 1, false); } else if (strcmp(*argv, "--getinterfaces") == 0) { eloop_event_add_w(fd->ctx->eloop, fd->fd, dhcpcd_getinterfaces, fd); return 0; } 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++) len += strlen(argv[opt]) + 1; tmp = malloc(len); if (tmp == NULL) return -1; p = tmp; for (opt = 0; opt < argc; opt++) { l = strlen(argv[opt]); strlcpy(p, argv[opt], len); len -= l + 1; p += l; *p++ = ' '; } *--p = '\0'; loginfox("control command: %s", tmp); free(tmp); optind = 0; oi = 0; opts = 0; do_reboot = do_renew = 0; while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1) { switch (opt) { case 'g': /* Assumed if below not set */ break; case 'k': opts |= DHCPCD_RELEASE; break; case 'n': do_reboot = 1; break; case 'p': opts |= DHCPCD_PERSISTENT; break; case 'x': opts |= DHCPCD_EXITING; break; case 'N': do_renew = 1; 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 (!ifp->active) continue; ifp->options->options |= opts; if (opts & DHCPCD_RELEASE) ifp->options->options &= ~DHCPCD_PERSISTENT; stop_interface(ifp); } return 0; } if (do_renew) { if (optind == argc) { dhcpcd_renew(ctx); return 0; } for (oi = optind; oi < argc; oi++) { if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) continue; dhcpcd_ifrenew(ifp); } return 0; } reload_config(ctx); /* XXX: Respect initial commandline options? */ reconf_reboot(ctx, do_reboot, argc, argv, optind - 1); return 0; } int main(int argc, char **argv) { struct dhcpcd_ctx ctx; struct ifaddrs *ifaddrs = NULL; struct if_options *ifo; struct interface *ifp; uint16_t family = 0; int opt, oi = 0, i; unsigned int logopts; time_t t; ssize_t len; #if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK) pid_t pid; #endif #ifdef USE_SIGNALS int sig = 0; const char *siga = NULL; #endif /* Test for --help and --version */ if (argc > 1) { if (strcmp(argv[1], "--help") == 0) { usage(); return EXIT_SUCCESS; } else if (strcmp(argv[1], "--version") == 0) { printf(""PACKAGE" "VERSION"\n%s\n", dhcpcd_copyright); printf("Compiled in features:" #ifdef INET " INET" #endif #ifdef ARP " ARP" #endif #ifdef ARPING " ARPing" #endif #ifdef IPV4LL " IPv4LL" #endif #ifdef INET6 " INET6" #endif #ifdef DHCP6 " DHCPv6" #endif #ifdef AUTH " AUTH" #endif "\n"); return EXIT_SUCCESS; } } memset(&ctx, 0, sizeof(ctx)); ifo = NULL; ctx.cffile = CONFIG; ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1; ctx.pf_inet_fd = -1; TAILQ_INIT(&ctx.control_fds); #ifdef PLUGIN_DEV ctx.dev_fd = -1; #endif #ifdef INET ctx.udp_fd = -1; #endif rt_init(&ctx); 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) { switch (opt) { case '4': family = AF_INET; break; case '6': family = AF_INET6; break; case 'f': ctx.cffile = optarg; break; case 'j': free(ctx.logfile); ctx.logfile = strdup(optarg); break; #ifdef USE_SIGNALS case 'k': sig = SIGALRM; siga = "ALRM"; break; case 'n': sig = SIGHUP; siga = "HUP"; break; case 'g': case 'p': /* Force going via command socket as we're * out of user definable signals. */ i = 4; break; case 'q': logopts |= LOGERR_QUIET; break; case 'x': sig = SIGTERM; siga = "TERM"; break; case 'N': sig = SIGUSR1; siga = "USR1"; break; #endif case 'P': ctx.options |= DHCPCD_PRINT_PIDFILE; logopts &= ~(LOGERR_LOG | LOGERR_ERR); break; case 'T': i = 1; logopts &= ~LOGERR_LOG; break; case 'U': i = 3; break; case 'V': i = 2; break; case '?': if (ctx.options & DHCPCD_PRINT_PIDFILE) continue; usage(); goto exit_failure; } } logsetopts(logopts); logopen(ctx.logfile); ctx.argv = argv; ctx.argc = argc; ctx.ifc = argc - optind; ctx.ifv = argv + optind; 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) goto printpidfile; if (opt == 0) usage(); goto exit_failure; } if (i == 2) { printf("Interface options:\n"); if (optind == argc - 1) { free_options(&ctx, ifo); ifo = read_config(&ctx, argv[optind], NULL, NULL); if (ifo == NULL) goto exit_failure; add_options(&ctx, NULL, ifo, argc, argv); } if_printoptions(); #ifdef INET if (family == 0 || family == AF_INET) { printf("\nDHCPv4 options:\n"); dhcp_printoptions(&ctx, ifo->dhcp_override, ifo->dhcp_override_len); } #endif #ifdef INET6 if (family == 0 || family == AF_INET6) { printf("\nND options:\n"); ipv6nd_printoptions(&ctx, ifo->nd_override, ifo->nd_override_len); #ifdef DHCP6 printf("\nDHCPv6 options:\n"); dhcp6_printoptions(&ctx, ifo->dhcp6_override, ifo->dhcp6_override_len); #endif } #endif goto exit_success; } ctx.options |= ifo->options; if (i == 1 || i == 3) { if (i == 1) ctx.options |= DHCPCD_TEST; else ctx.options |= DHCPCD_DUMPLEASE; ctx.options |= DHCPCD_PERSISTENT; ctx.options &= ~DHCPCD_DAEMONISE; } #ifdef THERE_IS_NO_FORK ctx.options &= ~DHCPCD_DAEMONISE; #endif if (ctx.options & DHCPCD_DEBUG) logsetopts(logopts | LOGERR_DEBUG); if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) { 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)) { const char *per; const char *ifname; ifname = *ctx.ifv; if (ifname == NULL || strlen(ifname) > IF_NAMESIZE) { errno = ifname == NULL ? EINVAL : E2BIG; logerr("%s: ", ifname); goto exit_failure; } /* Allow a dhcpcd interface per address family */ switch(family) { case AF_INET: per = "-4"; break; case AF_INET6: per = "-6"; break; default: per = ""; } snprintf(ctx.pidfile, sizeof(ctx.pidfile), PIDFILE, "-", ifname, per); } else { snprintf(ctx.pidfile, sizeof(ctx.pidfile), PIDFILE, "", "", ""); ctx.options |= DHCPCD_MASTER; } if (ctx.options & DHCPCD_PRINT_PIDFILE) { printf("%s\n", ctx.pidfile); goto exit_success; } } if (chdir("/") == -1) logerr("%s: chdir `/'", __func__); /* Freeing allocated addresses from dumping leases can trigger * eloop removals as well, so init here. */ if ((ctx.eloop = eloop_new()) == NULL) { logerr("%s: eloop_init", __func__); goto exit_failure; } #ifdef USE_SIGNALS /* Save signal mask, block and redirect signals to our handler */ if (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; } 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); } else { if ((ctx.ifaces = malloc(sizeof(*ctx.ifaces))) != NULL) TAILQ_INIT(ctx.ifaces); } if (ctx.ifaces == NULL) { logerr("%s: if_discover", __func__); goto exit_failure; } ifp = if_find(ctx.ifaces, argv[optind]); 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; } } configure_interface(ifp, ctx.argc, ctx.argv, 0); i = 0; if (family == 0 || family == AF_INET) { #ifdef INET if (dhcp_dump(ifp) == -1) i = -1; #else if (family == AF_INET) logerrx("No INET support"); #endif } if (family == 0 || family == AF_INET6) { #ifdef DHCP6 if (dhcp6_dump(ifp) == -1) i = -1; #else if (family == AF_INET6) logerrx("No DHCP6 support"); #endif } if (i == -1) goto exit_failure; goto exit_success; } #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]); if (ctx.control_fd == -1) ctx.control_fd = control_open(NULL); if (ctx.control_fd != -1) { loginfox("sending commands to master dhcpcd process"); len = control_send(&ctx, argc, argv); control_close(&ctx); if (len > 0) { logdebugx("send OK"); goto exit_success; } else { logerr("%s: control_send", __func__); goto exit_failure; } } 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"); goto exit_failure; } unlink(ctx.pidfile); if (sig != SIGHUP && sig != SIGUSR1) 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; } } if (!(ctx.options & DHCPCD_TEST)) { /* Ensure we have the needed directories */ 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); if ((pid = pidfile_lock(ctx.pidfile)) != 0) { if (pid == -1) logerr("%s: pidfile_lock", __func__); else logerrx(PACKAGE " already running on pid %d (%s)", pid, ctx.pidfile); goto exit_failure; } } 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) { logerr("%s: control_start", __func__); goto exit_failure; } #endif logdebugx(PACKAGE "-" VERSION " starting"); ctx.options |= DHCPCD_STARTED; setproctitle("%s%s%s", ctx.options & DHCPCD_MASTER ? "[master]" : argv[optind], ctx.options & DHCPCD_IPV4 ? " [ip4]" : "", ctx.options & DHCPCD_IPV6 ? " [ip6]" : ""); #ifdef BSD /* Disable the kernel RTADV sysctl as early as possible. */ if (ctx.options & DHCPCD_IPV6 && ctx.options & DHCPCD_IPV6RS) if_disable_rtadv(); #endif if (if_opensockets(&ctx) == -1) { logerr("%s: if_opensockets", __func__); goto exit_failure; } #ifndef SMALL 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; /* 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); ctx.ifaces = if_discover(&ctx, &ifaddrs, ctx.ifc, ctx.ifv); if (ctx.ifaces == NULL) { logerr("%s: if_discover", __func__); 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", ctx.ifv[i]); } TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active == IF_ACTIVE_USER) break; } if (ifp == NULL) { if (ctx.ifc == 0) { logfunc_t *logfunc; logfunc = ctx.options & DHCPCD_INACTIVE ? logdebugx : logerrx; logfunc("no valid interfaces found"); } else goto exit_failure; if (!(ctx.options & DHCPCD_LINK)) { logerrx("aborting as link detection is disabled"); goto exit_failure; } } TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) dhcpcd_initstate1(ifp, argc, argv, 0); } if_learnaddrs(&ctx, ctx.ifaces, &ifaddrs); if (ctx.options & DHCPCD_BACKGROUND && dhcpcd_daemonise(&ctx)) goto exit_success; opt = 0; TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) { run_preinit(ifp); if (!(ifp->options->options & DHCPCD_LINK) || ifp->carrier != LINK_DOWN) opt = 1; } } if (!(ctx.options & DHCPCD_BACKGROUND)) { if (ctx.options & DHCPCD_MASTER) t = ifo->timeout; else { t = 0; TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) { t = ifp->options->timeout; break; } } } if (opt == 0 && ctx.options & DHCPCD_LINK && !(ctx.options & DHCPCD_WAITIP)) { logfunc_t *logfunc; logfunc = ctx.options & DHCPCD_INACTIVE ? logdebugx : logwarnx; logfunc("no interfaces have a carrier"); if (dhcpcd_daemonise(&ctx)) goto exit_success; } else if (t > 0 && /* Test mode removes the daemonise bit, so check for both */ ctx.options & (DHCPCD_DAEMONISE | DHCPCD_TEST)) { eloop_timeout_add_sec(ctx.eloop, t, handle_exit_timeout, &ctx); } } free_options(&ctx, ifo); ifo = NULL; TAILQ_FOREACH(ifp, ctx.ifaces, next) { if (ifp->active) eloop_timeout_add_sec(ctx.eloop, 0, dhcpcd_prestartinterface, ifp); } i = eloop_start(ctx.eloop, &ctx.sigset); if (i < 0) { logerr("%s: eloop_start", __func__); goto exit_failure; } goto exit1; exit_success: i = EXIT_SUCCESS; goto exit1; exit_failure: i = EXIT_FAILURE; exit1: if (ifaddrs != NULL) freeifaddrs(ifaddrs); if (control_stop(&ctx) == -1) logerr("%s: control_stop", __func__); /* Free memory and close fd's */ if (ctx.ifaces) { while ((ifp = TAILQ_FIRST(ctx.ifaces))) { TAILQ_REMOVE(ctx.ifaces, ifp, next); if_free(ifp); } free(ctx.ifaces); ctx.ifaces = NULL; } free_options(&ctx, ifo); #ifdef HAVE_OPEN_MEMSTREAM if (ctx.script_fp) fclose(ctx.script_fp); #endif free(ctx.script_buf); free(ctx.script_env); rt_dispose(&ctx); free(ctx.duid); if (ctx.link_fd != -1) { eloop_event_delete(ctx.eloop, ctx.link_fd); close(ctx.link_fd); } if_closesockets(&ctx); free_globals(&ctx); #ifdef INET6 ipv6_ctxfree(&ctx); #endif dev_stop(&ctx); eloop_free(ctx.eloop); if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED)) loginfox(PACKAGE " exited"); logclose(); free(ctx.logfile); #ifdef SETPROCTITLE_H setproctitle_free(); #endif #ifdef USE_SIGNALS if (ctx.options & DHCPCD_FORKED) _exit(i); /* so atexit won't remove our pidfile */ #endif return i; }