// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2009 - 2014 Red Hat, Inc.
* Copyright (C) 2009 Novell, Inc.
*/
#include "nm-default.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);
}