reconfigure: implement DHCP reconfiguration
authorNicolas BESNARD <[email protected]>
Tue, 26 Nov 2024 16:29:27 +0000 (16:29 +0000)
committerÁlvaro Fernández Rojas <[email protected]>
Mon, 3 Nov 2025 15:21:24 +0000 (16:21 +0100)
Problem: To update DHCP parameters/options, the process needed to be
restarted with the new arguments. It makes odhcp6c not very flexible,
making it impossible to keep track of the total packet statistics.

Solution: Add a new ubus method to reconfigure DHCP without restarting
the process.

Signed-off-by: Nicolas BESNARD <[email protected]>
Signed-off-by: Paul Donald <[email protected]>
Link: https://github.com/openwrt/odhcp6c/pull/106
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
README
src/dhcpv6.c
src/odhcp6c.c
src/odhcp6c.h
src/ubus.c

diff --git a/README b/README
index e79fea106240e846abbadb24b91d1e3a8f085019..1afed15fe30d8794313eaa96cfeb99253e25bf06 100644 (file)
--- a/README
+++ b/README
@@ -85,3 +85,45 @@ Environment:
 * RA_RETRANSMIT        ND Retransmit time
 * AFTR                 The DS-Lite AFTR domain name
 * MAPE / MAPT / LW4O6  Softwire rules for MAPE, MAPT and LW4O6
+
+** Ubus Integration **
+
+Build with ENABLE_UBUS flag to connect odhcp6c to ubus. Object is registered at : "odhcp6c.{ifname}."
+
+Events are emitted whenever the DHCPv6 state changes and can replace the use of a state script. The variables are the same as those defined in the State Script section.
+
+The following RPC methods are available:
+
+* *get_state()* : Returns the DHCPv6 state
+       - OUT : see State Script section
+* *get_statistics()* : Returns the packet statistics 
+       - OUT :
+               - *dhcp_solicit* : Total number of SOLICIT messages sent
+               - *dhcp_advertise* : Total number of ADVERTISE messages received
+               - *dhcp_request* : Total number of REQUEST messages sent
+               - *dhcp_confirm* : Total number of CONFIRM messages sent
+               - *dhcp_renew* : Total number of RENEW messages sent
+               - *dhcp_rebind* : Total number of REBIND messages sent
+               - *dhcp_reply* : Total number of REPLY messages received
+               - *dhcp_release* : Total number of RELEASE messages sent
+               - *dhcp_decline* : Total number of DECLINE messages sent
+               - *dhcp_reconfigure* : Total number of RECONFIGURE messages received
+               - *dhcp_information_request* : Total number of INFORMATION-REQUEST messages sent
+               - *dhcp_discarded_packets* : Total number of discarded DHCP packets 
+               - *dhcp_transmit_failures* : Total number of DHCP messages that failed to be transmitted
+* *reset_statistics()* : Reset packet statistics
+* *reconfigure_dhcp(IN)* : Reconfigure DHCP settings
+       - IN (optional):
+               - *dscp* (int) : DSCP value used for DHCP packets
+               - *release* (bool) : Send a RELEASE message on exit/reset
+               - *sol_timeout* (int) : Maximum timeout for DHCPv6-SOLICIT
+               - *sk_prio* (int) : Packet kernel priority
+               - *opt_requested* (int[]) : Options to be requested
+               - *opt_strict* (bool) : Do not request any options except those specified
+               - *opt_reconfigure* (bool) : Send Accept Reconfigure option
+               - *opt_fqdn* (bool) : Send Client FQDN option
+               - *opt_unicast* (bool) : Ignore Server Unicast option
+               - *opt_send* (string[]) : Options to be sent
+               - *req_addresses* (string{try|force|none}) : Request addresses 
+               - *req_prefixes* (int) : Request Prefixes (0 = auto)
+               - *stateful_only* (bool) : Discard advertisements without any address or prefix proposed
index dc48fe08450019073cb47b409c7cc98c592ab2d9..50ec027a6bb43e85d722048b16deb2fc5e2bd13e 100644 (file)
@@ -79,7 +79,7 @@ static reply_handler dhcpv6_handle_reconfigure;
 static int dhcpv6_commit_advert(void);
 
 // RFC 3315 - 5.5 Timeout and Delay values
-static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = {
+static const struct dhcpv6_retx dhcpv6_retx_default[_DHCPV6_MSG_MAX] = {
        [DHCPV6_MSG_UNKNOWN] = {false, 1, 120, 0, "<POLL>",
                        dhcpv6_handle_reconfigure, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1, 0},
        [DHCPV6_MSG_SOLICIT] = {true, 1, DHCPV6_SOL_MAX_RT, 0, "SOLICIT",
@@ -95,6 +95,7 @@ static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = {
        [DHCPV6_MSG_INFO_REQ] = {true, 1, DHCPV6_INF_MAX_RT, 0, "INFOREQ",
                        dhcpv6_handle_reply, NULL, false, 0, 0, 0, {0, 0, 0}, 0, 0, 0, -1, 0},
 };
+static struct dhcpv6_retx dhcpv6_retx[_DHCPV6_MSG_MAX] = {0};
 
 // Sockets
 static int sock = -1;
@@ -403,6 +404,7 @@ void dhcpv6_reset_stats(void)
 
 int init_dhcpv6(const char *ifname, unsigned int options, int sk_prio, int sol_timeout, unsigned int dscp)
 {
+       memcpy(dhcpv6_retx, dhcpv6_retx_default, sizeof(dhcpv6_retx));
        client_options = options;
        dhcpv6_retx[DHCPV6_MSG_SOLICIT].max_timeo = sol_timeout;
 
index 2ac55b5fe952cab8a3c9887649814dfeeb6b1bf3..c5b8cb4573f7cd9142d65acd97cf6df1122e51f0 100644 (file)
@@ -39,6 +39,9 @@
 #include "ra.h"
 #include "ubus.h"
 
+#define DHCPV6_FD_INDEX 0
+#define UBUS_FD_INDEX 1
 #ifndef IN6_IS_ADDR_UNIQUELOCAL
 #define IN6_IS_ADDR_UNIQUELOCAL(a) \
        ((((__const uint32_t *) (a))[0] & htonl (0xfe000000)) \
@@ -66,6 +69,14 @@ static int urandom_fd = -1, allow_slaac_only = 0;
 static bool bound = false, release = true, ra = false;
 static time_t last_update = 0;
 static char *ifname = NULL;
+static unsigned int dscp = 0;
+static int sol_timeout = DHCPV6_SOL_MAX_RT;
+static int sk_prio = 0;
+bool stateful_only_mode = 0;
+static enum odhcp6c_ia_mode ia_na_mode = IA_MODE_TRY;
+static enum odhcp6c_ia_mode ia_pd_mode = IA_MODE_NONE;
+static unsigned int client_options = DHCPV6_CLIENT_FQDN | DHCPV6_ACCEPT_RECONFIGURE;
+static unsigned int oro_user_cnt = 0;
 
 static unsigned int script_sync_delay = 10;
 static unsigned int script_accu_delay = 1;
@@ -175,22 +186,15 @@ int main(_unused int argc, char* const argv[])
        uint8_t buf[134], *o_data;
        char *optpos;
        uint16_t opttype;
-       enum odhcp6c_ia_mode ia_na_mode = IA_MODE_TRY;
-       enum odhcp6c_ia_mode ia_pd_mode = IA_MODE_NONE;
-       bool stateful_only_mode = 0;
        struct odhcp6c_opt *opt;
        int ia_pd_iaid_index = 0;
-       int sk_prio = 0;
-       int sol_timeout = DHCPV6_SOL_MAX_RT;
        int verbosity = 0;
        bool help = false, daemonize = false;
        int logopt = LOG_PID;
        int c;
        int res = -1;
-       unsigned int client_options = DHCPV6_CLIENT_FQDN | DHCPV6_ACCEPT_RECONFIGURE;
        unsigned int ra_options = RA_RDNSS_DEFAULT_LIFETIME;
        unsigned int ra_holdoff_interval = RA_MIN_ADV_INTERVAL;
-       unsigned int dscp = 0;
        bool terminate = false;
 
        while ((c = getopt(argc, argv, "S::DN:V:P:FB:c:i:r:Ru:Ux:s:kK:t:C:m:Lhedp:fav")) != -1) {
@@ -467,7 +471,6 @@ int main(_unused int argc, char* const argv[])
        }
 
        if ((urandom_fd = open("/dev/urandom", O_CLOEXEC | O_RDONLY)) < 0 ||
-                       init_dhcpv6(ifname, client_options, sk_prio, sol_timeout, dscp) ||
                        ra_init(ifname, &ifid, ra_options, ra_holdoff_interval) ||
                        script_init(script, ifname)) {
                syslog(LOG_ERR, "failed to initialize: %s", strerror(errno));
@@ -477,17 +480,11 @@ int main(_unused int argc, char* const argv[])
        struct pollfd fds[2] = {0};
        int nfds = 0;
 
-       int dhcpv6_socket = dhcpv6_get_socket();
        int mode = DHCPV6_UNKNOWN;
        enum dhcpv6_msg msg_type = DHCPV6_MSG_UNKNOWN;
 
-       if (dhcpv6_socket < 0) {
-               syslog(LOG_ERR, "Invalid dhcpv6 file descriptor");
-               return 1;
-       }
-       
-       fds[nfds].fd = dhcpv6_socket;
-       fds[nfds].events = POLLIN;
+       fds[DHCPV6_FD_INDEX].fd = -1;
+       fds[DHCPV6_FD_INDEX].events = POLLIN;
        nfds++;
 
 #ifdef WITH_UBUS
@@ -503,8 +500,8 @@ int main(_unused int argc, char* const argv[])
                syslog(LOG_ERR, "Invalid ubus file descriptor");
                return 1;
        }
-       fds[nfds].fd = ubus_socket;
-       fds[nfds].events = POLLIN;
+       fds[UBUS_FD_INDEX].fd = ubus_socket;
+       fds[UBUS_FD_INDEX].events = POLLIN;
        nfds++;
 #endif /* WITH_UBUS */
 
@@ -527,6 +524,19 @@ int main(_unused int argc, char* const argv[])
                        odhcp6c_clear_state(STATE_SIP_FQDN);
                        bound = false;
 
+                       size_t oro_len = 0;
+                       odhcp6c_get_state(STATE_ORO, &oro_len);
+                       oro_user_cnt = oro_len / sizeof(uint16_t);
+
+                       syslog(LOG_NOTICE, "number of user requested options %u", oro_user_cnt);
+
+                       if (init_dhcpv6(ifname, client_options, sk_prio, sol_timeout, dscp)) {
+                               syslog(LOG_ERR, "failed to initialize: %s", strerror(errno));
+                               return 1;
+                       }
+
+                       fds[DHCPV6_FD_INDEX].fd = dhcpv6_get_socket();
+
                        syslog(LOG_NOTICE, "(re)starting transaction on %s", ifname);
 
                        signal_usr1 = signal_usr2 = false;
@@ -539,7 +549,7 @@ int main(_unused int argc, char* const argv[])
                                dhcpv6_set_state(DHCPV6_REQUEST);
                                break;
                        }
-                       
+
                        msg_type = DHCPV6_MSG_SOLICIT;
                        dhcpv6_send_request(msg_type);          
                        break;
@@ -717,10 +727,24 @@ int main(_unused int argc, char* const argv[])
                                terminate = true;
                        } else {
                                signal_usr2 = false;
-                               dhcpv6_set_state(DHCPV6_INIT);
+                               dhcpv6_set_state(DHCPV6_RESET);
                        }
                        break;
 
+               case DHCPV6_RESET:
+                       odhcp6c_clear_state(STATE_CLIENT_ID);
+
+                       size_t oro_user_len, oro_total_len;
+                       odhcp6c_get_state(STATE_ORO, &oro_total_len);
+                       oro_user_len = oro_user_cnt * sizeof(uint16_t);
+                       odhcp6c_remove_state(STATE_ORO, oro_user_len, oro_total_len - oro_user_len);
+
+                       close(dhcpv6_get_socket());
+                       fds[DHCPV6_FD_INDEX].fd = -1;
+
+                       dhcpv6_set_state(DHCPV6_INIT);
+                       break;
+
                default:
                        break;
                }
@@ -1399,3 +1423,106 @@ void notify_state_change(const char *status, int delay, bool resume)
        ubus_dhcp_event(status);
 #endif /* WITH_UBUS */
 }
+
+void config_set_release(bool enable) {
+       release = enable;
+}
+
+bool config_set_dscp(unsigned int value) {
+       if (value > 63)
+               return false;
+       dscp = value;
+       return true;
+}
+
+bool config_set_solicit_timeout(unsigned int timeout) {
+       if (timeout > INT32_MAX)
+               return false;
+
+       sol_timeout = timeout;
+       return true;
+}
+
+bool config_set_sk_priority(unsigned int priority) {
+       if (priority > 6)
+               return false;
+
+       sk_prio = priority;
+       return true;
+}
+
+void config_set_client_options(enum dhcpv6_config option, bool enable) {
+       if (enable) {
+               client_options |= option;
+       } else {
+               client_options &= ~option;
+       }
+}
+
+bool config_set_request_addresses(char* mode) {
+       if (!strcmp(mode, "force")) {
+               ia_na_mode = IA_MODE_FORCE;
+               allow_slaac_only = -1;
+       } else if (!strcmp(mode, "none")) {
+               ia_na_mode = IA_MODE_NONE;
+       } else if (!strcmp(mode, "try")) {
+               ia_na_mode = IA_MODE_TRY;
+       } else {
+               return false;
+       }
+
+       return true;
+}
+
+bool config_set_request_prefix(unsigned int length, unsigned int id) {
+       struct odhcp6c_request_prefix prefix = {0};
+
+       odhcp6c_clear_state(STATE_IA_PD_INIT);
+
+       if (ia_pd_mode != IA_MODE_FORCE)
+               ia_pd_mode = length > 128 ? IA_MODE_NONE : IA_MODE_TRY;
+
+       if (length <= 128) {
+               if (allow_slaac_only >= 0 && allow_slaac_only < 10)
+                       allow_slaac_only = 10;
+
+               prefix.length = length;
+               prefix.iaid = htonl(id);
+
+               if (odhcp6c_add_state(STATE_IA_PD_INIT, &prefix, sizeof(prefix))) {
+                       syslog(LOG_ERR, "Failed to set request IPv6-Prefix");
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+void config_set_stateful_only(bool enable) {
+       stateful_only_mode = enable;
+}
+
+void config_clear_requested_options(void) {
+       oro_user_cnt = 0;
+}
+
+bool config_add_requested_options(unsigned int option) {
+       if (option > UINT16_MAX)
+               return false;
+
+       option = htons(option);
+       if (odhcp6c_insert_state(STATE_ORO, 0, &option, 2)) {
+               syslog(LOG_ERR, "Failed to add requested option");
+               return false;
+       }
+       oro_user_cnt++;
+       return true;
+}
+
+void config_clear_send_options(void) {
+       odhcp6c_clear_state(STATE_OPTS);
+}
+
+bool config_add_send_options(char* option) {
+       return (parse_opt(option) == 0);
+}
index 740dce787cb0acce49b5e4b35a52ff9c2a5f65f4..fc751c62e30f8a1a41c2d751f319abcb557dae6a 100644 (file)
@@ -175,7 +175,8 @@ enum dhcpv6_state {
        DHCPV6_INFO,
        DHCPV6_INFO_PROCESSING,
        DHCPV6_INFO_REPLY,
-       DHCPV6_EXIT
+       DHCPV6_EXIT,
+       DHCPV6_RESET
 };
 
 enum dhcpv6_status {
@@ -471,6 +472,19 @@ int ra_get_retransmit(void);
 
 void notify_state_change(const char *status, int delay, bool resume);
 
+void config_set_release(bool enable);
+bool config_set_dscp(unsigned int value);
+bool config_set_solicit_timeout(unsigned int timeout);
+bool config_set_sk_priority(unsigned int value);
+void config_set_client_options(enum dhcpv6_config option, bool enable);
+bool config_set_request_addresses(char* mode);
+bool config_set_request_prefix(unsigned int length, unsigned int id);
+void config_set_stateful_only(bool enable);
+void config_clear_requested_options(void);
+bool config_add_requested_options(unsigned int option);
+void config_clear_send_options(void);
+bool config_add_send_options(char* option);
+
 int script_init(const char *path, const char *ifname);
 ssize_t script_unhexlify(uint8_t *dst, size_t len, const char *src);
 void script_hexlify(char *dst, const uint8_t *src, size_t len);
index 402ba681d64669d4c8b1aab878378edab23565f8..71418a4eaf6226dac3af6d81b66b0b160b21cc69 100644 (file)
@@ -97,6 +97,23 @@ enum entry_type {
        ENTRY_PREFIX
 };
 
+enum {
+       RECONFIGURE_DHCP_ATTR_DSCP,
+       RECONFIGURE_DHCP_ATTR_RELEASE,
+       RECONFIGURE_DHCP_ATTR_SOL_TIMEOUT,
+       RECONFIGURE_DHCP_ATTR_SK_PRIORITY,
+       RECONFIGURE_DHCP_ATTR_OPT_REQUESTED,
+       RECONFIGURE_DHCP_ATTR_OPT_STRICT,
+       RECONFIGURE_DHCP_ATTR_OPT_RECONFIGURE,
+       RECONFIGURE_DHCP_ATTR_OPT_FQDN,
+       RECONFIGURE_DHCP_ATTR_OPT_UNICAST,
+       RECONFIGURE_DHCP_ATTR_OPT_SEND,
+       RECONFIGURE_DHCP_ATTR_REQ_ADDRESSES,
+       RECONFIGURE_DHCP_ATTR_REQ_PREFIXES,
+       RECONFIGURE_DHCP_ATTR_STATEFUL,
+       RECONFIGURE_DHCP_ATTR_MAX,
+};
+
 struct ubus_context *ubus = NULL;
 static struct blob_buf b;
 static char ubus_name[24];
@@ -107,11 +124,30 @@ static int ubus_handle_get_stats(struct ubus_context *ctx, struct ubus_object *o
                struct ubus_request_data *req, const char *method, struct blob_attr *msg);
 static int ubus_handle_reset_stats(struct ubus_context *ctx, struct ubus_object *obj,
                struct ubus_request_data *req, const char *method, struct blob_attr *msg);
+static int ubus_handle_reconfigure_dhcp(struct ubus_context *ctx, struct ubus_object *obj,
+               struct ubus_request_data *req, const char *method, struct blob_attr *msg);
+
+static const struct blobmsg_policy reconfigure_dhcp_policy[RECONFIGURE_DHCP_ATTR_MAX] = {
+       [RECONFIGURE_DHCP_ATTR_DSCP] = { .name = "dscp", .type = BLOBMSG_TYPE_INT32},
+       [RECONFIGURE_DHCP_ATTR_RELEASE] = { .name = "release", .type = BLOBMSG_TYPE_BOOL},
+       [RECONFIGURE_DHCP_ATTR_SOL_TIMEOUT] = { .name = "sol_timeout", .type = BLOBMSG_TYPE_INT32},
+       [RECONFIGURE_DHCP_ATTR_SK_PRIORITY] = { .name = "sk_prio", .type = BLOBMSG_TYPE_INT32},
+       [RECONFIGURE_DHCP_ATTR_OPT_REQUESTED] = { .name = "opt_requested", .type = BLOBMSG_TYPE_ARRAY},
+       [RECONFIGURE_DHCP_ATTR_OPT_STRICT] = { .name = "opt_strict", .type = BLOBMSG_TYPE_BOOL},
+       [RECONFIGURE_DHCP_ATTR_OPT_RECONFIGURE] = { .name = "opt_reconfigure", .type = BLOBMSG_TYPE_BOOL},
+       [RECONFIGURE_DHCP_ATTR_OPT_FQDN] = { .name = "opt_fqdn", .type = BLOBMSG_TYPE_BOOL},
+       [RECONFIGURE_DHCP_ATTR_OPT_UNICAST] = { .name = "opt_unicast", .type = BLOBMSG_TYPE_BOOL},
+       [RECONFIGURE_DHCP_ATTR_OPT_SEND] = { .name = "opt_send", .type = BLOBMSG_TYPE_ARRAY},
+       [RECONFIGURE_DHCP_ATTR_REQ_ADDRESSES] = { .name = "req_addresses", .type = BLOBMSG_TYPE_STRING},
+       [RECONFIGURE_DHCP_ATTR_REQ_PREFIXES] = { .name = "req_prefixes", .type = BLOBMSG_TYPE_INT32},
+       [RECONFIGURE_DHCP_ATTR_STATEFUL] = { .name = "stateful_only", .type = BLOBMSG_TYPE_BOOL},
+};
 
 static struct ubus_method odhcp6c_object_methods[] = {
        UBUS_METHOD_NOARG("get_state", ubus_handle_get_state),
        UBUS_METHOD_NOARG("get_statistics", ubus_handle_get_stats),
        UBUS_METHOD_NOARG("reset_statistics", ubus_handle_reset_stats),
+       UBUS_METHOD("reconfigure_dhcp", ubus_handle_reconfigure_dhcp, reconfigure_dhcp_policy),
 };
 
 static struct ubus_object_type odhcp6c_object_type = 
@@ -582,6 +618,149 @@ static int ubus_handle_get_state(struct ubus_context *ctx, _unused struct ubus_o
        return UBUS_STATUS_OK;
 }
 
+static int ubus_handle_reconfigure_dhcp(_unused struct ubus_context *ctx, _unused struct ubus_object *obj,
+               _unused struct ubus_request_data *req, _unused const char *method,
+               struct blob_attr *msg)
+{
+       const struct blobmsg_policy *policy = reconfigure_dhcp_policy;
+       struct blob_attr *tb[RECONFIGURE_DHCP_ATTR_MAX];
+       struct blob_attr *cur = NULL;
+       struct blob_attr *elem = NULL;
+       char *string = NULL;
+       uint32_t value = 0;
+       uint32_t index = 0;
+       bool enabled = false;
+       bool valid_args = false;
+       bool need_reinit = false;
+
+       if (blobmsg_parse(policy, RECONFIGURE_DHCP_ATTR_MAX, tb, blob_data(msg), blob_len(msg)))
+               return UBUS_STATUS_INVALID_ARGUMENT;
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_DSCP])) {
+               value = blobmsg_get_u32(cur);
+               if (!config_set_dscp(value))
+                       return UBUS_STATUS_INVALID_ARGUMENT;
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_RELEASE])) {
+               enabled = blobmsg_get_bool(cur);
+               config_set_release(enabled);
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_SOL_TIMEOUT])) {
+               value = blobmsg_get_u32(cur);
+               if (!config_set_solicit_timeout(value))
+                       return UBUS_STATUS_INVALID_ARGUMENT;
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_SK_PRIORITY])) {
+               value = blobmsg_get_u32(cur);
+               if (!config_set_sk_priority(value))
+                       return UBUS_STATUS_INVALID_ARGUMENT;
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_REQUESTED])) {
+               if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY || !blobmsg_check_attr(cur, false))
+                       return UBUS_STATUS_INVALID_ARGUMENT;
+
+               config_clear_requested_options();
+
+               blobmsg_for_each_attr(elem, cur, index) {
+                       if (blobmsg_type(elem) != BLOBMSG_TYPE_INT32)
+                               return UBUS_STATUS_INVALID_ARGUMENT;
+
+                       value = blobmsg_get_u32(elem);
+                       if (!config_add_requested_options(value))
+                               return UBUS_STATUS_INVALID_ARGUMENT;
+               }
+
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_STRICT])) {
+               enabled = blobmsg_get_bool(cur);
+               config_set_client_options(DHCPV6_STRICT_OPTIONS, enabled);
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_RECONFIGURE])) {
+               enabled = blobmsg_get_bool(cur);
+               config_set_client_options(DHCPV6_ACCEPT_RECONFIGURE, enabled);
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_FQDN])) {
+               enabled = blobmsg_get_bool(cur);
+               config_set_client_options(DHCPV6_CLIENT_FQDN, enabled);
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_UNICAST])) {
+               enabled = blobmsg_get_bool(cur);
+               config_set_client_options(DHCPV6_IGNORE_OPT_UNICAST, enabled);
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_OPT_SEND])) {
+               if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY || !blobmsg_check_attr(cur, false))
+                       return UBUS_STATUS_INVALID_ARGUMENT;
+
+               config_clear_send_options();
+
+               blobmsg_for_each_attr(elem, cur, index) {
+                       string = blobmsg_get_string(elem);
+                       if (string == NULL || !config_add_send_options(string))
+                               return UBUS_STATUS_INVALID_ARGUMENT;
+               }
+
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_REQ_ADDRESSES])) {
+               string = blobmsg_get_string(cur);
+               if (string == NULL || !config_set_request_addresses(string))
+                       return UBUS_STATUS_INVALID_ARGUMENT;
+
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_REQ_PREFIXES])) {
+               value = blobmsg_get_u32(cur);
+
+               if (!config_set_request_prefix(value, 1))
+                       return UBUS_STATUS_INVALID_ARGUMENT;
+
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if ((cur = tb[RECONFIGURE_DHCP_ATTR_STATEFUL])) {
+               enabled = blobmsg_get_bool(cur);
+               config_set_stateful_only(enabled);
+               need_reinit = true;
+               valid_args = true;
+       }
+
+       if (need_reinit)
+               raise(SIGUSR2);
+
+       return valid_args ? UBUS_STATUS_OK : UBUS_STATUS_INVALID_ARGUMENT;
+}
+
 int ubus_dhcp_event(const char *status)
 {
        if (!ubus || !odhcp6c_object.has_subscribers)