You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1998 lines
49 KiB
1998 lines
49 KiB
/* |
|
* dhcpcd - DHCP client daemon |
|
* Copyright (c) 2006-2015 Roy Marples <roy@marples.name> |
|
* All rights reserved |
|
|
|
* Redistribution and use in source and binary forms, with or without |
|
* modification, are permitted provided that the following conditions |
|
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice, this list of conditions and the following disclaimer in the |
|
* documentation and/or other materials provided with the distribution. |
|
* |
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
* SUCH DAMAGE. |
|
*/ |
|
|
|
const char dhcpcd_copyright[] = "Copyright (c) 2006-2015 Roy Marples"; |
|
|
|
#define _WITH_DPRINTF /* Stop FreeBSD bitching */ |
|
|
|
#include <sys/file.h> |
|
#include <sys/socket.h> |
|
#include <sys/stat.h> |
|
#include <sys/time.h> |
|
#include <sys/types.h> |
|
#include <sys/uio.h> |
|
|
|
#include <ctype.h> |
|
#include <errno.h> |
|
#include <fcntl.h> |
|
#include <getopt.h> |
|
#include <limits.h> |
|
#include <paths.h> |
|
#include <signal.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
#include <time.h> |
|
|
|
#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; |
|
}
|
|
|