Permalink
Cannot retrieve contributors at this time
| /* SPDX-License-Identifier: BSD-2-Clause */ | |
| /* | |
| * dhcpcd - DHCP client daemon | |
| * Copyright (c) 2006-2021 Roy Marples <roy@marples.name> | |
| * All rights reserved | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
| * SUCH DAMAGE. | |
| */ | |
| static const char dhcpcd_copyright[] = "Copyright (c) 2006-2021 Roy Marples"; | |
| #include <sys/file.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/socket.h> | |
| #include <sys/stat.h> | |
| #include <sys/time.h> | |
| #include <sys/types.h> | |
| #include <sys/uio.h> | |
| #include <sys/wait.h> | |
| #include <ctype.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <getopt.h> | |
| #include <limits.h> | |
| #include <paths.h> | |
| #include <signal.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <syslog.h> | |
| #include <unistd.h> | |
| #include <time.h> | |
| #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 "privsep.h" | |
| #include "script.h" | |
| #ifdef HAVE_CAPSICUM | |
| #include <sys/capsicum.h> | |
| #endif | |
| #ifdef HAVE_UTIL_H | |
| #include <util.h> | |
| #endif | |
| #ifdef USE_SIGNALS | |
| const int dhcpcd_signals[] = { | |
| SIGTERM, | |
| SIGINT, | |
| SIGALRM, | |
| SIGHUP, | |
| SIGUSR1, | |
| SIGUSR2, | |
| 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 | |
| const char *dhcpcd_default_script = SCRIPT; | |
| 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_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; | |
| } | |
| 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. */ | |
| void | |
| dhcpcd_daemonise(struct dhcpcd_ctx *ctx) | |
| { | |
| #ifdef THERE_IS_NO_FORK | |
| eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); | |
| errno = ENOSYS; | |
| return; | |
| #else | |
| int i; | |
| unsigned int logopts = loggetopts(); | |
| if (ctx->options & DHCPCD_DAEMONISE && | |
| !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP))) | |
| { | |
| if (!dhcpcd_ipwaited(ctx)) | |
| return; | |
| } | |
| if (ctx->options & DHCPCD_ONESHOT) { | |
| loginfox("exiting due to oneshot"); | |
| eloop_exit(ctx->eloop, EXIT_SUCCESS); | |
| return; | |
| } | |
| eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); | |
| if (ctx->options & DHCPCD_DAEMONISED || | |
| !(ctx->options & DHCPCD_DAEMONISE)) | |
| return; | |
| /* 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 | |
| } | |
| 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, const char *reason) | |
| { | |
| struct dhcpcd_ctx *ctx; | |
| ctx = ifp->ctx; | |
| loginfox("%s: removing interface", ifp->name); | |
| ifp->options->options |= DHCPCD_STOPPING; | |
| dhcpcd_drop(ifp, 1); | |
| script_runreason(ifp, reason == NULL ? "STOPPED" : reason); | |
| /* Delete all timeouts for the interfaces */ | |
| eloop_q_timeout_delete(ctx->eloop, ELOOP_QUEUE_ALL, NULL, ifp); | |
| /* De-activate the interface */ | |
| ifp->active = IF_INACTIVE; | |
| ifp->options->options &= ~DHCPCD_STOPPING; | |
| if (!(ctx->options & (DHCPCD_MANAGER | 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->metric != -1) | |
| ifp->metric = (unsigned int)ifo->metric; | |
| #ifdef INET6 | |
| /* 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 (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)); | |
| } 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: config 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); | |
| } | |
| static void | |
| dhcpcd_nocarrier_roaming(struct interface *ifp) | |
| { | |
| loginfox("%s: carrier lost - roaming", ifp->name); | |
| #ifdef ARP | |
| arp_drop(ifp); | |
| #endif | |
| #ifdef INET | |
| dhcp_abort(ifp); | |
| #endif | |
| #ifdef DHCP6 | |
| dhcp6_abort(ifp); | |
| #endif | |
| 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; | |
| } | |
| 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); | |
| } | |
| #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(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); | |
| #endif | |
| } | |
| } | |
| if (!ifp->active) | |
| return; | |
| dhcpcd_initstate(ifp, 0); | |
| script_runreason(ifp, "CARRIER"); | |
| #ifdef INET6 | |
| /* 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 | |
| #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 (ifn->options->options & DHCPCD_ANONYMOUS) | |
| 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); | |
| } | |
| 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; | |
| 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) && | |
| !(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]; | |
| 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 | |
| /* 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; | |
| 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; | |
| struct dhcpcd_ctx *ctx = ifp->ctx; | |
| bool randmac_down; | |
| 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); | |
| } | |
| static void | |
| run_preinit(struct interface *ifp) | |
| { | |
| if (ifp->ctx->options & DHCPCD_TEST) | |
| 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"); | |
| } | |
| void | |
| dhcpcd_activateinterface(struct interface *ifp, unsigned long long options) | |
| { | |
| 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 = arg; | |
| struct ifaddrs *ifaddrs; | |
| struct if_head *ifs; | |
| struct interface *ifp, *iff; | |
| const char * const argv[] = { ifname }; | |
| int e; | |
| 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); | |
| stop_interface(ifp, "DEPARTED"); | |
| } | |
| 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, unsigned short events) | |
| { | |
| struct dhcpcd_ctx *ctx = arg; | |
| if (events != ELE_READ) | |
| logerrx("%s: unexpected event 0x%04x", __func__, events); | |
| 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 *ifp0 = arg, *ifp; | |
| ifp = if_find(ifp0->ctx->ifaces, ifp0->name); | |
| if (ifp == NULL || ifp->carrier == ifp0->carrier) | |
| return; | |
| dhcpcd_handlecarrier(ifp, ifp0->carrier, ifp0->flags); | |
| if_free(ifp0); | |
| } | |
| #ifndef SMALL | |
| static void | |
| dhcpcd_setlinkrcvbuf(struct dhcpcd_ctx *ctx) | |
| { | |
| socklen_t socklen; | |
| 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) | |
| logerr(__func__); | |
| } | |
| #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; | |
| socklen = sizeof(rcvbuflen); | |
| if (getsockopt(ctx->link_fd, SOL_SOCKET, | |
| SO_RCVBUF, &rcvbuflen, &socklen) == -1) { | |
| logerr("%s: getsockopt", __func__); | |
| rcvbuflen = 0; | |
| } | |
| #ifdef __linux__ | |
| else | |
| rcvbuflen /= 2; | |
| #endif | |
| 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); | |
| 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. | |
| * dhcpcd_checkcarrier will free ifp. */ | |
| eloop_timeout_add_sec(ctx->eloop, 0, | |
| dhcpcd_checkcarrier, ifp); | |
| continue; | |
| } | |
| TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); | |
| if (ifp->active) { | |
| dhcpcd_initstate(ifp, 0); | |
| eloop_timeout_add_sec(ctx->eloop, 0, | |
| dhcpcd_runprestartinterface, ifp); | |
| } | |
| } | |
| free(ifaces); | |
| /* Update address state. */ | |
| if_markaddrsstale(ctx->ifaces); | |
| if_learnaddrs(ctx, ctx->ifaces, &ifaddrs); | |
| if_deletestaleaddrs(ctx->ifaces); | |
| } | |
| void | |
| dhcpcd_handlehwaddr(struct interface *ifp, | |
| uint16_t hwtype, const void *hwaddr, uint8_t hwlen) | |
| { | |
| char buf[sizeof(ifp->hwaddr) * 3]; | |
| if (hwaddr == NULL || !if_valid_hwaddr(hwaddr, hwlen)) | |
| hwlen = 0; | |
| if (hwlen > sizeof(ifp->hwaddr)) { | |
| errno = ENOBUFS; | |
| logerr("%s: %s", __func__, ifp->name); | |
| return; | |
| } | |
| 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; | |
| 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; | |
| if (hwaddr != NULL) | |
| 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 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); | |
| } | |
| 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; | |
| if (ctx->ifaces == NULL) | |
| return; | |
| /* Drop the last interface first */ | |
| TAILQ_FOREACH_REVERSE(ifp, ctx->ifaces, if_head, next) { | |
| 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); | |
| } | |
| } | |
| static void | |
| dhcpcd_ifrenew(struct interface *ifp) | |
| { | |
| if (!ifp->active) | |
| return; | |
| if (ifp->options->options & DHCPCD_LINK && !if_is_link_up(ifp)) | |
| 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 | |
| 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) { | |
| 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"); | |
| #ifdef PRIVSEP | |
| if (IN_PRIVSEP(ctx)) { | |
| if (ps_root_logreopen(ctx) == -1) | |
| logerr("ps_root_logreopen"); | |
| return; | |
| } | |
| #endif | |
| if (logopen(ctx->logfile) == -1) | |
| logerr("logopen"); | |
| return; | |
| case SIGCHLD: | |
| while (waitpid(-1, NULL, WNOHANG) > 0) | |
| ; | |
| 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 | |
| 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, 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); | |
| } else if (strcmp(*argv, "--getconfigfile") == 0) { | |
| return control_queue(fd, UNCONST(fd->ctx->cffile), | |
| strlen(fd->ctx->cffile) + 1); | |
| } else if (strcmp(*argv, "--getinterfaces") == 0) { | |
| optind = argc = 0; | |
| goto dumplease; | |
| } else if (strcmp(*argv, "--listen") == 0) { | |
| fd->flags |= FD_LISTEN; | |
| return 0; | |
| } | |
| /* 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; | |
| case 'U': | |
| opts |= DHCPCD_DUMPLEASE; | |
| break; | |
| case '4': | |
| af = AF_INET; | |
| break; | |
| case '6': | |
| af = AF_INET6; | |
| break; | |
| } | |
| } | |
| if (opts & DHCPCD_DUMPLEASE) { | |
| ctx->options |= DHCPCD_DUMPLEASE; | |
| dumplease: | |
| nifaces = 0; | |
| 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) { | |
| opt = send_interface(NULL, ifp, af); | |
| if (opt == -1) | |
| goto dumperr; | |
| nifaces += (size_t)opt; | |
| } | |
| } | |
| 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) { | |
| 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; | |
| } | |
| static void dhcpcd_readdump1(void *, unsigned short); | |
| static void | |
| dhcpcd_readdump2(void *arg, unsigned short events) | |
| { | |
| struct dhcpcd_ctx *ctx = arg; | |
| ssize_t len; | |
| int exit_code = EXIT_FAILURE; | |
| if (events != ELE_READ) | |
| logerrx("%s: unexpected event 0x%04x", __func__, events); | |
| 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'); | |
| if (eloop_event_add(ctx->eloop, ctx->control_fd, ELE_READ, | |
| dhcpcd_readdump1, ctx) == -1) | |
| logerr("%s: eloop_event_add", __func__); | |
| return; | |
| } | |
| exit_code = EXIT_SUCCESS; | |
| finished: | |
| shutdown(ctx->control_fd, SHUT_RDWR); | |
| eloop_exit(ctx->eloop, exit_code); | |
| } | |
| static void | |
| dhcpcd_readdump1(void *arg, unsigned short events) | |
| { | |
| struct dhcpcd_ctx *ctx = arg; | |
| ssize_t len; | |
| if (events != ELE_READ) | |
| logerrx("%s: unexpected event 0x%04x", __func__, events); | |
| 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; | |
| if (eloop_event_add(ctx->eloop, ctx->control_fd, ELE_READ, | |
| dhcpcd_readdump2, ctx) == -1) | |
| logerr("%s: eloop_event_add", __func__); | |
| return; | |
| err: | |
| logerr(__func__); | |
| eloop_exit(ctx->eloop, EXIT_FAILURE); | |
| } | |
| static void | |
| dhcpcd_readdump0(void *arg, unsigned short events) | |
| { | |
| struct dhcpcd_ctx *ctx = arg; | |
| ssize_t len; | |
| if (events != ELE_READ) | |
| logerrx("%s: unexpected event 0x%04x", __func__, events); | |
| 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; | |
| } | |
| if (eloop_event_add(ctx->eloop, ctx->control_fd, ELE_READ, | |
| dhcpcd_readdump1, ctx) == -1) | |
| logerr("%s: eloop_event_add", __func__); | |
| } | |
| 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, ELE_READ, | |
| dhcpcd_readdump0, ctx); | |
| } | |
| static void | |
| dhcpcd_fork_cb(void *arg, unsigned short events) | |
| { | |
| struct dhcpcd_ctx *ctx = arg; | |
| int exit_code; | |
| ssize_t len; | |
| if (events != ELE_READ) | |
| logerrx("%s: unexpected event 0x%04x", __func__, events); | |
| 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, unsigned short events) | |
| { | |
| struct dhcpcd_ctx *ctx = arg; | |
| char log[BUFSIZ]; | |
| ssize_t len; | |
| if (events != ELE_READ) | |
| logerrx("%s: unexpected event 0x%04x", __func__, events); | |
| 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, char **envp) | |
| { | |
| struct dhcpcd_ctx ctx; | |
| struct ifaddrs *ifaddrs = NULL; | |
| struct if_options *ifo; | |
| struct interface *ifp; | |
| sa_family_t family = AF_UNSPEC; | |
| int opt, oi = 0, i; | |
| 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 */ | |
| 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 | |
| #ifdef PRIVSEP | |
| " PRIVSEP" | |
| #endif | |
| "\n"); | |
| return EXIT_SUCCESS; | |
| } | |
| } | |
| memset(&ctx, 0, sizeof(ctx)); | |
| 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_rfd = -1; | |
| ctx.udp_wfd = -1; | |
| #endif | |
| #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; | |
| 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': | |
| /* -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; | |
| 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; | |
| } | |
| } | |
| if (optind != argc - 1) | |
| ctx.options |= DHCPCD_MANAGER; | |
| logsetopts(logopts); | |
| logopen(ctx.logfile); | |
| ctx.argv = argv; | |
| ctx.argc = argc; | |
| 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) | |
| 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_MANAGER)) { | |
| 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_MANAGER; | |
| } | |
| 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 | |
| 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 */ | |
| eloop_signal_set_cb(ctx.eloop, | |
| dhcpcd_signals, dhcpcd_signals_len, | |
| dhcpcd_signal_cb, &ctx); | |
| if (eloop_signal_mask(ctx.eloop, &ctx.sigset) == -1) { | |
| logerr("%s: eloop_signal_mask", __func__); | |
| goto exit_failure; | |
| } | |
| 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; | |
| } | |
| } | |
| #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) { | |
| logerr(__func__); | |
| goto exit_failure; | |
| } | |
| ifp->ctx = &ctx; | |
| ifp->options = ifo; | |
| switch (family) { | |
| case AF_INET: | |
| #ifdef INET | |
| if (dhcp_dump(ifp) == -1) | |
| goto exit_failure; | |
| break; | |
| #else | |
| logerrx("No DHCP support"); | |
| goto exit_failure; | |
| #endif | |
| case AF_INET6: | |
| #ifdef DHCP6 | |
| if (dhcp6_dump(ifp) == -1) | |
| goto exit_failure; | |
| break; | |
| #else | |
| logerrx("No DHCP6 support"); | |
| goto exit_failure; | |
| #endif | |
| default: | |
| logerrx("Family not specified. Please use -4 or -6."); | |
| goto exit_failure; | |
| } | |
| goto exit_success; | |
| } | |
| #endif | |
| /* 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)) | |
| { | |
| 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, AF_UNSPEC, | |
| ctx.options & DHCPCD_DUMPLEASE); | |
| if (ctx.control_fd != -1) { | |
| #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); | |
| if (len > 0) | |
| logdebugx("send OK"); | |
| 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__); | |
| if (ctx.options & DHCPCD_DUMPLEASE) { | |
| if (errno == ENOENT) | |
| logerrx("dhcpcd is not running"); | |
| goto exit_failure; | |
| } | |
| if (errno == EPERM || errno == EACCES) | |
| 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 ((pid = pidfile_lock(ctx.pidfile)) != 0) { | |
| if (pid == -1) | |
| logerr("%s: pidfile_lock: %s", | |
| __func__, ctx.pidfile); | |
| else | |
| logerrx(PACKAGE | |
| " already running on pid %d (%s)", | |
| pid, ctx.pidfile); | |
| goto exit_failure; | |
| } | |
| } | |
| 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("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 | |
| if (eloop_event_add(ctx.eloop, ctx.fork_fd, ELE_READ, | |
| dhcpcd_fork_cb, &ctx) == -1) | |
| logerr("%s: eloop_event_add", __func__); | |
| /* | |
| * 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: | |
| eloop_forked(ctx.eloop); | |
| 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 | |
| if (eloop_event_add(ctx.eloop, ctx.fork_fd, ELE_READ, | |
| dhcpcd_fork_cb, &ctx) == -1) | |
| logerr("%s: eloop_event_add", __func__); | |
| 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 | |
| if (eloop_event_add(ctx.eloop, ctx.stderr_fd, ELE_READ, | |
| dhcpcd_stderr_cb, &ctx) == -1) | |
| logerr("%s: eloop_event_add", __func__); | |
| } | |
| #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 | |
| setproctitle("%s%s%s", | |
| ctx.options & DHCPCD_MANAGER ? "[manager]" : argv[optind], | |
| ctx.options & DHCPCD_IPV4 ? " [ip4]" : "", | |
| ctx.options & DHCPCD_IPV6 ? " [ip6]" : ""); | |
| if (if_opensockets(&ctx) == -1) { | |
| logerr("%s: if_opensockets", __func__); | |
| goto exit_failure; | |
| } | |
| #ifndef SMALL | |
| dhcpcd_setlinkrcvbuf(&ctx); | |
| #endif | |
| /* 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. */ | |
| if (eloop_event_add(ctx.eloop, ctx.link_fd, ELE_READ, | |
| dhcpcd_handlelink, &ctx) == -1) | |
| logerr("%s: eloop_event_add", __func__); | |
| #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) { | |
| 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) | |
| 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) { | |
| if (ifp->active == IF_ACTIVE_USER) | |
| break; | |
| } | |
| if (ifp == NULL) { | |
| if (ctx.ifc == 0) { | |
| int loglevel; | |
| 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)) { | |
| 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); | |
| opt = 0; | |
| TAILQ_FOREACH(ifp, ctx.ifaces, next) { | |
| if (ifp->active) { | |
| run_preinit(ifp); | |
| if (if_is_link_up(ifp)) | |
| opt = 1; | |
| } | |
| } | |
| if (!(ctx.options & DHCPCD_BACKGROUND)) { | |
| if (ctx.options & DHCPCD_MANAGER) | |
| 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)) | |
| { | |
| int loglevel; | |
| 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)) | |
| { | |
| 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); | |
| } | |
| run_loop: | |
| 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 (!(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); | |
| } | |
| #ifdef PRIVSEP | |
| ps_stop(&ctx); | |
| #endif | |
| /* 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 | |
| #ifdef PLUGIN_DEV | |
| dev_stop(&ctx); | |
| #endif | |
| if (ctx.script != dhcpcd_default_script) | |
| free(ctx.script); | |
| if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED)) | |
| loginfox(PACKAGE " exited"); | |
| #ifdef PRIVSEP | |
| ps_root_stop(&ctx); | |
| eloop_free(ctx.ps_eloop); | |
| #endif | |
| eloop_free(ctx.eloop); | |
| logclose(); | |
| free(ctx.logfile); | |
| free(ctx.ctl_buf); | |
| #ifdef SETPROCTITLE_H | |
| setproctitle_fini(); | |
| #endif | |
| #ifdef USE_SIGNALS | |
| 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 | DHCPCD_PRIVSEP)) | |
| _exit(i); /* so atexit won't remove our pidfile */ | |
| #endif | |
| return i; | |
| } |