summary refs log tree commit diff
path: root/src/core/devices/wwan/nm-modem.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
committerMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
commit80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch)
treee3b229aa94e8dcf0590f2317664176e7b8f7607b /src/core/devices/wwan/nm-modem.c
parent65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff)
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src/core/devices/wwan/nm-modem.c')
-rw-r--r--src/core/devices/wwan/nm-modem.c2037
1 files changed, 2037 insertions, 0 deletions
diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c
new file mode 100644
index 00000000..0d334fa4
--- /dev/null
+++ b/src/core/devices/wwan/nm-modem.c
@@ -0,0 +1,2037 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2009 - 2014 Red Hat, Inc.
+ * Copyright (C) 2009 Novell, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-modem.h"
+
+#include <fcntl.h>
+#include <termios.h>
+#include <linux/rtnetlink.h>
+
+#include "nm-core-internal.h"
+#include "platform/nm-platform.h"
+#include "nm-setting-connection.h"
+#include "NetworkManagerUtils.h"
+#include "devices/nm-device-private.h"
+#include "nm-netns.h"
+#include "nm-act-request.h"
+#include "nm-ip4-config.h"
+#include "nm-ip6-config.h"
+#include "ppp/nm-ppp-manager-call.h"
+#include "ppp/nm-ppp-status.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMModem,
+                             PROP_CONTROL_PORT,
+                             PROP_IP_IFINDEX,
+                             PROP_PATH,
+                             PROP_UID,
+                             PROP_DRIVER,
+                             PROP_STATE,
+                             PROP_DEVICE_ID,
+                             PROP_SIM_ID,
+                             PROP_IP_TYPES,
+                             PROP_SIM_OPERATOR_ID,
+                             PROP_OPERATOR_CODE,
+                             PROP_APN, );
+
+enum {
+    PPP_STATS,
+    PPP_FAILED,
+    PREPARE_RESULT,
+    IP4_CONFIG_RESULT,
+    IP6_CONFIG_RESULT,
+    AUTH_REQUESTED,
+    AUTH_RESULT,
+    REMOVED,
+    STATE_CHANGED,
+    LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct _NMModemPrivate {
+    char *uid;
+    char *path;
+    char *driver;
+    char *control_port;
+    char *data_port;
+
+    /* TODO: ip_iface is solely used for nm_modem_owns_port().
+     * We should rework the code that it's not necessary */
+    char *ip_iface;
+
+    int                ip_ifindex;
+    NMModemIPMethod    ip4_method;
+    NMModemIPMethod    ip6_method;
+    NMUtilsIPv6IfaceId iid;
+    NMModemState       state;
+    NMModemState       prev_state; /* revert to this state if enable/disable fails */
+    char *             device_id;
+    char *             sim_id;
+    NMModemIPType      ip_types;
+    char *             sim_operator_id;
+    char *             operator_code;
+    char *             apn;
+
+    NMPPPManager *ppp_manager;
+
+    NMActRequest *                act_request;
+    guint32                       secrets_tries;
+    NMActRequestGetSecretsCallId *secrets_id;
+
+    guint mm_ip_timeout;
+
+    guint32 ip4_route_table;
+    guint32 ip4_route_metric;
+    guint32 ip6_route_table;
+    guint32 ip6_route_metric;
+
+    /* PPP stats */
+    guint32 in_bytes;
+    guint32 out_bytes;
+
+    bool claimed : 1;
+} NMModemPrivate;
+
+G_DEFINE_TYPE(NMModem, nm_modem, G_TYPE_OBJECT)
+
+#define NM_MODEM_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMModem, NM_IS_MODEM)
+
+/*****************************************************************************/
+
+#define _NMLOG_PREFIX_BUFLEN 64
+#define _NMLOG_PREFIX_NAME   "modem"
+#define _NMLOG_DOMAIN        LOGD_MB
+
+static const char *
+_nmlog_prefix(char *prefix, NMModem *self)
+{
+    const char *uuid;
+    int         c;
+
+    if (!self)
+        return "";
+
+    uuid = nm_modem_get_uid(self);
+
+    if (uuid) {
+        char pp[_NMLOG_PREFIX_BUFLEN - 5];
+
+        c = g_snprintf(prefix, _NMLOG_PREFIX_BUFLEN, "[%s]", nm_strquote(pp, sizeof(pp), uuid));
+    } else
+        c = g_snprintf(prefix, _NMLOG_PREFIX_BUFLEN, "[%p]", self);
+    nm_assert(c < _NMLOG_PREFIX_BUFLEN);
+
+    return prefix;
+}
+
+#define _NMLOG(level, ...)                                                        \
+    G_STMT_START                                                                  \
+    {                                                                             \
+        char _prefix[_NMLOG_PREFIX_BUFLEN];                                       \
+                                                                                  \
+        nm_log((level),                                                           \
+               _NMLOG_DOMAIN,                                                     \
+               NULL,                                                              \
+               NULL,                                                              \
+               "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                       \
+               _NMLOG_PREFIX_NAME,                                                \
+               _nmlog_prefix(_prefix, (self)) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                                             \
+    G_STMT_END
+
+/*****************************************************************************/
+
+static void _set_ip_ifindex(NMModem *self, int ifindex, const char *ifname);
+
+/*****************************************************************************/
+/* State/enabled/connected */
+
+static const char *state_table[] = {
+    [NM_MODEM_STATE_UNKNOWN]       = "unknown",
+    [NM_MODEM_STATE_FAILED]        = "failed",
+    [NM_MODEM_STATE_INITIALIZING]  = "initializing",
+    [NM_MODEM_STATE_LOCKED]        = "locked",
+    [NM_MODEM_STATE_DISABLED]      = "disabled",
+    [NM_MODEM_STATE_DISABLING]     = "disabling",
+    [NM_MODEM_STATE_ENABLING]      = "enabling",
+    [NM_MODEM_STATE_ENABLED]       = "enabled",
+    [NM_MODEM_STATE_SEARCHING]     = "searching",
+    [NM_MODEM_STATE_REGISTERED]    = "registered",
+    [NM_MODEM_STATE_DISCONNECTING] = "disconnecting",
+    [NM_MODEM_STATE_CONNECTING]    = "connecting",
+    [NM_MODEM_STATE_CONNECTED]     = "connected",
+};
+
+const char *
+nm_modem_state_to_string(NMModemState state)
+{
+    if ((gsize) state < G_N_ELEMENTS(state_table))
+        return state_table[state];
+    return NULL;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_modem_is_claimed(NMModem *self)
+{
+    g_return_val_if_fail(NM_IS_MODEM(self), FALSE);
+
+    return NM_MODEM_GET_PRIVATE(self)->claimed;
+}
+
+NMModem *
+nm_modem_claim(NMModem *self)
+{
+    NMModemPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_MODEM(self), NULL);
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+
+    g_return_val_if_fail(!priv->claimed, NULL);
+
+    priv->claimed = TRUE;
+    return g_object_ref(self);
+}
+
+void
+nm_modem_unclaim(NMModem *self)
+{
+    NMModemPrivate *priv;
+
+    g_return_if_fail(NM_IS_MODEM(self));
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+
+    g_return_if_fail(priv->claimed);
+
+    /* we don't actually unclaim the instance. This instance should not be re-used
+     * by another owner, that is because we only claim modems as we receive them.
+     * There is no mechanism that somebody else would later re-use them again.
+     *
+     * // priv->claimed = FALSE; */
+
+    g_object_unref(self);
+}
+
+/*****************************************************************************/
+
+NMModemState
+nm_modem_get_state(NMModem *self)
+{
+    return NM_MODEM_GET_PRIVATE(self)->state;
+}
+
+void
+nm_modem_set_state(NMModem *self, NMModemState new_state, const char *reason)
+{
+    NMModemPrivate *priv      = NM_MODEM_GET_PRIVATE(self);
+    NMModemState    old_state = priv->state;
+
+    priv->prev_state = NM_MODEM_STATE_UNKNOWN;
+
+    if (new_state != old_state) {
+        _LOGI("modem state changed, '%s' --> '%s' (reason: %s)",
+              nm_modem_state_to_string(old_state),
+              nm_modem_state_to_string(new_state),
+              reason ?: "none");
+
+        priv->state = new_state;
+        _notify(self, PROP_STATE);
+        g_signal_emit(self, signals[STATE_CHANGED], 0, (int) new_state, (int) old_state);
+    }
+}
+
+void
+nm_modem_set_prev_state(NMModem *self, const char *reason)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    /* Reset modem to previous state if the state hasn't already changed */
+    if (priv->prev_state != NM_MODEM_STATE_UNKNOWN)
+        nm_modem_set_state(self, priv->prev_state, reason);
+}
+
+void
+nm_modem_set_mm_enabled(NMModem *self, gboolean enabled)
+{
+    NMModemPrivate *priv       = NM_MODEM_GET_PRIVATE(self);
+    NMModemState    prev_state = priv->state;
+
+    if (enabled && priv->state >= NM_MODEM_STATE_ENABLING) {
+        _LOGD("cannot enable modem: already enabled");
+        return;
+    }
+    if (!enabled && priv->state <= NM_MODEM_STATE_DISABLING) {
+        _LOGD("cannot disable modem: already disabled");
+        return;
+    }
+
+    if (priv->state <= NM_MODEM_STATE_INITIALIZING) {
+        _LOGD("cannot enable/disable modem: initializing or failed");
+        return;
+    } else if (priv->state == NM_MODEM_STATE_LOCKED) {
+        /* Don't try to enable if the modem is locked since that will fail */
+        _LOGW("cannot enable/disable modem: locked");
+
+        /* Try to unlock the modem if it's being enabled */
+        if (enabled)
+            g_signal_emit(self, signals[AUTH_REQUESTED], 0);
+        return;
+    }
+
+    /* Not all modem classes support set_mm_enabled */
+    if (NM_MODEM_GET_CLASS(self)->set_mm_enabled)
+        NM_MODEM_GET_CLASS(self)->set_mm_enabled(self, enabled);
+
+    /* Pre-empt the state change signal */
+    nm_modem_set_state(self,
+                       enabled ? NM_MODEM_STATE_ENABLING : NM_MODEM_STATE_DISABLING,
+                       "user preference");
+    priv->prev_state = prev_state;
+}
+
+void
+nm_modem_emit_removed(NMModem *self)
+{
+    g_signal_emit(self, signals[REMOVED], 0);
+}
+
+void
+nm_modem_emit_prepare_result(NMModem *self, gboolean success, NMDeviceStateReason reason)
+{
+    nm_assert(NM_IS_MODEM(self));
+
+    g_signal_emit(self, signals[PREPARE_RESULT], 0, success, (guint) reason);
+}
+
+void
+nm_modem_emit_ppp_failed(NMModem *self, NMDeviceStateReason reason)
+{
+    nm_assert(NM_IS_MODEM(self));
+
+    g_signal_emit(self, signals[PPP_FAILED], 0, (guint) reason);
+}
+
+NMModemIPType
+nm_modem_get_supported_ip_types(NMModem *self)
+{
+    return NM_MODEM_GET_PRIVATE(self)->ip_types;
+}
+
+const char *
+nm_modem_ip_type_to_string(NMModemIPType ip_type)
+{
+    switch (ip_type) {
+    case NM_MODEM_IP_TYPE_IPV4:
+        return "ipv4";
+    case NM_MODEM_IP_TYPE_IPV6:
+        return "ipv6";
+    case NM_MODEM_IP_TYPE_IPV4V6:
+        return "ipv4v6";
+    default:
+        g_return_val_if_reached("unknown");
+    }
+}
+
+static GArray *
+build_single_ip_type_array(NMModemIPType type)
+{
+    return g_array_append_val(g_array_sized_new(FALSE, FALSE, sizeof(NMModemIPType), 1), type);
+}
+
+/**
+ * nm_modem_get_connection_ip_type:
+ * @self: the #NMModem
+ * @connection: the #NMConnection to determine IP type to use
+ *
+ * Given a modem and a connection, determine which #NMModemIPTypes to use
+ * when connecting.
+ *
+ * Returns: an array of #NMModemIpType values, in the order in which they
+ * should be tried.
+ */
+GArray *
+nm_modem_get_connection_ip_type(NMModem *self, NMConnection *connection, GError **error)
+{
+    NMModemPrivate *   priv = NM_MODEM_GET_PRIVATE(self);
+    NMSettingIPConfig *s_ip4, *s_ip6;
+    const char *       method;
+    gboolean           ip4 = TRUE, ip6 = TRUE;
+    gboolean           ip4_may_fail = TRUE, ip6_may_fail = TRUE;
+
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    if (s_ip4) {
+        method = nm_setting_ip_config_get_method(s_ip4);
+        if (g_strcmp0(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0)
+            ip4 = FALSE;
+        ip4_may_fail = nm_setting_ip_config_get_may_fail(s_ip4);
+    }
+
+    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+    if (s_ip6) {
+        method = nm_setting_ip_config_get_method(s_ip6);
+        if (NM_IN_STRSET(method,
+                         NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                         NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
+            ip6 = FALSE;
+        ip6_may_fail = nm_setting_ip_config_get_may_fail(s_ip6);
+    }
+
+    if (ip4 && !ip6) {
+        if (!(priv->ip_types & NM_MODEM_IP_TYPE_IPV4)) {
+            g_set_error_literal(error,
+                                NM_DEVICE_ERROR,
+                                NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+                                "Connection requested IPv4 but IPv4 is "
+                                "unsupported by the modem.");
+            return NULL;
+        }
+        return build_single_ip_type_array(NM_MODEM_IP_TYPE_IPV4);
+    }
+
+    if (ip6 && !ip4) {
+        if (!(priv->ip_types & NM_MODEM_IP_TYPE_IPV6)) {
+            g_set_error_literal(error,
+                                NM_DEVICE_ERROR,
+                                NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+                                "Connection requested IPv6 but IPv6 is "
+                                "unsupported by the modem.");
+            return NULL;
+        }
+        return build_single_ip_type_array(NM_MODEM_IP_TYPE_IPV6);
+    }
+
+    if (ip4 && ip6) {
+        NMModemIPType type;
+        GArray *      out;
+
+        out = g_array_sized_new(FALSE, FALSE, sizeof(NMModemIPType), 3);
+
+        /* Modem supports dual-stack? */
+        if (priv->ip_types & NM_MODEM_IP_TYPE_IPV4V6) {
+            type = NM_MODEM_IP_TYPE_IPV4V6;
+            g_array_append_val(out, type);
+        }
+
+        /* If IPv6 may-fail=false, we should NOT try IPv4 as fallback */
+        if ((priv->ip_types & NM_MODEM_IP_TYPE_IPV4) && ip6_may_fail) {
+            type = NM_MODEM_IP_TYPE_IPV4;
+            g_array_append_val(out, type);
+        }
+
+        /* If IPv4 may-fail=false, we should NOT try IPv6 as fallback */
+        if ((priv->ip_types & NM_MODEM_IP_TYPE_IPV6) && ip4_may_fail) {
+            type = NM_MODEM_IP_TYPE_IPV6;
+            g_array_append_val(out, type);
+        }
+
+        if (out->len > 0)
+            return out;
+
+        /* Error... */
+        g_array_unref(out);
+        g_set_error_literal(error,
+                            NM_DEVICE_ERROR,
+                            NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+                            "Connection requested both IPv4 and IPv6 "
+                            "but dual-stack addressing is unsupported "
+                            "by the modem.");
+        return NULL;
+    }
+
+    g_set_error_literal(error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
+                        "Connection specified no IP configuration!");
+    return NULL;
+}
+
+const char *
+nm_modem_get_device_id(NMModem *self)
+{
+    return NM_MODEM_GET_PRIVATE(self)->device_id;
+}
+
+const char *
+nm_modem_get_sim_id(NMModem *self)
+{
+    return NM_MODEM_GET_PRIVATE(self)->sim_id;
+}
+
+const char *
+nm_modem_get_sim_operator_id(NMModem *self)
+{
+    return NM_MODEM_GET_PRIVATE(self)->sim_operator_id;
+}
+
+const char *
+nm_modem_get_operator_code(NMModem *self)
+{
+    return NM_MODEM_GET_PRIVATE(self)->operator_code;
+}
+
+const char *
+nm_modem_get_apn(NMModem *self)
+{
+    return NM_MODEM_GET_PRIVATE(self)->apn;
+}
+
+/*****************************************************************************/
+/* IP method PPP */
+
+static void
+ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data)
+{
+    switch (status) {
+    case NM_PPP_STATUS_DISCONNECT:
+        nm_modem_emit_ppp_failed(user_data, NM_DEVICE_STATE_REASON_PPP_DISCONNECT);
+        break;
+    case NM_PPP_STATUS_DEAD:
+        nm_modem_emit_ppp_failed(user_data, NM_DEVICE_STATE_REASON_PPP_FAILED);
+        break;
+    default:
+        break;
+    }
+}
+
+static void
+ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpointer user_data)
+{
+    NMModem *self = NM_MODEM(user_data);
+
+    nm_assert(ifindex >= 0);
+    nm_assert(NM_MODEM_GET_PRIVATE(self)->ppp_manager == ppp_manager);
+
+    if (ifindex <= 0 && iface) {
+        /* this might happen, if the ifname was already deleted
+         * and we failed to resolve ifindex.
+         *
+         * Forget about the name. */
+        iface = NULL;
+    }
+    _set_ip_ifindex(self, ifindex, iface);
+}
+
+static void
+ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data)
+{
+    NMModem *self = NM_MODEM(user_data);
+    guint32  i, num;
+    guint32  bad_dns1       = htonl(0x0A0B0C0D);
+    guint32  good_dns1      = htonl(0x04020201); /* GTE nameserver */
+    guint32  bad_dns2       = htonl(0x0A0B0C0E);
+    guint32  good_dns2      = htonl(0x04020202); /* GTE nameserver */
+    gboolean dns_workaround = FALSE;
+
+    /* Work around a PPP bug (#1732) which causes many mobile broadband
+     * providers to return 10.11.12.13 and 10.11.12.14 for the DNS servers.
+     * Apparently fixed in ppp-2.4.5 but we've had some reports that this is
+     * not the case.
+     *
+     * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=2e09ef6886bbf00bc5a9a641110f801e372ffde6
+     * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=f8191bf07df374f119a07910a79217c7618f113e
+     */
+
+    num = nm_ip4_config_get_num_nameservers(config);
+    if (num == 2) {
+        gboolean found1 = FALSE, found2 = FALSE;
+
+        for (i = 0; i < num; i++) {
+            guint32 ns = nm_ip4_config_get_nameserver(config, i);
+
+            if (ns == bad_dns1)
+                found1 = TRUE;
+            else if (ns == bad_dns2)
+                found2 = TRUE;
+        }
+
+        /* Be somewhat conservative about substitutions; the "bad" nameservers
+         * could actually be valid in some cases, so only substitute if ppp
+         * returns *only* the two bad nameservers.
+         */
+        dns_workaround = (found1 && found2);
+    }
+
+    if (!num || dns_workaround) {
+        _LOGW("compensating for invalid PPP-provided nameservers");
+        nm_ip4_config_reset_nameservers(config);
+        nm_ip4_config_add_nameserver(config, good_dns1);
+        nm_ip4_config_add_nameserver(config, good_dns2);
+    }
+
+    g_signal_emit(self, signals[IP4_CONFIG_RESULT], 0, config, NULL);
+}
+
+static void
+ppp_ip6_config(NMPPPManager *            ppp_manager,
+               const NMUtilsIPv6IfaceId *iid,
+               NMIP6Config *             config,
+               gpointer                  user_data)
+{
+    NMModem *self = NM_MODEM(user_data);
+
+    NM_MODEM_GET_PRIVATE(self)->iid = *iid;
+
+    nm_modem_emit_ip6_config_result(self, config, NULL);
+}
+
+static void
+ppp_stats(NMPPPManager *ppp_manager, guint i_in_bytes, guint i_out_bytes, gpointer user_data)
+{
+    NMModem *       self      = NM_MODEM(user_data);
+    NMModemPrivate *priv      = NM_MODEM_GET_PRIVATE(self);
+    guint32         in_bytes  = i_in_bytes;
+    guint32         out_bytes = i_out_bytes;
+
+    if (priv->in_bytes != in_bytes || priv->out_bytes != out_bytes) {
+        priv->in_bytes  = in_bytes;
+        priv->out_bytes = out_bytes;
+        g_signal_emit(self, signals[PPP_STATS], 0, (guint) in_bytes, (guint) out_bytes);
+    }
+}
+
+static gboolean
+port_speed_is_zero(const char *port)
+{
+    struct termios    options;
+    nm_auto_close int fd   = -1;
+    gs_free char *    path = NULL;
+
+    nm_assert(port);
+
+    if (port[0] != '/') {
+        if (!port[0] || strchr(port, '/') || NM_IN_STRSET(port, ".", ".."))
+            return FALSE;
+        path = g_build_path("/sys/class/tty", port, NULL);
+        port = path;
+    }
+
+    fd = open(port, O_RDWR | O_NONBLOCK | O_NOCTTY | O_CLOEXEC);
+    if (fd < 0)
+        return FALSE;
+
+    memset(&options, 0, sizeof(struct termios));
+    if (tcgetattr(fd, &options) != 0)
+        return FALSE;
+
+    return cfgetospeed(&options) == B0;
+}
+
+static NMActStageReturn
+ppp_stage3_ip_config_start(NMModem *            self,
+                           NMActRequest *       req,
+                           NMDeviceStateReason *out_failure_reason)
+{
+    NMModemPrivate *priv          = NM_MODEM_GET_PRIVATE(self);
+    const char *    ppp_name      = NULL;
+    GError *        error         = NULL;
+    guint           ip_timeout    = 30;
+    guint           baud_override = 0;
+
+    g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE);
+    g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NM_ACT_STAGE_RETURN_FAILURE);
+
+    /* If we're already running PPP don't restart it; for example, if both
+     * IPv4 and IPv6 are requested, IPv4 gets started first, but we use the
+     * same pppd for both v4 and v6.
+     */
+    if (priv->ppp_manager)
+        return NM_ACT_STAGE_RETURN_POSTPONE;
+
+    if (NM_MODEM_GET_CLASS(self)->get_user_pass) {
+        NMConnection *connection = nm_act_request_get_applied_connection(req);
+
+        g_assert(connection);
+        if (!NM_MODEM_GET_CLASS(self)->get_user_pass(self, connection, &ppp_name, NULL))
+            return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    if (!priv->data_port) {
+        _LOGE("error starting PPP (no data port)");
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    /* Check if ModemManager requested a specific IP timeout to be used. If 0 reported,
+     * use the default one (30s) */
+    if (priv->mm_ip_timeout > 0) {
+        _LOGI("using modem-specified IP timeout: %u seconds", priv->mm_ip_timeout);
+        ip_timeout = priv->mm_ip_timeout;
+    }
+
+    /* Some tty drivers and modems ignore port speed, but pppd requires the
+     * port speed to be > 0 or it exits. If the port speed is 0 pass an
+     * explicit speed to pppd to prevent the exit.
+     * https://bugzilla.redhat.com/show_bug.cgi?id=1281731
+     */
+    if (port_speed_is_zero(priv->data_port))
+        baud_override = 57600;
+
+    priv->ppp_manager = nm_ppp_manager_create(priv->data_port, &error);
+
+    if (priv->ppp_manager) {
+        nm_ppp_manager_set_route_parameters(priv->ppp_manager,
+                                            priv->ip4_route_table,
+                                            priv->ip4_route_metric,
+                                            priv->ip6_route_table,
+                                            priv->ip6_route_metric);
+    }
+
+    if (!priv->ppp_manager
+        || !nm_ppp_manager_start(priv->ppp_manager,
+                                 req,
+                                 ppp_name,
+                                 ip_timeout,
+                                 baud_override,
+                                 &error)) {
+        _LOGE("error starting PPP: %s", error->message);
+        g_error_free(error);
+        g_clear_object(&priv->ppp_manager);
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    g_signal_connect(priv->ppp_manager,
+                     NM_PPP_MANAGER_SIGNAL_STATE_CHANGED,
+                     G_CALLBACK(ppp_state_changed),
+                     self);
+    g_signal_connect(priv->ppp_manager,
+                     NM_PPP_MANAGER_SIGNAL_IFINDEX_SET,
+                     G_CALLBACK(ppp_ifindex_set),
+                     self);
+    g_signal_connect(priv->ppp_manager,
+                     NM_PPP_MANAGER_SIGNAL_IP4_CONFIG,
+                     G_CALLBACK(ppp_ip4_config),
+                     self);
+    g_signal_connect(priv->ppp_manager,
+                     NM_PPP_MANAGER_SIGNAL_IP6_CONFIG,
+                     G_CALLBACK(ppp_ip6_config),
+                     self);
+    g_signal_connect(priv->ppp_manager, NM_PPP_MANAGER_SIGNAL_STATS, G_CALLBACK(ppp_stats), self);
+
+    return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+NMActStageReturn
+nm_modem_stage3_ip4_config_start(NMModem *            self,
+                                 NMDevice *           device,
+                                 NMDeviceClass *      device_class,
+                                 NMDeviceStateReason *out_failure_reason)
+{
+    NMModemPrivate * priv;
+    NMActRequest *   req;
+    NMConnection *   connection;
+    const char *     method;
+    NMActStageReturn ret;
+
+    _LOGD("ip4_config_start");
+
+    g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE);
+    g_return_val_if_fail(NM_IS_DEVICE(device), NM_ACT_STAGE_RETURN_FAILURE);
+    g_return_val_if_fail(NM_IS_DEVICE_CLASS(device_class), NM_ACT_STAGE_RETURN_FAILURE);
+
+    req = nm_device_get_act_request(device);
+    g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
+
+    connection = nm_act_request_get_applied_connection(req);
+    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+    nm_modem_set_route_parameters_from_device(self, device);
+
+    method = nm_utils_get_ip_config_method(connection, AF_INET);
+
+    /* Only Disabled and Auto methods make sense for WWAN */
+    if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+        return NM_ACT_STAGE_RETURN_SUCCESS;
+
+    if (!nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+        _LOGE("unhandled WWAN IPv4 method '%s'; will fail", method);
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED);
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+    switch (priv->ip4_method) {
+    case NM_MODEM_IP_METHOD_PPP:
+        ret = ppp_stage3_ip_config_start(self, req, out_failure_reason);
+        break;
+    case NM_MODEM_IP_METHOD_STATIC:
+        _LOGD("MODEM_IP_METHOD_STATIC");
+        ret =
+            NM_MODEM_GET_CLASS(self)->static_stage3_ip4_config_start(self, req, out_failure_reason);
+        break;
+    case NM_MODEM_IP_METHOD_AUTO:
+        _LOGD("MODEM_IP_METHOD_AUTO");
+        ret = device_class->act_stage3_ip_config_start(device, AF_INET, NULL, out_failure_reason);
+        break;
+    default:
+        _LOGI("IPv4 configuration disabled");
+        ret = NM_ACT_STAGE_RETURN_IP_FAIL;
+        break;
+    }
+
+    return ret;
+}
+
+void
+nm_modem_ip4_pre_commit(NMModem *modem, NMDevice *device, NMIP4Config *config)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(modem);
+
+    /* If the modem has an ethernet-type data interface (ie, not PPP and thus
+     * not point-to-point) and IP config has a /32 prefix, then we assume that
+     * ARP will be pointless and we turn it off.
+     */
+    if (priv->ip4_method == NM_MODEM_IP_METHOD_STATIC
+        || priv->ip4_method == NM_MODEM_IP_METHOD_AUTO) {
+        const NMPlatformIP4Address *address = nm_ip4_config_get_first_address(config);
+
+        g_assert(address);
+        if (address->plen == 32)
+            nm_platform_link_set_noarp(nm_device_get_platform(device),
+                                       nm_device_get_ip_ifindex(device));
+    }
+}
+
+/*****************************************************************************/
+
+void
+nm_modem_emit_ip6_config_result(NMModem *self, NMIP6Config *config, GError *error)
+{
+    NMModemPrivate *            priv = NM_MODEM_GET_PRIVATE(self);
+    NMDedupMultiIter            ipconf_iter;
+    const NMPlatformIP6Address *addr;
+    gboolean                    do_slaac = TRUE;
+
+    if (error) {
+        g_signal_emit(self, signals[IP6_CONFIG_RESULT], 0, NULL, FALSE, error);
+        return;
+    }
+
+    if (config) {
+        /* If the IPv6 configuration only included a Link-Local address, then
+         * we have to run SLAAC to get the full IPv6 configuration.
+         */
+        nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, config, &addr) {
+            if (IN6_IS_ADDR_LINKLOCAL(&addr->address)) {
+                if (!priv->iid.id)
+                    priv->iid.id = ((guint64 *) (&addr->address.s6_addr))[1];
+            } else
+                do_slaac = FALSE;
+        }
+    }
+    g_assert(config || do_slaac);
+
+    g_signal_emit(self, signals[IP6_CONFIG_RESULT], 0, config, do_slaac, NULL);
+}
+
+static NMActStageReturn
+stage3_ip6_config_request(NMModem *self, NMDeviceStateReason *out_failure_reason)
+{
+    NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+    return NM_ACT_STAGE_RETURN_FAILURE;
+}
+
+NMActStageReturn
+nm_modem_stage3_ip6_config_start(NMModem *            self,
+                                 NMDevice *           device,
+                                 NMDeviceStateReason *out_failure_reason)
+{
+    NMModemPrivate * priv;
+    NMActRequest *   req;
+    NMActStageReturn ret;
+    NMConnection *   connection;
+    const char *     method;
+
+    g_return_val_if_fail(NM_IS_MODEM(self), NM_ACT_STAGE_RETURN_FAILURE);
+
+    req = nm_device_get_act_request(device);
+    g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
+
+    connection = nm_act_request_get_applied_connection(req);
+    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+    nm_modem_set_route_parameters_from_device(self, device);
+
+    method = nm_utils_get_ip_config_method(connection, AF_INET6);
+
+    /* Only Ignore, Disabled and Auto methods make sense for WWAN */
+    if (NM_IN_STRSET(method,
+                     NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                     NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
+        return NM_ACT_STAGE_RETURN_IP_DONE;
+
+    if (!nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
+        _LOGW("unhandled WWAN IPv6 method '%s'; will fail", method);
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+    switch (priv->ip6_method) {
+    case NM_MODEM_IP_METHOD_PPP:
+        ret = ppp_stage3_ip_config_start(self, req, out_failure_reason);
+        break;
+    case NM_MODEM_IP_METHOD_STATIC:
+    case NM_MODEM_IP_METHOD_AUTO:
+        /* Both static and DHCP/Auto retrieve a base IP config from the modem
+         * which in the static case is the full config, and the DHCP/Auto case
+         * is just the IPv6LL address to use for SLAAC.
+         */
+        ret = NM_MODEM_GET_CLASS(self)->stage3_ip6_config_request(self, out_failure_reason);
+        break;
+    default:
+        _LOGI("IPv6 configuration disabled");
+        ret = NM_ACT_STAGE_RETURN_IP_FAIL;
+        break;
+    }
+
+    return ret;
+}
+
+guint32
+nm_modem_get_configured_mtu(NMDevice *self, NMDeviceMtuSource *out_source, gboolean *out_force)
+{
+    NMConnection *connection;
+    NMSetting *   setting;
+    gint64        mtu_default;
+    guint         mtu = 0;
+    const char *  property_name;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(out_source);
+
+    connection = nm_device_get_applied_connection(self);
+    if (!connection)
+        g_return_val_if_reached(0);
+
+    setting = (NMSetting *) nm_connection_get_setting_gsm(connection);
+    if (!setting)
+        setting = (NMSetting *) nm_connection_get_setting_cdma(connection);
+
+    if (setting) {
+        g_object_get(setting, "mtu", &mtu, NULL);
+        if (mtu) {
+            *out_source = NM_DEVICE_MTU_SOURCE_CONNECTION;
+            return mtu;
+        }
+
+        property_name = NM_IS_SETTING_GSM(setting) ? "gsm.mtu" : "cdma.mtu";
+        mtu_default =
+            nm_device_get_configured_mtu_from_connection_default(self, property_name, G_MAXUINT32);
+        if (mtu_default >= 0) {
+            *out_source = NM_DEVICE_MTU_SOURCE_CONNECTION;
+            return (guint32) mtu_default;
+        }
+    }
+
+    *out_source = NM_DEVICE_MTU_SOURCE_NONE;
+    return 0;
+}
+
+/*****************************************************************************/
+
+static void
+cancel_get_secrets(NMModem *self)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    if (priv->secrets_id)
+        nm_act_request_cancel_secrets(priv->act_request, priv->secrets_id);
+}
+
+static void
+modem_secrets_cb(NMActRequest *                req,
+                 NMActRequestGetSecretsCallId *call_id,
+                 NMSettingsConnection *        connection,
+                 GError *                      error,
+                 gpointer                      user_data)
+{
+    NMModem *       self = NM_MODEM(user_data);
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    g_return_if_fail(call_id == priv->secrets_id);
+
+    priv->secrets_id = NULL;
+
+    if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)
+        || g_error_matches(error, NM_AGENT_MANAGER_ERROR, NM_AGENT_MANAGER_ERROR_NO_SECRETS))
+        return;
+
+    if (error)
+        _LOGW("modem-secrets: %s", error->message);
+
+    g_signal_emit(self, signals[AUTH_RESULT], 0, error);
+}
+
+void
+nm_modem_get_secrets(NMModem *   self,
+                     const char *setting_name,
+                     gboolean    request_new,
+                     const char *hint)
+{
+    NMModemPrivate *             priv  = NM_MODEM_GET_PRIVATE(self);
+    NMSecretAgentGetSecretsFlags flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
+
+    cancel_get_secrets(self);
+
+    if (request_new)
+        flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+    priv->secrets_id = nm_act_request_get_secrets(priv->act_request,
+                                                  FALSE,
+                                                  setting_name,
+                                                  flags,
+                                                  NM_MAKE_STRV(hint),
+                                                  modem_secrets_cb,
+                                                  self);
+    g_return_if_fail(priv->secrets_id);
+    g_signal_emit(self, signals[AUTH_REQUESTED], 0);
+}
+
+/*****************************************************************************/
+
+static NMActStageReturn
+modem_act_stage1_prepare(NMModem *            modem,
+                         NMConnection *       connection,
+                         NMDeviceStateReason *out_failure_reason)
+{
+    NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_UNKNOWN);
+    return NM_ACT_STAGE_RETURN_FAILURE;
+}
+
+NMActStageReturn
+nm_modem_act_stage1_prepare(NMModem *            self,
+                            NMActRequest *       req,
+                            NMDeviceStateReason *out_failure_reason)
+{
+    NMModemPrivate *  priv                    = NM_MODEM_GET_PRIVATE(self);
+    gs_unref_ptrarray GPtrArray *hints        = NULL;
+    const char *                 setting_name = NULL;
+    NMSecretAgentGetSecretsFlags flags        = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
+    NMConnection *               connection;
+
+    g_return_val_if_fail(NM_IS_ACT_REQUEST(req), NM_ACT_STAGE_RETURN_FAILURE);
+
+    if (priv->act_request)
+        g_object_unref(priv->act_request);
+    priv->act_request = g_object_ref(req);
+
+    connection = nm_act_request_get_applied_connection(req);
+    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+    setting_name = nm_connection_need_secrets(connection, &hints);
+    if (!setting_name) {
+        nm_assert(!hints);
+        return NM_MODEM_GET_CLASS(self)->modem_act_stage1_prepare(self,
+                                                                  connection,
+                                                                  out_failure_reason);
+    }
+
+    /* Secrets required... */
+    if (priv->secrets_tries++)
+        flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+
+    if (hints)
+        g_ptr_array_add(hints, NULL);
+
+    priv->secrets_id = nm_act_request_get_secrets(req,
+                                                  FALSE,
+                                                  setting_name,
+                                                  flags,
+                                                  hints ? (const char *const *) hints->pdata : NULL,
+                                                  modem_secrets_cb,
+                                                  self);
+    g_return_val_if_fail(priv->secrets_id, NM_ACT_STAGE_RETURN_FAILURE);
+    g_signal_emit(self, signals[AUTH_REQUESTED], 0);
+    return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+void
+nm_modem_act_stage2_config(NMModem *self)
+{
+    NMModemPrivate *priv;
+
+    g_return_if_fail(NM_IS_MODEM(self));
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+    /* Clear secrets tries counter since secrets were successfully used
+     * already if we get here.
+     */
+    priv->secrets_tries = 0;
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_modem_check_connection_compatible(NMModem *self, NMConnection *connection, GError **error)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    if (nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_GSM_SETTING_NAME)) {
+        NMSettingGsm *s_gsm;
+        const char *  str;
+
+        s_gsm = _nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, error);
+        if (!s_gsm)
+            return FALSE;
+
+        str = nm_setting_gsm_get_device_id(s_gsm);
+        if (str) {
+            if (!priv->device_id) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "GSM profile has device-id, device does not");
+                return FALSE;
+            }
+            if (!nm_streq(str, priv->device_id)) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "device has differing device-id than GSM profile");
+                return FALSE;
+            }
+        }
+
+        /* SIM properties may not be available before the SIM is unlocked, so
+         * to ensure that autoconnect works, the connection's SIM properties
+         * are only compared if present on the device.
+         */
+
+        if (priv->sim_id && (str = nm_setting_gsm_get_sim_id(s_gsm))) {
+            if (!nm_streq(str, priv->sim_id)) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "device has differing sim-id than GSM profile");
+                return FALSE;
+            }
+        }
+
+        if (priv->sim_operator_id && (str = nm_setting_gsm_get_sim_operator_id(s_gsm))) {
+            if (!nm_streq(str, priv->sim_operator_id)) {
+                nm_utils_error_set_literal(error,
+                                           NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                           "device has differing sim-operator-id than GSM profile");
+                return FALSE;
+            }
+        }
+    }
+
+    return NM_MODEM_GET_CLASS(self)->check_connection_compatible_with_modem(self,
+                                                                            connection,
+                                                                            error);
+}
+
+/*****************************************************************************/
+
+gboolean
+nm_modem_complete_connection(NMModem *            self,
+                             const char *         iface,
+                             NMConnection *       connection,
+                             NMConnection *const *existing_connections,
+                             GError **            error)
+{
+    NMModemClass *klass;
+
+    klass = NM_MODEM_GET_CLASS(self);
+    if (!klass->complete_connection) {
+        g_set_error(error,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_INVALID_CONNECTION,
+                    "Modem class %s had no complete_connection method",
+                    G_OBJECT_TYPE_NAME(self));
+        return FALSE;
+    }
+
+    return klass->complete_connection(self, iface, connection, existing_connections, error);
+}
+
+/*****************************************************************************/
+
+static void
+deactivate_cleanup(NMModem *self, NMDevice *device, gboolean stop_ppp_manager)
+{
+    NMModemPrivate *priv;
+    int             ifindex;
+
+    g_return_if_fail(NM_IS_MODEM(self));
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+
+    priv->secrets_tries = 0;
+
+    if (priv->act_request) {
+        cancel_get_secrets(self);
+        g_object_unref(priv->act_request);
+        priv->act_request = NULL;
+    }
+
+    priv->in_bytes = priv->out_bytes = 0;
+
+    if (priv->ppp_manager) {
+        g_signal_handlers_disconnect_by_data(priv->ppp_manager, self);
+        if (stop_ppp_manager)
+            nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL);
+        g_clear_object(&priv->ppp_manager);
+    }
+
+    if (device) {
+        g_return_if_fail(NM_IS_DEVICE(device));
+
+        if (priv->ip4_method == NM_MODEM_IP_METHOD_STATIC
+            || priv->ip4_method == NM_MODEM_IP_METHOD_AUTO
+            || priv->ip6_method == NM_MODEM_IP_METHOD_STATIC
+            || priv->ip6_method == NM_MODEM_IP_METHOD_AUTO) {
+            ifindex = nm_device_get_ip_ifindex(device);
+            if (ifindex > 0) {
+                NMPlatform *platform = nm_device_get_platform(device);
+
+                nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex);
+                nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex);
+                nm_platform_link_set_down(platform, ifindex);
+            }
+        }
+    }
+
+    nm_clear_g_free(&priv->data_port);
+    priv->mm_ip_timeout = 0;
+    priv->ip4_method    = NM_MODEM_IP_METHOD_UNKNOWN;
+    priv->ip6_method    = NM_MODEM_IP_METHOD_UNKNOWN;
+    _set_ip_ifindex(self, -1, NULL);
+}
+
+/*****************************************************************************/
+
+typedef struct {
+    NMModem *                 self;
+    NMDevice *                device;
+    GCancellable *            cancellable;
+    NMModemDeactivateCallback callback;
+    gpointer                  callback_user_data;
+} DeactivateContext;
+
+static void
+deactivate_context_complete(DeactivateContext *ctx, GError *error)
+{
+    NMModem *self = ctx->self;
+
+    _LOGD("modem deactivation finished %s%s%s",
+          NM_PRINT_FMT_QUOTED(error, "with failure: ", error->message, "", "successfully"));
+
+    if (ctx->callback)
+        ctx->callback(ctx->self, error, ctx->callback_user_data);
+    nm_g_object_unref(ctx->cancellable);
+    g_object_unref(ctx->device);
+    g_object_unref(ctx->self);
+    g_slice_free(DeactivateContext, ctx);
+}
+
+static void
+_deactivate_call_disconnect_cb(NMModem *self, GError *error, gpointer user_data)
+{
+    deactivate_context_complete(user_data, error);
+}
+
+static void
+_deactivate_call_disconnect(DeactivateContext *ctx)
+{
+    NM_MODEM_GET_CLASS(ctx->self)->disconnect(ctx->self,
+                                              FALSE,
+                                              ctx->cancellable,
+                                              _deactivate_call_disconnect_cb,
+                                              ctx);
+}
+
+static void
+_deactivate_ppp_manager_stop_cb(NMPPPManager *          ppp_manager,
+                                NMPPPManagerStopHandle *handle,
+                                gboolean                was_cancelled,
+                                gpointer                user_data)
+{
+    DeactivateContext *ctx = user_data;
+
+    g_object_unref(ppp_manager);
+
+    if (was_cancelled) {
+        gs_free_error GError *error = NULL;
+
+        if (!g_cancellable_set_error_if_cancelled(ctx->cancellable, &error))
+            nm_assert_not_reached();
+        deactivate_context_complete(ctx, error);
+        return;
+    }
+
+    nm_assert(!g_cancellable_is_cancelled(ctx->cancellable));
+    _deactivate_call_disconnect(ctx);
+}
+
+void
+nm_modem_deactivate_async(NMModem *                 self,
+                          NMDevice *                device,
+                          GCancellable *            cancellable,
+                          NMModemDeactivateCallback callback,
+                          gpointer                  user_data)
+{
+    NMModemPrivate *   priv = NM_MODEM_GET_PRIVATE(self);
+    DeactivateContext *ctx;
+    NMPPPManager *     ppp_manager;
+
+    g_return_if_fail(NM_IS_MODEM(self));
+    g_return_if_fail(NM_IS_DEVICE(device));
+    g_return_if_fail(G_IS_CANCELLABLE(cancellable));
+
+    ctx                     = g_slice_new(DeactivateContext);
+    ctx->self               = g_object_ref(self);
+    ctx->device             = g_object_ref(device);
+    ctx->cancellable        = g_object_ref(cancellable);
+    ctx->callback           = callback;
+    ctx->callback_user_data = user_data;
+
+    ppp_manager = nm_g_object_ref(priv->ppp_manager);
+
+    NM_MODEM_GET_CLASS(self)->deactivate_cleanup(self, ctx->device, FALSE);
+
+    if (ppp_manager) {
+        /* If we have a PPP manager, stop it.
+         *
+         * Pass on the reference in @ppp_manager. */
+        nm_ppp_manager_stop(ppp_manager, ctx->cancellable, _deactivate_ppp_manager_stop_cb, ctx);
+        return;
+    }
+
+    _deactivate_call_disconnect(ctx);
+}
+
+/*****************************************************************************/
+
+void
+nm_modem_deactivate(NMModem *self, NMDevice *device)
+{
+    /* First cleanup */
+    NM_MODEM_GET_CLASS(self)->deactivate_cleanup(self, device, TRUE);
+    /* Then disconnect without waiting */
+    NM_MODEM_GET_CLASS(self)->disconnect(self, FALSE, NULL, NULL, NULL);
+}
+
+/*****************************************************************************/
+
+void
+nm_modem_device_state_changed(NMModem *self, NMDeviceState new_state, NMDeviceState old_state)
+{
+    gboolean        was_connected = FALSE, warn = TRUE;
+    NMModemPrivate *priv;
+
+    g_return_if_fail(NM_IS_MODEM(self));
+
+    if (old_state >= NM_DEVICE_STATE_PREPARE && old_state <= NM_DEVICE_STATE_DEACTIVATING)
+        was_connected = TRUE;
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+
+    /* Make sure we don't leave the serial device open */
+    switch (new_state) {
+    case NM_DEVICE_STATE_UNMANAGED:
+    case NM_DEVICE_STATE_UNAVAILABLE:
+    case NM_DEVICE_STATE_FAILED:
+    case NM_DEVICE_STATE_DISCONNECTED:
+        if (priv->act_request) {
+            cancel_get_secrets(self);
+            g_object_unref(priv->act_request);
+            priv->act_request = NULL;
+        }
+
+        if (was_connected) {
+            /* Don't bother warning on FAILED since the modem is already gone */
+            if (new_state == NM_DEVICE_STATE_FAILED || new_state == NM_DEVICE_STATE_DISCONNECTED)
+                warn = FALSE;
+            /* First cleanup */
+            NM_MODEM_GET_CLASS(self)->deactivate_cleanup(self, NULL, TRUE);
+            NM_MODEM_GET_CLASS(self)->disconnect(self, warn, NULL, NULL, NULL);
+        }
+        break;
+    default:
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+const char *
+nm_modem_get_uid(NMModem *self)
+{
+    g_return_val_if_fail(NM_IS_MODEM(self), NULL);
+
+    return NM_MODEM_GET_PRIVATE(self)->uid;
+}
+
+const char *
+nm_modem_get_path(NMModem *self)
+{
+    g_return_val_if_fail(NM_IS_MODEM(self), NULL);
+
+    return NM_MODEM_GET_PRIVATE(self)->path;
+}
+
+const char *
+nm_modem_get_driver(NMModem *self)
+{
+    g_return_val_if_fail(NM_IS_MODEM(self), NULL);
+
+    return NM_MODEM_GET_PRIVATE(self)->driver;
+}
+
+const char *
+nm_modem_get_control_port(NMModem *self)
+{
+    g_return_val_if_fail(NM_IS_MODEM(self), NULL);
+
+    return NM_MODEM_GET_PRIVATE(self)->control_port;
+}
+
+int
+nm_modem_get_ip_ifindex(NMModem *self)
+{
+    NMModemPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_MODEM(self), 0);
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+
+    /* internally we track an unset ip_ifindex as -1.
+     * For the caller of nm_modem_get_ip_ifindex(), this
+     * shall be zero too. */
+    return priv->ip_ifindex != -1 ? priv->ip_ifindex : 0;
+}
+
+static void
+_set_ip_ifindex(NMModem *self, int ifindex, const char *ifname)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    nm_assert(ifindex >= -1);
+    nm_assert((ifindex > 0) == !!ifname);
+
+    if (!nm_streq0(priv->ip_iface, ifname)) {
+        g_free(priv->ip_iface);
+        priv->ip_iface = g_strdup(ifname);
+    }
+
+    if (priv->ip_ifindex != ifindex) {
+        priv->ip_ifindex = ifindex;
+        _notify(self, PROP_IP_IFINDEX);
+    }
+}
+
+gboolean
+nm_modem_set_data_port(NMModem *       self,
+                       NMPlatform *    platform,
+                       const char *    data_port,
+                       NMModemIPMethod ip4_method,
+                       NMModemIPMethod ip6_method,
+                       guint           timeout,
+                       GError **       error)
+{
+    NMModemPrivate *priv;
+    gboolean        is_ppp;
+    int             ifindex = -1;
+
+    g_return_val_if_fail(NM_IS_MODEM(self), FALSE);
+    g_return_val_if_fail(NM_IS_PLATFORM(platform), FALSE);
+    g_return_val_if_fail(!error || !*error, FALSE);
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+
+    if (priv->ppp_manager || priv->data_port || priv->ip_ifindex != -1) {
+        g_set_error_literal(error,
+                            NM_UTILS_ERROR,
+                            NM_UTILS_ERROR_UNKNOWN,
+                            "cannot set data port in activated state");
+        /* this really shouldn't happen. Assert. */
+        g_return_val_if_reached(FALSE);
+    }
+
+    if (!data_port) {
+        g_set_error_literal(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "missing data port");
+        return FALSE;
+    }
+
+    is_ppp = (ip4_method == NM_MODEM_IP_METHOD_PPP) || (ip6_method == NM_MODEM_IP_METHOD_PPP);
+    if (is_ppp) {
+        if (!NM_IN_SET(ip4_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_PPP)
+            || !NM_IN_SET(ip6_method, NM_MODEM_IP_METHOD_UNKNOWN, NM_MODEM_IP_METHOD_PPP)) {
+            g_set_error_literal(error,
+                                NM_UTILS_ERROR,
+                                NM_UTILS_ERROR_UNKNOWN,
+                                "conflicting ip methods");
+            return FALSE;
+        }
+    } else if (!NM_IN_SET(ip4_method,
+                          NM_MODEM_IP_METHOD_UNKNOWN,
+                          NM_MODEM_IP_METHOD_STATIC,
+                          NM_MODEM_IP_METHOD_AUTO)
+               || !NM_IN_SET(ip6_method,
+                             NM_MODEM_IP_METHOD_UNKNOWN,
+                             NM_MODEM_IP_METHOD_STATIC,
+                             NM_MODEM_IP_METHOD_AUTO)
+               || (ip4_method == NM_MODEM_IP_METHOD_UNKNOWN
+                   && ip6_method == NM_MODEM_IP_METHOD_UNKNOWN)) {
+        g_set_error_literal(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "invalid ip methods");
+        return FALSE;
+    }
+
+    if (!is_ppp) {
+        ifindex = nm_platform_if_nametoindex(platform, data_port);
+        if (ifindex <= 0) {
+            g_set_error(error,
+                        NM_UTILS_ERROR,
+                        NM_UTILS_ERROR_UNKNOWN,
+                        "cannot find network interface %s",
+                        data_port);
+            return FALSE;
+        }
+        if (!nm_platform_process_events_ensure_link(platform, ifindex, data_port)) {
+            g_set_error(error,
+                        NM_UTILS_ERROR,
+                        NM_UTILS_ERROR_UNKNOWN,
+                        "cannot find network interface %s in platform cache",
+                        data_port);
+            return FALSE;
+        }
+    }
+
+    priv->mm_ip_timeout = timeout;
+    priv->ip4_method    = ip4_method;
+    priv->ip6_method    = ip6_method;
+    if (is_ppp) {
+        priv->data_port = g_strdup(data_port);
+        _set_ip_ifindex(self, -1, NULL);
+    } else {
+        priv->data_port = NULL;
+        _set_ip_ifindex(self, ifindex, data_port);
+    }
+    return TRUE;
+}
+
+gboolean
+nm_modem_owns_port(NMModem *self, const char *iface)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    g_return_val_if_fail(iface != NULL, FALSE);
+
+    if (NM_MODEM_GET_CLASS(self)->owns_port)
+        return NM_MODEM_GET_CLASS(self)->owns_port(self, iface);
+
+    return NM_IN_STRSET(iface, priv->ip_iface, priv->data_port, priv->control_port);
+}
+
+gboolean
+nm_modem_get_iid(NMModem *self, NMUtilsIPv6IfaceId *out_iid)
+{
+    g_return_val_if_fail(NM_IS_MODEM(self), FALSE);
+
+    *out_iid = NM_MODEM_GET_PRIVATE(self)->iid;
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+void
+nm_modem_get_route_parameters(NMModem *self,
+                              guint32 *out_ip4_route_table,
+                              guint32 *out_ip4_route_metric,
+                              guint32 *out_ip6_route_table,
+                              guint32 *out_ip6_route_metric)
+{
+    NMModemPrivate *priv;
+
+    g_return_if_fail(NM_IS_MODEM(self));
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+    NM_SET_OUT(out_ip4_route_table, priv->ip4_route_table);
+    NM_SET_OUT(out_ip4_route_metric, priv->ip4_route_metric);
+    NM_SET_OUT(out_ip6_route_table, priv->ip6_route_table);
+    NM_SET_OUT(out_ip6_route_metric, priv->ip6_route_metric);
+}
+
+void
+nm_modem_set_route_parameters(NMModem *self,
+                              guint32  ip4_route_table,
+                              guint32  ip4_route_metric,
+                              guint32  ip6_route_table,
+                              guint32  ip6_route_metric)
+{
+    NMModemPrivate *priv;
+
+    g_return_if_fail(NM_IS_MODEM(self));
+
+    priv = NM_MODEM_GET_PRIVATE(self);
+    if (priv->ip4_route_table != ip4_route_table || priv->ip4_route_metric != ip4_route_metric
+        || priv->ip6_route_table != ip6_route_table || priv->ip6_route_metric != ip6_route_metric) {
+        priv->ip4_route_table  = ip4_route_table;
+        priv->ip4_route_metric = ip4_route_metric;
+        priv->ip6_route_table  = ip6_route_table;
+        priv->ip6_route_metric = ip6_route_metric;
+
+        _LOGT("route-parameters: table-v4: %u, metric-v4: %u, table-v6: %u, metric-v6: %u",
+              priv->ip4_route_table,
+              priv->ip4_route_metric,
+              priv->ip6_route_table,
+              priv->ip6_route_metric);
+    }
+
+    if (priv->ppp_manager) {
+        nm_ppp_manager_set_route_parameters(priv->ppp_manager,
+                                            priv->ip4_route_table,
+                                            priv->ip4_route_metric,
+                                            priv->ip6_route_table,
+                                            priv->ip6_route_metric);
+    }
+}
+
+void
+nm_modem_set_route_parameters_from_device(NMModem *self, NMDevice *device)
+{
+    g_return_if_fail(NM_IS_DEVICE(device));
+
+    nm_modem_set_route_parameters(self,
+                                  nm_device_get_route_table(device, AF_INET),
+                                  nm_device_get_route_metric(device, AF_INET),
+                                  nm_device_get_route_table(device, AF_INET6),
+                                  nm_device_get_route_metric(device, AF_INET6));
+}
+
+/*****************************************************************************/
+
+void
+nm_modem_get_capabilities(NMModem *                  self,
+                          NMDeviceModemCapabilities *modem_caps,
+                          NMDeviceModemCapabilities *current_caps)
+{
+    g_return_if_fail(NM_IS_MODEM(self));
+
+    NM_MODEM_GET_CLASS(self)->get_capabilities(self, modem_caps, current_caps);
+}
+
+/*****************************************************************************/
+
+void
+_nm_modem_set_operator_code(NMModem *self, const char *operator_code)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    if (g_strcmp0(priv->operator_code, operator_code) != 0) {
+        g_free(priv->operator_code);
+        priv->operator_code = g_strdup(operator_code);
+        _notify(self, PROP_OPERATOR_CODE);
+    }
+}
+
+void
+_nm_modem_set_apn(NMModem *self, const char *apn)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    if (g_strcmp0(priv->apn, apn) != 0) {
+        g_free(priv->apn);
+        priv->apn = g_strdup(apn);
+        _notify(self, PROP_APN);
+    }
+}
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMModem *       self = NM_MODEM(object);
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(self);
+
+    switch (prop_id) {
+    case PROP_PATH:
+        g_value_set_string(value, priv->path);
+        break;
+    case PROP_DRIVER:
+        g_value_set_string(value, priv->driver);
+        break;
+    case PROP_CONTROL_PORT:
+        g_value_set_string(value, priv->control_port);
+        break;
+    case PROP_IP_IFINDEX:
+        g_value_set_int(value, nm_modem_get_ip_ifindex(self));
+        break;
+    case PROP_UID:
+        g_value_set_string(value, priv->uid);
+        break;
+    case PROP_STATE:
+        g_value_set_int(value, priv->state);
+        break;
+    case PROP_DEVICE_ID:
+        g_value_set_string(value, priv->device_id);
+        break;
+    case PROP_SIM_ID:
+        g_value_set_string(value, priv->sim_id);
+        break;
+    case PROP_IP_TYPES:
+        g_value_set_uint(value, priv->ip_types);
+        break;
+    case PROP_SIM_OPERATOR_ID:
+        g_value_set_string(value, priv->sim_operator_id);
+        break;
+    case PROP_OPERATOR_CODE:
+        g_value_set_string(value, priv->operator_code);
+        break;
+    case PROP_APN:
+        g_value_set_string(value, priv->apn);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object);
+    const char *    s;
+
+    switch (prop_id) {
+    case PROP_PATH:
+        /* construct-only */
+        priv->path = g_value_dup_string(value);
+        g_return_if_fail(priv->path);
+        break;
+    case PROP_DRIVER:
+        /* construct-only */
+        priv->driver = g_value_dup_string(value);
+        break;
+    case PROP_CONTROL_PORT:
+        /* construct-only */
+        priv->control_port = g_value_dup_string(value);
+        break;
+    case PROP_UID:
+        /* construct-only */
+        priv->uid = g_value_dup_string(value);
+        break;
+    case PROP_STATE:
+        /* construct-only */
+        priv->state = g_value_get_int(value);
+        break;
+    case PROP_DEVICE_ID:
+        /* construct-only */
+        priv->device_id = g_value_dup_string(value);
+        break;
+    case PROP_SIM_ID:
+        g_free(priv->sim_id);
+        priv->sim_id = g_value_dup_string(value);
+        break;
+    case PROP_IP_TYPES:
+        priv->ip_types = g_value_get_uint(value);
+        break;
+    case PROP_SIM_OPERATOR_ID:
+        nm_clear_g_free(&priv->sim_operator_id);
+        s = g_value_get_string(value);
+        if (s && s[0])
+            priv->sim_operator_id = g_strdup(s);
+        break;
+    case PROP_OPERATOR_CODE:
+        /* construct-only */
+        priv->operator_code = g_value_dup_string(value);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_modem_init(NMModem *self)
+{
+    NMModemPrivate *priv;
+
+    self->_priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_MODEM, NMModemPrivate);
+    priv        = self->_priv;
+
+    priv->ip_ifindex       = -1;
+    priv->ip4_route_table  = RT_TABLE_MAIN;
+    priv->ip4_route_metric = 700;
+    priv->ip6_route_table  = RT_TABLE_MAIN;
+    priv->ip6_route_metric = 700;
+}
+
+static void
+constructed(GObject *object)
+{
+    NMModemPrivate *priv;
+
+    G_OBJECT_CLASS(nm_modem_parent_class)->constructed(object);
+
+    priv = NM_MODEM_GET_PRIVATE(NM_MODEM(object));
+
+    g_return_if_fail(priv->control_port);
+}
+
+/*****************************************************************************/
+
+static void
+dispose(GObject *object)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object);
+
+    g_clear_object(&priv->act_request);
+
+    G_OBJECT_CLASS(nm_modem_parent_class)->dispose(object);
+}
+
+static void
+finalize(GObject *object)
+{
+    NMModemPrivate *priv = NM_MODEM_GET_PRIVATE(object);
+
+    g_free(priv->uid);
+    g_free(priv->path);
+    g_free(priv->driver);
+    g_free(priv->control_port);
+    g_free(priv->data_port);
+    g_free(priv->ip_iface);
+    g_free(priv->device_id);
+    g_free(priv->sim_id);
+    g_free(priv->sim_operator_id);
+    g_free(priv->operator_code);
+    g_free(priv->apn);
+
+    G_OBJECT_CLASS(nm_modem_parent_class)->finalize(object);
+}
+
+static void
+nm_modem_class_init(NMModemClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    g_type_class_add_private(object_class, sizeof(NMModemPrivate));
+
+    object_class->constructed  = constructed;
+    object_class->set_property = set_property;
+    object_class->get_property = get_property;
+    object_class->dispose      = dispose;
+    object_class->finalize     = finalize;
+
+    klass->modem_act_stage1_prepare  = modem_act_stage1_prepare;
+    klass->stage3_ip6_config_request = stage3_ip6_config_request;
+    klass->deactivate_cleanup        = deactivate_cleanup;
+
+    obj_properties[PROP_UID] =
+        g_param_spec_string(NM_MODEM_UID,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_PATH] =
+        g_param_spec_string(NM_MODEM_PATH,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_DRIVER] =
+        g_param_spec_string(NM_MODEM_DRIVER,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_CONTROL_PORT] =
+        g_param_spec_string(NM_MODEM_CONTROL_PORT,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_IP_IFINDEX] = g_param_spec_int(NM_MODEM_IP_IFINDEX,
+                                                       "",
+                                                       "",
+                                                       0,
+                                                       G_MAXINT,
+                                                       0,
+                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_STATE] =
+        g_param_spec_int(NM_MODEM_STATE,
+                         "",
+                         "",
+                         NM_MODEM_STATE_UNKNOWN,
+                         _NM_MODEM_STATE_LAST,
+                         NM_MODEM_STATE_UNKNOWN,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_DEVICE_ID] =
+        g_param_spec_string(NM_MODEM_DEVICE_ID,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_SIM_ID] =
+        g_param_spec_string(NM_MODEM_SIM_ID,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_IP_TYPES] =
+        g_param_spec_uint(NM_MODEM_IP_TYPES,
+                          "IP Types",
+                          "Supported IP types",
+                          0,
+                          G_MAXUINT32,
+                          NM_MODEM_IP_TYPE_IPV4,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_SIM_OPERATOR_ID] =
+        g_param_spec_string(NM_MODEM_SIM_OPERATOR_ID,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_OPERATOR_CODE] =
+        g_param_spec_string(NM_MODEM_OPERATOR_CODE,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS);
+
+    obj_properties[PROP_APN] =
+        g_param_spec_string(NM_MODEM_APN, "", "", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+    signals[PPP_STATS] = g_signal_new(NM_MODEM_PPP_STATS,
+                                      G_OBJECT_CLASS_TYPE(object_class),
+                                      G_SIGNAL_RUN_FIRST,
+                                      0,
+                                      NULL,
+                                      NULL,
+                                      NULL,
+                                      G_TYPE_NONE,
+                                      2,
+                                      G_TYPE_UINT /*guint32 in_bytes*/,
+                                      G_TYPE_UINT /*guint32 out_bytes*/);
+
+    signals[PPP_FAILED] = g_signal_new(NM_MODEM_PPP_FAILED,
+                                       G_OBJECT_CLASS_TYPE(object_class),
+                                       G_SIGNAL_RUN_FIRST,
+                                       0,
+                                       NULL,
+                                       NULL,
+                                       NULL,
+                                       G_TYPE_NONE,
+                                       1,
+                                       G_TYPE_UINT);
+
+    signals[IP4_CONFIG_RESULT] = g_signal_new(NM_MODEM_IP4_CONFIG_RESULT,
+                                              G_OBJECT_CLASS_TYPE(object_class),
+                                              G_SIGNAL_RUN_FIRST,
+                                              0,
+                                              NULL,
+                                              NULL,
+                                              NULL,
+                                              G_TYPE_NONE,
+                                              2,
+                                              G_TYPE_OBJECT,
+                                              G_TYPE_POINTER);
+
+    /**
+     * NMModem::ip6-config-result:
+     * @modem: the #NMModem  on which the signal is emitted
+     * @config: the #NMIP6Config to apply to the modem's data port
+     * @do_slaac: %TRUE if IPv6 SLAAC should be started
+     * @error: a #GError if any error occurred during IP configuration
+     *
+     * This signal is emitted when IPv6 configuration has completed or failed.
+     * If @error is set the configuration failed.  If @config is set, then
+     * the details should be applied to the data port before any further
+     * configuration (like SLAAC) is done.  @do_slaac indicates whether SLAAC
+     * should be started after applying @config to the data port.
+     */
+    signals[IP6_CONFIG_RESULT] = g_signal_new(NM_MODEM_IP6_CONFIG_RESULT,
+                                              G_OBJECT_CLASS_TYPE(object_class),
+                                              G_SIGNAL_RUN_FIRST,
+                                              0,
+                                              NULL,
+                                              NULL,
+                                              NULL,
+                                              G_TYPE_NONE,
+                                              3,
+                                              G_TYPE_OBJECT,
+                                              G_TYPE_BOOLEAN,
+                                              G_TYPE_POINTER);
+
+    signals[PREPARE_RESULT] = g_signal_new(NM_MODEM_PREPARE_RESULT,
+                                           G_OBJECT_CLASS_TYPE(object_class),
+                                           G_SIGNAL_RUN_FIRST,
+                                           0,
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           G_TYPE_NONE,
+                                           2,
+                                           G_TYPE_BOOLEAN,
+                                           G_TYPE_UINT);
+
+    signals[AUTH_REQUESTED] = g_signal_new(NM_MODEM_AUTH_REQUESTED,
+                                           G_OBJECT_CLASS_TYPE(object_class),
+                                           G_SIGNAL_RUN_FIRST,
+                                           0,
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           G_TYPE_NONE,
+                                           0);
+
+    signals[AUTH_RESULT] = g_signal_new(NM_MODEM_AUTH_RESULT,
+                                        G_OBJECT_CLASS_TYPE(object_class),
+                                        G_SIGNAL_RUN_FIRST,
+                                        0,
+                                        NULL,
+                                        NULL,
+                                        NULL,
+                                        G_TYPE_NONE,
+                                        1,
+                                        G_TYPE_POINTER);
+
+    signals[REMOVED] = g_signal_new(NM_MODEM_REMOVED,
+                                    G_OBJECT_CLASS_TYPE(object_class),
+                                    G_SIGNAL_RUN_FIRST,
+                                    0,
+                                    NULL,
+                                    NULL,
+                                    NULL,
+                                    G_TYPE_NONE,
+                                    0);
+
+    signals[STATE_CHANGED] = g_signal_new(NM_MODEM_STATE_CHANGED,
+                                          G_OBJECT_CLASS_TYPE(object_class),
+                                          G_SIGNAL_RUN_FIRST,
+                                          0,
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          G_TYPE_NONE,
+                                          2,
+                                          G_TYPE_INT,
+                                          G_TYPE_INT);
+}