]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
manager: Fix loosing iface options on CARRIER
authorDaniel Gröber <dxld@darkboxed.org>
Fri, 6 Mar 2026 22:47:42 +0000 (22:47 +0000)
committerRoy Marples <roy@marples.name>
Fri, 6 Mar 2026 22:48:11 +0000 (22:48 +0000)
When an interface (re-)gains carrier dhcpcd_handlecarrier() runs
dhcpcd_initstate() to kick off profile re-selection. Previously this used
args originally passed when starting the manager (ctx->argv).

However interfaces started via the manager control
interface (dhcpcd_initstate1() in dhcpcd_handleargs()) may be started with
different args.

For example if we start a manager with

    dhcpcd -M --inactive

and then start only IPv4 on an interface with

    dhcpcd -4 iface0

a subsequent CARRIER event will reset the interface to what amounts to
"default config + `-M --inactive`" which in this case will enable ipv6
also!

To fix this we keep a copy of the arguments used to start an interface in
the manager (dhcpcd_handleargs()) code path around around (ifp->argv).

In the current implementation args passed for renew following the initial
interface start will not be persisted. This causes the interface to reset
to a state of "defaults + config + profile + start-cmdline".

For example (continuing the scenario above) after enabling ipv6 with -n:

    $ dhcpcd -6 -n iface0

A subsequent CARRIER event will disable ipv6 again as the effective
arguments remain `-4 iface0` as passed during interface start.

Note the per-interface daemon code path wasn't affected as ctx->args
already contains the interface start args.

src/dhcpcd.c
src/dhcpcd.h
src/if-options.c
src/if-options.h
src/if.c

index 149c2a3d03f9dba2091fda231e5f0edb58e64a54..6299c67ce6f4011d8b55b1f4bf1d2baa6464a414 100644 (file)
@@ -729,7 +729,10 @@ static void
 dhcpcd_initstate(struct interface *ifp, unsigned long long options)
 {
 
-       dhcpcd_initstate1(ifp, ifp->ctx->argc, ifp->ctx->argv, options);
+       dhcpcd_initstate1(ifp,
+                         ifp->argv ? ifp->argc : ifp->ctx->argc,
+                         ifp->argv ? ifp->argv : ifp->ctx->argv,
+                         options);
 }
 
 static void
@@ -1399,28 +1402,48 @@ reload_config(struct dhcpcd_ctx *ctx)
 }
 
 static void
-reconf_reboot(struct dhcpcd_ctx *ctx, int action, int argc, char **argv, int oi)
+reconf_reboot(struct dhcpcd_ctx *ctx, const bool reboot,
+    const int argc, char **argv, const int oi)
 {
        int i;
        struct interface *ifp;
+       bool all_interfaces = argc == oi, iface_found;
 
        TAILQ_FOREACH(ifp, ctx->ifaces, next) {
                for (i = oi; i < argc; i++) {
                        if (strcmp(ifp->name, argv[i]) == 0)
                                break;
                }
-               if (oi != argc && i == argc)
+
+               iface_found = i != argc;
+               if (!all_interfaces && !iface_found)
                        continue;
+
                if (ifp->active == IF_ACTIVE_USER) {
-                       if (action)
+                       if (reboot)
                                if_reboot(ifp, argc, argv);
 #ifdef INET
                        else
                                ipv4_applyaddr(ifp);
 #endif
-               } else if (i != argc) {
+               } else if (iface_found) {
                        ifp->active = IF_ACTIVE_USER;
                        dhcpcd_initstate1(ifp, argc, argv, 0);
+
+                       free(ifp->argv);
+                       if (argc > 0) {
+                               ifp->argv = alloc_args(argc, argv);
+                               if (ifp->argv == NULL) {
+                                       logerr("alloc_args");
+                                       goto alloc_args_err;
+                               }
+                               ifp->argc = argc;
+                       } else {
+alloc_args_err:
+                               ifp->argv = NULL;
+                               ifp->argc = 0;
+                       }
+
                        run_preinit(ifp);
                        dhcpcd_prestartinterface(ifp);
                }
@@ -1529,7 +1552,7 @@ dhcpcd_signal_cb(int sig, void *arg)
                reload_config(ctx);
                /* Preserve any options passed on the commandline
                 * when we were started. */
-               reconf_reboot(ctx, 1, ctx->argc, ctx->argv,
+               reconf_reboot(ctx, true, ctx->argc, ctx->argv,
                    ctx->argc - ctx->ifc);
                return;
        case SIGUSR1:
@@ -1590,7 +1613,8 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd,
        struct interface *ifp;
        struct if_options *ifo;
        unsigned long long opts, orig_opts;
-       int opt, oi, oifind, do_reboot, do_renew, af = AF_UNSPEC;
+       int opt, oi, oifind, af = AF_UNSPEC;
+       bool do_reboot, do_renew;
        size_t len, l, nifaces;
        char *tmp, *p;
 
@@ -1635,7 +1659,7 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd,
        optind = 0;
        oi = 0;
        opts = 0;
-       do_reboot = do_renew = 0;
+       do_reboot = do_renew = false;
        while ((opt = getopt_long(argc, argv, IF_OPTS, cf_options, &oi)) != -1)
        {
                switch (opt) {
@@ -1646,7 +1670,7 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd,
                        opts |= DHCPCD_RELEASE;
                        break;
                case 'n':
-                       do_reboot = 1;
+                       do_reboot = true;
                        break;
                case 'p':
                        opts |= DHCPCD_PERSISTENT;
@@ -1655,7 +1679,7 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd,
                        opts |= DHCPCD_EXITING;
                        break;
                case 'N':
-                       do_renew = 1;
+                       do_renew = true;
                        break;
                case 'U':
                        opts |= DHCPCD_DUMPLEASE;
@@ -1773,7 +1797,6 @@ dumperr:
        }
 
        reload_config(ctx);
-       /* XXX: Respect initial commandline options? */
        reconf_reboot(ctx, do_reboot, argc, argv, oifind);
        return 0;
 }
index 5f7fed00c71a2295699cd59ac2586b712245ab49..9464a902839c7e568dd5dc2667ca53903c397dd8 100644 (file)
@@ -85,6 +85,9 @@ struct interface {
        uint8_t ssid[IF_SSIDLEN];
        unsigned int ssid_len;
 
+       int argc;
+       char **argv;
+
        char profile[PROFILE_LEN];
        struct if_options *options;
        void *if_data[IF_DATA_MAX];
index 60f8e660651cd09195517ad1e35ee1761a45a5a3..c4b1141f3971a211d400d1fb6794bfbd1d7b221d 100644 (file)
@@ -44,6 +44,7 @@
 #include <string.h>
 #include <unistd.h>
 #include <time.h>
+#include <assert.h>
 
 #include "config.h"
 #include "common.h"
@@ -2994,6 +2995,48 @@ add_options(struct dhcpcd_ctx *ctx, const char *ifname,
        return r;
 }
 
+char
+**alloc_args(int argc, char **argv)
+{
+       int i;
+       size_t strslen = 0, len;
+       size_t nptrs = (size_t)argc;
+       size_t ptrslen =  nptrs * sizeof(char *);
+       void *buf;
+       char **ptrs, *strsp;
+
+       for (i = 0; i < argc; i++) {
+               strslen += strlen(argv[i]) + 1;
+       }
+       if (strslen == 0)
+               return NULL;
+
+       buf = malloc(ptrslen + strslen);
+       if (!buf)
+               return NULL;
+
+       ptrs = buf;
+       strsp = (char *)&ptrs[nptrs];
+
+       for (i = 0; i < argc; i++) {
+               len = strlcpy(strsp, argv[i], strslen);
+               if (len >= strslen) /* truncated */
+                       goto err;
+
+               ptrs[i] = strsp;
+               strsp += len + 1;
+               assert(strslen >= len + 1);
+               strslen -= len + 1;
+       }
+
+       assert(strslen == 0);
+       return ptrs;
+
+err:
+       free(buf);
+       return NULL;
+}
+
 void
 free_options(struct dhcpcd_ctx *ctx, struct if_options *ifo)
 {
index a92697e12a4b1eb0c8668dbbf8e65fdb0a755b4f..51d282dcaf432c10072a2a80d8afaddb3473d112 100644 (file)
@@ -322,4 +322,6 @@ int add_options(struct dhcpcd_ctx *, const char *,
 void free_dhcp_opt_embenc(struct dhcp_opt *);
 void free_options(struct dhcpcd_ctx *, struct if_options *);
 
+char **alloc_args(int argc, char **argv);
+
 #endif
index 9b5ac2be2c918b0ed68426c0614ef79287277cb5..0283f060a42cbcf001fa4f98c13d631c9278f8ae 100644 (file)
--- a/src/if.c
+++ b/src/if.c
@@ -100,6 +100,7 @@ if_free(struct interface *ifp)
 #endif
        rt_freeif(ifp);
        free_options(ifp->ctx, ifp->options);
+       free(ifp->argv);
        free(ifp);
 }