NetBSD/external/bsd/dhcpcd/dist/configure.c
roy b2b2e70f68 Import dhcpcd-5.6.1 with the following changes:
Improve IPv6 RA support by allowing dhcpcd to manage addreses and
routes instead of the kernel. dhcpcd will only do this if RA has been
disabled in the kernel or instructed to do so via the dhcpcd.conf(5)
ipv6ra_own and/or ipv6ra_owndefault directives.

Send and process IPv6 Neighbor Solicitions and Adverts to prove router
reachability. If a router cannot be reached in this way then it is
expired.
2012-07-12 16:47:58 +00:00

831 lines
20 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.
*/
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include "config.h"
#include "common.h"
#include "configure.h"
#include "dhcp.h"
#include "if-options.h"
#include "if-pref.h"
#include "ipv6rs.h"
#include "net.h"
#include "signals.h"
#define DEFAULT_PATH "PATH=/usr/bin:/usr/sbin:/bin:/sbin"
static struct rt *routes;
static int
exec_script(char *const *argv, char *const *env)
{
pid_t pid;
sigset_t full;
sigset_t old;
/* OK, we need to block signals */
sigfillset(&full);
sigprocmask(SIG_SETMASK, &full, &old);
signal_reset();
switch (pid = vfork()) {
case -1:
syslog(LOG_ERR, "vfork: %m");
break;
case 0:
sigprocmask(SIG_SETMASK, &old, NULL);
execve(argv[0], argv, env);
syslog(LOG_ERR, "%s: %m", argv[0]);
_exit(127);
/* NOTREACHED */
}
/* Restore our signals */
signal_setup();
sigprocmask(SIG_SETMASK, &old, NULL);
return pid;
}
static char *
make_var(const char *prefix, const char *var)
{
size_t len;
char *v;
len = strlen(prefix) + strlen(var) + 2;
v = xmalloc(len);
snprintf(v, len, "%s_%s", prefix, var);
return v;
}
static void
append_config(char ***env, ssize_t *len,
const char *prefix, const char *const *config)
{
ssize_t i, j, e1;
char **ne, *eq;
if (config == NULL)
return;
ne = *env;
for (i = 0; config[i] != NULL; i++) {
eq = strchr(config[i], '=');
e1 = eq - config[i] + 1;
for (j = 0; j < *len; j++) {
if (strncmp(ne[j] + strlen(prefix) + 1,
config[i], e1) == 0)
{
free(ne[j]);
ne[j] = make_var(prefix, config[i]);
break;
}
}
if (j == *len) {
j++;
ne = xrealloc(ne, sizeof(char *) * (j + 1));
ne[j - 1] = make_var(prefix, config[i]);
*len = j;
}
}
*env = ne;
}
static size_t
arraytostr(const char *const *argv, char **s)
{
const char *const *ap;
char *p;
size_t len, l;
len = 0;
ap = argv;
while (*ap)
len += strlen(*ap++) + 1;
*s = p = xmalloc(len);
ap = argv;
while (*ap) {
l = strlen(*ap) + 1;
memcpy(p, *ap, l);
p += l;
ap++;
}
return len;
}
static ssize_t
make_env(const struct interface *iface, const char *reason, char ***argv)
{
char **env, *p;
ssize_t e, elen, l;
const struct if_options *ifo = iface->state->options;
const struct interface *ifp;
int dhcp, ra;
dhcp = ra = 0;
if (strcmp(reason, "TEST") == 0) {
if (ipv6rs_has_ra(iface))
ra = 1;
else
dhcp = 1;
} else if (strcmp(reason, "ROUTERADVERT") == 0)
ra = 1;
else
dhcp = 1;
/* When dumping the lease, we only want to report interface and
reason - the other interface variables are meaningless */
if (options & DHCPCD_DUMPLEASE)
elen = 2;
else
elen = 10;
/* Make our env */
env = xmalloc(sizeof(char *) * (elen + 1));
e = strlen("interface") + strlen(iface->name) + 2;
env[0] = xmalloc(e);
snprintf(env[0], e, "interface=%s", iface->name);
e = strlen("reason") + strlen(reason) + 2;
env[1] = xmalloc(e);
snprintf(env[1], e, "reason=%s", reason);
if (options & DHCPCD_DUMPLEASE)
goto dumplease;
e = 20;
env[2] = xmalloc(e);
snprintf(env[2], e, "pid=%d", getpid());
env[3] = xmalloc(e);
snprintf(env[3], e, "ifmetric=%d", iface->metric);
env[4] = xmalloc(e);
snprintf(env[4], e, "ifwireless=%d", iface->wireless);
env[5] = xmalloc(e);
snprintf(env[5], e, "ifflags=%u", iface->flags);
env[6] = xmalloc(e);
snprintf(env[6], e, "ifmtu=%d", get_mtu(iface->name));
l = e = strlen("interface_order=");
for (ifp = ifaces; ifp; ifp = ifp->next)
e += strlen(ifp->name) + 1;
p = env[7] = xmalloc(e);
strlcpy(p, "interface_order=", e);
e -= l;
p += l;
for (ifp = ifaces; ifp; ifp = ifp->next) {
l = strlcpy(p, ifp->name, e);
p += l;
e -= l;
*p++ = ' ';
e--;
}
*--p = '\0';
if ((dhcp && iface->state->new) || (ra && ipv6rs_has_ra(iface))) {
env[8] = strdup("if_up=true");
env[9] = strdup("if_down=false");
} else {
env[8] = strdup("if_up=false");
env[9] = strdup("if_down=true");
}
if (*iface->state->profile) {
e = strlen("profile=") + strlen(iface->state->profile) + 2;
env[elen] = xmalloc(e);
snprintf(env[elen++], e, "profile=%s", iface->state->profile);
}
if (iface->wireless) {
e = strlen("new_ssid=") + strlen(iface->ssid) + 2;
if (iface->state->new != NULL ||
strcmp(iface->state->reason, "CARRIER") == 0)
{
env = xrealloc(env, sizeof(char *) * (elen + 2));
env[elen] = xmalloc(e);
snprintf(env[elen++], e, "new_ssid=%s", iface->ssid);
}
if (iface->state->old != NULL ||
strcmp(iface->state->reason, "NOCARRIER") == 0)
{
env = xrealloc(env, sizeof(char *) * (elen + 2));
env[elen] = xmalloc(e);
snprintf(env[elen++], e, "old_ssid=%s", iface->ssid);
}
}
if (dhcp && iface->state->old) {
e = configure_env(NULL, NULL, iface->state->old, ifo);
if (e > 0) {
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
elen += configure_env(env + elen, "old",
iface->state->old, ifo);
}
append_config(&env, &elen, "old",
(const char *const *)ifo->config);
}
dumplease:
if (dhcp && iface->state->new) {
e = configure_env(NULL, NULL, iface->state->new, ifo);
if (e > 0) {
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
elen += configure_env(env + elen, "new",
iface->state->new, ifo);
}
append_config(&env, &elen, "new",
(const char *const *)ifo->config);
}
if (ra) {
e = ipv6rs_env(NULL, NULL, iface);
if (e > 0) {
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
elen += ipv6rs_env(env + elen, NULL, iface);
}
}
/* Add our base environment */
if (ifo->environ) {
e = 0;
while (ifo->environ[e++])
;
env = xrealloc(env, sizeof(char *) * (elen + e + 1));
e = 0;
while (ifo->environ[e]) {
env[elen + e] = xstrdup(ifo->environ[e]);
e++;
}
elen += e;
}
env[elen] = '\0';
*argv = env;
return elen;
}
static int
send_interface1(int fd, const struct interface *iface, const char *reason)
{
char **env, **ep, *s;
ssize_t elen;
struct iovec iov[2];
int retval;
make_env(iface, reason, &env);
elen = arraytostr((const char *const *)env, &s);
iov[0].iov_base = &elen;
iov[0].iov_len = sizeof(ssize_t);
iov[1].iov_base = s;
iov[1].iov_len = elen;
retval = writev(fd, iov, 2);
ep = env;
while (*ep)
free(*ep++);
free(env);
free(s);
return retval;
}
int
send_interface(int fd, const struct interface *iface)
{
int retval = 0;
if (send_interface1(fd, iface, iface->state->reason) == -1)
retval = -1;
if (ipv6rs_has_ra(iface)) {
if (send_interface1(fd, iface, "ROUTERADVERT") == -1)
retval = -1;
}
return retval;
}
int
run_script_reason(const struct interface *iface, const char *reason)
{
char *const argv[2] = { UNCONST(iface->state->options->script), NULL };
char **env = NULL, **ep;
char *path, *bigenv;
ssize_t e, elen = 0;
pid_t pid;
int status = 0;
const struct fd_list *fd;
struct iovec iov[2];
if (iface->state->options->script == NULL ||
iface->state->options->script[0] == '\0' ||
strcmp(iface->state->options->script, "/dev/null") == 0)
return 0;
if (reason == NULL)
reason = iface->state->reason;
syslog(LOG_DEBUG, "%s: executing `%s', reason %s",
iface->name, argv[0], reason);
/* Make our env */
elen = make_env(iface, reason, &env);
env = xrealloc(env, sizeof(char *) * (elen + 2));
/* Add path to it */
path = getenv("PATH");
if (path) {
e = strlen("PATH") + strlen(path) + 2;
env[elen] = xmalloc(e);
snprintf(env[elen], e, "PATH=%s", path);
} else
env[elen] = xstrdup(DEFAULT_PATH);
env[++elen] = '\0';
pid = exec_script(argv, env);
if (pid == -1)
status = -1;
else if (pid != 0) {
/* Wait for the script to finish */
while (waitpid(pid, &status, 0) == -1) {
if (errno != EINTR) {
syslog(LOG_ERR, "waitpid: %m");
status = -1;
break;
}
}
}
/* Send to our listeners */
bigenv = NULL;
for (fd = fds; fd != NULL; fd = fd->next) {
if (fd->listener) {
if (bigenv == NULL) {
elen = arraytostr((const char *const *)env,
&bigenv);
iov[0].iov_base = &elen;
iov[0].iov_len = sizeof(ssize_t);
iov[1].iov_base = bigenv;
iov[1].iov_len = elen;
}
if (writev(fd->fd, iov, 2) == -1)
syslog(LOG_ERR, "writev: %m");
}
}
free(bigenv);
/* Cleanup */
ep = env;
while (*ep)
free(*ep++);
free(env);
return status;
}
static struct rt *
find_route(struct rt *rts, const struct rt *r, struct rt **lrt,
const struct rt *srt)
{
struct rt *rt;
if (lrt)
*lrt = NULL;
for (rt = rts; rt; rt = rt->next) {
if (rt->dest.s_addr == r->dest.s_addr &&
#if HAVE_ROUTE_METRIC
(srt || (!rt->iface ||
rt->iface->metric == r->iface->metric)) &&
#endif
(!srt || srt != rt) &&
rt->net.s_addr == r->net.s_addr)
return rt;
if (lrt)
*lrt = rt;
}
return NULL;
}
static void
desc_route(const char *cmd, const struct rt *rt)
{
char addr[sizeof("000.000.000.000") + 1];
const char *ifname = rt->iface->name;
strlcpy(addr, inet_ntoa(rt->dest), sizeof(addr));
if (rt->gate.s_addr == INADDR_ANY)
syslog(LOG_DEBUG, "%s: %s route to %s/%d", ifname, cmd,
addr, inet_ntocidr(rt->net));
else if (rt->gate.s_addr == rt->dest.s_addr &&
rt->net.s_addr == INADDR_BROADCAST)
syslog(LOG_DEBUG, "%s: %s host route to %s", ifname, cmd,
addr);
else if (rt->dest.s_addr == INADDR_ANY && rt->net.s_addr == INADDR_ANY)
syslog(LOG_DEBUG, "%s: %s default route via %s", ifname, cmd,
inet_ntoa(rt->gate));
else
syslog(LOG_DEBUG, "%s: %s route to %s/%d via %s", ifname, cmd,
addr, inet_ntocidr(rt->net), inet_ntoa(rt->gate));
}
/* If something other than dhcpcd removes a route,
* we need to remove it from our internal table. */
int
route_deleted(const struct rt *rt)
{
struct rt *f, *l;
f = find_route(routes, rt, &l, NULL);
if (f == NULL)
return 0;
desc_route("removing", f);
if (l)
l->next = f->next;
else
routes = f->next;
free(f);
return 1;
}
static int
n_route(struct rt *rt)
{
/* Don't set default routes if not asked to */
if (rt->dest.s_addr == 0 &&
rt->net.s_addr == 0 &&
!(rt->iface->state->options->options & DHCPCD_GATEWAY))
return -1;
desc_route("adding", rt);
if (!add_route(rt))
return 0;
if (errno == EEXIST) {
/* Pretend we added the subnet route */
if (rt->dest.s_addr ==
(rt->iface->addr.s_addr & rt->iface->net.s_addr) &&
rt->net.s_addr == rt->iface->net.s_addr &&
rt->gate.s_addr == 0)
return 0;
else
return -1;
}
syslog(LOG_ERR, "%s: add_route: %m", rt->iface->name);
return -1;
}
static int
c_route(struct rt *ort, struct rt *nrt)
{
/* Don't set default routes if not asked to */
if (nrt->dest.s_addr == 0 &&
nrt->net.s_addr == 0 &&
!(nrt->iface->state->options->options & DHCPCD_GATEWAY))
return -1;
desc_route("changing", nrt);
/* We delete and add the route so that we can change metric.
* This also has the nice side effect of flushing ARP entries so
* we don't have to do that manually. */
del_route(ort);
if (!add_route(nrt))
return 0;
syslog(LOG_ERR, "%s: add_route: %m", nrt->iface->name);
return -1;
}
static int
d_route(struct rt *rt)
{
int retval;
desc_route("deleting", rt);
retval = del_route(rt);
if (retval != 0 && errno != ENOENT && errno != ESRCH)
syslog(LOG_ERR,"%s: del_route: %m", rt->iface->name);
return retval;
}
static struct rt *
get_subnet_route(struct dhcp_message *dhcp)
{
in_addr_t addr;
struct in_addr net;
struct rt *rt;
addr = dhcp->yiaddr;
if (addr == 0)
addr = dhcp->ciaddr;
/* Ensure we have all the needed values */
if (get_option_addr(&net, dhcp, DHO_SUBNETMASK) == -1)
net.s_addr = get_netmask(addr);
if (net.s_addr == INADDR_BROADCAST || net.s_addr == INADDR_ANY)
return NULL;
rt = malloc(sizeof(*rt));
rt->dest.s_addr = addr & net.s_addr;
rt->net.s_addr = net.s_addr;
rt->gate.s_addr = 0;
return rt;
}
static struct rt *
add_subnet_route(struct rt *rt, const struct interface *iface)
{
struct rt *r;
if (iface->net.s_addr == INADDR_BROADCAST ||
iface->net.s_addr == INADDR_ANY ||
(iface->state->options->options &
(DHCPCD_INFORM | DHCPCD_STATIC) &&
iface->state->options->req_addr.s_addr == INADDR_ANY))
return rt;
r = xmalloc(sizeof(*r));
r->dest.s_addr = iface->addr.s_addr & iface->net.s_addr;
r->net.s_addr = iface->net.s_addr;
r->gate.s_addr = 0;
r->next = rt;
return r;
}
static struct rt *
get_routes(const struct interface *iface)
{
struct rt *rt, *nrt = NULL, *r = NULL;
if (iface->state->options->routes != NULL) {
for (rt = iface->state->options->routes;
rt != NULL;
rt = rt->next)
{
if (rt->gate.s_addr == 0)
break;
if (r == NULL)
r = nrt = xmalloc(sizeof(*r));
else {
r->next = xmalloc(sizeof(*r));
r = r->next;
}
memcpy(r, rt, sizeof(*r));
r->next = NULL;
}
return nrt;
}
return get_option_routes(iface->state->new,
iface->name, &iface->state->options->options);
}
/* Some DHCP servers add set host routes by setting the gateway
* to the assinged IP address. This differs from our notion of a host route
* where the gateway is the destination address, so we fix it. */
static struct rt *
massage_host_routes(struct rt *rt, const struct interface *iface)
{
struct rt *r;
for (r = rt; r; r = r->next)
if (r->gate.s_addr == iface->addr.s_addr &&
r->net.s_addr == INADDR_BROADCAST)
r->gate.s_addr = r->dest.s_addr;
return rt;
}
static struct rt *
add_destination_route(struct rt *rt, const struct interface *iface)
{
struct rt *r;
if (!(iface->flags & IFF_POINTOPOINT) ||
!has_option_mask(iface->state->options->dstmask, DHO_ROUTER))
return rt;
r = xmalloc(sizeof(*r));
r->dest.s_addr = INADDR_ANY;
r->net.s_addr = INADDR_ANY;
r->gate.s_addr = iface->dst.s_addr;
r->next = rt;
return r;
}
/* We should check to ensure the routers are on the same subnet
* OR supply a host route. If not, warn and add a host route. */
static struct rt *
add_router_host_route(struct rt *rt, const struct interface *ifp)
{
struct rt *rtp, *rtl, *rtn;
const char *cp, *cp2, *cp3, *cplim;
for (rtp = rt, rtl = NULL; rtp; rtl = rtp, rtp = rtp->next) {
if (rtp->dest.s_addr != INADDR_ANY)
continue;
/* Scan for a route to match */
for (rtn = rt; rtn != rtp; rtn = rtn->next) {
/* match host */
if (rtn->dest.s_addr == rtp->gate.s_addr)
break;
/* match subnet */
cp = (const char *)&rtp->gate.s_addr;
cp2 = (const char *)&rtn->dest.s_addr;
cp3 = (const char *)&rtn->net.s_addr;
cplim = cp3 + sizeof(rtn->net.s_addr);
while (cp3 < cplim) {
if ((*cp++ ^ *cp2++) & *cp3++)
break;
}
if (cp3 == cplim)
break;
}
if (rtn != rtp)
continue;
if (ifp->flags & IFF_NOARP) {
syslog(LOG_WARNING,
"%s: forcing router %s through interface",
ifp->name, inet_ntoa(rtp->gate));
rtp->gate.s_addr = 0;
continue;
}
syslog(LOG_WARNING, "%s: router %s requires a host route",
ifp->name, inet_ntoa(rtp->gate));
rtn = xmalloc(sizeof(*rtn));
rtn->dest.s_addr = rtp->gate.s_addr;
rtn->net.s_addr = INADDR_BROADCAST;
rtn->gate.s_addr = rtp->gate.s_addr;
rtn->next = rtp;
if (rtl == NULL)
rt = rtn;
else
rtl->next = rtn;
}
return rt;
}
void
build_routes(void)
{
struct rt *nrs = NULL, *dnr, *or, *rt, *rtn, *rtl, *lrt = NULL;
const struct interface *ifp;
for (ifp = ifaces; ifp; ifp = ifp->next) {
if (ifp->state->new == NULL)
continue;
dnr = get_routes(ifp);
dnr = massage_host_routes(dnr, ifp);
dnr = add_subnet_route(dnr, ifp);
dnr = add_router_host_route(dnr, ifp);
dnr = add_destination_route(dnr, ifp);
for (rt = dnr; rt && (rtn = rt->next, 1); lrt = rt, rt = rtn) {
rt->iface = ifp;
rt->metric = ifp->metric;
/* Is this route already in our table? */
if ((find_route(nrs, rt, NULL, NULL)) != NULL)
continue;
rt->src.s_addr = ifp->addr.s_addr;
/* Do we already manage it? */
if ((or = find_route(routes, rt, &rtl, NULL))) {
if (or->iface != ifp ||
or->src.s_addr != ifp->addr.s_addr ||
rt->gate.s_addr != or->gate.s_addr ||
rt->metric != or->metric)
{
if (c_route(or, rt) != 0)
continue;
}
if (rtl != NULL)
rtl->next = or->next;
else
routes = or->next;
free(or);
} else {
if (n_route(rt) != 0)
continue;
}
if (dnr == rt)
dnr = rtn;
else if (lrt)
lrt->next = rtn;
rt->next = nrs;
nrs = rt;
rt = lrt; /* When we loop this makes lrt correct */
}
free_routes(dnr);
}
/* Remove old routes we used to manage */
for (rt = routes; rt; rt = rt->next) {
if (find_route(nrs, rt, NULL, NULL) == NULL)
d_route(rt);
}
free_routes(routes);
routes = nrs;
}
static int
delete_address(struct interface *iface)
{
int retval;
struct if_options *ifo;
ifo = iface->state->options;
if (ifo->options & DHCPCD_INFORM ||
(ifo->options & DHCPCD_STATIC && ifo->req_addr.s_addr == 0))
return 0;
syslog(LOG_DEBUG, "%s: deleting IP address %s/%d",
iface->name,
inet_ntoa(iface->addr),
inet_ntocidr(iface->net));
retval = del_address(iface, &iface->addr, &iface->net);
if (retval == -1 && errno != EADDRNOTAVAIL)
syslog(LOG_ERR, "del_address: %m");
iface->addr.s_addr = 0;
iface->net.s_addr = 0;
return retval;
}
int
configure(struct interface *iface)
{
struct dhcp_message *dhcp = iface->state->new;
struct dhcp_lease *lease = &iface->state->lease;
struct if_options *ifo = iface->state->options;
struct rt *rt;
/* As we are now adjusting an interface, we need to ensure
* we have them in the right order for routing and configuration. */
sort_interfaces();
if (dhcp == NULL) {
if (!(ifo->options & DHCPCD_PERSISTENT)) {
build_routes();
if (iface->addr.s_addr != 0)
delete_address(iface);
run_script(iface);
}
return 0;
}
/* This also changes netmask */
if (!(ifo->options & DHCPCD_INFORM) ||
!has_address(iface->name, &lease->addr, &lease->net))
{
syslog(LOG_DEBUG, "%s: adding IP address %s/%d",
iface->name, inet_ntoa(lease->addr),
inet_ntocidr(lease->net));
if (add_address(iface,
&lease->addr, &lease->net, &lease->brd) == -1 &&
errno != EEXIST)
{
syslog(LOG_ERR, "add_address: %m");
return -1;
}
}
/* Now delete the old address if different */
if (iface->addr.s_addr != lease->addr.s_addr &&
iface->addr.s_addr != 0)
delete_address(iface);
iface->addr.s_addr = lease->addr.s_addr;
iface->net.s_addr = lease->net.s_addr;
/* We need to delete the subnet route to have our metric or
* prefer the interface. */
rt = get_subnet_route(dhcp);
if (rt != NULL) {
rt->iface = iface;
rt->metric = 0;
if (!find_route(routes, rt, NULL, NULL))
del_route(rt);
free(rt);
}
build_routes();
if (!iface->state->lease.frominfo &&
!(ifo->options & (DHCPCD_INFORM | DHCPCD_STATIC)))
if (write_lease(iface, dhcp) == -1)
syslog(LOG_ERR, "write_lease: %m");
run_script(iface);
return 0;
}