diff options
Diffstat (limited to 'dhcpcd.c')
-rw-r--r-- | dhcpcd.c | 1889 |
1 files changed, 1889 insertions, 0 deletions
diff --git a/dhcpcd.c b/dhcpcd.c new file mode 100644 index 0000000..2ffdac1 --- /dev/null +++ b/dhcpcd.c @@ -0,0 +1,1889 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2015 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. + */ + +const char dhcpcd_copyright[] = "Copyright (c) 2006-2015 Roy Marples"; + +#define _WITH_DPRINTF /* Stop FreeBSD bitching */ + +#include <sys/file.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/uio.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 <unistd.h> +#include <time.h> + +#include "config.h" +#include "arp.h" +#include "common.h" +#include "control.h" +#include "dev.h" +#include "dhcpcd.h" +#include "dhcp6.h" +#include "duid.h" +#include "eloop.h" +#include "if.h" +#include "if-options.h" +#include "ipv4.h" +#include "ipv6.h" +#include "ipv6nd.h" +#include "script.h" + +#ifdef USE_SIGNALS +const int dhcpcd_handlesigs[] = { + SIGTERM, + SIGINT, + SIGALRM, + SIGHUP, + SIGUSR1, + SIGUSR2, + SIGPIPE, + 0 +}; + +/* Handling signals needs *some* context */ +static struct dhcpcd_ctx *dhcpcd_ctx; +#endif + +#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK) +static pid_t +read_pid(const char *pidfile) +{ + FILE *fp; + pid_t pid; + + if ((fp = fopen(pidfile, "r")) == NULL) { + errno = ENOENT; + return 0; + } + if (fscanf(fp, "%d", &pid) != 1) + pid = 0; + fclose(fp); + return pid; +} + +static int +write_pid(int fd, pid_t pid) +{ + + if (ftruncate(fd, (off_t)0) == -1) + return -1; + lseek(fd, (off_t)0, SEEK_SET); + return dprintf(fd, "%d\n", (int)pid); +} +#endif + +static void +usage(void) +{ + +printf("usage: "PACKAGE"\t[-46ABbDdEGgHJKkLnpqTVw]\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] [-l, --leasetime seconds]\n" + "\t\t[-m, --metric metric] [-O, --nooption option]\n" + "\t\t[-o, --option option] [-Q, --require option]\n" + "\t\t[-r, --request address] [-S, --static value]\n" + "\t\t[-s, --inform address[/cidr]] [-t, --timeout seconds]\n" + "\t\t[-u, --userclass class] [-v, --vendor code, value]\n" + "\t\t[-W, --whitelist address[/cidr]] [-y, --reboot seconds]\n" + "\t\t[-X, --blacklist address[/cidr]] [-Z, --denyinterfaces pattern]\n" + "\t\t[-z, --allowinterfaces pattern] [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->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 + 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; + logger(ctx, LOG_ERR, "timed out"); + if (!(ctx->options & DHCPCD_MASTER)) { + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + ctx->options |= DHCPCD_NOWAITIP; + dhcpcd_daemonise(ctx); +} + +int +dhcpcd_oneup(struct dhcpcd_ctx *ctx) +{ + const struct interface *ifp; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if (D_STATE_RUNNING(ifp) || + RS_STATE_RUNNING(ifp) || + D6_STATE_RUNNING(ifp)) + return 1; + } + return 0; +} + +int +dhcpcd_ipwaited(struct dhcpcd_ctx *ctx) +{ + + if (ctx->options & DHCPCD_WAITIP4 && + !ipv4_addrexists(ctx, NULL)) + return 0; + if (ctx->options & DHCPCD_WAITIP6 && + !ipv6nd_findaddr(ctx, NULL, 0) && + !dhcp6_findaddr(ctx, NULL, 0)) + return 0; + if (ctx->options & DHCPCD_WAITIP && + !(ctx->options & (DHCPCD_WAITIP4 | DHCPCD_WAITIP6)) && + !ipv4_addrexists(ctx, NULL) && + !ipv6nd_findaddr(ctx, NULL, 0) && + !dhcp6_findaddr(ctx, NULL, 0)) + 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; + char buf = '\0'; + int sidpipe[2], fd; + + if (ctx->options & DHCPCD_DAEMONISE && + !(ctx->options & (DHCPCD_DAEMONISED | DHCPCD_NOWAITIP))) + { + if (!dhcpcd_ipwaited(ctx)) + return 0; + } + + eloop_timeout_delete(ctx->eloop, handle_exit_timeout, ctx); + if (ctx->options & DHCPCD_DAEMONISED || + !(ctx->options & DHCPCD_DAEMONISE)) + return 0; + /* Setup a signal pipe so parent knows when to exit. */ + if (pipe(sidpipe) == -1) { + logger(ctx, LOG_ERR, "pipe: %m"); + return 0; + } + logger(ctx, LOG_DEBUG, "forking to background"); + switch (pid = fork()) { + case -1: + logger(ctx, LOG_ERR, "fork: %m"); + return 0; + case 0: + setsid(); + /* Some polling methods don't survive after forking, + * so ensure we can requeue all our events. */ + if (eloop_requeue(ctx->eloop) == -1) { + logger(ctx, LOG_ERR, "eloop_requeue: %m"); + eloop_exit(ctx->eloop, EXIT_FAILURE); + } + /* Notify parent it's safe to exit as we've detached. */ + close(sidpipe[0]); + if (write(sidpipe[1], &buf, 1) == -1) + logger(ctx, LOG_ERR, "failed to notify parent: %m"); + close(sidpipe[1]); + if ((fd = open(_PATH_DEVNULL, O_RDWR, 0)) != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + break; + default: + /* Wait for child to detach */ + close(sidpipe[1]); + if (read(sidpipe[0], &buf, 1) == -1) + logger(ctx, LOG_ERR, "failed to read child: %m"); + close(sidpipe[0]); + break; + } + /* Done with the fd now */ + if (pid != 0) { + logger(ctx, LOG_INFO, "forked to background, child pid %d", pid); + write_pid(ctx->pid_fd, pid); + close(ctx->pid_fd); + ctx->pid_fd = -1; + ctx->options |= DHCPCD_FORKED; + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return pid; + } + ctx->options |= DHCPCD_DAEMONISED; + return pid; +#endif +} + +static void +dhcpcd_drop(struct interface *ifp, int stop) +{ + + dhcp6_drop(ifp, stop ? NULL : "EXPIRE6"); + ipv6nd_drop(ifp); + ipv6_drop(ifp); + dhcp_drop(ifp, stop ? "STOP" : "EXPIRE"); + arp_close(ifp); +} + +static void +stop_interface(struct interface *ifp) +{ + struct dhcpcd_ctx *ctx; + + ctx = ifp->ctx; + logger(ctx, LOG_INFO, "%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); + + /* Remove the interface from our list */ + TAILQ_REMOVE(ifp->ctx->ifaces, ifp, next); + if_free(ifp); + + 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; + int ra_global, ra_iface; +#ifdef INET6 + size_t i; +#endif + + /* 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 && !(ifo->options & DHCPCD_INFORM)) + ifo->options |= DHCPCD_STATIC; + if (ifp->flags & IFF_NOARP || + ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) + ifo->options &= ~(DHCPCD_ARP | DHCPCD_IPV4LL); + if (ifp->flags & (IFF_POINTOPOINT | IFF_LOOPBACK) || + !(ifp->flags & IFF_MULTICAST)) + ifo->options &= ~DHCPCD_IPV6RS; + + if (ifo->metric != -1) + ifp->metric = (unsigned int)ifo->metric; + + if (!(ifo->options & DHCPCD_IPV4)) + ifo->options &= ~(DHCPCD_DHCP | DHCPCD_IPV4LL); + + if (!(ifo->options & DHCPCD_IPV6)) + ifo->options &= ~(DHCPCD_IPV6RS | DHCPCD_DHCP6); + + if (ifo->options & DHCPCD_SLAACPRIVATE && + !(ifp->ctx->options & (DHCPCD_DUMPLEASE | DHCPCD_TEST))) + ifo->options |= DHCPCD_IPV6RA_OWN; + + /* If we're a psuedo interface, ensure we disable as much as we can */ + if (ifp->options->options & DHCPCD_PFXDLGONLY) + ifp->options->options &= ~(DHCPCD_IPV4 | DHCPCD_IPV6RS); + + /* We want to disable kernel interface RA as early as possible. */ + if (ifo->options & DHCPCD_IPV6RS && + !(ifp->ctx->options & DHCPCD_DUMPLEASE)) + { + /* If not doing any DHCP, disable the RDNSS requirement. */ + if (!(ifo->options & (DHCPCD_DHCP | DHCPCD_DHCP6))) + ifo->options &= ~DHCPCD_IPV6RA_REQRDNSS; + ra_global = if_checkipv6(ifp->ctx, NULL, + ifp->ctx->options & DHCPCD_IPV6RA_OWN ? 1 : 0); + ra_iface = if_checkipv6(ifp->ctx, ifp, + ifp->options->options & DHCPCD_IPV6RA_OWN ? 1 : 0); + if (ra_global == -1 || ra_iface == -1) + ifo->options &= ~DHCPCD_IPV6RS; + else if (ra_iface == 0 && + !(ifp->ctx->options & DHCPCD_TEST)) + ifo->options |= DHCPCD_IPV6RA_OWN; + } + + /* If we haven't specified a ClientID and our hardware address + * length is greater than DHCP_CHADDR_LEN then we enforce a ClientID + * of the hardware address family and the hardware address. + * If there is no hardware address and no ClientID set, + * force a DUID based ClientID. */ + if (ifp->hwlen > DHCP_CHADDR_LEN) + ifo->options |= DHCPCD_CLIENTID; + else if (ifp->hwlen == 0 && !(ifo->options & DHCPCD_CLIENTID)) + ifo->options |= DHCPCD_CLIENTID | DHCPCD_DUID; + + /* Firewire and InfiniBand interfaces require ClientID and + * the broadcast option being set. */ + switch (ifp->family) { + case ARPHRD_IEEE1394: /* FALLTHROUGH */ + case ARPHRD_INFINIBAND: + ifo->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST; + break; + } + + 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. + * + * 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->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(len)); + } + } + ifo->options |= DHCPCD_IAID; + } + +#ifdef INET6 + if (ifo->ia_len == 0 && ifo->options & DHCPCD_IPV6 && + ifp->name[0] != '\0') + { + ifo->ia = malloc(sizeof(*ifo->ia)); + if (ifo->ia == NULL) + logger(ifp->ctx, LOG_ERR, "%s: %m", __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)); + ifo->ia->sla = NULL; + ifo->ia->sla_len = 0; + } + } else { + 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 we are not sending an authentication option, don't require it */ + if (!(ifo->auth.options & DHCPCD_AUTH_SEND)) + ifo->auth.options &= ~DHCPCD_AUTH_REQUIRE; +} + +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), ESCSTRING, + ifp->ssid, ifp->ssid_len); + if (r == -1) { + logger(ifp->ctx, LOG_ERR, + "%s: %s: %m", ifp->name, __func__); + pssid[0] = '\0'; + } + } else + pssid[0] = '\0'; + ifo = read_config(ifp->ctx, ifp->name, pssid, profile); + if (ifo == NULL) { + logger(ifp->ctx, LOG_DEBUG, "%s: no profile %s", + ifp->name, profile); + return -1; + } + if (profile != NULL) { + strlcpy(ifp->profile, profile, sizeof(ifp->profile)); + logger(ifp->ctx, LOG_INFO, "%s: selected profile %s", + ifp->name, profile); + } else + *ifp->profile = '\0'; + + free_options(ifp->options); + ifp->options = ifo; + if (profile) + 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); + 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 (ifp->options && old != 0 && ifp->options->mtime != old) { + logger(ifp->ctx, LOG_WARNING, + "%s: confile file changed, expiring leases", ifp->name); + dhcpcd_drop(ifp, 0); + } +} + +static void +dhcpcd_pollup(void *arg) +{ + struct interface *ifp = arg; + int carrier; + + carrier = if_carrier(ifp); /* will set ifp->flags */ + if (carrier == LINK_UP && !(ifp->flags & IFF_UP)) { + struct timespec tv; + + tv.tv_sec = 0; + tv.tv_nsec = IF_POLL_UP * MSEC_PER_NSEC; + eloop_timeout_add_tv(ifp->ctx->eloop, &tv, dhcpcd_pollup, ifp); + return; + } + + dhcpcd_handlecarrier(ifp->ctx, carrier, ifp->flags, ifp->name); +} + +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->options & DHCPCD_LINK)) + return; + + switch(carrier) { + case LINK_UNKNOWN: + carrier = if_carrier(ifp); /* will set ifp->flags */ + break; + case LINK_UP: + /* we have a carrier! Still need to check for IFF_UP */ + if (flags & IFF_UP) + ifp->flags = flags; + else { + /* So we need to poll for IFF_UP as there is no + * kernel notification when it's set. */ + dhcpcd_pollup(ifp); + return; + } + break; + default: + ifp->flags = flags; + } + + /* If we here, we don't need to poll for IFF_UP any longer + * if generated by a kernel event. */ + eloop_timeout_delete(ifp->ctx->eloop, dhcpcd_pollup, ifp); + + if (carrier == LINK_UNKNOWN) { + if (errno != ENOTTY) /* For example a PPP link on BSD */ + logger(ctx, LOG_ERR, "%s: carrier_status: %m", ifname); + } else if (carrier == LINK_DOWN || (ifp->flags & IFF_UP) == 0) { + if (ifp->carrier != LINK_DOWN) { + if (ifp->carrier == LINK_UP) + logger(ctx, LOG_INFO, "%s: carrier lost", + ifp->name); + ifp->carrier = LINK_DOWN; + script_runreason(ifp, "NOCARRIER"); +#ifdef NOCARRIER_PRESERVE_IP + arp_close(ifp); + ipv4_buildroutes(ifp->ctx); + ipv6nd_expire(ifp, 0); +#else + dhcpcd_drop(ifp, 0); +#endif + } + } else if (carrier == LINK_UP && ifp->flags & IFF_UP) { + if (ifp->carrier != LINK_UP) { + logger(ctx, LOG_INFO, "%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_SSIDSIZE]; +#ifdef NOCARRIER_PRESERVE_IP + size_t olen; + + olen = ifp->ssid_len; +#endif + memcpy(ossid, ifp->ssid, ifp->ssid_len); + if_getssid(ifp); +#ifdef NOCARRIER_PRESERVE_IP + /* If we changed SSID network, drop leases */ + if (ifp->ssid_len != olen || + memcmp(ifp->ssid, ossid, ifp->ssid_len)) + dhcpcd_drop(ifp, 0); +#endif + } + dhcpcd_initstate(ifp, 0); + script_runreason(ifp, "CARRIER"); +#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_expire(ifp, RTR_CARRIER_EXPIRE); +#endif + /* RFC4941 Section 3.5 */ + if (ifp->options->options & DHCPCD_IPV6RA_OWN) + ipv6_gentempifid(ifp); + dhcpcd_startinterface(ifp); + } + } +} + +static void +warn_iaid_conflict(struct interface *ifp, uint8_t *iaid) +{ + struct interface *ifn; + size_t i; + + TAILQ_FOREACH(ifn, ifp->ctx->ifaces, next) { + if (ifn == ifp) + continue; + if (ifn->options->options & DHCPCD_PFXDLGONLY) + continue; + if (memcmp(ifn->options->iaid, iaid, + sizeof(ifn->options->iaid)) == 0) + break; + for (i = 0; i < ifn->options->ia_len; i++) { + if (memcmp(&ifn->options->ia[i].iaid, iaid, + sizeof(ifn->options->ia[i].iaid)) == 0) + break; + } + } + + /* This is only a problem if the interfaces are on the same network. */ + if (ifn && strcmp(ifp->name, ifn->name)) + logger(ifp->ctx, LOG_ERR, + "%s: IAID conflicts with one assigned to %s", + ifp->name, ifn->name); +} + +static void +pre_start(struct interface *ifp) +{ + + /* Add our link-local address before upping the interface + * so our RFC7217 address beats the hwaddr based one. + * This is also a safety check incase it was ripped out + * from under us. */ + if (ifp->options->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) { + logger(ifp->ctx, LOG_ERR, "%s: ipv6_start: %m", ifp->name); + ifp->options->options &= ~DHCPCD_IPV6; + } +} + +void +dhcpcd_startinterface(void *arg) +{ + struct interface *ifp = arg; + struct if_options *ifo = ifp->options; + size_t i; + char buf[DUID_LEN * 3]; + int carrier; + struct timespec tv; + + if (ifo->options & DHCPCD_LINK) { + switch (ifp->carrier) { + case LINK_UP: + break; + case LINK_DOWN: + logger(ifp->ctx, LOG_INFO, "%s: waiting for carrier", + ifp->name); + return; + case LINK_UNKNOWN: + /* No media state available. + * Loop until both IFF_UP and IFF_RUNNING are set */ + if ((carrier = if_carrier(ifp)) == LINK_UNKNOWN) { + tv.tv_sec = 0; + tv.tv_nsec = IF_POLL_UP * MSEC_PER_NSEC; + eloop_timeout_add_tv(ifp->ctx->eloop, + &tv, dhcpcd_startinterface, ifp); + } else + 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; + if (!(ifo->options & DHCPCD_PFXDLGONLY)) + logger(ifp->ctx, LOG_INFO, "DUID %s", + hwaddr_ntoa(ifp->ctx->duid, + ifp->ctx->duid_len, + buf, sizeof(buf))); + } + } + + if (ifo->options & (DHCPCD_DUID | DHCPCD_IPV6) && + !(ifo->options & DHCPCD_PFXDLGONLY)) + { + /* Report IAIDs */ + logger(ifp->ctx, LOG_INFO, "%s: IAID %s", ifp->name, + hwaddr_ntoa(ifo->iaid, sizeof(ifo->iaid), + buf, sizeof(buf))); + warn_iaid_conflict(ifp, ifo->iaid); + for (i = 0; i < ifo->ia_len; i++) { + if (memcmp(ifo->iaid, ifo->ia[i].iaid, + sizeof(ifo->iaid))) + { + logger(ifp->ctx, LOG_INFO, "%s: IAID %s", + ifp->name, hwaddr_ntoa(ifo->ia[i].iaid, + sizeof(ifo->ia[i].iaid), + buf, sizeof(buf))); + warn_iaid_conflict(ifp, ifo->ia[i].iaid); + } + } + } + + if (ifo->options & DHCPCD_IPV6) { + if (ifo->options & DHCPCD_IPV6RS && + !(ifo->options & DHCPCD_INFORM)) + ipv6nd_startrs(ifp); + + if (ifo->options & DHCPCD_DHCP6) + dhcp6_find_delegates(ifp); + + if (!(ifo->options & DHCPCD_IPV6RS) || + ifo->options & DHCPCD_IA_FORCED) + { + ssize_t nolease; + + if (ifo->options & DHCPCD_IA_FORCED) + nolease = dhcp6_start(ifp, DH6S_INIT); + else { + nolease = 0; + /* Enabling the below doesn't really make + * sense as there is currently no standard + * to push routes via DHCPv6. + * (There is an expired working draft, + * maybe abandoned?) + * You can also get it to work by forcing + * an IA as shown above. */ +#if 0 + /* With no RS or delegates we might + * as well try and solicit a DHCPv6 address */ + if (nolease == 0) + nolease = dhcp6_start(ifp, DH6S_INIT); +#endif + } + if (nolease == -1) + logger(ifp->ctx, LOG_ERR, + "%s: dhcp6_start: %m", ifp->name); + } + } + + if (ifo->options & DHCPCD_IPV4) + dhcp_start(ifp); +} + +static void +dhcpcd_prestartinterface(void *arg) +{ + struct interface *ifp = arg; + + pre_start(ifp); + if (if_up(ifp) == -1) + logger(ifp->ctx, LOG_ERR, "%s: if_up: %m", ifp->name); + + if (ifp->options->options & DHCPCD_LINK && + ifp->carrier == LINK_UNKNOWN) + { + int carrier; + + if ((carrier = if_carrier(ifp)) != LINK_UNKNOWN) { + dhcpcd_handlecarrier(ifp->ctx, carrier, + ifp->flags, ifp->name); + return; + } + logger(ifp->ctx, LOG_INFO, + "%s: unknown carrier, waiting for interface flags", + ifp->name); + } + + dhcpcd_startinterface(ifp); +} + +static void +handle_link(void *arg) +{ + struct dhcpcd_ctx *ctx; + + ctx = arg; + if (if_managelink(ctx) == -1) { + logger(ctx, LOG_ERR, "if_managelink: %m"); + eloop_event_delete(ctx->eloop, ctx->link_fd, 0); + close(ctx->link_fd); + ctx->link_fd = -1; + } +} + +static void +dhcpcd_initstate1(struct interface *ifp, int argc, char **argv, + unsigned long long options) +{ + struct if_options *ifo; + + configure_interface(ifp, argc, argv, options); + ifo = ifp->options; + + if (ifo->options & DHCPCD_IPV4 && ipv4_init(ifp->ctx) == -1) { + logger(ifp->ctx, LOG_ERR, "ipv4_init: %m"); + ifo->options &= ~DHCPCD_IPV4; + } + if (ifo->options & DHCPCD_IPV6 && ipv6_init(ifp->ctx) == NULL) { + logger(ifp->ctx, LOG_ERR, "ipv6_init: %m"); + ifo->options &= ~DHCPCD_IPV6RS; + } + + /* Add our link-local address before upping the interface + * so our RFC7217 address beats the hwaddr based one. + * This needs to happen before PREINIT incase a hook script + * inadvertently ups the interface. */ + if (ifo->options & DHCPCD_IPV6 && ipv6_start(ifp) == -1) { + logger(ifp->ctx, LOG_ERR, "%s: ipv6_start: %m", ifp->name); + ifo->options &= ~DHCPCD_IPV6; + } +} + +void +dhcpcd_initstate(struct interface *ifp, unsigned long long options) +{ + + dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options); +} + +static void +run_preinit(struct interface *ifp) +{ + + pre_start(ifp); + if (ifp->ctx->options & DHCPCD_TEST) + return; + + script_runreason(ifp, "PREINIT"); + + if (ifp->options->options & DHCPCD_LINK && ifp->carrier != LINK_UNKNOWN) + script_runreason(ifp, + ifp->carrier == LINK_UP ? "CARRIER" : "NOCARRIER"); +} + +int +dhcpcd_handleinterface(void *arg, int action, const char *ifname) +{ + struct dhcpcd_ctx *ctx; + struct if_head *ifs; + struct interface *ifp, *iff, *ifn; + const char * const argv[] = { ifname }; + int i; + + ctx = arg; + if (action == -1) { + ifp = if_find(ctx->ifaces, ifname); + if (ifp == NULL) { + errno = ESRCH; + return -1; + } + logger(ctx, LOG_DEBUG, "%s: interface departed", ifp->name); + ifp->options->options |= DHCPCD_DEPARTED; + stop_interface(ifp); + return 0; + } + + /* If running off an interface list, check it's in it. */ + if (ctx->ifc && action != 2) { + for (i = 0; i < ctx->ifc; i++) + if (strcmp(ctx->ifv[i], ifname) == 0) + break; + if (i >= ctx->ifc) + return 0; + } + + i = -1; + ifs = if_discover(ctx, -1, UNCONST(argv)); + if (ifs == NULL) { + logger(ctx, LOG_ERR, "%s: if_discover: %m", __func__); + return -1; + } + TAILQ_FOREACH_SAFE(ifp, ifs, next, ifn) { + if (strcmp(ifp->name, ifname) != 0) + continue; + i = 0; + /* Check if we already have the interface */ + iff = if_find(ctx->ifaces, ifp->name); + if (iff) { + logger(ctx, LOG_DEBUG, "%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 { + logger(ctx, LOG_DEBUG, "%s: interface added", ifp->name); + TAILQ_REMOVE(ifs, ifp, next); + TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); + dhcpcd_initstate(ifp, 0); + run_preinit(ifp); + iff = ifp; + } + if (action > 0) + dhcpcd_prestartinterface(iff); + } + + /* Free our discovered list */ + while ((ifp = TAILQ_FIRST(ifs))) { + TAILQ_REMOVE(ifs, ifp, next); + if_free(ifp); + } + free(ifs); + + if (i == -1) + errno = ENOENT; + return i; +} + +void +dhcpcd_handlehwaddr(struct dhcpcd_ctx *ctx, const char *ifname, + const uint8_t *hwaddr, uint8_t hwlen) +{ + struct interface *ifp; + char buf[sizeof(ifp->hwaddr) * 3]; + + ifp = if_find(ctx->ifaces, ifname); + if (ifp == NULL) + return; + + if (hwlen > sizeof(ifp->hwaddr)) { + errno = ENOBUFS; + logger(ctx, LOG_ERR, "%s: %s: %m", ifp->name, __func__); + return; + } + + if (ifp->hwlen == hwlen && memcmp(ifp->hwaddr, hwaddr, hwlen) == 0) + return; + + logger(ctx, LOG_INFO, "%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) +{ + unsigned long long oldopts; + + oldopts = ifp->options->options; + script_runreason(ifp, "RECONFIGURE"); + dhcpcd_initstate1(ifp, argc, argv, 0); + dhcp_reboot_newopts(ifp, oldopts); + dhcp6_reboot(ifp); + dhcpcd_prestartinterface(ifp); +} + +static void +reload_config(struct dhcpcd_ctx *ctx) +{ + struct if_options *ifo; + + free_globals(ctx); + ifo = read_config(ctx, NULL, NULL, NULL); + 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(ifo); +} + +static void +reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi) +{ + struct if_head *ifs; + struct interface *ifn, *ifp; + + ifs = if_discover(ctx, argc - oi, argv + oi); + if (ifs == NULL) { + logger(ctx, LOG_ERR, "%s: if_discover: %m", __func__); + return; + } + + while ((ifp = TAILQ_FIRST(ifs))) { + TAILQ_REMOVE(ifs, ifp, next); + ifn = if_find(ctx->ifaces, ifp->name); + if (ifn) { + if (action) + if_reboot(ifn, argc, argv); + else + ipv4_applyaddr(ifn); + if_free(ifp); + } else { + TAILQ_INSERT_TAIL(ctx->ifaces, ifp, next); + dhcpcd_initstate1(ifp, argc, argv, 0); + run_preinit(ifp); + dhcpcd_prestartinterface(ifp); + } + } + free(ifs); +} + +static void +stop_all_interfaces(struct dhcpcd_ctx *ctx, int do_release) +{ + struct interface *ifp; + + /* drop_dhcp could change the order, so we do it like this. */ + for (;;) { + /* Be sane and drop the last config first, + * skipping any pseudo interfaces */ + TAILQ_FOREACH_REVERSE(ifp, ctx->ifaces, if_head, next) { + if (!(ifp->options->options & DHCPCD_PFXDLGONLY)) + break; + } + if (ifp == NULL) + break; + if (do_release) { + ifp->options->options |= DHCPCD_RELEASE; + ifp->options->options &= ~DHCPCD_PERSISTENT; + } + ifp->options->options |= DHCPCD_EXITING; + stop_interface(ifp); + } +} + +#ifdef USE_SIGNALS +struct dhcpcd_siginfo dhcpcd_siginfo; +#define sigmsg "received %s, %s" +void +dhcpcd_handle_signal(void *arg) +{ + struct dhcpcd_ctx *ctx; + struct dhcpcd_siginfo *si; + struct interface *ifp; + int do_release, exit_code;; + + ctx = dhcpcd_ctx; + si = arg; + do_release = 0; + exit_code = EXIT_FAILURE; + switch (si->signo) { + case SIGINT: + logger(ctx, LOG_INFO, sigmsg, "SIGINT", "stopping"); + break; + case SIGTERM: + logger(ctx, LOG_INFO, sigmsg, "SIGTERM", "stopping"); + exit_code = EXIT_SUCCESS; + break; + case SIGALRM: + logger(ctx, LOG_INFO, sigmsg, "SIGALRM", "releasing"); + do_release = 1; + exit_code = EXIT_SUCCESS; + break; + case SIGHUP: + logger(ctx, LOG_INFO, 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: + logger(ctx, LOG_INFO, sigmsg, "SIGUSR1", "reconfiguring"); + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + ipv4_applyaddr(ifp); + } + return; + case SIGUSR2: + logger_close(ctx); + logger_open(ctx); + logger(ctx, LOG_INFO, sigmsg, "SIGUSR2", "reopened logfile"); + return; + case SIGPIPE: + logger(ctx, LOG_WARNING, "received SIGPIPE"); + return; + default: + logger(ctx, LOG_ERR, + "received signal %d, " + "but don't know what to do with it", + si->signo); + return; + } + + if (!(ctx->options & DHCPCD_TEST)) + stop_all_interfaces(ctx, do_release); + eloop_exit(ctx->eloop, exit_code); +} + +#ifndef HAVE_KQUEUE +static void +handle_signal(int sig, __unused siginfo_t *siginfo, __unused void *context) +{ + + /* So that we can operate safely under a signal we instruct + * eloop to pass a copy of the siginfo structure to handle_signal1 + * as the very first thing to do. */ + dhcpcd_siginfo.signo = sig; + eloop_timeout_add_now(dhcpcd_ctx->eloop, + dhcpcd_handle_signal, &dhcpcd_siginfo); +} +#endif + +static int +signal_init(sigset_t *oldset) +{ + sigset_t newset; +#ifndef HAVE_KQUEUE + int i; + struct sigaction sa; +#endif + + sigfillset(&newset); + if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1) + return -1; + +#ifndef HAVE_KQUEUE + memset(&sa, 0, sizeof(sa)); + sa.sa_sigaction = handle_signal; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + + for (i = 0; dhcpcd_handlesigs[i]; i++) { + if (sigaction(dhcpcd_handlesigs[i], &sa, NULL) == -1) + return -1; + } +#endif + return 0; +} +#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) { + len++; + if (D_STATE_RUNNING(ifp)) + len++; + if (RS_STATE_RUNNING(ifp)) + len++; + if (D6_STATE_RUNNING(ifp)) + len++; + } + if (write(fd->fd, &len, sizeof(len)) != sizeof(len)) + return; + eloop_event_delete(fd->ctx->eloop, fd->fd, 1); + TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) { + if (send_interface(fd, ifp) == -1) + logger(ifp->ctx, LOG_ERR, + "send_interface %d: %m", fd->fd); + } +} + +int +dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, + int argc, char **argv) +{ + struct interface *ifp; + int do_exit = 0, do_release = 0, do_reboot = 0; + int opt, oi = 0; + 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, 0); + } else if (strcmp(*argv, "--getconfigfile") == 0) { + return control_queue(fd, UNCONST(fd->ctx->cffile), + strlen(fd->ctx->cffile) + 1, 0); + } else if (strcmp(*argv, "--getinterfaces") == 0) { + eloop_event_add(fd->ctx->eloop, fd->fd, NULL, NULL, + 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'; + logger(ctx, LOG_INFO, "control command: %s", tmp); + free(tmp); + + optind = 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': + do_release = 1; + break; + case 'n': + do_reboot = 1; + break; + case 'x': + do_exit = 1; + break; + } + } + + if (do_release || do_exit) { + if (optind == argc) { + stop_all_interfaces(ctx, do_release); + 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 (do_release) { + ifp->options->options |= DHCPCD_RELEASE; + ifp->options->options &= ~DHCPCD_PERSISTENT; + } + ifp->options->options |= DHCPCD_EXITING; + stop_interface(ifp); + } + return 0; + } + + reload_config(ctx); + /* XXX: Respect initial commandline options? */ + reconf_reboot(ctx, do_reboot, argc, argv, optind); + return 0; +} + +int +main(int argc, char **argv) +{ + struct dhcpcd_ctx ctx; + struct if_options *ifo; + struct interface *ifp; + uint16_t family = 0; + int opt, oi = 0, i; + time_t t; + ssize_t len; +#if defined(USE_SIGNALS) || !defined(THERE_IS_NO_FORK) + pid_t pid; +#endif +#ifdef USE_SIGNALS + int sig; + const char *siga; +#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); + return EXIT_SUCCESS; + } + } + + memset(&ctx, 0, sizeof(ctx)); +#ifdef USE_SIGNALS + dhcpcd_ctx = &ctx; + sig = 0; + siga = NULL; +#endif + closefrom(3); + + ctx.log_fd = -1; + logger_open(&ctx); + logger_mask(&ctx, LOG_UPTO(LOG_INFO)); + + ifo = NULL; + ctx.cffile = CONFIG; + ctx.pid_fd = ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1; + TAILQ_INIT(&ctx.control_fds); +#ifdef PLUGIN_DEV + ctx.dev_fd = -1; +#endif +#ifdef INET + ctx.udp_fd = -1; +#endif + i = 0; + while ((opt = getopt_long(argc, argv, 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; +#ifdef USE_SIGNALS + case 'g': + sig = SIGUSR1; + siga = "USR1"; + break; + case 'j': + ctx.logfile = strdup(optarg); + logger_close(&ctx); + logger_open(&ctx); + break; + case 'k': + sig = SIGALRM; + siga = "ARLM"; + break; + case 'n': + sig = SIGHUP; + siga = "HUP"; + break; + case 'x': + sig = SIGTERM; + siga = "TERM";; + break; +#endif + case 'T': + i = 1; + break; + case 'U': + if (i == 3) + i = 4; + else if (i != 4) + i = 3; + break; + case 'V': + i = 2; + break; + case '?': + usage(); + goto exit_failure; + } + } + + ctx.argv = argv; + ctx.argc = argc; + ctx.ifc = argc - optind; + ctx.ifv = argv + optind; + + ifo = read_config(&ctx, NULL, NULL, NULL); + if (ifo == NULL) + goto exit_failure; + opt = add_options(&ctx, NULL, ifo, argc, argv); + if (opt != 1) { + if (opt == 0) + usage(); + goto exit_failure; + } + if (i == 2) { + printf("Interface options:\n"); + if (optind == argc - 1) { + free_options(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("\nDHCPv6 options:\n"); + dhcp6_printoptions(&ctx, + ifo->dhcp6_override, ifo->dhcp6_override_len); + } +#endif + goto exit_success; + } + ctx.options = ifo->options; + if (i != 0) { + if (i == 1) + ctx.options |= DHCPCD_TEST; + else + ctx.options |= DHCPCD_DUMPLEASE; + if (i == 4) + ctx.options |= DHCPCD_PFXDLGONLY; + ctx.options |= DHCPCD_PERSISTENT; + ctx.options &= ~DHCPCD_DAEMONISE; + } + +#ifdef THERE_IS_NO_FORK + ctx.options &= ~DHCPCD_DAEMONISE; +#endif + + if (ctx.options & DHCPCD_DEBUG) + logger_mask(&ctx, LOG_UPTO(LOG_DEBUG)); + if (ctx.options & DHCPCD_QUIET) { + i = open(_PATH_DEVNULL, O_RDWR); + if (i == -1) + logger(&ctx, LOG_ERR, "%s: open: %m", __func__); + else { + dup2(i, STDERR_FILENO); + close(i); + } + } + + if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) { + /* 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; + + if (strlen(argv[optind]) > IF_NAMESIZE) { + logger(&ctx, LOG_ERR, + "%s: interface name too long", + argv[optind]); + 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, "-", argv[optind], per); + } else { + snprintf(ctx.pidfile, sizeof(ctx.pidfile), + PIDFILE, "", "", ""); + ctx.options |= DHCPCD_MASTER; + } + } + + if (chdir("/") == -1) + logger(&ctx, LOG_ERR, "chdir `/': %m"); + + /* Freeing allocated addresses from dumping leases can trigger + * eloop removals as well, so init here. */ + ctx.eloop = eloop_init(&ctx); + if (ctx.eloop == NULL) { + logger(&ctx, LOG_ERR, "%s: eloop_init: %m", __func__); + goto exit_failure; + } + + if (ctx.options & DHCPCD_DUMPLEASE) { + if (optind != argc - 1) { + logger(&ctx, LOG_ERR, + "dumplease requires an interface"); + goto exit_failure; + } + i = 0; + /* We need to try and find the interface so we can + * load the hardware address to compare automated IAID */ + ctx.ifaces = if_discover(&ctx, 1, argv + optind); + if (ctx.ifaces == NULL) { + logger(&ctx, LOG_ERR, "if_discover: %m"); + goto exit_failure; + } + ifp = TAILQ_FIRST(ctx.ifaces); + if (ifp == NULL) { + ifp = calloc(1, sizeof(*ifp)); + if (ifp == NULL) { + logger(&ctx, LOG_ERR, "%s: %m", __func__); + goto exit_failure; + } + strlcpy(ctx.pidfile, argv[optind], sizeof(ctx.pidfile)); + ifp->ctx = &ctx; + TAILQ_INSERT_HEAD(ctx.ifaces, ifp, next); + if (family == 0) { + if (ctx.pidfile[strlen(ctx.pidfile) - 1] == '6') + family = AF_INET6; + else + family = AF_INET; + } + } + configure_interface(ifp, ctx.argc, ctx.argv, 0); + if (ctx.options & DHCPCD_PFXDLGONLY) + ifp->options->options |= DHCPCD_PFXDLGONLY; + if (family == 0 || family == AF_INET) { + if (dhcp_dump(ifp) == -1) + i = 1; + } + if (family == 0 || family == AF_INET6) { + if (dhcp6_dump(ifp) == -1) + i = 1; + } + if (i == -1) + goto exit_failure; + goto exit_success; + } + +#ifdef USE_SIGNALS + if (!(ctx.options & DHCPCD_TEST) && + (sig == 0 || ctx.ifc != 0)) + { +#endif + if (ctx.options & DHCPCD_MASTER) + i = -1; + else + i = control_open(&ctx, argv[optind]); + if (i == -1) + i = control_open(&ctx, NULL); + if (i != -1) { + logger(&ctx, LOG_INFO, + "sending commands to master dhcpcd process"); + len = control_send(&ctx, argc, argv); + control_close(&ctx); + if (len > 0) { + logger(&ctx, LOG_DEBUG, "send OK"); + goto exit_success; + } else { + logger(&ctx, LOG_ERR, + "failed to send commands"); + goto exit_failure; + } + } else { + if (errno != ENOENT) + logger(&ctx, LOG_ERR, "control_open: %m"); + } +#ifdef USE_SIGNALS + } +#endif + + if (geteuid()) + logger(&ctx, LOG_WARNING, + PACKAGE " will not work correctly unless run as root"); + +#ifdef USE_SIGNALS + if (sig != 0) { + pid = read_pid(ctx.pidfile); + if (pid != 0) + logger(&ctx, LOG_INFO, "sending signal %s to pid %d", + siga, pid); + if (pid == 0 || kill(pid, sig) != 0) { + if (sig != SIGHUP && errno != EPERM) + logger(&ctx, LOG_ERR, ""PACKAGE" not running"); + if (pid != 0 && errno != ESRCH) { + logger(&ctx, LOG_ERR, "kill: %m"); + goto exit_failure; + } + unlink(ctx.pidfile); + if (sig != SIGHUP) + goto exit_failure; + } else { + struct timespec ts; + + if (sig == SIGHUP || sig == SIGUSR1) + goto exit_success; + /* Spin until it exits */ + logger(&ctx, LOG_INFO, + "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 (read_pid(ctx.pidfile) == 0) + goto exit_success; + } + logger(&ctx, LOG_ERR, "pid %d failed to exit", pid); + goto exit_failure; + } + } + + if (!(ctx.options & DHCPCD_TEST)) { + if ((pid = read_pid(ctx.pidfile)) > 0 && + kill(pid, 0) == 0) + { + logger(&ctx, LOG_ERR, ""PACKAGE + " already running on pid %d (%s)", + pid, ctx.pidfile); + goto exit_failure; + } + + /* Ensure we have the needed directories */ + if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) + logger(&ctx, LOG_ERR, "mkdir `%s': %m", RUNDIR); + if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST) + logger(&ctx, LOG_ERR, "mkdir `%s': %m", DBDIR); + + opt = O_WRONLY | O_CREAT | O_NONBLOCK; +#ifdef O_CLOEXEC + opt |= O_CLOEXEC; +#endif + ctx.pid_fd = open(ctx.pidfile, opt, 0664); + if (ctx.pid_fd == -1) + logger(&ctx, LOG_ERR, "open `%s': %m", ctx.pidfile); + else { +#ifdef LOCK_EX + /* Lock the file so that only one instance of dhcpcd + * runs on an interface */ + if (flock(ctx.pid_fd, LOCK_EX | LOCK_NB) == -1) { + logger(&ctx, LOG_ERR, "flock `%s': %m", ctx.pidfile); + close(ctx.pid_fd); + ctx.pid_fd = -1; + goto exit_failure; + } +#endif +#ifndef O_CLOEXEC + if (fcntl(ctx.pid_fd, F_GETFD, &opt) == -1 || + fcntl(ctx.pid_fd, F_SETFD, opt | FD_CLOEXEC) == -1) + { + logger(&ctx, LOG_ERR, "fcntl: %m"); + close(ctx.pid_fd); + ctx.pid_fd = -1; + goto exit_failure; + } +#endif + write_pid(ctx.pid_fd, getpid()); + } + } + + if (ctx.options & DHCPCD_MASTER) { + if (control_start(&ctx, NULL) == -1) + logger(&ctx, LOG_ERR, "control_start: %m"); + } +#else + if (control_start(&ctx, + ctx.options & DHCPCD_MASTER ? NULL : argv[optind]) == -1) + { + logger(&ctx, LOG_ERR, "control_start: %m"); + goto exit_failure; + } +#endif + + logger(&ctx, LOG_DEBUG, PACKAGE "-" VERSION " starting"); + ctx.options |= DHCPCD_STARTED; +#ifdef USE_SIGNALS + /* Save signal mask, block and redirect signals to our handler */ + if (signal_init(&ctx.sigset) == -1) { + logger(&ctx, LOG_ERR, "signal_setup: %m"); + 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; + + /* RTM_NEWADDR goes through the link socket as well which we + * need for IPv6 DAD, so we check for DHCPCD_LINK in + * dhcpcd_handlecarrier instead. + * We also need to open this before checking for interfaces below + * so that we pickup any new addresses during the discover phase. */ + ctx.link_fd = if_openlinksocket(); + if (ctx.link_fd == -1) + logger(&ctx, LOG_ERR, "open_link_socket: %m"); + else + eloop_event_add(ctx.eloop, ctx.link_fd, + handle_link, &ctx, NULL, NULL); + + /* 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, ctx.ifc, ctx.ifv); + if (ctx.ifaces == NULL) { + logger(&ctx, LOG_ERR, "if_discover: %m"); + goto exit_failure; + } + for (i = 0; i < ctx.ifc; i++) { + if (if_find(ctx.ifaces, ctx.ifv[i]) == NULL) + logger(&ctx, LOG_ERR, + "%s: interface not found or invalid", + ctx.ifv[i]); + } + if (TAILQ_FIRST(ctx.ifaces) == NULL) { + if (ctx.ifc == 0) + logger(&ctx, LOG_ERR, "no valid interfaces found"); + else + goto exit_failure; + if (!(ctx.options & DHCPCD_LINK)) { + logger(&ctx, LOG_ERR, + "aborting as link detection is disabled"); + goto exit_failure; + } + } + + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + dhcpcd_initstate1(ifp, argc, argv, 0); + } + + if (ctx.options & DHCPCD_BACKGROUND && dhcpcd_daemonise(&ctx)) + goto exit_success; + + opt = 0; + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + run_preinit(ifp); + if (ifp->carrier != LINK_DOWN) + opt = 1; + } + + if (!(ctx.options & DHCPCD_BACKGROUND)) { + if (ctx.options & DHCPCD_MASTER) + t = ifo->timeout; + else if ((ifp = TAILQ_FIRST(ctx.ifaces))) + t = ifp->options->timeout; + else + t = 0; + if (opt == 0 && + ctx.options & DHCPCD_LINK && + !(ctx.options & DHCPCD_WAITIP)) + { + logger(&ctx, LOG_WARNING, + "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(ifo); + ifo = NULL; + + if_sortinterfaces(&ctx); + TAILQ_FOREACH(ifp, ctx.ifaces, next) { + eloop_timeout_add_sec(ctx.eloop, 0, + dhcpcd_prestartinterface, ifp); + } + + i = eloop_start(ctx.eloop); + goto exit1; + +exit_success: + i = EXIT_SUCCESS; + goto exit1; + +exit_failure: + i = EXIT_FAILURE; + +exit1: + /* 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); + } + free(ctx.duid); + if (ctx.link_fd != -1) { + eloop_event_delete(ctx.eloop, ctx.link_fd, 0); + close(ctx.link_fd); + } + + free_options(ifo); + free_globals(&ctx); + ipv4_ctxfree(&ctx); + ipv6_ctxfree(&ctx); + dev_stop(&ctx); + if (control_stop(&ctx) == -1) + logger(&ctx, LOG_ERR, "control_stop: %m:"); + if (ctx.pid_fd != -1) { + close(ctx.pid_fd); + unlink(ctx.pidfile); + } + eloop_free(ctx.eloop); + + if (ctx.options & DHCPCD_STARTED && !(ctx.options & DHCPCD_FORKED)) + logger(&ctx, LOG_INFO, PACKAGE " exited"); + logger_close(&ctx); + free(ctx.logfile); + return i; +} |