| /* |
| * dhcpcd - DHCP client daemon |
| * Copyright (c) 2006-2013 Roy Marples <roy@marples.name> |
| * All rights reserved |
| |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| const char copyright[] = "Copyright (c) 2006-2013 Roy Marples"; |
| |
| #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 <sys/utsname.h> |
| |
| #include <net/route.h> /* For RTM_CHGADDR */ |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <getopt.h> |
| #include <limits.h> |
| #include <paths.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| #include <time.h> |
| |
| #include "arp.h" |
| #include "config.h" |
| #include "common.h" |
| #include "control.h" |
| #include "dhcpcd.h" |
| #include "dhcp6.h" |
| #include "eloop.h" |
| #include "if-options.h" |
| #include "if-pref.h" |
| #include "ipv4.h" |
| #include "ipv6.h" |
| #include "ipv6ns.h" |
| #include "ipv6rs.h" |
| #include "net.h" |
| #include "platform.h" |
| #include "script.h" |
| |
| struct if_head *ifaces = NULL; |
| char vendor[VENDORCLASSID_MAX_LEN]; |
| int pidfd = -1; |
| struct if_options *if_options = NULL; |
| int ifac = 0; |
| char **ifav = NULL; |
| int ifdc = 0; |
| char **ifdv = NULL; |
| |
| sigset_t dhcpcd_sigset; |
| const int handle_sigs[] = { |
| SIGALRM, |
| SIGHUP, |
| SIGINT, |
| SIGPIPE, |
| SIGTERM, |
| SIGUSR1, |
| 0 |
| }; |
| |
| static char *cffile; |
| static char *pidfile; |
| static int linkfd = -1; |
| static char **ifv; |
| static int ifc; |
| static char **margv; |
| static int margc; |
| |
| static pid_t |
| read_pid(void) |
| { |
| 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 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 |
| cleanup(void) |
| { |
| #ifdef DEBUG_MEMORY |
| struct interface *ifp; |
| int i; |
| |
| free_options(if_options); |
| |
| if (ifaces) { |
| while ((ifp = TAILQ_FIRST(ifaces))) { |
| TAILQ_REMOVE(ifaces, ifp, next); |
| free_interface(ifp); |
| } |
| free(ifaces); |
| } |
| |
| for (i = 0; i < ifac; i++) |
| free(ifav[i]); |
| free(ifav); |
| for (i = 0; i < ifdc; i++) |
| free(ifdv[i]); |
| free(ifdv); |
| #endif |
| |
| if (linkfd != -1) |
| close(linkfd); |
| if (pidfd > -1) { |
| if (options & DHCPCD_MASTER) { |
| if (control_stop() == -1) |
| syslog(LOG_ERR, "control_stop: %m"); |
| } |
| close(pidfd); |
| unlink(pidfile); |
| } |
| #ifdef DEBUG_MEMORY |
| free(pidfile); |
| #endif |
| |
| if (options & DHCPCD_STARTED && !(options & DHCPCD_FORKED)) |
| syslog(LOG_INFO, "exited"); |
| } |
| |
| /* ARGSUSED */ |
| static void |
| handle_exit_timeout(__unused void *arg) |
| { |
| int timeout; |
| |
| syslog(LOG_ERR, "timed out"); |
| if (!(options & DHCPCD_IPV4) || !(options & DHCPCD_TIMEOUT_IPV4LL)) { |
| if (options & DHCPCD_MASTER) { |
| daemonise(); |
| return; |
| } else |
| exit(EXIT_FAILURE); |
| } |
| options &= ~DHCPCD_TIMEOUT_IPV4LL; |
| timeout = (PROBE_NUM * PROBE_MAX) + (PROBE_WAIT * 2); |
| syslog(LOG_WARNING, "allowing %d seconds for IPv4LL timeout", timeout); |
| eloop_timeout_add_sec(timeout, handle_exit_timeout, NULL); |
| } |
| |
| pid_t |
| daemonise(void) |
| { |
| #ifdef THERE_IS_NO_FORK |
| return -1; |
| #else |
| pid_t pid; |
| char buf = '\0'; |
| int sidpipe[2], fd; |
| |
| eloop_timeout_delete(handle_exit_timeout, NULL); |
| if (options & DHCPCD_DAEMONISED || !(options & DHCPCD_DAEMONISE)) |
| return 0; |
| /* Setup a signal pipe so parent knows when to exit. */ |
| if (pipe(sidpipe) == -1) { |
| syslog(LOG_ERR, "pipe: %m"); |
| return -1; |
| } |
| syslog(LOG_DEBUG, "forking to background"); |
| switch (pid = fork()) { |
| case -1: |
| syslog(LOG_ERR, "fork: %m"); |
| exit(EXIT_FAILURE); |
| /* NOTREACHED */ |
| case 0: |
| setsid(); |
| /* Notify parent it's safe to exit as we've detached. */ |
| close(sidpipe[0]); |
| if (write(sidpipe[1], &buf, 1) == -1) |
| syslog(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) |
| syslog(LOG_ERR, "failed to read child: %m"); |
| close(sidpipe[0]); |
| break; |
| } |
| /* Done with the fd now */ |
| if (pid != 0) { |
| syslog(LOG_INFO, "forked to background, child pid %d",pid); |
| writepid(pidfd, pid); |
| close(pidfd); |
| pidfd = -1; |
| options |= DHCPCD_FORKED; |
| exit(EXIT_SUCCESS); |
| } |
| options |= DHCPCD_DAEMONISED; |
| return pid; |
| #endif |
| } |
| |
| struct interface * |
| find_interface(const char *ifname) |
| { |
| struct interface *ifp; |
| |
| TAILQ_FOREACH(ifp, ifaces, next) { |
| if (strcmp(ifp->name, ifname) == 0) |
| return ifp; |
| } |
| return NULL; |
| } |
| |
| static void |
| stop_interface(struct interface *ifp) |
| { |
| |
| syslog(LOG_INFO, "%s: removing interface", ifp->name); |
| ifp->options->options |= DHCPCD_STOPPING; |
| |
| // Remove the interface from our list |
| TAILQ_REMOVE(ifaces, ifp, next); |
| dhcp6_drop(ifp, NULL); |
| ipv6rs_drop(ifp); |
| dhcp_drop(ifp, "STOP"); |
| dhcp_close(ifp); |
| eloop_timeout_delete(NULL, ifp); |
| if (ifp->options->options & DHCPCD_DEPARTED) |
| script_runreason(ifp, "DEPARTED"); |
| free_interface(ifp); |
| if (!(options & (DHCPCD_MASTER | DHCPCD_TEST))) |
| exit(EXIT_FAILURE); |
| } |
| |
| static void |
| configure_interface1(struct interface *ifp) |
| { |
| struct if_options *ifo = ifp->options; |
| |
| /* Do any platform specific configuration */ |
| if_conf(ifp); |
| |
| 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 | IFF_MULTICAST))) |
| ifo->options &= ~DHCPCD_IPV6RS; |
| if (ifo->options & DHCPCD_LINK && carrier_status(ifp) == LINK_UNKNOWN) |
| ifo->options &= ~DHCPCD_LINK; |
| |
| if (ifo->metric != -1) |
| ifp->metric = ifo->metric; |
| |
| /* We want to disable kernel interface RA as early as possible. */ |
| if (ifo->options & DHCPCD_IPV6RS) { |
| if (check_ipv6(NULL) != 1 || check_ipv6(ifp->name) != 1) |
| ifo->options &= ~DHCPCD_IPV6RS; |
| } |
| |
| /* 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 (ifp->hwlen > DHCP_CHADDR_LEN) |
| ifo->options |= DHCPCD_CLIENTID; |
| |
| /* 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; |
| } |
| } |
| |
| int |
| select_profile(struct interface *ifp, const char *profile) |
| { |
| struct if_options *ifo; |
| int ret; |
| |
| ret = 0; |
| ifo = read_config(cffile, ifp->name, ifp->ssid, profile); |
| if (ifo == NULL) { |
| syslog(LOG_DEBUG, "%s: no profile %s", ifp->name, profile); |
| ret = -1; |
| goto exit; |
| } |
| if (profile != NULL) { |
| strlcpy(ifp->profile, profile, sizeof(ifp->profile)); |
| syslog(LOG_INFO, "%s: selected profile %s", |
| ifp->name, profile); |
| } else |
| *ifp->profile = '\0'; |
| free_options(ifp->options); |
| ifp->options = ifo; |
| |
| exit: |
| if (profile) |
| configure_interface1(ifp); |
| return ret; |
| } |
| |
| static void |
| configure_interface(struct interface *ifp, int argc, char **argv) |
| { |
| |
| select_profile(ifp, NULL); |
| add_options(ifp->options, argc, argv); |
| configure_interface1(ifp); |
| } |
| |
| void |
| handle_carrier(int carrier, int flags, const char *ifname) |
| { |
| struct interface *ifp; |
| |
| if (!(options & DHCPCD_LINK)) |
| return; |
| ifp = find_interface(ifname); |
| if (ifp == NULL) { |
| handle_interface(1, ifname); |
| return; |
| } |
| if (!(ifp->options->options & DHCPCD_LINK)) |
| return; |
| |
| if (carrier == LINK_UNKNOWN) |
| carrier = carrier_status(ifp); /* will set ifp->flags */ |
| else |
| ifp->flags = flags; |
| |
| if (carrier == LINK_UNKNOWN) |
| syslog(LOG_ERR, "%s: carrier_status: %m", ifname); |
| /* IFF_RUNNING is checked, if needed, earlier and is OS dependant */ |
| else if (carrier == LINK_DOWN || (ifp->flags & IFF_UP) == 0) { |
| if (ifp->carrier != LINK_DOWN) { |
| if (ifp->carrier == LINK_UP) |
| syslog(LOG_INFO, "%s: carrier lost", ifp->name); |
| ifp->carrier = LINK_DOWN; |
| dhcp_close(ifp); |
| dhcp6_drop(ifp, "EXPIRE6"); |
| ipv6rs_drop(ifp); |
| /* Don't blindly delete our knowledge of LL addresses. |
| * We need to listen to what the kernel does with |
| * them as some OS's will remove, mark tentative or |
| * do nothing. */ |
| ipv6_free_ll_callbacks(ifp); |
| dhcp_drop(ifp, "NOCARRIER"); |
| } |
| } else if (carrier == LINK_UP && ifp->flags & IFF_UP) { |
| if (ifp->carrier != LINK_UP) { |
| syslog(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. */ |
| handle_interface(0, ifp->name); |
| #endif |
| if (ifp->wireless) |
| getifssid(ifp->name, ifp->ssid); |
| configure_interface(ifp, margc, margv); |
| script_runreason(ifp, "CARRIER"); |
| start_interface(ifp); |
| } |
| } |
| } |
| |
| void |
| start_interface(void *arg) |
| { |
| struct interface *ifp = arg; |
| struct if_options *ifo = ifp->options; |
| int nolease; |
| |
| handle_carrier(LINK_UNKNOWN, 0, ifp->name); |
| if (ifp->carrier == LINK_DOWN) { |
| syslog(LOG_INFO, "%s: waiting for carrier", ifp->name); |
| return; |
| } |
| |
| if (ifo->options & DHCPCD_IPV6) { |
| if (ifo->options & DHCPCD_IPV6RS && |
| !(ifo->options & DHCPCD_INFORM)) |
| ipv6rs_start(ifp); |
| |
| if (!(ifo->options & DHCPCD_IPV6RS)) { |
| if (ifo->options & DHCPCD_IA_FORCED) |
| nolease = dhcp6_start(ifp, DH6S_INIT); |
| else { |
| nolease = dhcp6_find_delegates(ifp); |
| /* 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) |
| syslog(LOG_ERR, |
| "%s: dhcp6_start: %m", ifp->name); |
| } |
| } |
| |
| if (ifo->options & DHCPCD_IPV4) |
| dhcp_start(ifp); |
| } |
| |
| /* ARGSUSED */ |
| static void |
| handle_link(__unused void *arg) |
| { |
| |
| if (manage_link(linkfd) == -1 && errno != ENXIO && errno != ENODEV) |
| syslog(LOG_ERR, "manage_link: %m"); |
| } |
| |
| static void |
| init_state(struct interface *ifp, int argc, char **argv) |
| { |
| struct if_options *ifo; |
| const char *reason = NULL; |
| |
| configure_interface(ifp, argc, argv); |
| ifo = ifp->options; |
| |
| if (ifo->options & DHCPCD_IPV4 && ipv4_init() == -1) { |
| syslog(LOG_ERR, "ipv4_init: %m"); |
| ifo->options &= ~DHCPCD_IPV4; |
| } |
| if (ifo->options & DHCPCD_IPV6 && ipv6_init() == -1) { |
| syslog(LOG_ERR, "ipv6_init: %m"); |
| ifo->options &= ~DHCPCD_IPV6RS; |
| } |
| |
| if (!(options & DHCPCD_TEST)) |
| script_runreason(ifp, "PREINIT"); |
| |
| if (ifo->options & DHCPCD_LINK) { |
| switch (carrier_status(ifp)) { |
| case 0: |
| ifp->carrier = LINK_DOWN; |
| reason = "NOCARRIER"; |
| break; |
| case 1: |
| ifp->carrier = LINK_UP; |
| reason = "CARRIER"; |
| break; |
| default: |
| ifp->carrier = LINK_UNKNOWN; |
| return; |
| } |
| if (reason && !(options & DHCPCD_TEST)) |
| script_runreason(ifp, reason); |
| } else |
| ifp->carrier = LINK_UNKNOWN; |
| } |
| |
| void |
| handle_interface(int action, const char *ifname) |
| { |
| struct if_head *ifs; |
| struct interface *ifp, *ifn, *ifl = NULL; |
| const char * const argv[] = { ifname }; |
| int i; |
| |
| if (action == -1) { |
| ifp = find_interface(ifname); |
| if (ifp != NULL) { |
| ifp->options->options |= DHCPCD_DEPARTED; |
| stop_interface(ifp); |
| } |
| return; |
| } |
| |
| /* If running off an interface list, check it's in it. */ |
| if (ifc) { |
| for (i = 0; i < ifc; i++) |
| if (strcmp(ifv[i], ifname) == 0) |
| break; |
| if (i >= ifc) |
| return; |
| } |
| |
| ifs = discover_interfaces(-1, UNCONST(argv)); |
| TAILQ_FOREACH_SAFE(ifp, ifs, next, ifn) { |
| if (strcmp(ifp->name, ifname) != 0) |
| continue; |
| /* Check if we already have the interface */ |
| ifl = find_interface(ifp->name); |
| if (ifl) { |
| /* The flags and hwaddr could have changed */ |
| ifl->flags = ifp->flags; |
| ifl->hwlen = ifp->hwlen; |
| if (ifp->hwlen != 0) |
| memcpy(ifl->hwaddr, ifp->hwaddr, ifl->hwlen); |
| } else { |
| TAILQ_REMOVE(ifs, ifp, next); |
| TAILQ_INSERT_TAIL(ifaces, ifp, next); |
| } |
| if (action == 1) { |
| init_state(ifp, margc, margv); |
| start_interface(ifp); |
| } |
| } |
| |
| /* Free our discovered list */ |
| while ((ifp = TAILQ_FIRST(ifs))) { |
| TAILQ_REMOVE(ifs, ifp, next); |
| free_interface(ifp); |
| } |
| free(ifs); |
| } |
| |
| void |
| handle_hwaddr(const char *ifname, const uint8_t *hwaddr, size_t hwlen) |
| { |
| struct interface *ifp; |
| |
| ifp = find_interface(ifname); |
| if (ifp == NULL) |
| return; |
| |
| if (hwlen > sizeof(ifp->hwaddr)) { |
| errno = ENOBUFS; |
| syslog(LOG_ERR, "%s: %s: %m", ifp->name, __func__); |
| return; |
| } |
| |
| if (ifp->hwlen == hwlen && memcmp(ifp->hwaddr, hwaddr, hwlen) == 0) |
| return; |
| |
| syslog(LOG_INFO, "%s: new hardware address: %s", ifp->name, |
| hwaddr_ntoa(hwaddr, hwlen)); |
| ifp->hwlen = hwlen; |
| memcpy(ifp->hwaddr, hwaddr, hwlen); |
| } |
| |
| static void |
| if_reboot(struct interface *ifp, int argc, char **argv) |
| { |
| int oldopts; |
| |
| oldopts = ifp->options->options; |
| script_runreason(ifp, "RECONFIGURE"); |
| configure_interface(ifp, argc, argv); |
| dhcp_reboot_newopts(ifp, oldopts); |
| dhcp6_reboot(ifp); |
| start_interface(ifp); |
| } |
| |
| static void |
| reconf_reboot(int action, int argc, char **argv, int oi) |
| { |
| struct if_head *ifs; |
| struct interface *ifn, *ifp; |
| |
| ifs = discover_interfaces(argc - oi, argv + oi); |
| if (ifs == NULL) |
| return; |
| |
| while ((ifp = TAILQ_FIRST(ifs))) { |
| TAILQ_REMOVE(ifs, ifp, next); |
| ifn = find_interface(ifp->name); |
| if (ifn) { |
| if (action) |
| if_reboot(ifn, argc, argv); |
| else |
| ipv4_applyaddr(ifn); |
| free_interface(ifp); |
| } else { |
| init_state(ifp, argc, argv); |
| TAILQ_INSERT_TAIL(ifaces, ifp, next); |
| start_interface(ifp); |
| } |
| } |
| free(ifs); |
| |
| sort_interfaces(); |
| } |
| |
| /* ARGSUSED */ |
| static void |
| sig_reboot(void *arg) |
| { |
| siginfo_t *siginfo = arg; |
| struct if_options *ifo; |
| int i; |
| |
| syslog(LOG_INFO, "received SIGALRM from PID %d, rebinding", |
| (int)siginfo->si_pid); |
| |
| for (i = 0; i < ifac; i++) |
| free(ifav[i]); |
| free(ifav); |
| ifav = NULL; |
| ifac = 0; |
| for (i = 0; i < ifdc; i++) |
| free(ifdv[i]); |
| free(ifdv); |
| ifdc = 0; |
| ifdv = NULL; |
| ifo = read_config(cffile, NULL, NULL, NULL); |
| add_options(ifo, margc, margv); |
| /* We need to preserve these two options. */ |
| if (options & DHCPCD_MASTER) |
| ifo->options |= DHCPCD_MASTER; |
| if (options & DHCPCD_DAEMONISED) |
| ifo->options |= DHCPCD_DAEMONISED; |
| options = ifo->options; |
| free_options(ifo); |
| reconf_reboot(1, ifc, ifv, 0); |
| } |
| |
| static void |
| sig_reconf(void *arg) |
| { |
| siginfo_t *siginfo = arg; |
| struct interface *ifp; |
| |
| syslog(LOG_INFO, "received SIGUSR from PID %d, reconfiguring", |
| (int)siginfo->si_pid); |
| TAILQ_FOREACH(ifp, ifaces, next) { |
| ipv4_applyaddr(ifp); |
| } |
| } |
| |
| static void |
| handle_signal(int sig, siginfo_t *siginfo, __unused void *context) |
| { |
| struct interface *ifp; |
| int do_release; |
| |
| do_release = 0; |
| switch (sig) { |
| case SIGINT: |
| syslog(LOG_INFO, "received SIGINT from PID %d, stopping", |
| (int)siginfo->si_pid); |
| break; |
| case SIGTERM: |
| syslog(LOG_INFO, "received SIGTERM from PID %d, stopping", |
| (int)siginfo->si_pid); |
| break; |
| case SIGALRM: |
| eloop_timeout_add_now(sig_reboot, siginfo); |
| return; |
| case SIGHUP: |
| syslog(LOG_INFO, "received SIGHUP from PID %d, releasing", |
| (int)siginfo->si_pid); |
| do_release = 1; |
| break; |
| case SIGUSR1: |
| eloop_timeout_add_now(sig_reconf, siginfo); |
| return; |
| case SIGPIPE: |
| syslog(LOG_WARNING, "received SIGPIPE"); |
| return; |
| default: |
| syslog(LOG_ERR, |
| "received signal %d from PID %d, " |
| "but don't know what to do with it", |
| sig, (int)siginfo->si_pid); |
| return; |
| } |
| |
| if (options & DHCPCD_TEST) |
| exit(EXIT_FAILURE); |
| |
| /* As drop_dhcp could re-arrange the order, we do it like this. */ |
| for (;;) { |
| /* Be sane and drop the last config first */ |
| ifp = TAILQ_LAST(ifaces, if_head); |
| if (ifp == NULL) |
| break; |
| if (do_release) |
| ifp->options->options |= DHCPCD_RELEASE; |
| stop_interface(ifp); |
| } |
| exit(EXIT_FAILURE); |
| } |
| |
| int |
| handle_args(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; |
| ssize_t len; |
| size_t l; |
| struct iovec iov[2]; |
| char *tmp, *p; |
| |
| if (fd != NULL) { |
| /* Special commands for our control socket */ |
| if (strcmp(*argv, "--version") == 0) { |
| len = strlen(VERSION) + 1; |
| iov[0].iov_base = &len; |
| iov[0].iov_len = sizeof(ssize_t); |
| iov[1].iov_base = UNCONST(VERSION); |
| iov[1].iov_len = len; |
| if (writev(fd->fd, iov, 2) == -1) { |
| syslog(LOG_ERR, "writev: %m"); |
| return -1; |
| } |
| return 0; |
| } else if (strcmp(*argv, "--getconfigfile") == 0) { |
| len = strlen(cffile ? cffile : CONFIG) + 1; |
| iov[0].iov_base = &len; |
| iov[0].iov_len = sizeof(ssize_t); |
| iov[1].iov_base = cffile ? cffile : UNCONST(CONFIG); |
| iov[1].iov_len = len; |
| if (writev(fd->fd, iov, 2) == -1) { |
| syslog(LOG_ERR, "writev: %m"); |
| return -1; |
| } |
| return 0; |
| } else if (strcmp(*argv, "--getinterfaces") == 0) { |
| len = 0; |
| if (argc == 1) { |
| TAILQ_FOREACH(ifp, ifaces, next) { |
| len++; |
| if (D6_STATE_RUNNING(ifp)) |
| len++; |
| if (ipv6rs_has_ra(ifp)) |
| len++; |
| } |
| len = write(fd->fd, &len, sizeof(len)); |
| if (len != sizeof(len)) |
| return -1; |
| TAILQ_FOREACH(ifp, ifaces, next) { |
| send_interface(fd->fd, ifp); |
| } |
| return 0; |
| } |
| opt = 0; |
| while (argv[++opt] != NULL) { |
| TAILQ_FOREACH(ifp, ifaces, next) { |
| if (strcmp(argv[opt], ifp->name) == 0) { |
| len++; |
| if (D6_STATE_RUNNING(ifp)) |
| len++; |
| if (ipv6rs_has_ra(ifp)) |
| len++; |
| } |
| } |
| } |
| len = write(fd->fd, &len, sizeof(len)); |
| if (len != sizeof(len)) |
| return -1; |
| opt = 0; |
| while (argv[++opt] != NULL) { |
| TAILQ_FOREACH(ifp, ifaces, next) { |
| if (strcmp(argv[opt], ifp->name) == 0) |
| send_interface(fd->fd, ifp); |
| } |
| } |
| return 0; |
| } else if (strcmp(*argv, "--listen") == 0) { |
| fd->listener = 1; |
| return 0; |
| } |
| } |
| |
| /* Log the command */ |
| len = 0; |
| for (opt = 0; opt < argc; opt++) |
| len += strlen(argv[opt]) + 1; |
| tmp = p = malloc(len + 1); |
| if (tmp == NULL) { |
| syslog(LOG_ERR, "%s: %m", __func__); |
| return -1; |
| } |
| for (opt = 0; opt < argc; opt++) { |
| l = strlen(argv[opt]); |
| strlcpy(p, argv[opt], l + 1); |
| p += l; |
| *p++ = ' '; |
| } |
| *--p = '\0'; |
| syslog(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; |
| } |
| } |
| |
| /* We need at least one interface */ |
| if (optind == argc) { |
| syslog(LOG_ERR, "%s: no interface", __func__); |
| return -1; |
| } |
| |
| if (do_release || do_exit) { |
| for (oi = optind; oi < argc; oi++) { |
| if ((ifp = find_interface(argv[oi])) == NULL) |
| continue; |
| if (do_release) |
| ifp->options->options |= DHCPCD_RELEASE; |
| stop_interface(ifp); |
| } |
| return 0; |
| } |
| |
| reconf_reboot(do_reboot, argc, argv, optind); |
| return 0; |
| } |
| |
| static int |
| signal_init(void (*func)(int, siginfo_t *, void *), sigset_t *oldset) |
| { |
| unsigned int i; |
| struct sigaction sa; |
| sigset_t newset; |
| |
| sigfillset(&newset); |
| if (sigprocmask(SIG_SETMASK, &newset, oldset) == -1) |
| return -1; |
| |
| memset(&sa, 0, sizeof(sa)); |
| sa.sa_sigaction = func; |
| sa.sa_flags = SA_SIGINFO; |
| sigemptyset(&sa.sa_mask); |
| |
| for (i = 0; handle_sigs[i]; i++) { |
| if (sigaction(handle_sigs[i], &sa, NULL) == -1) |
| return -1; |
| } |
| return 0; |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| struct interface *ifp; |
| uint16_t family = 0; |
| int opt, oi = 0, sig = 0, i, control_fd; |
| size_t len; |
| pid_t pid; |
| struct timespec ts; |
| struct utsname utn; |
| const char *platform; |
| |
| closefrom(3); |
| openlog(PACKAGE, LOG_PERROR | LOG_PID, LOG_DAEMON); |
| setlogmask(LOG_UPTO(LOG_INFO)); |
| |
| /* Test for --help and --version */ |
| if (argc > 1) { |
| if (strcmp(argv[1], "--help") == 0) { |
| usage(); |
| exit(EXIT_SUCCESS); |
| } else if (strcmp(argv[1], "--version") == 0) { |
| printf(""PACKAGE" "VERSION"\n%s\n", copyright); |
| exit(EXIT_SUCCESS); |
| } |
| } |
| |
| platform = hardware_platform(); |
| if (uname(&utn) == 0) |
| snprintf(vendor, VENDORCLASSID_MAX_LEN, |
| "%s-%s:%s-%s:%s%s%s", PACKAGE, VERSION, |
| utn.sysname, utn.release, utn.machine, |
| platform ? ":" : "", platform ? platform : ""); |
| else |
| snprintf(vendor, VENDORCLASSID_MAX_LEN, |
| "%s-%s", PACKAGE, VERSION); |
| |
| 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': |
| cffile = optarg; |
| break; |
| case 'g': |
| sig = SIGUSR1; |
| break; |
| case 'k': |
| sig = SIGHUP; |
| break; |
| case 'n': |
| sig = SIGALRM; |
| break; |
| case 'x': |
| sig = SIGTERM; |
| break; |
| case 'T': |
| i = 1; |
| break; |
| case 'U': |
| i = 2; |
| break; |
| case 'V': |
| printf("Interface options:\n"); |
| if_printoptions(); |
| #ifdef INET |
| if (family == 0 || family == AF_INET) { |
| printf("\nDHCPv4 options:\n"); |
| dhcp_printoptions(); |
| } |
| #endif |
| #ifdef INET6 |
| if (family == 0 || family == AF_INET6) { |
| printf("\nDHCPv6 options:\n"); |
| dhcp6_printoptions(); |
| } |
| #endif |
| exit(EXIT_SUCCESS); |
| case '?': |
| usage(); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| margv = argv; |
| margc = argc; |
| if_options = read_config(cffile, NULL, NULL, NULL); |
| opt = add_options(if_options, argc, argv); |
| if (opt != 1) { |
| if (opt == 0) |
| usage(); |
| exit(EXIT_FAILURE); |
| } |
| options = if_options->options; |
| if (i != 0) { |
| if (i == 1) |
| options |= DHCPCD_TEST; |
| else |
| options |= DHCPCD_DUMPLEASE; |
| options |= DHCPCD_PERSISTENT; |
| options &= ~DHCPCD_DAEMONISE; |
| } |
| |
| #ifdef THERE_IS_NO_FORK |
| options &= ~DHCPCD_DAEMONISE; |
| #endif |
| |
| if (options & DHCPCD_DEBUG) |
| setlogmask(LOG_UPTO(LOG_DEBUG)); |
| if (options & DHCPCD_QUIET) { |
| i = open(_PATH_DEVNULL, O_RDWR); |
| if (i == -1) |
| syslog(LOG_ERR, "%s: open: %m", __func__); |
| else { |
| dup2(i, STDERR_FILENO); |
| close(i); |
| } |
| } |
| |
| if (!(options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) { |
| /* If we have any other args, we should run as a single dhcpcd |
| * instance for that interface. */ |
| len = strlen(PIDFILE) + IF_NAMESIZE + 2; |
| pidfile = malloc(len); |
| if (pidfile == NULL) { |
| syslog(LOG_ERR, "%s: %m", __func__); |
| exit(EXIT_FAILURE); |
| } |
| if (optind == argc - 1) |
| snprintf(pidfile, len, PIDFILE, "-", argv[optind]); |
| else { |
| snprintf(pidfile, len, PIDFILE, "", ""); |
| options |= DHCPCD_MASTER; |
| } |
| } |
| |
| if (chdir("/") == -1) |
| syslog(LOG_ERR, "chdir `/': %m"); |
| atexit(cleanup); |
| |
| if (options & DHCPCD_DUMPLEASE) { |
| if (optind != argc - 1) { |
| syslog(LOG_ERR, "dumplease requires an interface"); |
| exit(EXIT_FAILURE); |
| } |
| if (dhcp_dump(argv[optind]) == -1) |
| exit(EXIT_FAILURE); |
| exit(EXIT_SUCCESS); |
| } |
| |
| if (!(options & (DHCPCD_MASTER | DHCPCD_TEST))) { |
| control_fd = control_open(); |
| if (control_fd != -1) { |
| syslog(LOG_INFO, |
| "sending commands to master dhcpcd process"); |
| i = control_send(argc, argv); |
| if (i > 0) { |
| syslog(LOG_DEBUG, "send OK"); |
| exit(EXIT_SUCCESS); |
| } else { |
| syslog(LOG_ERR, "failed to send commands"); |
| exit(EXIT_FAILURE); |
| } |
| } else { |
| if (errno != ENOENT) |
| syslog(LOG_ERR, "control_open: %m"); |
| } |
| } |
| |
| if (geteuid()) |
| syslog(LOG_WARNING, |
| PACKAGE " will not work correctly unless run as root"); |
| |
| if (sig != 0) { |
| pid = read_pid(); |
| if (pid != 0) |
| syslog(LOG_INFO, "sending signal %d to pid %d", |
| sig, pid); |
| if (pid == 0 || kill(pid, sig) != 0) { |
| if (sig != SIGALRM && errno != EPERM) |
| syslog(LOG_ERR, ""PACKAGE" not running"); |
| if (pid != 0 && errno != ESRCH) { |
| syslog(LOG_ERR, "kill: %m"); |
| exit(EXIT_FAILURE); |
| } |
| unlink(pidfile); |
| if (sig != SIGALRM) |
| exit(EXIT_FAILURE); |
| } else { |
| if (sig == SIGALRM || sig == SIGUSR1) |
| exit(EXIT_SUCCESS); |
| /* Spin until it exits */ |
| syslog(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() == 0) |
| exit(EXIT_SUCCESS); |
| } |
| syslog(LOG_ERR, "pid %d failed to exit", pid); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| if (!(options & DHCPCD_TEST)) { |
| if ((pid = read_pid()) > 0 && |
| kill(pid, 0) == 0) |
| { |
| syslog(LOG_ERR, ""PACKAGE |
| " already running on pid %d (%s)", |
| pid, pidfile); |
| exit(EXIT_FAILURE); |
| } |
| |
| /* Ensure we have the needed directories */ |
| if (mkdir(RUNDIR, 0755) == -1 && errno != EEXIST) |
| syslog(LOG_ERR, "mkdir `%s': %m", RUNDIR); |
| if (mkdir(DBDIR, 0755) == -1 && errno != EEXIST) |
| syslog(LOG_ERR, "mkdir `%s': %m", DBDIR); |
| |
| pidfd = open(pidfile, O_WRONLY | O_CREAT | O_NONBLOCK, 0664); |
| if (pidfd == -1) |
| syslog(LOG_ERR, "open `%s': %m", pidfile); |
| else { |
| /* Lock the file so that only one instance of dhcpcd |
| * runs on an interface */ |
| if (flock(pidfd, LOCK_EX | LOCK_NB) == -1) { |
| syslog(LOG_ERR, "flock `%s': %m", pidfile); |
| exit(EXIT_FAILURE); |
| } |
| if (set_cloexec(pidfd) == -1) |
| exit(EXIT_FAILURE); |
| writepid(pidfd, getpid()); |
| } |
| } |
| |
| syslog(LOG_INFO, "version " VERSION " starting"); |
| options |= DHCPCD_STARTED; |
| |
| #ifdef DEBUG_MEMORY |
| eloop_init(); |
| #endif |
| |
| /* Save signal mask, block and redirect signals to our handler */ |
| if (signal_init(handle_signal, &dhcpcd_sigset) == -1) { |
| syslog(LOG_ERR, "signal_setup: %m"); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (options & DHCPCD_MASTER) { |
| if (control_start() == -1) |
| syslog(LOG_ERR, "control_start: %m"); |
| } |
| |
| if (open_sockets() == -1) { |
| syslog(LOG_ERR, "open_sockets: %m"); |
| exit(EXIT_FAILURE); |
| } |
| |
| #if 0 |
| if (options & DHCPCD_IPV6RS && disable_rtadv() == -1) { |
| syslog(LOG_ERR, "ipv6rs: %m"); |
| options &= ~DHCPCD_IPV6RS; |
| } |
| #endif |
| |
| ifc = argc - optind; |
| ifv = argv + optind; |
| |
| /* When running dhcpcd against a single interface, we need to retain |
| * the old behaviour of waiting for an IP address */ |
| if (ifc == 1) |
| 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 handle_carrier |
| * instead. |
| * We also need to open this before checking for interfaces below |
| * so that we pickup any new addresses during the discover phase. */ |
| if (linkfd == -1) { |
| linkfd = open_link_socket(); |
| if (linkfd == -1) |
| syslog(LOG_ERR, "open_link_socket: %m"); |
| else |
| eloop_event_add(linkfd, handle_link, NULL); |
| } |
| |
| ifaces = discover_interfaces(ifc, ifv); |
| for (i = 0; i < ifc; i++) { |
| if (find_interface(ifv[i]) == NULL) |
| syslog(LOG_ERR, "%s: interface not found or invalid", |
| ifv[i]); |
| } |
| if (ifaces == NULL) { |
| if (ifc == 0) |
| syslog(LOG_ERR, "no valid interfaces found"); |
| else |
| exit(EXIT_FAILURE); |
| if (!(options & DHCPCD_LINK)) { |
| syslog(LOG_ERR, |
| "aborting as link detection is disabled"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| if (options & DHCPCD_BACKGROUND) |
| daemonise(); |
| |
| opt = 0; |
| TAILQ_FOREACH(ifp, ifaces, next) { |
| init_state(ifp, argc, argv); |
| if (ifp->carrier != LINK_DOWN) |
| opt = 1; |
| } |
| |
| if (!(options & DHCPCD_BACKGROUND)) { |
| /* If we don't have a carrier, we may have to wait for a second |
| * before one becomes available if we brought an interface up */ |
| if (opt == 0 && |
| options & DHCPCD_LINK && |
| options & DHCPCD_WAITUP && |
| !(options & DHCPCD_WAITIP)) |
| { |
| ts.tv_sec = 1; |
| ts.tv_nsec = 0; |
| nanosleep(&ts, NULL); |
| TAILQ_FOREACH(ifp, ifaces, next) { |
| handle_carrier(LINK_UNKNOWN, 0, ifp->name); |
| if (ifp->carrier != LINK_DOWN) { |
| opt = 1; |
| break; |
| } |
| } |
| } |
| if (options & DHCPCD_MASTER) |
| i = if_options->timeout; |
| else if ((ifp = TAILQ_FIRST(ifaces))) |
| i = ifp->options->timeout; |
| else |
| i = 0; |
| if (opt == 0 && |
| options & DHCPCD_LINK && |
| !(options & DHCPCD_WAITIP)) |
| { |
| syslog(LOG_WARNING, "no interfaces have a carrier"); |
| daemonise(); |
| } else if (i > 0) { |
| if (options & DHCPCD_IPV4LL) |
| options |= DHCPCD_TIMEOUT_IPV4LL; |
| eloop_timeout_add_sec(i, handle_exit_timeout, NULL); |
| } |
| } |
| free_options(if_options); |
| if_options = NULL; |
| |
| sort_interfaces(); |
| TAILQ_FOREACH(ifp, ifaces, next) { |
| eloop_timeout_add_sec(0, start_interface, ifp); |
| } |
| |
| eloop_start(&dhcpcd_sigset); |
| exit(EXIT_SUCCESS); |
| } |