/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2015 Roy Marples * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ const char dhcpcd_copyright[] = "Copyright (c) 2006-2015 Roy Marples"; #define _WITH_DPRINTF /* Stop FreeBSD bitching */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__ANDROID__) #include #include #include #endif /* __ANDROID__ */ #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 "rpc-interface.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); } void dhcpcd_start_interface(struct dhcpcd_ctx *ctx, const char *ifname) { struct interface *ifp; ifp = if_find(ctx->ifaces, ifname); if (ifp == NULL) { logger(ctx, LOG_ERR, "start_interface: %s not found", ifname); return; } dhcpcd_startinterface(ifp); } void dhcpcd_stop_interface(struct dhcpcd_ctx *ctx, const char *ifname) { struct interface *ifp; ifp = if_find(ctx->ifaces, ifname); if (ifp == NULL) { logger(ctx, LOG_ERR, "stop_interface: %s not found", ifname); return; } stop_interface(ifp); } void dhcpcd_stop_interfaces(struct dhcpcd_ctx *ctx) { struct interface *ifp; TAILQ_FOREACH(ifp, ctx->ifaces, next) { stop_interface(ifp); } } void dhcpcd_release_ipv4(struct dhcpcd_ctx *ctx, const char *ifname) { struct interface *ifp; ifp = if_find(ctx->ifaces, ifname); if (ifp == NULL) { logger(ctx, LOG_ERR, "IPv4 release: %s not found", ifname); return; } dhcp_drop(ifp, "RELEASE"); } 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; } #if defined(__ANDROID__) static void switch_user(void) { gid_t groups[] = { AID_DBUS, AID_INET, AID_SHELL }; struct __user_cap_header_struct header; struct __user_cap_data_struct cap; setgroups(sizeof(groups)/sizeof(groups[0]), groups); prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); setgid(AID_DHCP); setuid(AID_DHCP); header.version = _LINUX_CAPABILITY_VERSION; header.pid = 0; cap.effective = cap.permitted = (1 << CAP_NET_ADMIN) | (1 << CAP_NET_RAW) | (1 << CAP_NET_BROADCAST) | (1 << CAP_NET_BIND_SERVICE); cap.inheritable = 0; capset(&header, &cap); } #endif /* __ANDROID__ */ 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 char ifn[IF_NAMESIZE]; #if defined(__ANDROID__) && !defined(__BRILLO__) switch_user(); #endif /* __ANDROID__ && !__BRILLO__ */ /* 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; int intf_len = strlen(argv[optind]); split_interface_lease(argv[optind], &intf_len, NULL); if (intf_len > IF_NAMESIZE) { logger(&ctx, LOG_ERR, "%s: interface name too long", argv[optind]); goto exit_failure; } strlcpy(ifn, argv[optind], intf_len + 1); /* 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, "-", ifn, 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_NOTICE, PACKAGE " is running with reduced privileges"); #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; } #if !defined(__ANDROID__) /* Ensure we have the needed directories * On Android, we assume that these directories have been created * by calls to mkdir in an init.rc file. */ 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); #endif /* __ANDROID__ */ 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); if (rpc_init(&ctx) == -1) { /* NB: rpc_init generates a syslog msg */ exit(EXIT_FAILURE); } rpc_signal_status("Init"); 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++) { int intf_len = strlen(ctx.ifv[i]); split_interface_lease(ctx.ifv[i], &intf_len, NULL); if (intf_len > IF_NAMESIZE) { logger(&ctx, LOG_ERR, "%s: interface name too long", ctx.ifv[i]); continue; } strlcpy(ifn, ctx.ifv[i], intf_len + 1); if (if_find(ctx.ifaces, ifn) == NULL) logger(&ctx, LOG_ERR, "%s: interface not found or invalid", ifn); } 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; }