/*
* 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.
*/
#include <sys/time.h>
#include <sys/types.h>
#include <arpa/inet.h>
#ifdef __linux__
# include <netinet/ether.h>
#endif
#include <ctype.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "config.h"
#include "common.h"
#ifdef ENABLE_ARP
# include "arp.h"
#endif
#include "client.h"
#include "configure.h"
#include "dhcp.h"
#include "dhcpcd.h"
#include "info.h"
#include "interface.h"
#ifdef ENABLE_IPV4LL
# include "ipv4ll.h"
#endif
#include "logger.h"
#include "signal.h"
#include "socket.h"
#include "logwriter.h"
#ifdef ENABLE_DUID
# include "duid.h"
#endif
#ifdef ENABLE_INFO
# include "info.h"
#endif
#ifdef THERE_IS_NO_FORK
# ifndef ENABLE_INFO
# error "Non MMU requires ENABLE_INFO to work"
# endif
#endif
/* Some platforms don't define INFTIM */
#ifndef INFTIM
# define INFTIM -1
#endif
/* This is out mini timeout.
* Basically we resend the last request every TIMEOUT_MINI seconds. */
#define TIMEOUT_MINI 3
/* Except for an infinite timeout. We keep adding TIMEOUT_MINI to
* ourself until TIMEOUT_MINI_INF is reached. */
#define TIMEOUT_MINI_INF 60
#define STATE_INIT 0
#define STATE_REQUESTING 1
#define STATE_BOUND 2
#define STATE_RENEWING 3
#define STATE_REBINDING 4
#define STATE_REBOOT 5
#define STATE_RENEW_REQUESTED 6
#define STATE_RELEASED 7
/* We should define a maximum for the NAK exponential backoff */
#define NAKOFF_MAX 60
#define SOCKET_CLOSED 0
#define SOCKET_OPEN 1
/* Indexes for pollfds */
#define POLLFD_SIGNAL 0
#define POLLFD_IFACE 1
typedef struct _state
{
int *pidfd;
bool forked;
int state;
uint32_t xid;
dhcp_t *dhcp;
int socket;
interface_t *interface;
time_t start;
time_t last_sent;
time_t last_type;
long timeout;
time_t nakoff;
bool daemonised;
bool persistent;
unsigned char *buffer;
size_t buffer_len;
size_t buffer_pos;
} state_t;
static pid_t daemonise (int *pidfd)
{
pid_t pid;
sigset_t full;
sigset_t old;
#ifdef THERE_IS_NO_FORK
char **argv;
int i;
#endif
sigfillset (&full);
sigprocmask (SIG_SETMASK, &full, &old);
#ifndef THERE_IS_NO_FORK
logger (LOG_DEBUG, "forking to background");
switch (pid = fork())
{
case -1:
logger (LOG_ERR, "fork: %s", strerror (errno));
exit (EXIT_FAILURE);
/* NOT REACHED */
case 0:
setsid ();
close_fds ();
break;
default:
/* Reset our signals as we're the parent about to exit. */
signal_reset ();
break;
}
#else
logger (LOG_INFO, "forking to background");
/* We need to add --daemonise to our options */
argv = xmalloc (sizeof (char *) * (dhcpcd_argc + 4));
argv[0] = dhcpcd;
for (i = 1; i < dhcpcd_argc; i++)
argv[i] = dhcpcd_argv[i];
argv[i] = (char *) "--daemonised";
if (dhcpcd_skiproutes)
{
argv[++i] = (char *) "--skiproutes";
argv[++i] = dhcpcd_skiproutes;
}
argv[i + 1] = NULL;
switch (pid = vfork ())
{
case -1:
logger (LOG_ERR, "vfork: %s", strerror (errno));
_exit (EXIT_FAILURE);
case 0:
signal_reset ();
sigprocmask (SIG_SETMASK, &old, NULL);
execvp (dhcpcd, argv);
logger (LOG_ERR, "execl `%s': %s", dhcpcd,
strerror (errno));
_exit (EXIT_FAILURE);
}
free (argv);
#endif
/* Done with the fd now */
if (pid != 0)
{
writepid (*pidfd, pid);
close (*pidfd);
*pidfd = -1;
}
sigprocmask (SIG_SETMASK, &old, NULL);
return (pid);
}
#ifdef ENABLE_INFO
static bool get_old_lease (state_t *state, const options_t *options)
{
interface_t *iface = state->interface;
dhcp_t *dhcp = state->dhcp;
struct timeval tv;
unsigned int offset = 0;
if (! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)))
logger (LOG_INFO, "trying to use old lease in `%s'",
iface->infofile);
if (! read_info (iface, dhcp))
return (false);
/* Vitaly important we remove the server information here */
memset (&dhcp->serveraddress, 0, sizeof (dhcp->serveraddress));
memset (dhcp->servername, 0, sizeof (dhcp->servername));
#ifdef ENABLE_ARP
/* Check that no-one is using the address */
if ((options->dolastlease ||
(IN_LINKLOCAL (ntohl (dhcp->address.s_addr)) &&
(! options->doipv4ll ||
arp_claim (iface, dhcp->address)))))
{
memset (&dhcp->address, 0, sizeof (dhcp->address));
memset (&dhcp->netmask, 0, sizeof (dhcp->netmask));
memset (&dhcp->broadcast, 0, sizeof (dhcp->broadcast));
return (false);
}
/* Ok, lets use this */
if (IN_LINKLOCAL (dhcp->address.s_addr))
return (true);
#endif
/* Ensure that we can still use the lease */
if (gettimeofday (&tv, NULL) == -1)
{
logger (LOG_ERR, "gettimeofday: %s", strerror (errno));
return (false);
}
offset = tv.tv_sec - dhcp->leasedfrom;
if (dhcp->leasedfrom &&
tv.tv_sec - dhcp->leasedfrom > dhcp->leasetime)
{
logger (LOG_ERR, "lease expired %u seconds ago",
offset + dhcp->leasetime);
return (false);
}
if (dhcp->leasedfrom == 0)
offset = 0;
state->timeout = dhcp->renewaltime - offset;
iface->start_uptime = uptime ();
return (true);
}
#endif
#ifdef THERE_IS_NO_FORK
static void remove_skiproutes (dhcp_t *dhcp, interface_t *iface)
{
int i = -1;
route_t *route;
route_t *newroute;
free_route (iface->previous_routes);
iface->previous_routes = NULL;
NSTAILQ_FOREACH (route, dhcp->routes, entries)
{
i++;
/* Check that we did add this route or not */
if (dhcpcd_skiproutes)
{
char *sk = xstrdup (dhcpcd_skiproutes);
char *skp = sk;
char *token;
bool found = false;
while ((token = strsep (&skp, ",")))
{
if (isdigit (*token) && atoi (token) == i)
{
found = true;
break;
}
}
free (sk);
if (found)
continue;
}
if (! iface->previous_routes)
{
iface->previous_routes = xmalloc (sizeof (*iface->previous_routes));
STAILQ_INIT (iface->previous_routes);
}
newroute = xmalloc (sizeof (*newroute));
memcpy (newroute, route, sizeof (*newroute));
STAILQ_INSERT_TAIL (iface->previous_routes, newroute, entries);
}
/* We no longer need this argument */
free (dhcpcd_skiproutes);
dhcpcd_skiproutes = NULL;
}
#endif
static bool client_setup (state_t *state, const options_t *options)
{
dhcp_t *dhcp = state->dhcp;
interface_t *iface = state->interface;
state->state = STATE_INIT;
state->last_type = DHCP_DISCOVER;
state->nakoff = 1;
state->daemonised = options->daemonised;
state->persistent = options->persistent;
if (options->request_address.s_addr == 0 &&
(options->doinform || options->dorequest || options->daemonised))
{
#ifdef ENABLE_INFO
if (! get_old_lease (state, options))
#endif
{
free (dhcp);
return (false);
}
state->timeout = 0;
if (! options->daemonised &&
IN_LINKLOCAL (ntohl (dhcp->address.s_addr)))
{
logger (LOG_ERR, "cannot request a link local address");
return (false);
}
#ifdef THERE_IS_NO_FORK
if (options->daemonised)
{
state->state = STATE_BOUND;
state->timeout = dhcp->renewaltime;
iface->previous_address = dhcp->address;
iface->previous_netmask = dhcp->netmask;
remove_skiproutes (dhcp, iface);
}
#endif
}
else
{
dhcp->address = options->request_address;
dhcp->netmask = options->request_netmask;
if (dhcp->netmask.s_addr == 0)
dhcp->netmask.s_addr = get_netmask (dhcp->address.s_addr);
dhcp->broadcast.s_addr = dhcp->address.s_addr |
~dhcp->netmask.s_addr;
}
/* Remove all existing addresses.
* After all, we ARE a DHCP client whose job it is to configure the
* interface. We only do this on start, so persistent addresses
* can be added afterwards by the user if needed. */
if (! options->test && ! options->daemonised)
{
if (! options->doinform)
{
flush_addresses (iface->name);
}
else
{
/* The inform address HAS to be configured for it to
* work with most DHCP servers */
if (options->doinform &&
has_address (iface->name, dhcp->address) < 1)
{
add_address (iface->name, dhcp->address,
dhcp->netmask, dhcp->broadcast);
iface->previous_address = dhcp->address;
iface->previous_netmask = dhcp->netmask;
}
}
}
if (*options->clientid)
{
/* Attempt to see if the ClientID is a hardware address */
iface->clientid_len = hwaddr_aton (NULL, options->clientid);
if (iface->clientid_len)
{
iface->clientid = xmalloc (iface->clientid_len);
hwaddr_aton (iface->clientid, options->clientid);
}
else
{
/* Nope, so mark it as-is */
iface->clientid_len = strlen (options->clientid) + 1;
iface->clientid = xmalloc (iface->clientid_len);
*iface->clientid = '\0';
memcpy (iface->clientid + 1,
options->clientid, iface->clientid_len - 1);
}
}
else
{
#ifdef ENABLE_DUID
unsigned char *duid = NULL;
size_t duid_len = 0;
if (options->doduid)
{
duid = xmalloc (DUID_LEN);
duid_len = get_duid (duid, iface);
}
if (duid_len > 0)
{
logger (LOG_INFO, "DUID = %s", hwaddr_ntoa (duid, duid_len));
iface->clientid_len = duid_len + 5;
iface->clientid = xmalloc (iface->clientid_len);
*iface->clientid = 255; /* RFC 4361 */
/* IAID is 4 bytes, so if the iface name is 4 bytes use it */
if (strlen (iface->name) == 4)
{
memcpy (iface->clientid + 1, iface->name, 4);
}
else
{
/* Name isn't 4 bytes, so use the index */
uint32_t ul = htonl (if_nametoindex (iface->name));
memcpy (iface->clientid + 1, &ul, 4);
}
memcpy (iface->clientid + 5, duid, duid_len);
free (duid);
}
else
{
#else
{
#endif
iface->clientid_len = iface->hwlen + 1;
iface->clientid = xmalloc (iface->clientid_len);
*iface->clientid = iface->family;
memcpy (iface->clientid + 1, iface->hwaddr, iface->hwlen);
}
}
return (true);
}
static bool do_socket (state_t *state, int mode)
{
if (state->interface->fd >= 0)
close (state->interface->fd);
#ifdef __linux
if (mode == SOCKET_CLOSED && state->interface->listen_fd >= 0)
{
close (state->interface->listen_fd);
state->interface->listen_fd = -1;
}
#endif
state->interface->fd = -1;
if (mode == SOCKET_OPEN)
if (open_socket (state->interface, ETHERTYPE_IP) == -1)
return (false);
state->socket = mode;
return (true);
}
static bool _send_message (state_t *state, int type, const options_t *options)
{
ssize_t retval;
state->last_type = type;
state->last_sent = uptime ();
logSendToQt(type);
retval = send_message (state->interface, state->dhcp, state->xid,
type, options);
return (retval == -1 ? false : true);
}
static void drop_config (state_t *state, const options_t *options)
{
if (! state->persistent)
configure (options, state->interface, state->dhcp, false);
free_dhcp (state->dhcp);
memset (state->dhcp, 0, sizeof (*state->dhcp));
}
static int wait_for_packet (struct pollfd *fds, state_t *state,
const options_t *options)
{
dhcp_t *dhcp = state->dhcp;
interface_t *iface = state->interface;
int timeout = 0;
int retval = 0;
if (! (state->timeout > 0 ||
(options->timeout == 0 &&
(state->state != STATE_INIT || state->xid))))
{
/* We need to zero our signal fd, otherwise we will block
* trying to read a signal. */
fds[POLLFD_SIGNAL].revents = 0;
return (0);
}
fds[POLLFD_IFACE].fd = iface->fd;
if ((options->timeout == 0 && state->xid) ||
(dhcp->leasetime == (unsigned) - 1 &&
state->state == STATE_BOUND))
{
logger (LOG_DEBUG, "waiting for infinity");
while (retval == 0)
{
if (iface->fd == -1)
retval = poll (fds, 1, INFTIM);
else
{
/* Slow down our requests */
if (timeout < TIMEOUT_MINI_INF)
timeout += TIMEOUT_MINI;
else if (timeout > TIMEOUT_MINI_INF)
timeout = TIMEOUT_MINI_INF;
retval = poll (fds, 2, timeout * 1000);
if (retval == -1 && errno == EINTR)
{
/* If interupted, continue as normal as
* the signal will be delivered down
* the pipe */
retval = 0;
continue;
}
if (retval == 0)
_send_message (state, state->last_type,
options);
}
}
return (retval);
}
/* Resend our message if we're getting loads of packets.
* As we use BPF or LPF, we shouldn't hit this as much, but it's
* still nice to have. */
if (iface->fd > -1 && uptime () - state->last_sent >= TIMEOUT_MINI)
_send_message (state, state->last_type, options);
logger (LOG_DEBUG, "waiting for %ld seconds",
(unsigned long) state->timeout);
/* If we're waiting for a reply, then we re-send the last
* DHCP request periodically in-case of a bad line */
retval = 0;
while (state->timeout > 0 && retval == 0)
{
if (iface->fd == -1)
timeout = (int) state->timeout;
else
{
timeout = TIMEOUT_MINI;
if (state->timeout < timeout)
timeout = (int) state->timeout;
}
timeout *= 1000;
state->start = uptime ();
retval = poll (fds, iface->fd == -1 ? 1 : 2, timeout);
state->timeout -= uptime () - state->start;
if (retval == -1 && errno == EINTR)
{
/* If interupted, continue as normal as the signal
* will be delivered down the pipe */
retval = 0;
continue;
}
if (retval == 0 && iface->fd != -1 && state->timeout > 0)
_send_message (state, state->last_type, options);
}
return (retval);
}
static bool handle_signal (int sig, state_t *state, const options_t *options)
{
switch (sig)
{
case SIGINT:
logger (LOG_INFO, "received SIGINT, stopping");
return (false);
case SIGTERM:
logger (LOG_INFO, "received SIGTERM, stopping");
return (false);
case SIGALRM:
logger (LOG_INFO, "received SIGALRM, renewing lease");
switch (state->state)
{
case STATE_BOUND:
case STATE_RENEWING:
case STATE_REBINDING:
state->state = STATE_RENEW_REQUESTED;
break;
case STATE_RENEW_REQUESTED:
case STATE_REQUESTING:
case STATE_RELEASED:
state->state = STATE_INIT;
break;
}
state->timeout = 0;
state->xid = 0;
return (true);
case SIGHUP:
if (state->state != STATE_BOUND &&
state->state != STATE_RENEWING &&
state->state != STATE_REBINDING)
{
logger (LOG_ERR,
"received SIGHUP, but we no have lease to release");
return (false);
}
logger (LOG_INFO, "received SIGHUP, releasing lease");
if (! IN_LINKLOCAL (ntohl (state->dhcp->address.s_addr)))
{
do_socket (state, SOCKET_OPEN);
state->xid = (uint32_t) random ();
if ((open_socket (state->interface, false)) >= 0)
{
_send_message (state, DHCP_RELEASE, options);
}
do_socket (state, SOCKET_CLOSED);
}
unlink (state->interface->infofile);
return (false);
default:
logger (LOG_ERR,
"received signal %d, but don't know what to do with it",
sig);
}
return (false);
}
static int handle_timeout (state_t *state, const options_t *options)
{
dhcp_t *dhcp = state->dhcp;
interface_t *iface = state->interface;
/* No NAK, so reset the backoff */
state->nakoff = 1;
if (state->state == STATE_INIT && state->xid != 0)
{
if (iface->previous_address.s_addr != 0 &&
! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)) &&
! options->doinform)
{
logger (LOG_ERR, "lost lease");
if (! options->persistent)
drop_config (state, options);
}
else if (! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)))
logger (LOG_ERR, "timed out");
do_socket (state, SOCKET_CLOSED);
free_dhcp (dhcp);
memset (dhcp, 0, sizeof (*dhcp));
#ifdef ENABLE_INFO
if (! options->test &&
(options->doipv4ll || options->dolastlease))
{
errno = 0;
if (! get_old_lease (state, options))
{
if (errno == EINTR)
return (0);
if (options->dolastlease)
return (-1);
free_dhcp (dhcp);
memset (dhcp, 0, sizeof (*dhcp));
}
else if (errno == EINTR)
return (0);
}
#endif
#ifdef ENABLE_IPV4LL
if (! options->test && options->doipv4ll &&
(! dhcp->address.s_addr ||
(! IN_LINKLOCAL (ntohl (dhcp->address.s_addr)) &&
! options->dolastlease)))
{
logger (LOG_INFO, "probing for an IPV4LL address");
free_dhcp (dhcp);
memset (dhcp, 0, sizeof (*dhcp));
if (ipv4ll_get_address (iface, dhcp) == -1)
{
if (! state->daemonised)
return (-1);
/* start over */
state->xid = 0;
return (0);
}
state->timeout = dhcp->renewaltime;
}
#endif
#if defined (ENABLE_INFO) || defined (ENABLE_IPV4LL)
if (dhcp->address.s_addr)
{
if (! state->daemonised &&
IN_LINKLOCAL (ntohl (dhcp->address.s_addr)))
logger (LOG_WARNING, "using IPV4LL address %s",
inet_ntoa (dhcp->address));
if (configure (options, iface, dhcp, true) == -1 &&
! state->daemonised)
return (-1);
state->state = STATE_BOUND;
if (! state->daemonised && options->daemonise)
{
switch (daemonise (state->pidfd))
{
case -1:
return (-1);
case 0:
state->daemonised = true;
return (0);
default:
state->persistent = true;
state->forked = true;
return (-1);
}
}
state->timeout = dhcp->renewaltime;
state->xid = 0;
return (0);
}
#endif
if (! state->daemonised)
return (-1);
}
switch (state->state)
{
case STATE_INIT:
state->xid = (uint32_t) random ();
do_socket (state, SOCKET_OPEN);
state->timeout = options->timeout;
iface->start_uptime = uptime ();
if (dhcp->address.s_addr == 0)
{
if (! IN_LINKLOCAL (ntohl (iface->previous_address.s_addr)))
logger (LOG_INFO, "broadcasting for a lease");
_send_message (state, DHCP_DISCOVER, options);
}
else if (options->doinform)
{
logger (LOG_INFO, "broadcasting inform for %s",
inet_ntoa (dhcp->address));
_send_message (state, DHCP_INFORM, options);
state->state = STATE_REQUESTING;
}
else
{
logger (LOG_INFO, "broadcasting for a lease of %s",
inet_ntoa (dhcp->address));
_send_message (state, DHCP_REQUEST, options);
state->state = STATE_REQUESTING;
}
break;
case STATE_BOUND:
case STATE_RENEW_REQUESTED:
if (IN_LINKLOCAL (ntohl (dhcp->address.s_addr)))
{
memset (&dhcp->address, 0, sizeof (dhcp->address));
state->state = STATE_INIT;
state->xid = 0;
break;
}
state->state = STATE_RENEWING;
state->xid = (uint32_t) random ();
/* FALLTHROUGH */
case STATE_RENEWING:
iface->start_uptime = uptime ();
logger (LOG_INFO, "renewing lease of %s", inet_ntoa
(dhcp->address));
do_socket (state, SOCKET_OPEN);
_send_message (state, DHCP_REQUEST, options);
state->timeout = dhcp->rebindtime - dhcp->renewaltime;
state->state = STATE_REBINDING;
break;
case STATE_REBINDING:
logger (LOG_ERR, "lost lease, attemping to rebind");
memset (&dhcp->address, 0, sizeof (dhcp->address));
do_socket (state, SOCKET_OPEN);
if (state->xid == 0)
state->xid = (uint32_t) random ();
dhcp->serveraddress.s_addr = 0;
_send_message (state, DHCP_REQUEST, options);
state->timeout = dhcp->leasetime - dhcp->rebindtime;
state->state = STATE_REQUESTING;
break;
case STATE_REQUESTING:
state->state = STATE_INIT;
do_socket (state, SOCKET_CLOSED);
state->timeout = 0;
break;
case STATE_RELEASED:
dhcp->leasetime = 0;
break;
}
return (0);
}
static int handle_dhcp (state_t *state, int type, const options_t *options)
{
struct timespec ts;
interface_t *iface = state->interface;
dhcp_t *dhcp = state->dhcp;
/* We should restart on a NAK */
if (type == DHCP_NAK)
{
logger (LOG_INFO, "received NAK: %s", dhcp->message);
logToQt(LOG_INFO, DHCP_NAK, "");
state->state = STATE_INIT;
state->timeout = 0;
state->xid = 0;
free_dhcp (dhcp);
memset (dhcp, 0, sizeof (*dhcp));
/* If we constantly get NAKS then we should slowly back off */
if (state->nakoff > 0)
{
logger (LOG_DEBUG, "sleeping for %ld seconds",
(long) state->nakoff);
ts.tv_sec = state->nakoff;
ts.tv_nsec = 0;
state->nakoff *= 2;
if (state->nakoff > NAKOFF_MAX)
state->nakoff = NAKOFF_MAX;
nanosleep (&ts, NULL);
}
return (0);
}
/* No NAK, so reset the backoff */
state->nakoff = 1;
if (type == DHCP_OFFER && state->state == STATE_INIT)
{
char *addr = strdup (inet_ntoa (dhcp->address));
if (dhcp->servername[0])
logger (LOG_INFO, "offered %s from %s `%s'",
addr, inet_ntoa (dhcp->serveraddress),
dhcp->servername);
else
logger (LOG_INFO, "offered %s from %s",
addr, inet_ntoa (dhcp->serveraddress));
free (addr);
logToQt(LOG_INFO, DHCP_OFFER, "");
#ifdef ENABLE_INFO
if (options->test)
{
write_info (iface, dhcp, options, false);
errno = 0;
return (-1);
}
#endif
_send_message (state, DHCP_REQUEST, options);
state->state = STATE_REQUESTING;
return (0);
}
if (type == DHCP_OFFER)
{
logger (LOG_INFO, "got subsequent offer of %s, ignoring ",
inet_ntoa (dhcp->address));
return (0);
}
/* We should only be dealing with acks */
if (type != DHCP_ACK)
{
logger (LOG_ERR, "%d not an ACK or OFFER", type);
return (0);
}
/* if we are here, than we received an ACK and can go on with configuration */
logToQt(LOG_INFO, DHCP_ACK, "");
switch (state->state)
{
case STATE_RENEW_REQUESTED:
case STATE_REQUESTING:
case STATE_RENEWING:
case STATE_REBINDING:
break;
default:
logger (LOG_ERR, "wrong state %d", state->state);
}
do_socket (state, SOCKET_CLOSED);
#ifdef ENABLE_ARP
if (options->doarp && iface->previous_address.s_addr !=
dhcp->address.s_addr)
{
errno = 0;
logToQt(LOG_INFO, DHCPCD_ARP_TEST, "");
if (arp_claim (iface, dhcp->address))
{
do_socket (state, SOCKET_OPEN);
_send_message (state, DHCP_DECLINE, options);
do_socket (state, SOCKET_CLOSED);
free_dhcp (dhcp);
memset (dhcp, 0, sizeof (*dhcp));
state->xid = 0;
state->timeout = 0;
state->state = STATE_INIT;
/* RFC 2131 says that we should wait for 10 seconds
* before doing anything else */
logger (LOG_INFO, "sleeping for 10 seconds");
ts.tv_sec = 10;
ts.tv_nsec = 0;
nanosleep (&ts, NULL);
return (0);
}
else if (errno == EINTR)
return (0);
}
#endif
if (options->doinform)
{
if (options->request_address.s_addr != 0)
dhcp->address = options->request_address;
else
dhcp->address = iface->previous_address;
logger (LOG_INFO, "received approval for %s",
inet_ntoa (dhcp->address));
if (iface->previous_netmask.s_addr != dhcp->netmask.s_addr)
{
add_address (iface->name, dhcp->address,
dhcp->netmask, dhcp->broadcast);
iface->previous_netmask.s_addr = dhcp->netmask.s_addr;
}
state->timeout = options->leasetime;
if (state->timeout == 0)
state->timeout = DEFAULT_LEASETIME;
state->state = STATE_INIT;
}
else if (dhcp->leasetime == (unsigned) - 1)
{
dhcp->renewaltime = dhcp->rebindtime = dhcp->leasetime;
state->timeout = 1; /* So we wait for infinity */
logger (LOG_INFO, "leased %s for infinity",
inet_ntoa (dhcp->address));
state->state = STATE_BOUND;
}
else
{
if (! dhcp->leasetime)
{
dhcp->leasetime = DEFAULT_LEASETIME;
logger(LOG_INFO,
"no lease time supplied, assuming %d seconds",
dhcp->leasetime);
}
logger (LOG_INFO, "leased %s for %u seconds",
inet_ntoa (dhcp->address), dhcp->leasetime);
if (dhcp->rebindtime >= dhcp->leasetime)
{
dhcp->rebindtime = (dhcp->leasetime * 0.875);
logger (LOG_ERR,
"rebind time greater than lease "
"time, forcing to %u seconds",
dhcp->rebindtime);
}
if (dhcp->renewaltime > dhcp->rebindtime)
{
dhcp->renewaltime = (dhcp->leasetime * 0.5);
logger (LOG_ERR,
"renewal time greater than rebind time, "
"forcing to %u seconds",
dhcp->renewaltime);
}
if (! dhcp->renewaltime)
{
dhcp->renewaltime = (dhcp->leasetime * 0.5);
logger (LOG_INFO,
"no renewal time supplied, assuming %d seconds",
dhcp->renewaltime);
}
else
logger (LOG_DEBUG, "renew in %u seconds",
dhcp->renewaltime);
if (! dhcp->rebindtime)
{
dhcp->rebindtime = (dhcp->leasetime * 0.875);
logger (LOG_INFO,
"no rebind time supplied, assuming %d seconds",
dhcp->rebindtime);
}
else
logger (LOG_DEBUG, "rebind in %u seconds",
dhcp->rebindtime);
state->timeout = dhcp->renewaltime;
state->state = STATE_BOUND;
}
state->xid = 0;
logToQt(LOG_INFO, DHCPCD_CONFIGURE, "");
if (configure (options, iface, dhcp, true) == -1 &&
! state->daemonised)
return (-1);
if (! state->daemonised && options->daemonise)
{
switch (daemonise (state->pidfd))
{
case 0:
state->daemonised = true;
return (0);
case -1:
return (-1);
default:
state->persistent = true;
state->forked = true;
return (-1);
}
}
return (0);
}
static int handle_packet (state_t *state, const options_t *options)
{
interface_t *iface = state->interface;
bool valid = false;
int type;
struct dhcp_t *new_dhcp;
dhcpmessage_t message;
/* Allocate our buffer space for BPF.
* We cannot do this until we have opened our socket as we don't
* know how much of a buffer we need until then. */
if (! state->buffer)
state->buffer = xmalloc (iface->buffer_length);
state->buffer_len = iface->buffer_length;
state->buffer_pos = 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. */
memset (&message, 0, sizeof (message));
new_dhcp = xmalloc (sizeof (*new_dhcp));
do
{
if (get_packet (iface, (unsigned char *) &message,
state->buffer,
&state->buffer_len, &state->buffer_pos) == -1)
break;
if (state->xid != message.xid)
{
logger (LOG_DEBUG,
"ignoring packet with xid 0x%x as it's not ours (0x%x)",
message.xid, state->xid);
continue;
}
logger (LOG_DEBUG, "got a packet with xid 0x%x", message.xid);
memset (new_dhcp, 0, sizeof (*new_dhcp));
type = parse_dhcpmessage (new_dhcp, &message);
if (type == -1)
{
logger (LOG_ERR, "failed to parse packet");
free_dhcp (new_dhcp);
/* We don't abort on this, so return zero */
return (0);
}
/* If we got here then the DHCP packet is valid and appears to
* be for us, so let's clear the buffer as we don't care about
* any more DHCP packets at this point. */
valid = true;
break;
}
while (state->buffer_pos != 0);
/* No packets for us, so wait until we get one */
if (! valid)
{
free (new_dhcp);
return (0);
}
/* new_dhcp is now our master DHCP message */
free_dhcp (state->dhcp);
free (state->dhcp);
state->dhcp = new_dhcp;
new_dhcp = NULL;
return (handle_dhcp (state, type, options));
}
int dhcp_run (const options_t *options, int *pidfd)
{
interface_t *iface;
state_t *state = NULL;
struct pollfd fds[] =
{
{ -1, POLLIN, 0 },
{ -1, POLLIN, 0 }
};
int retval = -1;
int sig;
if (! options)
return (-1);
/*read_interface : defined in interface.c*/
iface = read_interface (options->interface, options->metric);
if (! iface)
goto eexit;
state = xzalloc (sizeof (*state));
state->dhcp = xzalloc (sizeof (*state->dhcp));
state->pidfd = pidfd;
state->interface = iface;
if (! client_setup (state, options))
goto eexit;
if (signal_init () == -1)
goto eexit;
if (signal_setup () == -1)
goto eexit;
fds[POLLFD_SIGNAL].fd = signal_fd ();
for (;;)
{
retval = wait_for_packet (fds, state, options);
/* We should always handle our signals first */
if ((sig = (signal_read (&fds[POLLFD_SIGNAL]))) != -1)
{
if (handle_signal (sig, state, options))
retval = 0;
else
retval = -1;
}
else if (retval == 0)
retval = handle_timeout (state, options);
else if (retval > 0 &&
state->socket != SOCKET_CLOSED &&
fds[POLLFD_IFACE].revents & POLLIN)
retval = handle_packet (state, options);
else if (retval == -1 && errno == EINTR)
{
/* The interupt will be handled above */
retval = 0;
}
else
{
logger (LOG_ERR, "poll: %s", strerror (errno));
retval = -1;
}
if (retval != 0)
break;
}
eexit:
if (iface)
{
do_socket (state, SOCKET_CLOSED);
drop_config (state, options);
free_route (iface->previous_routes);
free (iface->clientid);
free (iface);
}
if (state)
{
if (state->forked)
retval = 0;
if (state->daemonised)
unlink (options->pidfile);
free_dhcp (state->dhcp);
free (state->dhcp);
free (state->buffer);
free (state);
}
return (retval);
}