dhcpcd/dhcpcd.c
2008-09-04 08:27:38 +00:00

1076 lines
25 KiB
C

/*
* dhcpcd - DHCP client daemon
* Copyright 2006-2008 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-2008 Roy Marples";
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <arpa/inet.h>
#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 <unistd.h>
#include <time.h>
#include "arp.h"
#include "bind.h"
#include "config.h"
#include "common.h"
#include "configure.h"
#include "control.h"
#include "dhcpcd.h"
#include "dhcpf.h"
#include "duid.h"
#include "eloop.h"
#include "if-options.h"
#include "ipv4ll.h"
#include "logger.h"
#include "net.h"
#include "signals.h"
/* We should define a maximum for the NAK exponential backoff */
#define NAKOFF_MAX 60
int master = 0;
int pidfd = -1;
static int linkfd = -1;
static char cffile[PATH_MAX];
static char pidfile[PATH_MAX] = { '\0' };
static struct interface *ifaces = NULL;
struct dhcp_op {
uint8_t value;
const char *name;
};
static const struct dhcp_op const dhcp_ops[] = {
{ DHCP_DISCOVER, "DHCP_DISCOVER" },
{ DHCP_OFFER, "DHCP_OFFER" },
{ DHCP_REQUEST, "DHCP_REQUEST" },
{ DHCP_DECLINE, "DHCP_DECLINE" },
{ DHCP_ACK, "DHCP_ACK" },
{ DHCP_NAK, "DHCP_NAK" },
{ DHCP_RELEASE, "DHCP_RELEASE" },
{ DHCP_INFORM, "DHCP_INFORM" },
{ 0, NULL }
};
static const char *
get_dhcp_op(uint8_t type)
{
const struct dhcp_op *d;
for (d = dhcp_ops; d->name; d++)
if (d->value == type)
return d->name;
return NULL;
}
static pid_t
read_pid(void)
{
FILE *fp;
pid_t pid = 0;
if ((fp = fopen(pidfile, "r")) == NULL) {
errno = ENOENT;
return 0;
}
fscanf(fp, "%d", &pid);
fclose(fp);
return pid;
}
static void
usage(void)
{
printf("usage: "PACKAGE" [-dknpqxADEGHKLOTV] [-c script] [-f file ] [-h hostname]\n"
" [-i classID ] [-l leasetime] [-m metric] [-o option] [-r ipaddr]\n"
" [-s ipaddr] [-t timeout] [-u userclass] [-F none|ptr|both]\n"
" [-I clientID] [-C hookscript] [-Q option] [-X ipaddr] <interface>\n");
}
static void
cleanup(void)
{
#ifdef DEBUG_MEMORY
struct interface *iface;
while (ifaces) {
iface = ifaces;
ifaces = iface->next;
free_interface(iface);
}
#endif
if (linkfd != -1)
close(linkfd);
if (pidfd > -1) {
if (master) {
if (stop_control() == -1)
logger(LOG_ERR, "stop_control: %s",
strerror(errno));
}
close(pidfd);
unlink(pidfile);
}
}
void
handle_exit_timeout(_unused void *arg)
{
logger(LOG_ERR, "timed out");
exit(EXIT_FAILURE);
}
void
drop_config(struct interface *iface, const char *reason)
{
if (iface->state->new || strcmp(reason, "FAIL") == 0) {
free(iface->state->new);
iface->state->new = NULL;
configure(iface, reason);
free(iface->state->old);
iface->state->old = NULL;
}
iface->state->lease.addr.s_addr = 0;
}
void
close_sockets(struct interface *iface)
{
if (iface->arp_fd != -1) {
delete_event(iface->arp_fd);
close(iface->arp_fd);
iface->arp_fd = -1;
}
if (iface->raw_fd != -1) {
delete_event(iface->raw_fd);
close(iface->raw_fd);
iface->raw_fd = -1;
}
if (iface->udp_fd != -1) {
close(iface->udp_fd);
iface->udp_fd = -1;
}
}
static void
send_message(struct interface *iface, int type,
void (*callback)(void *))
{
struct if_state *state = iface->state;
struct dhcp_message *dhcp;
uint8_t *udp;
ssize_t len, r;
struct in_addr from, to;
in_addr_t a = 0;
struct timeval tv;
if (!callback)
logger(LOG_DEBUG, "%s: sending %s with xid 0x%x",
iface->name, get_dhcp_op(type), state->xid);
else {
if (state->interval == 0)
state->interval = 4;
else {
state->interval *= 2;
if (state->interval > 64)
state->interval = 64;
}
tv.tv_sec = state->interval + DHCP_RAND_MIN;
tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U);
logger(LOG_DEBUG,
"%s: sending %s with xid 0x%x, next in %0.2f seconds",
iface->name, get_dhcp_op(type), state->xid,
timeval_to_double(&tv));
}
/* If we couldn't open a UDP port for our IP address
* then we cannot renew.
* This could happen if our IP was pulled out from underneath us. */
if (iface->udp_fd == -1) {
a = iface->addr.s_addr;
iface->addr.s_addr = 0;
}
len = make_message(&dhcp, iface, type);
if (iface->udp_fd == -1)
iface->addr.s_addr = a;
from.s_addr = dhcp->ciaddr;
if (from.s_addr)
to.s_addr = state->lease.server.s_addr;
else
to.s_addr = 0;
if (to.s_addr && to.s_addr != INADDR_BROADCAST) {
r = send_packet(iface, to, (uint8_t *)dhcp, len);
if (r == -1)
logger(LOG_ERR, "%s: send_packet: %s",
iface->name, strerror(errno));
} else {
len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to);
r = send_raw_packet(iface, ETHERTYPE_IP, udp, len);
free(udp);
if (r == -1)
logger(LOG_ERR, "%s: send_raw_packet: %s",
iface->name, strerror(errno));
}
free(dhcp);
if (callback)
add_timeout_tv(&tv, callback, iface);
}
static void
send_discover(void *arg)
{
send_message((struct interface *)arg, DHCP_DISCOVER, send_discover);
}
void
send_request(void *arg)
{
send_message((struct interface *)arg, DHCP_REQUEST, send_request);
}
static void
send_renew(void *arg)
{
send_message((struct interface *)arg, DHCP_REQUEST, send_renew);
}
void
start_renew(void *arg)
{
struct interface *iface = arg;
logger(LOG_INFO, "%s: renewing lease of %s",
iface->name, inet_ntoa(iface->state->lease.addr));
iface->state->state = DHS_RENEWING;
iface->state->xid = arc4random();
send_renew(iface);
}
static void
send_rebind(void *arg)
{
send_message((struct interface *)arg, DHCP_REQUEST, send_rebind);
}
void
start_rebind(void *arg)
{
struct interface *iface = arg;
logger(LOG_ERR, "%s: failed to renew, attmepting to rebind",
iface->name);
iface->state->state = DHS_REBINDING;
delete_timeout(send_renew, iface);
iface->state->lease.server.s_addr = 0;
send_rebind(iface);
}
void
start_expire(void *arg)
{
struct interface *iface = arg;
int ll = IN_LINKLOCAL(htonl(iface->state->lease.addr.s_addr));
logger(LOG_ERR, "%s: lease expired", iface->name);
delete_timeout(NULL, iface);
drop_config(iface, "EXPIRE");
iface->state->interval = 0;
if (iface->state->carrier != LINK_DOWN) {
if (ll)
start_interface(iface);
else
start_ipv4ll(iface);
}
}
void
send_decline(struct interface *iface)
{
send_message(iface, DHCP_DECLINE, NULL);
}
static void
log_dhcp(int lvl, const char *msg,
const struct interface *iface, const struct dhcp_message *dhcp)
{
char *a;
struct in_addr addr;
int r;
if (strcmp(msg, "NAK:") == 0)
a = get_option_string(dhcp, DHO_MESSAGE);
else {
addr.s_addr = dhcp->yiaddr;
a = xstrdup(inet_ntoa(addr));
}
r = get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID);
if (dhcp->servername[0] && r == 0)
logger(lvl, "%s: %s %s from %s `%s'", iface->name, msg, a,
inet_ntoa(addr), dhcp->servername);
else if (r == 0)
logger(lvl, "%s: %s %s from %s",
iface->name, msg, a, inet_ntoa(addr));
else
logger(lvl, "%s: %s %s", iface->name, msg, a);
free(a);
}
static void
handle_dhcp(struct interface *iface, struct dhcp_message **dhcpp)
{
struct if_state *state = iface->state;
struct if_options *ifo = state->options;
struct dhcp_message *dhcp = *dhcpp;
struct dhcp_lease *lease = &state->lease;
uint8_t type, tmp;
struct in_addr addr;
size_t i;
/* reset the message counter */
state->interval = 0;
/* We have to have DHCP type to work */
if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1) {
log_dhcp(LOG_ERR, "no DHCP type in", iface, dhcp);
return;
}
/* Ensure that it's not from a blacklisted server.
* We should expand this to check IP and/or hardware address
* at the packet level. */
if (ifo->blacklist_len != 0 &&
get_option_addr(&addr.s_addr, dhcp, DHO_SERVERID) == 0)
{
for (i = 0; i < ifo->blacklist_len; i++) {
if (ifo->blacklist[i] != addr.s_addr)
continue;
if (dhcp->servername[0])
logger(LOG_WARNING,
"%s: ignoring blacklisted server %s `%s'",
iface->name,
inet_ntoa(addr), dhcp->servername);
else
logger(LOG_WARNING,
"%s: ignoring blacklisted server %s",
iface->name, inet_ntoa(addr));
return;
}
}
/* We should restart on a NAK */
if (type == DHCP_NAK) {
log_dhcp(LOG_WARNING, "NAK:", iface, dhcp);
drop_config(iface, "EXPIRE");
delete_event(iface->raw_fd);
close(iface->raw_fd);
iface->raw_fd = -1;
close(iface->udp_fd);
iface->udp_fd = -1;
/* If we constantly get NAKS then we should slowly back off */
add_timeout_sec(state->nakoff, start_interface, iface);
state->nakoff *= 2;
if (state->nakoff > NAKOFF_MAX)
state->nakoff = NAKOFF_MAX;
return;
}
/* No NAK, so reset the backoff */
state->nakoff = 1;
/* Ensure that all required options are present */
for (i = 1; i < 255; i++) {
if (has_option_mask(ifo->requiremask, i) &&
get_option_uint8(&tmp, dhcp, i) != 0)
{
log_dhcp(LOG_WARNING, "reject", iface, dhcp);
return;
}
}
if (type == DHCP_OFFER && state->state == DHS_DISCOVERING) {
lease->addr.s_addr = dhcp->yiaddr;
get_option_addr(&lease->server.s_addr, dhcp, DHO_SERVERID);
log_dhcp(LOG_INFO, "offered", iface, dhcp);
if (ifo->options & DHCPCD_TEST) {
run_script(iface, "TEST");
exit(EXIT_SUCCESS);
}
free(state->offer);
state->offer = dhcp;
*dhcpp = NULL;
delete_timeout(send_discover, iface);
if (ifo->options & DHCPCD_ARP &&
iface->addr.s_addr != state->offer->yiaddr)
{
/* If the interface already has the address configured
* then we can't ARP for duplicate detection. */
addr.s_addr = state->offer->yiaddr;
if (!has_address(iface->name, &addr, NULL)) {
state->state = DHS_PROBING;
state->claims = 0;
state->probes = 0;
state->conflicts = 0;
send_arp_probe(iface);
return;
}
}
state->state = DHS_REQUESTING;
send_request(iface);
return;
}
if (type == DHCP_OFFER) {
log_dhcp(LOG_INFO, "ignoring offer of", iface, dhcp);
return;
}
/* We should only be dealing with acks */
if (type != DHCP_ACK) {
log_dhcp(LOG_ERR, "not ACK or OFFER", iface, dhcp);
return;
}
if (!(ifo->options & DHCPCD_INFORM))
log_dhcp(LOG_INFO, "acknowledged", iface, dhcp);
close_sockets(iface);
free(state->offer);
state->offer = dhcp;
*dhcpp = NULL;
/* Delete all timeouts for this interface. */
delete_timeout(NULL, iface);
bind_interface(iface);
}
static void
handle_dhcp_packet(void *arg)
{
struct interface *iface = arg;
uint8_t *packet;
struct dhcp_message *dhcp = NULL;
const uint8_t *pp;
uint8_t *p;
ssize_t bytes;
/* We loop through until our buffer is empty.
* The benefit is that if we get >1 DHCP packet in our buffer and
* the first one fails for any reason, we can use the next. */
packet = xmalloc(udp_dhcp_len);
for(;;) {
bytes = get_raw_packet(iface, ETHERTYPE_IP,
packet, udp_dhcp_len);
if (bytes == 0 || bytes == -1)
break;
if (valid_udp_packet(packet) == -1)
continue;
bytes = get_udp_data(&pp, packet);
if ((size_t)bytes > sizeof(*dhcp)) {
logger(LOG_ERR, "%s: packet greater than DHCP size",
iface->name);
continue;
}
if (!dhcp)
dhcp = xmalloc(sizeof(*dhcp));
memcpy(dhcp, pp, bytes);
if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
logger(LOG_DEBUG, "%s: bogus cookie, ignoring",
iface->name);
continue;
}
/* Ensure it's the right transaction */
if (iface->state->xid != dhcp->xid) {
logger(LOG_DEBUG,
"%s: ignoring packet with xid 0x%x as"
" it's not ours (0x%x)",
iface->name, dhcp->xid, iface->state->xid);
continue;
}
/* Ensure packet is for us */
if (iface->hwlen <= sizeof(dhcp->chaddr) &&
memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen))
{
logger(LOG_DEBUG, "%s: xid 0x%x is not for our hwaddr %s",
iface->name, dhcp->xid,
hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr)));
continue;
}
/* We should ensure that the packet is terminated correctly
* if we have space for the terminator */
if ((size_t)bytes < sizeof(struct dhcp_message)) {
p = (uint8_t *)dhcp + bytes - 1;
while (p > dhcp->options && *p == DHO_PAD)
p--;
if (*p != DHO_END)
*++p = DHO_END;
}
handle_dhcp(iface, &dhcp);
if (iface->raw_fd == -1)
break;
}
free(packet);
free(dhcp);
}
static void
open_sockets(struct interface *iface)
{
if (iface->udp_fd != -1)
close(iface->udp_fd);
if (open_udp_socket(iface) == -1 &&
(errno != EADDRINUSE || iface->addr.s_addr != 0))
logger(LOG_ERR, "open_udp_socket: %s", strerror(errno));
if (iface->raw_fd != -1)
delete_event(iface->raw_fd);
if (open_socket(iface, ETHERTYPE_IP) == -1)
logger(LOG_ERR, "open_socket: %s", strerror(errno));
if (iface->raw_fd != -1)
add_event(iface->raw_fd, handle_dhcp_packet, iface);
}
static void
handle_link(_unused void *arg)
{
struct interface *iface;
int retval;
retval = link_changed(linkfd, ifaces);
if (retval == -1) {
logger(LOG_ERR, "link_changed: %s", strerror(errno));
return;
}
if (retval == 0)
return;
for (iface = ifaces; iface; iface = iface->next) {
if (iface->state->options->options & DHCPCD_LINK) {
switch (carrier_status(iface->name)) {
case -1:
logger(LOG_ERR, "carrier_status: %s",
strerror(errno));
break;
case 0:
if (iface->state->carrier != LINK_DOWN) {
iface->state->carrier = LINK_DOWN;
logger(LOG_INFO, "%s: carrier lost",
iface->name);
close_sockets(iface);
delete_timeouts(iface, start_expire, NULL);
}
break;
default:
if (iface->state->carrier != LINK_UP) {
iface->state->carrier = LINK_UP;
logger(LOG_INFO, "%s: carrier acquired",
iface->name);
start_interface(iface);
}
break;
}
}
}
}
void
start_discover(void *arg)
{
struct interface *iface = arg;
struct if_options *ifo = iface->state->options;
iface->state->state = DHS_DISCOVERING;
iface->state->xid = arc4random();
open_sockets(iface);
delete_timeout(NULL, iface);
if (ifo->options & DHCPCD_IPV4LL &&
!IN_LINKLOCAL(htonl(iface->addr.s_addr)))
{
if (IN_LINKLOCAL(htonl(iface->state->fail.s_addr)))
add_timeout_sec(RATE_LIMIT_INTERVAL, start_ipv4ll, iface);
else
add_timeout_sec(ifo->timeout, start_ipv4ll, iface);
}
logger(LOG_INFO, "%s: broadcasting for a lease", iface->name);
send_discover(iface);
}
void
start_reboot(struct interface *iface)
{
struct if_options *ifo = iface->state->options;
logger(LOG_INFO, "%s: rebinding lease of %s",
iface->name, inet_ntoa(iface->state->lease.addr));
iface->state->state = DHS_REBINDING;
iface->state->xid = arc4random();
iface->state->lease.server.s_addr = 0;
delete_timeout(NULL, iface);
add_timeout_sec(ifo->timeout, start_expire, iface);
open_sockets(iface);
send_rebind(iface);
}
static void
send_release(struct interface *iface)
{
if (iface->state->lease.addr.s_addr &&
!IN_LINKLOCAL(htonl(iface->state->lease.addr.s_addr)))
{
logger(LOG_INFO, "%s: releasing lease of %s",
iface->name, inet_ntoa(iface->state->lease.addr));
open_sockets(iface);
send_message(iface, DHCP_RELEASE, NULL);
}
}
void
start_interface(void *arg)
{
struct interface *iface = arg;
iface->start_uptime = uptime();
if (!iface->state->lease.addr.s_addr)
start_discover(iface);
else if (IN_LINKLOCAL(htonl(iface->state->lease.addr.s_addr)))
start_ipv4ll(iface);
else
start_reboot(iface);
}
static void
configure_interface(struct interface *iface, int argc, char **argv)
{
struct if_state *ifs = iface->state;
struct if_options *ifo;
uint8_t *duid;
size_t len = 0, ifl;
free_options(ifs->options);
ifo = ifs->options = read_config(cffile, iface->name);
add_options(ifo, argc, argv);
if (ifo->metric != -1)
iface->metric = ifo->metric;
free(iface->clientid);
if (*ifo->clientid) {
iface->clientid = xmalloc(ifo->clientid[0] + 1);
memcpy(iface->clientid, ifo->clientid, ifo->clientid[0] + 1);
} else if (ifo->options & DHCPCD_CLIENTID) {
if (ifo->options & DHCPCD_DUID) {
duid = xmalloc(DUID_LEN);
if ((len = get_duid(duid, iface)) == 0)
logger(LOG_ERR, "get_duid: %s", strerror(errno));
}
if (len > 0) {
iface->clientid = xmalloc(len + 6);
iface->clientid[0] = len + 5;
iface->clientid[1] = 255; /* RFC 4361 */
ifl = strlen(iface->name);
if (ifl < 5) {
memcpy(iface->clientid + 2, iface->name, ifl);
if (ifl < 4)
memset(iface->clientid + 2 + ifl,
0, 4 - ifl);
} else {
ifl = htonl(if_nametoindex(iface->name));
memcpy(iface->clientid + 2, &ifl, 4);
}
} else if (len == 0) {
len = iface->hwlen + 1;
iface->clientid = xmalloc(len + 1);
iface->clientid[0] = len;
iface->clientid[1] = iface->family;
memcpy(iface->clientid + 2, iface->hwaddr, iface->hwlen);
}
}
}
static void
init_state(struct interface *iface, int argc, char **argv)
{
struct if_state *ifs;
if (iface->state) {
ifs = iface->state;
} else
ifs = iface->state = xzalloc(sizeof(*ifs));
ifs->state = DHS_INIT;
ifs->nakoff = 1;
configure_interface(iface, argc, argv);
if (!(ifs->options->options & DHCPCD_TEST))
run_script(iface, "PREINIT");
if (ifs->options->options & DHCPCD_LINK) {
switch (carrier_status(iface->name)) {
case 0:
ifs->carrier = LINK_DOWN;
break;
case 1:
ifs->carrier = LINK_UP;
break;
default:
ifs->carrier = LINK_UNKNOWN;
}
}
if (ifs->carrier == LINK_DOWN)
logger(LOG_INFO, "%s: waiting for carrier", iface->name);
else
start_interface(iface);
}
static void
handle_signal(_unused void *arg)
{
struct interface *iface;
int sig = signal_read();
int do_reboot = 0, do_release = 0;
switch (sig) {
case SIGINT:
logger(LOG_INFO, "received SIGINT, stopping");
break;
case SIGTERM:
logger(LOG_INFO, "received SIGTERM, stopping");
break;
case SIGALRM:
logger(LOG_INFO, "received SIGALRM, rebinding lease");
do_reboot = 1;
break;
case SIGHUP:
logger(LOG_INFO, "received SIGHUP, releasing lease");
do_release = 1;
break;
default:
logger (LOG_ERR,
"received signal %d, but don't know what to do with it",
sig);
return;
}
for (iface = ifaces; iface; iface = iface->next) {
if (!iface->state)
continue;
if (do_reboot)
start_reboot(iface);
else {
if (do_release)
send_release(iface);
if (!(iface->state->options->options & DHCPCD_PERSISTENT))
drop_config(iface, do_release ? "RELEASE" : "STOP");
}
}
exit(EXIT_FAILURE);
}
int
handle_args(int argc, char **argv)
{
struct interface *ifs, *ifp, *ifl = NULL;
int do_exit = 0, do_release = 0, do_reboot = 0, opt, oi = 0;
optind = 0;
while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
{
switch (opt) {
case 'k':
do_release = 1;
break;
case 'n':
do_reboot = 1;
break;
case 'x':
do_exit = 1;
break;
}
}
/* We only deal with one interface here */
if (optind == argc) {
logger(LOG_ERR, "handle_args: no interface");
return -1;
}
if (do_release || do_reboot || do_exit) {
for (oi = optind; oi < argc; oi++) {
for (ifp = ifaces; ifp; ifp = ifp->next) {
if (strcmp(ifp->name, argv[oi]) == 0)
break;
ifl = ifp;
}
if (!ifp)
continue;
if (do_release)
send_release(ifp);
if (do_exit || do_release) {
drop_config(ifp, do_release ? "RELEASE" : "STOP");
close_sockets(ifp);
delete_timeout(NULL, ifp);
if (ifl)
ifl->next = ifp->next;
else
ifaces = ifp->next;
free_interface(ifp);
} else if (do_reboot) {
configure_interface(ifp, argc, argv);
start_reboot(ifp);
}
}
return 0;
}
if ((ifs = discover_interfaces(argc, argv))) {
argc += optind;
argv -= optind;
for (ifp = ifs; ifp; ifp = ifp->next)
init_state(ifp, argc, argv);
if (ifaces) {
ifp = ifaces;
while (ifp->next)
ifp = ifp->next;
ifp->next = ifs;
} else
ifaces = ifs;
}
return 0;
}
int
main(int argc, char **argv)
{
struct if_options *ifo;
struct interface *iface;
int opt, oi = 0, test = 0, signal_fd, sig = 0, i, control_fd;
pid_t pid;
struct timespec ts;
closefrom(3);
/* Saves calling fflush(stream) in the logger */
setlinebuf(stdout);
openlog(PACKAGE, LOG_PID, LOG_LOCAL0);
strlcpy(cffile, CONFIG, sizeof(cffile));
/* 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);
}
}
while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
{
switch (opt) {
case 'd':
setloglevel(LOG_DEBUG);
break;
case 'f':
strlcpy(cffile, optarg, sizeof(cffile));
break;
case 'k':
sig = SIGHUP;
break;
case 'n':
sig = SIGALRM;
break;
case 'x':
sig = SIGTERM;
break;
case 'T':
test = 1;
break;
case 'V':
print_options();
exit(EXIT_SUCCESS);
case '?':
usage();
exit(EXIT_FAILURE);
}
}
ifo = read_config(cffile, NULL);
opt = add_options(ifo, argc, argv);
if (opt != 1) {
if (opt == 0)
usage();
exit(EXIT_FAILURE);
}
/* If we have any other args, we should run as a single dhcpcd instance
* for that interface. */
if (optind == argc - 1)
snprintf(pidfile, sizeof(pidfile), PIDFILE, "-", argv[optind]);
else {
snprintf(pidfile, sizeof(pidfile), PIDFILE, "", "");
master = 1;
}
if (test)
ifo->options |= DHCPCD_TEST | DHCPCD_PERSISTENT;
#ifdef THERE_IS_NO_FORK
ifo->options &= ~DHCPCD_DAEMONISE;
#endif
chdir("/");
umask(022);
atexit(cleanup);
if (!master) {
control_fd = open_control();
if (control_fd != -1) {
logger(LOG_INFO, "sending commands to master dhcpcd process");
i = send_control(argc, argv);
if (i > 0) {
logger(LOG_DEBUG, "send OK");
exit(EXIT_SUCCESS);
} else {
logger(LOG_ERR, "failed to send commands");
exit(EXIT_FAILURE);
}
}
}
if (geteuid())
logger(LOG_WARNING, PACKAGE " will not work correctly unless"
" run as root");
if (sig != 0) {
i = -1;
pid = read_pid();
if (pid != 0)
logger(LOG_INFO, "sending signal %d to pid %d",
sig, pid);
if (!pid || (i = kill(pid, sig))) {
if (sig != SIGALRM)
logger(LOG_ERR, ""PACKAGE" not running");
unlink(pidfile);
exit(EXIT_FAILURE);
}
/* Spin until it exits */
logger(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);
}
logger(LOG_ERR, "pid %d failed to exit", pid);
exit(EXIT_FAILURE);
}
if (!(ifo->options & DHCPCD_TEST)) {
if ((pid = read_pid()) > 0 &&
kill(pid, 0) == 0)
{
logger(LOG_ERR, ""PACKAGE
" already running on pid %d (%s)",
pid, pidfile);
exit(EXIT_FAILURE);
}
pidfd = open(pidfile, O_WRONLY | O_CREAT | O_NONBLOCK, 0664);
if (pidfd == -1) {
logger(LOG_ERR, "open `%s': %s",
pidfile, strerror(errno));
exit(EXIT_FAILURE);
}
/* Lock the file so that only one instance of dhcpcd runs
* on an interface */
if (flock(pidfd, LOCK_EX | LOCK_NB) == -1) {
logger(LOG_ERR, "flock `%s': %s",
pidfile, strerror(errno));
exit(EXIT_FAILURE);
}
if (set_cloexec(pidfd) == -1)
exit(EXIT_FAILURE);
writepid(pidfd, getpid());
}
logger(LOG_INFO, PACKAGE " " VERSION " starting");
if ((signal_fd =signal_init()) == -1)
exit(EXIT_FAILURE);
if (signal_setup() == -1)
exit(EXIT_FAILURE);
add_event(signal_fd, handle_signal, NULL);
if (master) {
if (start_control() == -1) {
logger(LOG_ERR, "start_control: %s", strerror(errno));
exit(EXIT_FAILURE);
}
}
if (ifo->options & DHCPCD_LINK) {
linkfd = open_link_socket();
if (linkfd == -1)
logger(LOG_ERR, "open_link_socket: %s",
strerror(errno));
else
add_event(linkfd, handle_link, NULL);
}
if (!(ifo->options & DHCPCD_DAEMONISE))
can_daemonise = 0;
if (can_daemonise && !(ifo->options & DHCPCD_BACKGROUND)) {
oi = ifo->timeout;
if (ifo->options & DHCPCD_IPV4LL)
oi += 10;
add_timeout_sec(oi, handle_exit_timeout, NULL);
}
free_options(ifo);
argc -= optind;
argv += optind;
ifaces = discover_interfaces(argc, argv);
argc += optind;
argv -= optind;
for (iface = ifaces; iface; iface = iface->next)
init_state(iface, argc, argv);
start_eloop();
/* NOTREACHED */
}