summary refs log tree commit diff
path: root/src/core/dhcp/nm-dhcp-client.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/dhcp/nm-dhcp-client.c')
-rw-r--r--src/core/dhcp/nm-dhcp-client.c80
1 files changed, 73 insertions, 7 deletions
diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index cd6e67e2..18ad4024 100644
--- a/src/core/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -91,6 +91,11 @@ typedef struct _NMDhcpClientPrivate {
 
     union {
         struct {
+            /* Timer for restarting DHCP after the IPv6-only timeout */
+            GSource *ipv6_only_restart_source;
+            /* Minimum value accepted for the IPv6-only option. For test/debug only.*/
+            guint ipv6_only_min_wait;
+
             struct {
                 NML3CfgCommitTypeHandle *l3cfg_commit_handle;
                 GSource                 *done_source;
@@ -336,7 +341,7 @@ _emit_notify_data(NMDhcpClient *self, const NMDhcpClientNotifyData *notify_data)
 #define _emit_notify(self, _notify_type, ...) \
     _emit_notify_data(                        \
         (self),                               \
-        &((const NMDhcpClientNotifyData){.notify_type = (_notify_type), __VA_ARGS__}))
+        &((const NMDhcpClientNotifyData) {.notify_type = (_notify_type), __VA_ARGS__}))
 
 /*****************************************************************************/
 
@@ -684,7 +689,7 @@ _acd_check_lease(NMDhcpClient *self, NMOptionBool *out_acd_state)
     now_msec = nm_utils_get_monotonic_timestamp_msec();
 
     g_array_append_val(priv->v4.acd.reglist,
-                       ((AcdRegListData){
+                       ((AcdRegListData) {
                            .l3cd        = nm_l3_config_data_ref(priv->l3cd_next),
                            .addr        = addr,
                            .expiry_msec = now_msec + ACD_REGLIST_GRACE_PERIOD_MSEC,
@@ -1375,6 +1380,8 @@ nm_dhcp_client_start(NMDhcpClient *self, GError **error)
     g_return_val_if_fail(priv->config.uuid, FALSE);
     nm_assert(!priv->effective_client_id);
 
+    priv->is_stopped = FALSE;
+
     IS_IPv4 = NM_IS_IPv4(priv->config.addr_family);
 
     if (!IS_IPv4) {
@@ -1416,6 +1423,51 @@ nm_dhcp_client_start(NMDhcpClient *self, GError **error)
 
 /*****************************************************************************/
 
+static gboolean
+ipv6_only_restart_timeout_cb(gpointer user_data)
+{
+    NMDhcpClient         *self  = user_data;
+    NMDhcpClientPrivate  *priv  = NM_DHCP_CLIENT_GET_PRIVATE(self);
+    gs_free_error GError *error = NULL;
+
+    nm_assert(priv->config.addr_family == AF_INET);
+
+    nm_clear_g_source_inst(&priv->v4.ipv6_only_restart_source);
+    if (!nm_dhcp_client_start(self, &error)) {
+        _LOGW("failed to restart the DHCP client after the IPv6-only timeout: %s", error->message);
+        _emit_notify(self,
+                     NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD,
+                     .it_looks_bad.reason = error->message);
+    }
+
+    return G_SOURCE_CONTINUE;
+}
+
+/**
+ * nm_dhcp_client_schedule_ipv6_only_restart():
+ * @self: the client
+ * @timeout: the raw value from the DHCP option
+ *
+ * Stops the DHCPv4 client and restarts it after the timeout announced
+ * by the "IPv6-Only preferred" option.
+ */
+void
+nm_dhcp_client_schedule_ipv6_only_restart(NMDhcpClient *self, guint timeout)
+{
+    NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
+
+    nm_assert(priv->config.addr_family == AF_INET);
+    nm_assert(!priv->is_stopped);
+
+    timeout = NM_MAX(priv->v4.ipv6_only_min_wait, timeout);
+    _LOGI("received option \"ipv6-only-preferred\": stopping DHCPv4 for %u seconds", timeout);
+
+    nm_dhcp_client_stop(self, FALSE);
+    nm_clear_g_source_inst(&priv->no_lease_timeout_source);
+    priv->v4.ipv6_only_restart_source =
+        nm_g_timeout_add_seconds_source(timeout, ipv6_only_restart_timeout_cb, self);
+}
+
 void
 nm_dhcp_client_stop_existing(const char *pid_file, const char *binary_name)
 {
@@ -1488,7 +1540,10 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release)
     if (priv->is_stopped)
         return;
 
+    nm_clear_pointer(&priv->effective_client_id, g_bytes_unref);
     nm_clear_g_source_inst(&priv->previous_lease_timeout_source);
+    if (priv->config.addr_family == AF_INET)
+        nm_clear_g_source_inst(&priv->v4.ipv6_only_restart_source);
 
     priv->is_stopped = TRUE;
 
@@ -1934,6 +1989,8 @@ static void
 set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(object);
+    const char          *str;
+    guint                min_wait;
 
     switch (prop_id) {
     case PROP_CONFIG:
@@ -1943,7 +2000,8 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
         /* I know, this is technically not necessary. It just feels nicer to
          * explicitly initialize the respective union member. */
         if (NM_IS_IPv4(priv->config.addr_family)) {
-            priv->v4 = (typeof(priv->v4)){
+            priv->v4 = (typeof(priv->v4)) {
+                .ipv6_only_min_wait = NM_DHCP_MIN_V6ONLY_WAIT_DEFAULT,
                 .acd =
                     {
                         .addr                = INADDR_ANY,
@@ -1952,8 +2010,16 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
                         .done_source         = NULL,
                     },
             };
+
+            str = g_getenv("NM_TEST_IPV6_ONLY_MIN_WAIT");
+            if (str) {
+                min_wait = _nm_utils_ascii_str_to_int64(str, 10, 1, G_MAXUINT, 0);
+                if (min_wait != 0) {
+                    priv->v4.ipv6_only_min_wait = min_wait;
+                }
+            }
         } else {
-            priv->v6 = (typeof(priv->v6)){
+            priv->v6 = (typeof(priv->v6)) {
                 .lladdr_timeout_source = NULL,
             };
         }
@@ -1990,13 +2056,13 @@ dispose(GObject *object)
     nm_clear_g_source_inst(&priv->previous_lease_timeout_source);
     nm_clear_g_source_inst(&priv->no_lease_timeout_source);
 
-    if (!NM_IS_IPv4(priv->config.addr_family)) {
+    if (priv->config.addr_family == AF_INET) {
+        nm_clear_g_source_inst(&priv->v4.ipv6_only_restart_source);
+    } else {
         nm_clear_g_source_inst(&priv->v6.lladdr_timeout_source);
         nm_clear_g_source_inst(&priv->v6.dad_timeout_source);
     }
 
-    nm_clear_pointer(&priv->effective_client_id, g_bytes_unref);
-
     nm_assert(!priv->watch_source);
     nm_assert(!priv->l3cd_next);
     nm_assert(!priv->l3cd_curr);