dhcpcd/dhcpcd.c
2012-11-11 19:56:01 +00:00

2206 lines
54 KiB
C

/*
* dhcpcd - DHCP client daemon
* Copyright (c) 2006-2012 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-2012 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 <arpa/inet.h>
#include <net/route.h>
#ifdef __linux__
# include <asm/types.h> /* for systems with broken headers */
# include <linux/rtnetlink.h>
#endif
#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 "bind.h"
#include "config.h"
#include "common.h"
#include "configure.h"
#include "control.h"
#include "dhcpcd.h"
#include "dhcp6.h"
#include "duid.h"
#include "eloop.h"
#include "if-options.h"
#include "if-pref.h"
#include "ipv4ll.h"
#include "ipv6.h"
#include "ipv6ns.h"
#include "ipv6rs.h"
#include "net.h"
#include "platform.h"
#include "signals.h"
/* We should define a maximum for the NAK exponential backoff */
#define NAKOFF_MAX 60
/* Wait N nanoseconds between sending a RELEASE and dropping the address.
* This gives the kernel enough time to actually send it. */
#define RELEASE_DELAY_S 0
#define RELEASE_DELAY_NS 10000000
char vendor[VENDORCLASSID_MAX_LEN];
int pidfd = -1;
struct interface *ifaces = NULL;
int ifac = 0;
char **ifav = NULL;
int ifdc = 0;
char **ifdv = NULL;
sigset_t dhcpcd_sigset;
static char *cffile;
static struct if_options *if_options;
static char *pidfile;
static int linkfd = -1, ipv6rsfd = -1, ipv6nsfd = -1;
static uint8_t *packet;
static char **ifv;
static int ifc;
static char **margv;
static int margc;
struct dhcp_op {
uint8_t value;
const char *name;
};
static const struct dhcp_op dhcp_ops[] = {
{ DHCP_DISCOVER, "DISCOVER" },
{ DHCP_OFFER, "OFFER" },
{ DHCP_REQUEST, "REQUEST" },
{ DHCP_DECLINE, "DECLINE" },
{ DHCP_ACK, "ACK" },
{ DHCP_NAK, "NAK" },
{ DHCP_RELEASE, "RELEASE" },
{ DHCP_INFORM, "INFORM" },
{ 0, NULL }
};
static void send_release(struct interface *);
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;
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 *iface;
int i;
free_options(if_options);
while (ifaces) {
iface = ifaces;
ifaces = iface->next;
free_interface(iface);
}
for (i = 0; i < ifac; i++)
free(ifav[i]);
free(ifav);
for (i = 0; i < ifdc; i++)
free(ifdv[i]);
free(ifdv);
free(packet);
#endif
if (linkfd != -1)
close(linkfd);
if (pidfd > -1) {
if (options & DHCPCD_MASTER) {
if (stop_control() == -1)
syslog(LOG_ERR, "stop_control: %m");
}
close(pidfd);
unlink(pidfile);
}
#ifdef DEBUG_MEMORY
free(pidfile);
#endif
if (options & DHCPCD_STARTED && !(options & DHCPCD_FORKED))
syslog(LOG_INFO, "exited");
}
/* ARGSUSED */
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);
add_timeout_sec(timeout, handle_exit_timeout, NULL);
}
void
drop_dhcp(struct interface *iface, const char *reason)
{
free(iface->state->old);
iface->state->old = iface->state->new;
iface->state->new = NULL;
iface->state->reason = reason;
configure(iface);
free(iface->state->old);
iface->state->old = NULL;
iface->state->lease.addr.s_addr = 0;
}
struct interface *
find_interface(const char *ifname)
{
struct interface *ifp;
for (ifp = ifaces; ifp; ifp = ifp->next)
if (strcmp(ifp->name, ifname) == 0)
return ifp;
return NULL;
}
static void
stop_interface(struct interface *iface)
{
struct interface *ifp, *ifl = NULL;
syslog(LOG_INFO, "%s: removing interface", iface->name);
// Remove the interface from our list
for (ifp = ifaces; ifp; ifp = ifp->next) {
if (ifp == iface)
break;
ifl = ifp;
}
if (ifl)
ifl->next = ifp->next;
else
ifaces = ifp->next;
dhcp6_drop(iface, NULL);
ipv6rs_drop(iface);
if (strcmp(iface->state->reason, "RELEASE") != 0)
drop_dhcp(iface, "STOP");
close_sockets(iface);
delete_timeout(NULL, iface);
free_interface(ifp);
if (!(options & (DHCPCD_MASTER | DHCPCD_TEST)))
exit(EXIT_FAILURE);
}
static uint32_t
dhcp_xid(struct interface *iface)
{
uint32_t xid;
if (iface->state->options->options & DHCPCD_XID_HWADDR &&
iface->hwlen >= sizeof(xid))
/* The lower bits are probably more unique on the network */
memcpy(&xid, (iface->hwaddr + iface->hwlen) - sizeof(xid),
sizeof(xid));
else
xid = arc4random();
return xid;
}
static void
send_message(struct interface *iface, int type,
void (*callback)(void *))
{
struct if_state *state = iface->state;
struct if_options *ifo = state->options;
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)
syslog(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);
syslog(LOG_DEBUG,
"%s: sending %s (xid 0x%x), next in %0.2f seconds",
iface->name, get_dhcp_op(type), state->xid,
timeval_to_double(&tv));
}
/* Ensure sockets are open. */
if (open_sockets(iface) == -1) {
if (!(options & DHCPCD_TEST))
drop_dhcp(iface, "FAIL");
return;
}
/* 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.
* Also, we should not unicast from a BOOTP lease. */
if (iface->udp_fd == -1 ||
(!(ifo->options & DHCPCD_INFORM) && is_bootp(iface->state->new)))
{
a = iface->addr.s_addr;
iface->addr.s_addr = 0;
}
len = make_message(&dhcp, iface, type);
if (a)
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) {
syslog(LOG_ERR, "%s: send_packet: %m", iface->name);
close_sockets(iface);
}
} else {
len = make_udp_packet(&udp, (uint8_t *)dhcp, len, from, to);
r = send_raw_packet(iface, ETHERTYPE_IP, udp, len);
free(udp);
/* If we failed to send a raw packet this normally means
* we don't have the ability to work beneath the IP layer
* for this interface.
* As such we remove it from consideration without actually
* stopping the interface. */
if (r == -1) {
syslog(LOG_ERR, "%s: send_raw_packet: %m", iface->name);
if (!(options & DHCPCD_TEST))
drop_dhcp(iface, "FAIL");
close_sockets(iface);
delete_timeout(NULL, iface);
callback = NULL;
}
}
free(dhcp);
/* Even if we fail to send a packet we should continue as we are
* as our failure timeouts will change out codepath when needed. */
if (callback)
add_timeout_tv(&tv, callback, iface);
}
static void
send_inform(void *arg)
{
send_message((struct interface *)arg, DHCP_INFORM, send_inform);
}
static void
send_discover(void *arg)
{
send_message((struct interface *)arg, DHCP_DISCOVER, send_discover);
}
static 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);
}
static void
send_rebind(void *arg)
{
send_message((struct interface *)arg, DHCP_REQUEST, send_rebind);
}
void
start_expire(void *arg)
{
struct interface *iface = arg;
iface->state->interval = 0;
if (iface->addr.s_addr == 0) {
/* We failed to reboot, so enter discovery. */
iface->state->lease.addr.s_addr = 0;
start_discover(iface);
return;
}
syslog(LOG_ERR, "%s: lease expired", iface->name);
delete_timeout(NULL, iface);
drop_dhcp(iface, "EXPIRE");
unlink(iface->leasefile);
if (iface->carrier != LINK_DOWN)
start_interface(iface);
}
static void
log_dhcp(int lvl, const char *msg,
const struct interface *iface, const struct dhcp_message *dhcp,
const struct in_addr *from)
{
const char *tfrom;
char *a;
struct in_addr addr;
int r;
if (strcmp(msg, "NAK:") == 0)
a = get_option_string(dhcp, DHO_MESSAGE);
else if (dhcp->yiaddr != 0) {
addr.s_addr = dhcp->yiaddr;
a = xstrdup(inet_ntoa(addr));
} else
a = NULL;
tfrom = "from";
r = get_option_addr(&addr, dhcp, DHO_SERVERID);
if (dhcp->servername[0] && r == 0)
syslog(lvl, "%s: %s %s %s %s `%s'", iface->name, msg, a,
tfrom, inet_ntoa(addr), dhcp->servername);
else {
if (r != 0) {
tfrom = "via";
addr = *from;
}
if (a == NULL)
syslog(lvl, "%s: %s %s %s",
iface->name, msg, tfrom, inet_ntoa(addr));
else
syslog(lvl, "%s: %s %s %s %s",
iface->name, msg, a, tfrom, inet_ntoa(addr));
}
free(a);
}
static int
blacklisted_ip(const struct if_options *ifo, in_addr_t addr)
{
size_t i;
for (i = 0; i < ifo->blacklist_len; i += 2)
if (ifo->blacklist[i] == (addr & ifo->blacklist[i + 1]))
return 1;
return 0;
}
static int
whitelisted_ip(const struct if_options *ifo, in_addr_t addr)
{
size_t i;
if (ifo->whitelist_len == 0)
return -1;
for (i = 0; i < ifo->whitelist_len; i += 2)
if (ifo->whitelist[i] == (addr & ifo->whitelist[i + 1]))
return 1;
return 0;
}
static void
handle_dhcp(struct interface *iface, struct dhcp_message **dhcpp, const struct in_addr *from)
{
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 may have found a BOOTP server */
if (get_option_uint8(&type, dhcp, DHO_MESSAGETYPE) == -1)
type = 0;
if (type == DHCP_NAK) {
/* For NAK, only check if we require the ServerID */
if (has_option_mask(ifo->requiremask, DHO_SERVERID) &&
get_option_addr(&addr, dhcp, DHO_SERVERID) == -1)
{
log_dhcp(LOG_WARNING, "reject NAK", iface, dhcp, from);
return;
}
/* We should restart on a NAK */
log_dhcp(LOG_WARNING, "NAK:", iface, dhcp, from);
if (!(options & DHCPCD_TEST)) {
drop_dhcp(iface, "NAK");
unlink(iface->leasefile);
}
close_sockets(iface);
/* If we constantly get NAKS then we should slowly back off */
add_timeout_sec(state->nakoff, start_interface, iface);
if (state->nakoff == 0)
state->nakoff = 1;
else {
state->nakoff *= 2;
if (state->nakoff > NAKOFF_MAX)
state->nakoff = NAKOFF_MAX;
}
return;
}
/* 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)
{
/* If we are bootp, then ignore the need for serverid.
* To ignore bootp, require dhcp_message_type instead. */
if (type == 0 && i == DHO_SERVERID)
continue;
log_dhcp(LOG_WARNING, "reject DHCP", iface, dhcp, from);
return;
}
}
/* Ensure that the address offered is valid */
if ((type == 0 || type == DHCP_OFFER || type == DHCP_ACK) &&
(dhcp->ciaddr == INADDR_ANY || dhcp->ciaddr == INADDR_BROADCAST) &&
(dhcp->yiaddr == INADDR_ANY || dhcp->yiaddr == INADDR_BROADCAST))
{
log_dhcp(LOG_WARNING, "reject invalid address",
iface, dhcp, from);
return;
}
/* No NAK, so reset the backoff */
state->nakoff = 0;
if ((type == 0 || type == DHCP_OFFER) &&
state->state == DHS_DISCOVER)
{
lease->frominfo = 0;
lease->addr.s_addr = dhcp->yiaddr;
lease->cookie = dhcp->cookie;
if (type == 0 ||
get_option_addr(&lease->server, dhcp, DHO_SERVERID) != 0)
lease->server.s_addr = INADDR_ANY;
log_dhcp(LOG_INFO, "offered", iface, dhcp, from);
free(state->offer);
state->offer = dhcp;
*dhcpp = NULL;
if (options & DHCPCD_TEST) {
free(state->old);
state->old = state->new;
state->new = state->offer;
state->offer = NULL;
state->reason = "TEST";
run_script(iface);
exit(EXIT_SUCCESS);
}
delete_timeout(send_discover, iface);
/* We don't request BOOTP addresses */
if (type) {
/* We used to ARP check here, but that seems to be in
* violation of RFC2131 where it only describes
* DECLINE after REQUEST.
* It also seems that some MS DHCP servers actually
* ignore DECLINE if no REQUEST, ie we decline a
* DISCOVER. */
start_request(iface);
return;
}
}
if (type) {
if (type == DHCP_OFFER) {
log_dhcp(LOG_INFO, "ignoring offer of",
iface, dhcp, from);
return;
}
/* We should only be dealing with acks */
if (type != DHCP_ACK) {
log_dhcp(LOG_ERR, "not ACK or OFFER",
iface, dhcp, from);
return;
}
if (!(ifo->options & DHCPCD_INFORM))
log_dhcp(LOG_INFO, "acknowledged", iface, dhcp, from);
}
/* BOOTP could have already assigned this above, so check we still
* have a pointer. */
if (*dhcpp) {
free(state->offer);
state->offer = dhcp;
*dhcpp = NULL;
}
lease->frominfo = 0;
delete_timeout(NULL, iface);
/* We now have an offer, so close the DHCP sockets.
* This allows us to safely ARP when broken DHCP servers send an ACK
* follows by an invalid NAK. */
close_sockets(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) != 1) {
state->claims = 0;
state->probes = 0;
state->conflicts = 0;
state->state = DHS_PROBE;
send_arp_probe(iface);
return;
}
}
bind_interface(iface);
}
static void
handle_dhcp_packet(void *arg)
{
struct interface *iface = arg;
struct dhcp_message *dhcp = NULL;
const uint8_t *pp;
ssize_t bytes;
struct in_addr from;
int i, partialcsum = 0;
/* 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. */
if (packet == NULL)
packet = xmalloc(udp_dhcp_len);
for(;;) {
bytes = get_raw_packet(iface, ETHERTYPE_IP,
packet, udp_dhcp_len, &partialcsum);
if (bytes == 0 || bytes == -1)
break;
if (valid_udp_packet(packet, bytes, &from, partialcsum) == -1) {
syslog(LOG_ERR, "%s: invalid UDP packet from %s",
iface->name, inet_ntoa(from));
continue;
}
i = whitelisted_ip(iface->state->options, from.s_addr);
if (i == 0) {
syslog(LOG_WARNING,
"%s: non whitelisted DHCP packet from %s",
iface->name, inet_ntoa(from));
continue;
} else if (i != 1 &&
blacklisted_ip(iface->state->options, from.s_addr) == 1)
{
syslog(LOG_WARNING,
"%s: blacklisted DHCP packet from %s",
iface->name, inet_ntoa(from));
continue;
}
if (iface->flags & IFF_POINTOPOINT &&
iface->dst.s_addr != from.s_addr)
{
syslog(LOG_WARNING,
"%s: server %s is not destination",
iface->name, inet_ntoa(from));
}
bytes = get_udp_data(&pp, packet);
if ((size_t)bytes > sizeof(*dhcp)) {
syslog(LOG_ERR,
"%s: packet greater than DHCP size from %s",
iface->name, inet_ntoa(from));
continue;
}
if (!dhcp)
dhcp = xzalloc(sizeof(*dhcp));
memcpy(dhcp, pp, bytes);
if (dhcp->cookie != htonl(MAGIC_COOKIE)) {
syslog(LOG_DEBUG, "%s: bogus cookie from %s",
iface->name, inet_ntoa(from));
continue;
}
/* Ensure it's the right transaction */
if (iface->state->xid != ntohl(dhcp->xid)) {
syslog(LOG_DEBUG,
"%s: wrong xid 0x%x (expecting 0x%x) from %s",
iface->name, ntohl(dhcp->xid), iface->state->xid,
inet_ntoa(from));
continue;
}
/* Ensure packet is for us */
if (iface->hwlen <= sizeof(dhcp->chaddr) &&
memcmp(dhcp->chaddr, iface->hwaddr, iface->hwlen))
{
syslog(LOG_DEBUG, "%s: xid 0x%x is not for hwaddr %s",
iface->name, ntohl(dhcp->xid),
hwaddr_ntoa(dhcp->chaddr, sizeof(dhcp->chaddr)));
continue;
}
handle_dhcp(iface, &dhcp, &from);
if (iface->raw_fd == -1)
break;
}
free(packet);
packet = NULL;
free(dhcp);
}
static void
send_release(struct interface *iface)
{
struct timespec ts;
if (iface->state->new != NULL &&
iface->state->new->cookie == htonl(MAGIC_COOKIE))
{
syslog(LOG_INFO, "%s: releasing lease of %s",
iface->name, inet_ntoa(iface->state->lease.addr));
iface->state->xid = dhcp_xid(iface);
send_message(iface, DHCP_RELEASE, NULL);
/* Give the packet a chance to go before dropping the ip */
ts.tv_sec = RELEASE_DELAY_S;
ts.tv_nsec = RELEASE_DELAY_NS;
nanosleep(&ts, NULL);
drop_dhcp(iface, "RELEASE");
}
unlink(iface->leasefile);
}
void
send_decline(struct interface *iface)
{
send_message(iface, DHCP_DECLINE, NULL);
}
static void
configure_interface1(struct interface *iface)
{
struct if_state *ifs = iface->state;
struct if_options *ifo = ifs->options;
uint8_t *duid;
size_t len, ifl;
/* Do any platform specific configuration */
if_conf(iface);
if (iface->flags & IFF_POINTOPOINT && !(ifo->options & DHCPCD_INFORM))
ifo->options |= DHCPCD_STATIC;
if (iface->flags & IFF_NOARP ||
ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))
ifo->options &= ~(DHCPCD_ARP | DHCPCD_IPV4LL);
if (!(iface->flags & (IFF_POINTOPOINT | IFF_LOOPBACK | IFF_MULTICAST)))
ifo->options &= ~DHCPCD_IPV6RS;
if (ifo->options & DHCPCD_LINK && carrier_status(iface) == -1)
ifo->options &= ~DHCPCD_LINK;
if (ifo->metric != -1)
iface->metric = ifo->metric;
/* We want to disable kernel interface RA as early as possible. */
if (options & DHCPCD_IPV6RS && ifo->options & DHCPCD_IPV6RS) {
if (check_ipv6(iface->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 (iface->hwlen > DHCP_CHADDR_LEN)
ifo->options |= DHCPCD_CLIENTID;
/* Firewire and InfiniBand interfaces require ClientID and
* the broadcast option being set. */
switch (iface->family) {
case ARPHRD_IEEE1394: /* FALLTHROUGH */
case ARPHRD_INFINIBAND:
ifo->options |= DHCPCD_CLIENTID | DHCPCD_BROADCAST;
break;
}
free(iface->clientid);
iface->clientid = NULL;
if (!(ifo->options & DHCPCD_IPV4))
return;
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) {
len = 0;
if (ifo->options & DHCPCD_DUID) {
duid = xmalloc(DUID_LEN);
if ((len = get_duid(duid, iface)) == 0)
syslog(LOG_ERR, "get_duid: %m");
} else
duid = NULL;
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(iface->index);
memcpy(iface->clientid + 2, &ifl, 4);
}
memcpy(iface->clientid + 6, duid, len);
} 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);
}
free(duid);
}
if (ifo->options & DHCPCD_CLIENTID)
syslog(LOG_DEBUG, "%s: using ClientID %s", iface->name,
hwaddr_ntoa(iface->clientid + 1, *iface->clientid));
else if (iface->hwlen)
syslog(LOG_DEBUG, "%s: using hwaddr %s", iface->name,
hwaddr_ntoa(iface->hwaddr, iface->hwlen));
}
int
select_profile(struct interface *iface, const char *profile)
{
struct if_options *ifo;
int ret;
ret = 0;
ifo = read_config(cffile, iface->name, iface->ssid, profile);
if (ifo == NULL) {
syslog(LOG_DEBUG, "%s: no profile %s", iface->name, profile);
ret = -1;
goto exit;
}
if (profile != NULL) {
strlcpy(iface->state->profile, profile,
sizeof(iface->state->profile));
syslog(LOG_INFO, "%s: selected profile %s",
iface->name, profile);
} else
*iface->state->profile = '\0';
free_options(iface->state->options);
iface->state->options = ifo;
exit:
if (profile)
configure_interface1(iface);
return ret;
}
static void
start_fallback(void *arg)
{
struct interface *iface;
iface = (struct interface *)arg;
select_profile(iface, iface->state->options->fallback);
start_interface(iface);
}
static void
configure_interface(struct interface *iface, int argc, char **argv)
{
select_profile(iface, NULL);
add_options(iface->state->options, argc, argv);
configure_interface1(iface);
}
void
handle_carrier(int action, int flags, const char *ifname)
{
struct interface *iface;
int carrier;
if (!(options & DHCPCD_LINK))
return;
for (iface = ifaces; iface; iface = iface->next)
if (strcmp(iface->name, ifname) == 0)
break;
if (!iface) {
if (options & DHCPCD_LINK)
handle_interface(1, ifname);
return;
}
if (!(iface->state->options->options & DHCPCD_LINK))
return;
if (action) {
carrier = action == 1 ? 1 : 0;
iface->flags = flags;
} else
carrier = carrier_status(iface);
if (carrier == -1)
syslog(LOG_ERR, "%s: carrier_status: %m", ifname);
else if (carrier == 0 || ~iface->flags & IFF_UP) {
if (iface->carrier != LINK_DOWN) {
iface->carrier = LINK_DOWN;
syslog(LOG_INFO, "%s: carrier lost", iface->name);
close_sockets(iface);
delete_timeouts(iface, start_expire, NULL);
dhcp6_drop(iface, "EXPIRE6");
ipv6rs_drop(iface);
drop_dhcp(iface, "NOCARRIER");
}
} else if (carrier == 1 && !(~iface->flags & IFF_UP)) {
if (iface->carrier != LINK_UP) {
iface->carrier = LINK_UP;
syslog(LOG_INFO, "%s: carrier acquired", iface->name);
if (iface->wireless)
getifssid(iface->name, iface->ssid);
configure_interface(iface, margc, margv);
iface->state->interval = 0;
iface->state->reason = "CARRIER";
run_script(iface);
start_interface(iface);
}
}
}
void
start_discover(void *arg)
{
struct interface *iface = arg;
struct if_options *ifo = iface->state->options;
int timeout = ifo->timeout;
/* If we're rebooting and we're not daemonised then we need
* to shorten the normal timeout to ensure we try correctly
* for a fallback or IPv4LL address. */
if (iface->state->state == DHS_REBOOT &&
!(options & DHCPCD_DAEMONISED))
{
timeout -= ifo->reboot;
if (timeout <= 0)
timeout = 2;
}
iface->state->state = DHS_DISCOVER;
iface->state->xid = dhcp_xid(iface);
delete_timeout(NULL, iface);
if (ifo->fallback)
add_timeout_sec(timeout, start_fallback, iface);
else 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(timeout, start_ipv4ll, iface);
}
if (ifo->options & DHCPCD_REQUEST)
syslog(LOG_INFO, "%s: broadcasting for a lease (requesting %s)",
iface->name, inet_ntoa(ifo->req_addr));
else
syslog(LOG_INFO, "%s: broadcasting for a lease", iface->name);
send_discover(iface);
}
void
start_request(void *arg)
{
struct interface *iface = arg;
iface->state->state = DHS_REQUEST;
send_request(iface);
}
void
start_renew(void *arg)
{
struct interface *iface = arg;
struct dhcp_lease *lease = &iface->state->lease;
syslog(LOG_INFO, "%s: renewing lease of %s",
iface->name, inet_ntoa(lease->addr));
syslog(LOG_DEBUG, "%s: rebind in %u seconds, expire in %u seconds",
iface->name, lease->rebindtime - lease->renewaltime,
lease->leasetime - lease->renewaltime);
iface->state->state = DHS_RENEW;
iface->state->xid = dhcp_xid(iface);
send_renew(iface);
}
void
start_rebind(void *arg)
{
struct interface *iface = arg;
struct dhcp_lease *lease = &iface->state->lease;
syslog(LOG_ERR, "%s: failed to renew, attempting to rebind",
iface->name);
syslog(LOG_DEBUG, "%s: expire in %u seconds",
iface->name, lease->leasetime - lease->rebindtime);
iface->state->state = DHS_REBIND;
delete_timeout(send_renew, iface);
iface->state->lease.server.s_addr = 0;
send_rebind(iface);
}
static void
start_timeout(void *arg)
{
struct interface *iface = arg;
bind_interface(iface);
iface->state->interval = 0;
start_discover(iface);
}
static struct dhcp_message *
dhcp_message_new(struct in_addr *addr, struct in_addr *mask)
{
struct dhcp_message *dhcp;
uint8_t *p;
dhcp = xzalloc(sizeof(*dhcp));
dhcp->yiaddr = addr->s_addr;
p = dhcp->options;
if (mask && mask->s_addr != INADDR_ANY) {
*p++ = DHO_SUBNETMASK;
*p++ = sizeof(mask->s_addr);
memcpy(p, &mask->s_addr, sizeof(mask->s_addr));
p+= sizeof(mask->s_addr);
}
*p++ = DHO_END;
return dhcp;
}
static int
handle_3rdparty(struct interface *iface)
{
struct if_options *ifo;
struct in_addr addr, net, dst;
ifo = iface->state->options;
if (ifo->req_addr.s_addr != INADDR_ANY)
return 0;
if (get_address(iface->name, &addr, &net, &dst) == 1)
handle_ifa(RTM_NEWADDR, iface->name, &addr, &net, &dst);
else {
syslog(LOG_INFO,
"%s: waiting for 3rd party to configure IP address",
iface->name);
iface->state->reason = "3RDPARTY";
run_script(iface);
}
return 1;
}
static void
start_static(struct interface *iface)
{
struct if_options *ifo;
if (handle_3rdparty(iface))
return;
ifo = iface->state->options;
iface->state->offer =
dhcp_message_new(&ifo->req_addr, &ifo->req_mask);
delete_timeout(NULL, iface);
bind_interface(iface);
}
static void
start_inform(struct interface *iface)
{
if (handle_3rdparty(iface))
return;
if (options & DHCPCD_TEST) {
iface->addr.s_addr = iface->state->options->req_addr.s_addr;
iface->net.s_addr = iface->state->options->req_mask.s_addr;
} else {
iface->state->options->options |= DHCPCD_STATIC;
start_static(iface);
}
iface->state->state = DHS_INFORM;
iface->state->xid = dhcp_xid(iface);
send_inform(iface);
}
void
start_reboot(struct interface *iface)
{
struct if_options *ifo = iface->state->options;
if (ifo->options & DHCPCD_LINK && iface->carrier == LINK_DOWN) {
syslog(LOG_INFO, "%s: waiting for carrier", iface->name);
return;
}
if (ifo->options & DHCPCD_STATIC) {
start_static(iface);
return;
}
if (ifo->reboot == 0 || iface->state->offer == NULL) {
start_discover(iface);
return;
}
if (ifo->options & DHCPCD_INFORM) {
syslog(LOG_INFO, "%s: informing address of %s",
iface->name, inet_ntoa(iface->state->lease.addr));
} else if (iface->state->offer->cookie == 0) {
if (ifo->options & DHCPCD_IPV4LL) {
iface->state->claims = 0;
send_arp_announce(iface);
} else
start_discover(iface);
return;
} else {
syslog(LOG_INFO, "%s: rebinding lease of %s",
iface->name, inet_ntoa(iface->state->lease.addr));
}
iface->state->state = DHS_REBOOT;
iface->state->xid = dhcp_xid(iface);
iface->state->lease.server.s_addr = 0;
delete_timeout(NULL, iface);
if (ifo->fallback)
add_timeout_sec(ifo->reboot, start_fallback, iface);
else if (ifo->options & DHCPCD_LASTLEASE &&
iface->state->lease.frominfo)
add_timeout_sec(ifo->reboot, start_timeout, iface);
else if (!(ifo->options & DHCPCD_INFORM &&
options & (DHCPCD_MASTER | DHCPCD_DAEMONISED)))
add_timeout_sec(ifo->reboot, start_expire, iface);
/* Don't bother ARP checking as the server could NAK us first. */
if (ifo->options & DHCPCD_INFORM)
send_inform(iface);
else
send_request(iface);
}
void
start_interface(void *arg)
{
struct interface *iface = arg;
struct if_options *ifo = iface->state->options;
struct stat st;
struct timeval now;
uint32_t l;
int nolease;
handle_carrier(0, 0, iface->name);
if (iface->carrier == LINK_DOWN) {
syslog(LOG_INFO, "%s: waiting for carrier", iface->name);
return;
}
iface->start_uptime = uptime();
free(iface->state->offer);
iface->state->offer = NULL;
if (options & DHCPCD_IPV6RS && ifo->options & DHCPCD_IPV6RS &&
!(ifo->options & DHCPCD_INFORM))
ipv6rs_start(iface);
if (iface->state->arping_index < ifo->arping_len) {
start_arping(iface);
return;
}
if (ifo->options & DHCPCD_STATIC) {
start_static(iface);
return;
}
if (ifo->options & DHCPCD_IPV6) {
if (ifo->options & DHCPCD_INFORM)
dhcp6_start(iface, 0);
else if (!(ifo->options & DHCPCD_IPV6RS))
dhcp6_start(iface, 1);
}
if (!(ifo->options & DHCPCD_IPV4))
return;
if (ifo->options & DHCPCD_INFORM) {
start_inform(iface);
return;
}
if (iface->hwlen == 0 && ifo->clientid[0] == '\0') {
syslog(LOG_WARNING, "%s: needs a clientid to configure",
iface->name);
drop_dhcp(iface, "FAIL");
close_sockets(iface);
delete_timeout(NULL, iface);
return;
}
/* We don't want to read the old lease if we NAK an old test */
nolease = iface->state->offer && options & DHCPCD_TEST;
if (!nolease)
iface->state->offer = read_lease(iface);
if (iface->state->offer) {
get_lease(&iface->state->lease, iface->state->offer);
iface->state->lease.frominfo = 1;
if (iface->state->offer->cookie == 0) {
if (iface->state->offer->yiaddr ==
iface->addr.s_addr)
{
free(iface->state->offer);
iface->state->offer = NULL;
}
} else if (iface->state->lease.leasetime != ~0U &&
stat(iface->leasefile, &st) == 0)
{
/* Offset lease times and check expiry */
gettimeofday(&now, NULL);
if ((time_t)iface->state->lease.leasetime <
now.tv_sec - st.st_mtime)
{
syslog(LOG_DEBUG,
"%s: discarding expired lease",
iface->name);
free(iface->state->offer);
iface->state->offer = NULL;
iface->state->lease.addr.s_addr = 0;
} else {
l = now.tv_sec - st.st_mtime;
iface->state->lease.leasetime -= l;
iface->state->lease.renewaltime -= l;
iface->state->lease.rebindtime -= l;
}
}
}
if (iface->state->offer == NULL)
start_discover(iface);
else if (iface->state->offer->cookie == 0 &&
iface->state->options->options & DHCPCD_IPV4LL)
start_ipv4ll(iface);
else
start_reboot(iface);
}
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->reason = "PREINIT";
ifs->nakoff = 0;
configure_interface(iface, argc, argv);
if (!(options & DHCPCD_TEST))
run_script(iface);
/* We need to drop the leasefile so that start_interface
* doesn't load it. */
if (ifs->options->options & DHCPCD_REQUEST)
unlink(iface->leasefile);
if (ifs->options->options & DHCPCD_LINK) {
switch (carrier_status(iface)) {
case 0:
iface->carrier = LINK_DOWN;
ifs->reason = "NOCARRIER";
break;
case 1:
iface->carrier = LINK_UP;
ifs->reason = "CARRIER";
break;
default:
iface->carrier = LINK_UNKNOWN;
return;
}
if (!(options & DHCPCD_TEST))
run_script(iface);
} else
iface->carrier = LINK_UNKNOWN;
}
void
handle_interface(int action, const char *ifname)
{
struct interface *ifs, *ifp, *ifn, *ifl = NULL;
const char * const argv[] = { ifname };
int i;
if (action == -1) {
ifp = find_interface(ifname);
if (ifp != NULL)
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));
for (ifp = ifs; ifp; ifp = ifp->next) {
if (strcmp(ifp->name, ifname) != 0)
continue;
/* Check if we already have the interface */
for (ifn = ifaces; ifn; ifn = ifn->next) {
if (strcmp(ifn->name, ifp->name) == 0)
break;
ifl = ifn;
}
if (ifn) {
/* The flags and hwaddr could have changed */
ifn->flags = ifp->flags;
ifn->hwlen = ifp->hwlen;
if (ifp->hwlen != 0)
memcpy(ifn->hwaddr, ifp->hwaddr, ifn->hwlen);
} else {
if (ifl)
ifl->next = ifp;
else
ifaces = ifp;
}
init_state(ifp, 0, NULL);
start_interface(ifp);
}
}
#ifdef RTM_CHGADDR
void
handle_hwaddr(const char *ifname, unsigned char *hwaddr, size_t hwlen)
{
struct interface *ifp;
struct if_options *ifo;
for (ifp = ifaces; ifp; ifp = ifp->next)
if (strcmp(ifp->name, ifname) == 0 && ifp->hwlen <= hwlen) {
ifo = ifp->state->options;
if (!(ifo->options &
(DHCPCD_INFORM | DHCPCD_STATIC | DHCPCD_CLIENTID))
&& ifp->state->new != NULL &&
ifp->state->new->cookie == htonl(MAGIC_COOKIE))
{
syslog(LOG_INFO,
"%s: expiring for new hardware address",
ifp->name);
drop_dhcp(ifp, "EXPIRE");
}
memcpy(ifp->hwaddr, hwaddr, hwlen);
ifp->hwlen = hwlen;
if (!(ifo->options &
(DHCPCD_INFORM | DHCPCD_STATIC | DHCPCD_CLIENTID)))
{
syslog(LOG_DEBUG, "%s: using hwaddr %s",
ifp->name,
hwaddr_ntoa(ifp->hwaddr, ifp->hwlen));
ifp->state->interval = 0;
ifp->state->nakoff = 0;
start_interface(ifp);
}
}
free(hwaddr);
}
#endif
void
handle_ifa(int type, const char *ifname,
struct in_addr *addr, struct in_addr *net, struct in_addr *dst)
{
struct interface *ifp;
struct if_options *ifo;
int i;
if (addr->s_addr == INADDR_ANY)
return;
for (ifp = ifaces; ifp; ifp = ifp->next)
if (strcmp(ifp->name, ifname) == 0)
break;
if (ifp == NULL)
return;
if (type == RTM_DELADDR) {
if (ifp->state->new &&
ifp->state->new->yiaddr == addr->s_addr)
syslog(LOG_INFO, "%s: removing IP address %s/%d",
ifp->name, inet_ntoa(ifp->state->lease.addr),
inet_ntocidr(ifp->state->lease.net));
return;
}
if (type != RTM_NEWADDR)
return;
ifo = ifp->state->options;
if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)) == 0 ||
ifo->req_addr.s_addr != INADDR_ANY)
return;
free(ifp->state->old);
ifp->state->old = ifp->state->new;
ifp->state->new = dhcp_message_new(addr, net);
ifp->dst.s_addr = dst ? dst->s_addr : INADDR_ANY;
if (dst) {
for (i = 1; i < 255; i++)
if (i != DHO_ROUTER && has_option_mask(ifo->dstmask,i))
dhcp_message_add_addr(ifp->state->new, i, *dst);
}
ifp->state->reason = "STATIC";
build_routes();
run_script(ifp);
if (ifo->options & DHCPCD_INFORM) {
ifp->state->state = DHS_INFORM;
ifp->state->xid = dhcp_xid(ifp);
ifp->state->lease.server.s_addr =
dst ? dst->s_addr : INADDR_ANY;
ifp->addr = *addr;
ifp->net = *net;
send_inform(ifp);
}
}
/* ARGSUSED */
static void
handle_link(_unused void *arg)
{
if (manage_link(linkfd) == -1)
syslog(LOG_ERR, "manage_link: %m");
}
static void
if_reboot(struct interface *iface, int argc, char **argv)
{
const struct if_options *ifo;
int opt;
ifo = iface->state->options;
opt = ifo->options;
configure_interface(iface, argc, argv);
ifo = iface->state->options;
iface->state->interval = 0;
if ((ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC) &&
iface->addr.s_addr != ifo->req_addr.s_addr) ||
(opt & (DHCPCD_INFORM | DHCPCD_STATIC) &&
!(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC))))
{
drop_dhcp(iface, "EXPIRE");
} else {
free(iface->state->offer);
iface->state->offer = NULL;
}
start_interface(iface);
}
static void
reconf_reboot(int action, int argc, char **argv, int oi)
{
struct interface *ifl, *ifn, *ifp, *ifs, *ift;
ifs = discover_interfaces(argc - oi, argv + oi);
if (ifs == NULL)
return;
for (ifp = ifs; ifp && (ift = ifp->next, 1); ifp = ift) {
ifl = NULL;
for (ifn = ifaces; ifn; ifn = ifn->next) {
if (strcmp(ifn->name, ifp->name) == 0)
break;
ifl = ifn;
}
if (ifn) {
if (action)
if_reboot(ifn, argc, argv);
else if (ifn->state->new)
configure(ifn);
free_interface(ifp);
} else {
ifp->next = NULL;
init_state(ifp, argc, argv);
start_interface(ifp);
if (ifl)
ifl->next = ifp;
else
ifaces = ifp;
}
}
sort_interfaces();
}
void
handle_signal(int sig)
{
struct interface *ifp;
struct if_options *ifo;
int do_release, i;
do_release = 0;
switch (sig) {
case SIGINT:
syslog(LOG_INFO, "received SIGINT, stopping");
break;
case SIGTERM:
syslog(LOG_INFO, "received SIGTERM, stopping");
break;
case SIGALRM:
syslog(LOG_INFO, "received SIGALRM, rebinding");
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);
return;
case SIGHUP:
syslog(LOG_INFO, "received SIGHUP, releasing");
do_release = 1;
break;
case SIGUSR1:
syslog(LOG_INFO, "received SIGUSR, reconfiguring");
for (ifp = ifaces; ifp; ifp = ifp->next)
if (ifp->state->new)
configure(ifp);
return;
case SIGPIPE:
syslog(LOG_WARNING, "received SIGPIPE");
return;
default:
syslog(LOG_ERR,
"received signal %d, but don't know what to do with it",
sig);
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 */
for (ifp = ifaces; ifp; ifp = ifp->next) {
if (ifp->next == NULL)
break;
}
if (ifp == NULL)
break;
if (ifp->carrier != LINK_DOWN &&
(do_release ||
ifp->state->options->options & DHCPCD_RELEASE))
send_release(ifp);
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) {
for (ifp = ifaces; ifp; ifp = ifp->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;
for (ifp = ifaces; ifp; ifp = ifp->next)
send_interface(fd->fd, ifp);
return 0;
}
opt = 0;
while (argv[++opt] != NULL) {
for (ifp = ifaces; ifp; ifp = ifp->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) {
for (ifp = ifaces; ifp; ifp = ifp->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 = xmalloc(len + 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, "handle_args: no interface");
return -1;
}
if (do_release || do_exit) {
for (oi = optind; oi < argc; oi++) {
for (ifp = ifaces; ifp; ifp = ifp->next)
if (strcmp(ifp->name, argv[oi]) == 0)
break;
if (!ifp)
continue;
if (do_release)
ifp->state->options->options |= DHCPCD_RELEASE;
if (ifp->state->options->options & DHCPCD_RELEASE &&
ifp->carrier != LINK_DOWN)
send_release(ifp);
stop_interface(ifp);
}
return 0;
}
reconf_reboot(do_reboot, argc, argv, optind);
return 0;
}
int
open_sockets(struct interface *iface)
{
int r = 0;
if (iface->raw_fd == -1) {
if ((r = open_socket(iface, ETHERTYPE_IP)) == -1)
syslog(LOG_ERR, "%s: open_socket: %m", iface->name);
else
add_event(iface->raw_fd, handle_dhcp_packet, iface);
}
if (iface->udp_fd == -1 &&
iface->addr.s_addr != 0 &&
iface->state->new != NULL &&
(iface->state->new->cookie == htonl(MAGIC_COOKIE) ||
iface->state->options->options & DHCPCD_INFORM))
{
if (open_udp_socket(iface) == -1 && errno != EADDRINUSE) {
syslog(LOG_ERR, "%s: open_udp_socket: %m", iface->name);
r = -1;
}
}
return r;
}
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) {
/* we don't listen to events on the udp */
close(iface->udp_fd);
iface->udp_fd = -1;
}
}
int
main(int argc, char **argv)
{
struct interface *iface;
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();
if (family == 0 || family == AF_INET) {
printf("\nDHCPv4 options:\n");
print_options();
}
if (family == 0 || family == AF_INET6) {
printf("\nDHCPv6 options:\n");
dhcp6_printoptions();
}
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)
close(STDERR_FILENO);
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 = xmalloc(len);
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);
}
ifaces = iface = xzalloc(sizeof(*iface));
strlcpy(iface->name, argv[optind], sizeof(iface->name));
snprintf(iface->leasefile, sizeof(iface->leasefile),
LEASEFILE, iface->name);
iface->state = xzalloc(sizeof(*iface->state));
iface->state->options = xzalloc(sizeof(*iface->state->options));
strlcpy(iface->state->options->script, if_options->script,
sizeof(iface->state->options->script));
iface->state->new = read_lease(iface);
if (iface->state->new == NULL && errno == ENOENT) {
strlcpy(iface->leasefile, argv[optind],
sizeof(iface->leasefile));
iface->state->new = read_lease(iface);
}
if (iface->state->new == NULL) {
if (errno == ENOENT)
syslog(LOG_ERR, "%s: no lease to dump",
iface->name);
exit(EXIT_FAILURE);
}
iface->state->reason = "DUMP";
run_script(iface);
exit(EXIT_SUCCESS);
}
if (!(options & (DHCPCD_MASTER | DHCPCD_TEST))) {
control_fd = open_control();
if (control_fd != -1) {
syslog(LOG_INFO,
"sending commands to master dhcpcd process");
i = send_control(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, "open_control: %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
/* This blocks all signals we're interested in.
* eloop uses pselect(2) so that the signals are unblocked
* when we're testing fd's.
* This allows us to ensure a consistent state is maintained
* regardless of when we are interrupted .*/
if (signal_setup(handle_signal, &dhcpcd_sigset) == -1) {
syslog(LOG_ERR, "signal_setup: %m");
exit(EXIT_FAILURE);
}
if (options & DHCPCD_MASTER) {
if (start_control() == -1)
syslog(LOG_ERR, "start_control: %m");
}
if (init_sockets() == -1) {
syslog(LOG_ERR, "init_socket: %m");
exit(EXIT_FAILURE);
}
if (if_options->options & DHCPCD_LINK) {
linkfd = open_link_socket();
if (linkfd == -1)
syslog(LOG_ERR, "open_link_socket: %m");
else
add_event(linkfd, handle_link, NULL);
}
#if 0
if (options & DHCPCD_IPV6RS && disable_rtadv() == -1) {
syslog(LOG_ERR, "ipv6rs: %m");
options &= ~DHCPCD_IPV6RS;
}
#endif
if (options & DHCPCD_IPV6 && ipv6_init() == -1) {
options &= ~DHCPCD_IPV6;
syslog(LOG_ERR, "ipv6_init: %m");
}
if (options & DHCPCD_IPV6RS && !check_ipv6(NULL))
options &= ~DHCPCD_IPV6RS;
if (options & DHCPCD_IPV6RS) {
ipv6rsfd = ipv6rs_open();
if (ipv6rsfd == -1) {
syslog(LOG_ERR, "ipv6rs: %m");
options &= ~DHCPCD_IPV6RS;
} else {
add_event(ipv6rsfd, ipv6rs_handledata, NULL);
// atexit(restore_rtadv);
}
if (options & DHCPCD_IPV6RA_OWN ||
options & DHCPCD_IPV6RA_OWN_DEFAULT)
{
ipv6nsfd = ipv6ns_open();
if (ipv6nsfd == -1)
syslog(LOG_ERR, "ipv6nd: %m");
else
add_event(ipv6nsfd, ipv6ns_handledata, NULL);
}
}
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;
ifaces = discover_interfaces(ifc, ifv);
for (i = 0; i < ifc; i++) {
for (iface = ifaces; iface; iface = iface->next)
if (strcmp(iface->name, ifv[i]) == 0)
break;
if (!iface)
syslog(LOG_ERR, "%s: interface not found or invalid",
ifv[i]);
}
if (!ifaces) {
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;
for (iface = ifaces; iface; iface = iface->next) {
init_state(iface, argc, argv);
if (iface->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);
for (iface = ifaces; iface; iface = iface->next) {
handle_carrier(0, 0, iface->name);
if (iface->carrier != LINK_DOWN) {
opt = 1;
break;
}
}
}
if (options & DHCPCD_MASTER)
i = if_options->timeout;
else
i = ifaces->state->options->timeout;
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;
add_timeout_sec(i, handle_exit_timeout, NULL);
}
}
free_options(if_options);
if_options = NULL;
sort_interfaces();
for (iface = ifaces; iface; iface = iface->next)
add_timeout_sec(0, start_interface, iface);
start_eloop(&dhcpcd_sigset);
exit(EXIT_SUCCESS);
}