Mercurial > hg > dhcpcd
view configure.c @ 1693:829716214dbc draft dhcpcd-5.6.0
Improve IPv6 RA support by allowing dhcpcd to manage the address and routes
instead of the kernel. dhcpcd will only do this if RA is disabled in the kernel
or dhcpcd has been instructed to do this via dhcpcd.conf(5) ipv6ra_own and
ipv6ra_own_default 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.
When debugging, all ND messages are displayed which will create a lot of log
spam.
To ease packaging, ./configure now accepts LDFLAGS and --enable-static.
| author | Roy Marples <roy@marples.name> |
|---|---|
| date | Thu, 05 Jul 2012 16:37:41 +0000 |
| parents | db3d4639eec4 |
| children | e372c338898b |
line wrap: on
line source
/* * 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; }
