Mercurial > hg > dhcpcd
view src/script.c @ 5567:4fe5c2a71254 draft
hooks: add NOCARRIER_ROAMING reason
This is given when the OS supports the concept of wireless roaming
or the IP setup can be persisted when the carrier drops.
When this happens, routes are moved to a higher metric (if supported)
to support non preferred but non roaming routes.
The `interface_order` hook variable will now order the interfaces
according to priority and move roaming interfaces to the back of the
list.
If resolvconf is present then it is called with the -C option
to deprecate DNS and if carrier comes back it is called again with the
-c option to activate it once more.
As part of this change, default route metrics have been changed to
support a larger number of interfaces.
base metric 1000 (was 200)
wireless offset 2000 (was 100)
IPv4LL offset 1000000 (was 10000)
roaming offset 2000000
| author | Roy Marples <roy@marples.name> |
|---|---|
| date | Sun, 27 Dec 2020 19:53:31 +0000 |
| parents | a0d828e25482 |
| children | 465cc5abc6d6 |
line wrap: on
line source
/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2020 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 <assert.h> #include <ctype.h> #include <errno.h> #include <pwd.h> #include <signal.h> #include <spawn.h> #include <stdarg.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "config.h" #include "common.h" #include "dhcp.h" #include "dhcp6.h" #include "eloop.h" #include "if.h" #include "if-options.h" #include "ipv4ll.h" #include "ipv6nd.h" #include "logerr.h" #include "privsep.h" #include "script.h" #define DEFAULT_PATH "/usr/bin:/usr/sbin:/bin:/sbin" static const char * const if_params[] = { "interface", "protocol", "reason", "pid", "ifcarrier", "ifmetric", "ifwireless", "ifflags", "ssid", "profile", "interface_order", NULL }; static const char * true_str = "true"; static const char * false_str = "false"; void if_printoptions(void) { const char * const *p; for (p = if_params; *p; p++) printf(" - %s\n", *p); } pid_t script_exec(char *const *argv, char *const *env) { pid_t pid = 0; posix_spawnattr_t attr; int r; #ifdef USE_SIGNALS size_t i; short flags; sigset_t defsigs; #else UNUSED(ctx); #endif /* posix_spawn is a safe way of executing another image * and changing signals back to how they should be. */ if (posix_spawnattr_init(&attr) == -1) return -1; #ifdef USE_SIGNALS flags = POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF; posix_spawnattr_setflags(&attr, flags); sigemptyset(&defsigs); posix_spawnattr_setsigmask(&attr, &defsigs); for (i = 0; i < dhcpcd_signals_len; i++) sigaddset(&defsigs, dhcpcd_signals[i]); for (i = 0; i < dhcpcd_signals_ignore_len; i++) sigaddset(&defsigs, dhcpcd_signals_ignore[i]); posix_spawnattr_setsigdefault(&attr, &defsigs); #endif errno = 0; r = posix_spawn(&pid, argv[0], NULL, &attr, argv, env); posix_spawnattr_destroy(&attr); if (r) { errno = r; return -1; } return pid; } #ifdef INET static int append_config(FILE *fp, const char *prefix, const char *const *config) { size_t i; if (config == NULL) return 0; /* Do we need to replace existing config rather than append? */ for (i = 0; config[i] != NULL; i++) { if (efprintf(fp, "%s_%s", prefix, config[i]) == -1) return -1; } return 1; } #endif #define PROTO_LINK 0 #define PROTO_DHCP 1 #define PROTO_IPV4LL 2 #define PROTO_RA 3 #define PROTO_DHCP6 4 #define PROTO_STATIC6 5 static const char *protocols[] = { "link", "dhcp", "ipv4ll", "ra", "dhcp6", "static6" }; int efprintf(FILE *fp, const char *fmt, ...) { va_list args; int r; va_start(args, fmt); r = vfprintf(fp, fmt, args); va_end(args); if (r == -1) return -1; /* Write a trailing NULL so we can easily create env strings. */ if (fputc('\0', fp) == EOF) return -1; return r; } char ** script_buftoenv(struct dhcpcd_ctx *ctx, char *buf, size_t len) { char **env, **envp, *bufp, *endp; size_t nenv; /* Count the terminated env strings. * Assert that the terminations are correct. */ nenv = 0; endp = buf + len; for (bufp = buf; bufp < endp; bufp++) { if (*bufp == '\0') { #ifndef NDEBUG if (bufp + 1 < endp) assert(*(bufp + 1) != '\0'); #endif nenv++; } } assert(*(bufp - 1) == '\0'); if (nenv == 0) return NULL; if (ctx->script_envlen < nenv) { env = reallocarray(ctx->script_env, nenv + 1, sizeof(*env)); if (env == NULL) return NULL; ctx->script_env = env; ctx->script_envlen = nenv; } bufp = buf; envp = ctx->script_env; *envp++ = bufp++; endp--; /* Avoid setting the last \0 to an invalid pointer */ for (; bufp < endp; bufp++) { if (*bufp == '\0') *envp++ = bufp + 1; } *envp = NULL; return ctx->script_env; } static long make_env(struct dhcpcd_ctx *ctx, const struct interface *ifp, const char *reason) { FILE *fp; long buf_pos, i; char *path; int protocol = PROTO_LINK; const struct if_options *ifo; const struct interface *ifp2; int af; bool is_stdin = ifp->name[0] == '\0'; const char *if_up, *if_down; rb_tree_t ifaces; struct rt *rt; #ifdef INET const struct dhcp_state *state; #ifdef IPV4LL const struct ipv4ll_state *istate; #endif #endif #ifdef DHCP6 const struct dhcp6_state *d6_state; #endif #ifdef HAVE_OPEN_MEMSTREAM if (ctx->script_fp == NULL) { fp = open_memstream(&ctx->script_buf, &ctx->script_buflen); if (fp == NULL) goto eexit; ctx->script_fp = fp; } else { fp = ctx->script_fp; rewind(fp); } #else char tmpfile[] = "/tmp/dhcpcd-script-env-XXXXXX"; int tmpfd; fp = NULL; tmpfd = mkstemp(tmpfile); if (tmpfd == -1) { logerr("%s: mkstemp", __func__); return -1; } unlink(tmpfile); fp = fdopen(tmpfd, "w+"); if (fp == NULL) { close(tmpfd); goto eexit; } #endif if (!(ifp->ctx->options & DHCPCD_DUMPLEASE)) { /* Needed for scripts */ path = getenv("PATH"); if (efprintf(fp, "PATH=%s", path == NULL ? DEFAULT_PATH : path) == -1) goto eexit; if (efprintf(fp, "pid=%d", getpid()) == -1) goto eexit; } if (!is_stdin) { if (efprintf(fp, "reason=%s", reason) == -1) goto eexit; } ifo = ifp->options; #ifdef INET state = D_STATE(ifp); #ifdef IPV4LL istate = IPV4LL_CSTATE(ifp); #endif #endif #ifdef DHCP6 d6_state = D6_CSTATE(ifp); #endif if (strcmp(reason, "TEST") == 0) { if (1 == 2) { /* This space left intentionally blank * as all the below statements are optional. */ } #ifdef INET6 #ifdef DHCP6 else if (d6_state && d6_state->new) protocol = PROTO_DHCP6; #endif else if (ipv6nd_hasra(ifp)) protocol = PROTO_RA; #endif #ifdef INET #ifdef IPV4LL else if (istate && istate->addr != NULL) protocol = PROTO_IPV4LL; #endif else protocol = PROTO_DHCP; #endif } #ifdef INET6 else if (strcmp(reason, "STATIC6") == 0) protocol = PROTO_STATIC6; #ifdef DHCP6 else if (reason[strlen(reason) - 1] == '6') protocol = PROTO_DHCP6; #endif else if (strcmp(reason, "ROUTERADVERT") == 0) protocol = PROTO_RA; #endif else if (strcmp(reason, "PREINIT") == 0 || strcmp(reason, "CARRIER") == 0 || strcmp(reason, "NOCARRIER") == 0 || strcmp(reason, "NOCARRIER_ROAMING") == 0 || strcmp(reason, "UNKNOWN") == 0 || strcmp(reason, "DEPARTED") == 0 || strcmp(reason, "STOPPED") == 0) protocol = PROTO_LINK; #ifdef INET #ifdef IPV4LL else if (strcmp(reason, "IPV4LL") == 0) protocol = PROTO_IPV4LL; #endif else protocol = PROTO_DHCP; #endif if (!is_stdin) { if (efprintf(fp, "interface=%s", ifp->name) == -1) goto eexit; if (protocols[protocol] != NULL) { if (efprintf(fp, "protocol=%s", protocols[protocol]) == -1) goto eexit; } } if (ifp->ctx->options & DHCPCD_DUMPLEASE && protocol != PROTO_LINK) goto dumplease; if (efprintf(fp, "if_configured=%s", ifo->options & DHCPCD_CONFIGURE ? "true" : "false") == -1) goto eexit; if (efprintf(fp, "ifcarrier=%s", ifp->carrier == LINK_UNKNOWN ? "unknown" : ifp->carrier == LINK_UP ? "up" : "down") == -1) goto eexit; if (efprintf(fp, "ifmetric=%d", ifp->metric) == -1) goto eexit; if (efprintf(fp, "ifwireless=%d", ifp->wireless) == -1) goto eexit; if (efprintf(fp, "ifflags=%u", ifp->flags) == -1) goto eexit; if (efprintf(fp, "ifmtu=%d", if_getmtu(ifp)) == -1) goto eexit; if (ifp->wireless) { char pssid[IF_SSIDLEN * 4]; if (print_string(pssid, sizeof(pssid), OT_ESCSTRING, ifp->ssid, ifp->ssid_len) != -1) { if (efprintf(fp, "ifssid=%s", pssid) == -1) goto eexit; } } if (*ifp->profile != '\0') { if (efprintf(fp, "profile=%s", ifp->profile) == -1) goto eexit; } if (ifp->ctx->options & DHCPCD_DUMPLEASE) goto dumplease; rb_tree_init(&ifaces, &rt_compare_proto_ops); TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { rt = rt_new(UNCONST(ifp2)); if (rt == NULL) goto eexit; if (rb_tree_insert_node(&ifaces, rt) != rt) goto eexit; } if (fprintf(fp, "interface_order=") == -1) goto eexit; RB_TREE_FOREACH(rt, &ifaces) { if (rt != RB_TREE_MIN(&ifaces) && fprintf(fp, "%s", " ") == -1) goto eexit; if (fprintf(fp, "%s", rt->rt_ifp->name) == -1) goto eexit; } rt_headclear(&ifaces, AF_UNSPEC); if (fputc('\0', fp) == EOF) goto eexit; if (strcmp(reason, "STOPPED") == 0) { if_up = false_str; if_down = ifo->options & DHCPCD_RELEASE ? true_str : false_str; } else if (strcmp(reason, "TEST") == 0 || strcmp(reason, "PREINIT") == 0 || strcmp(reason, "CARRIER") == 0 || strcmp(reason, "UNKNOWN") == 0) { if_up = false_str; if_down = false_str; } else if (strcmp(reason, "NOCARRIER") == 0) { if_up = false_str; if_down = true_str; } else if (strcmp(reason, "NOCARRIER_ROAMING") == 0) { if_up = true_str; if_down = false_str; } else if (1 == 2 /* appease ifdefs */ #ifdef INET || (protocol == PROTO_DHCP && state && state->new) #ifdef IPV4LL || (protocol == PROTO_IPV4LL && IPV4LL_STATE_RUNNING(ifp)) #endif #endif #ifdef INET6 || (protocol == PROTO_STATIC6 && IPV6_STATE_RUNNING(ifp)) #ifdef DHCP6 || (protocol == PROTO_DHCP6 && d6_state && d6_state->new) #endif || (protocol == PROTO_RA && ipv6nd_hasra(ifp)) #endif ) { if_up = true_str; if_down = false_str; } else { if_up = false_str; if_down = true_str; } if (efprintf(fp, "if_up=%s", if_up) == -1) goto eexit; if (efprintf(fp, "if_down=%s", if_down) == -1) goto eexit; if ((af = dhcpcd_ifafwaiting(ifp)) != AF_MAX) { if (efprintf(fp, "if_afwaiting=%d", af) == -1) goto eexit; } if ((af = dhcpcd_afwaiting(ifp->ctx)) != AF_MAX) { TAILQ_FOREACH(ifp2, ifp->ctx->ifaces, next) { if ((af = dhcpcd_ifafwaiting(ifp2)) != AF_MAX) break; } } if (af != AF_MAX) { if (efprintf(fp, "af_waiting=%d", af) == -1) goto eexit; } if (ifo->options & DHCPCD_DEBUG) { if (efprintf(fp, "syslog_debug=true") == -1) goto eexit; } #ifdef INET if (protocol == PROTO_DHCP && state && state->old) { if (dhcp_env(fp, "old", ifp, state->old, state->old_len) == -1) goto eexit; if (append_config(fp, "old", (const char *const *)ifo->config) == -1) goto eexit; } #endif #ifdef DHCP6 if (protocol == PROTO_DHCP6 && d6_state && d6_state->old) { if (dhcp6_env(fp, "old", ifp, d6_state->old, d6_state->old_len) == -1) goto eexit; } #endif dumplease: #ifdef INET #ifdef IPV4LL if (protocol == PROTO_IPV4LL && istate) { if (ipv4ll_env(fp, istate->down ? "old" : "new", ifp) == -1) goto eexit; } #endif if (protocol == PROTO_DHCP && state && state->new) { if (dhcp_env(fp, "new", ifp, state->new, state->new_len) == -1) goto eexit; if (append_config(fp, "new", (const char *const *)ifo->config) == -1) goto eexit; } #endif #ifdef INET6 if (protocol == PROTO_STATIC6) { if (ipv6_env(fp, "new", ifp) == -1) goto eexit; } #ifdef DHCP6 if (protocol == PROTO_DHCP6 && D6_STATE_RUNNING(ifp)) { if (dhcp6_env(fp, "new", ifp, d6_state->new, d6_state->new_len) == -1) goto eexit; } #endif if (protocol == PROTO_RA) { if (ipv6nd_env(fp, ifp) == -1) goto eexit; } #endif /* Add our base environment */ if (ifo->environ) { for (i = 0; ifo->environ[i] != NULL; i++) if (efprintf(fp, "%s", ifo->environ[i]) == -1) goto eexit; } /* Convert buffer to argv */ fflush(fp); buf_pos = ftell(fp); if (buf_pos == -1) { logerr(__func__); goto eexit; } #ifndef HAVE_OPEN_MEMSTREAM size_t buf_len = (size_t)buf_pos; if (ctx->script_buflen < buf_len) { char *buf = realloc(ctx->script_buf, buf_len); if (buf == NULL) goto eexit; ctx->script_buf = buf; ctx->script_buflen = buf_len; } rewind(fp); if (fread(ctx->script_buf, sizeof(char), buf_len, fp) != buf_len) goto eexit; fclose(fp); fp = NULL; #endif if (is_stdin) return buf_pos; if (script_buftoenv(ctx, ctx->script_buf, (size_t)buf_pos) == NULL) goto eexit; return buf_pos; eexit: logerr(__func__); #ifndef HAVE_OPEN_MEMSTREAM if (fp != NULL) fclose(fp); #endif return -1; } static int send_interface1(struct fd_list *fd, const struct interface *ifp, const char *reason) { struct dhcpcd_ctx *ctx = ifp->ctx; long len; len = make_env(ifp->ctx, ifp, reason); if (len == -1) return -1; return control_queue(fd, ctx->script_buf, (size_t)len); } int send_interface(struct fd_list *fd, const struct interface *ifp, int af) { int retval = 0; #ifdef INET const struct dhcp_state *d; #endif #ifdef DHCP6 const struct dhcp6_state *d6; #endif #ifndef AF_LINK #define AF_LINK AF_PACKET #endif if (af == AF_UNSPEC || af == AF_LINK) { const char *reason; switch (ifp->carrier) { case LINK_UP: reason = "CARRIER"; break; case LINK_DOWN: reason = "NOCARRIER"; break; default: reason = "UNKNOWN"; break; } if (fd != NULL) { if (send_interface1(fd, ifp, reason) == -1) retval = -1; } else retval++; } #ifdef INET if (af == AF_UNSPEC || af == AF_INET) { if (D_STATE_RUNNING(ifp)) { d = D_CSTATE(ifp); if (fd != NULL) { if (send_interface1(fd, ifp, d->reason) == -1) retval = -1; } else retval++; } #ifdef IPV4LL if (IPV4LL_STATE_RUNNING(ifp)) { if (fd != NULL) { if (send_interface1(fd, ifp, "IPV4LL") == -1) retval = -1; } else retval++; } #endif } #endif #ifdef INET6 if (af == AF_UNSPEC || af == AF_INET6) { if (IPV6_STATE_RUNNING(ifp)) { if (fd != NULL) { if (send_interface1(fd, ifp, "STATIC6") == -1) retval = -1; } else retval++; } if (RS_STATE_RUNNING(ifp)) { if (fd != NULL) { if (send_interface1(fd, ifp, "ROUTERADVERT") == -1) retval = -1; } else retval++; } #ifdef DHCP6 if (D6_STATE_RUNNING(ifp)) { d6 = D6_CSTATE(ifp); if (fd != NULL) { if (send_interface1(fd, ifp, d6->reason) == -1) retval = -1; } else retval++; } #endif } #endif return retval; } static int script_run(struct dhcpcd_ctx *ctx, char **argv) { pid_t pid; int status = 0; pid = script_exec(argv, ctx->script_env); if (pid == -1) logerr("%s: %s", __func__, argv[0]); else if (pid != 0) { /* Wait for the script to finish */ while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { logerr("%s: waitpid", __func__); status = 0; break; } } if (WIFEXITED(status)) { if (WEXITSTATUS(status)) logerrx("%s: %s: WEXITSTATUS %d", __func__, argv[0], WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) logerrx("%s: %s: %s", __func__, argv[0], strsignal(WTERMSIG(status))); } return WEXITSTATUS(status); } int script_dump(const char *env, size_t len) { const char *ep = env + len; if (len == 0) return 0; if (*(ep - 1) != '\0') { errno = EINVAL; return -1; } for (; env < ep; env += strlen(env) + 1) { if (strncmp(env, "new_", 4) == 0) env += 4; printf("%s\n", env); } return 0; } int script_runreason(const struct interface *ifp, const char *reason) { struct dhcpcd_ctx *ctx = ifp->ctx; char *argv[2]; int status = 0; struct fd_list *fd; long buflen; if (ctx->script == NULL && TAILQ_FIRST(&ifp->ctx->control_fds) == NULL) return 0; /* Make our env */ if ((buflen = make_env(ifp->ctx, ifp, reason)) == -1) { logerr(__func__); return -1; } if (strncmp(reason, "DUMP", 4) == 0) return script_dump(ctx->script_buf, (size_t)buflen); if (ctx->script == NULL) goto send_listeners; argv[0] = ctx->script; argv[1] = NULL; logdebugx("%s: executing: %s %s", ifp->name, argv[0], reason); #ifdef PRIVSEP if (ctx->options & DHCPCD_PRIVSEP) { if (ps_root_script(ctx, ctx->script_buf, ctx->script_buflen) == -1) logerr(__func__); goto send_listeners; } #endif script_run(ctx, argv); send_listeners: /* Send to our listeners */ status = 0; TAILQ_FOREACH(fd, &ctx->control_fds, next) { if (!(fd->flags & FD_LISTEN)) continue; if (control_queue(fd, ctx->script_buf, ctx->script_buflen)== -1) logerr("%s: control_queue", __func__); else status = 1; } return status; }
