| /* |
| * dhcpcd - DHCP client daemon |
| * Copyright (c) 2006-2015 Roy Marples <[email protected]> |
| * 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> |
| |
| #if defined(__ANDROID__) |
| #include <sys/capability.h> |
| #include <sys/prctl.h> |
| #include <private/android_filesystem_config.h> |
| #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; |
| } |