/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2013 - 2016 Canonical Ltd.
*/
#include "src/core/nm-default-daemon.h"
#include "nm-modem-ofono.h"
#include "libnm-core-intern/nm-core-internal.h"
#include "devices/nm-device-private.h"
#include "nm-modem.h"
#include "libnm-platform/nm-platform.h"
#include "nm-ip4-config.h"
#define VARIANT_IS_OF_TYPE_BOOLEAN(v) \
((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_BOOLEAN)))
#define VARIANT_IS_OF_TYPE_STRING(v) \
((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_STRING)))
#define VARIANT_IS_OF_TYPE_OBJECT_PATH(v) \
((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_OBJECT_PATH)))
#define VARIANT_IS_OF_TYPE_STRING_ARRAY(v) \
((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_STRING_ARRAY)))
#define VARIANT_IS_OF_TYPE_DICTIONARY(v) \
((v) != NULL && (g_variant_is_of_type((v), G_VARIANT_TYPE_DICTIONARY)))
/*****************************************************************************/
typedef struct {
GHashTable *connect_properties;
GDBusProxy *modem_proxy;
GDBusProxy *connman_proxy;
GDBusProxy *context_proxy;
GDBusProxy *sim_proxy;
GCancellable *modem_proxy_cancellable;
GCancellable *connman_proxy_cancellable;
GCancellable *context_proxy_cancellable;
GCancellable *sim_proxy_cancellable;
GError *property_error;
char *context_path;
char *imsi;
gboolean modem_online;
gboolean gprs_attached;
NMIP4Config *ip4_config;
} NMModemOfonoPrivate;
struct _NMModemOfono {
NMModem parent;
NMModemOfonoPrivate _priv;
};
struct _NMModemOfonoClass {
NMModemClass parent;
};
G_DEFINE_TYPE(NMModemOfono, nm_modem_ofono, NM_TYPE_MODEM)
#define NM_MODEM_OFONO_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMModemOfono, NM_IS_MODEM_OFONO)
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_MB
#define _NMLOG_PREFIX_NAME "modem-ofono"
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
const NMLogLevel _level = (level); \
\
if (nm_logging_enabled(_level, (_NMLOG_DOMAIN))) { \
NMModemOfono *const __self = (self); \
char __prefix_name[128]; \
const char * __uid; \
\
_nm_log(_level, \
(_NMLOG_DOMAIN), \
0, \
NULL, \
NULL, \
"%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
_NMLOG_PREFIX_NAME, \
(__self ? ({ \
((__uid = nm_modem_get_uid((NMModem *) __self)) \
? nm_sprintf_buf(__prefix_name, "[%s]", __uid) \
: "(null)"); \
}) \
: "") _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
/*****************************************************************************/
static void
get_capabilities(NMModem * _self,
NMDeviceModemCapabilities *modem_caps,
NMDeviceModemCapabilities *current_caps)
{
/* FIXME: auto-detect capabilities to allow LTE */
*modem_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
*current_caps = NM_DEVICE_MODEM_CAPABILITY_GSM_UMTS;
}
static void
update_modem_state(NMModemOfono *self)
{
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
NMModemState state = nm_modem_get_state(NM_MODEM(self));
NMModemState new_state = NM_MODEM_STATE_DISABLED;
const char * reason = NULL;
_LOGI("'Attached': %s 'Online': %s 'IMSI': %s",
priv->gprs_attached ? "true" : "false",
priv->modem_online ? "true" : "false",
priv->imsi);
if (priv->modem_online == FALSE) {
reason = "modem 'Online=false'";
} else if (priv->imsi == NULL && state != NM_MODEM_STATE_ENABLING) {
reason = "modem not ready";
} else if (priv->gprs_attached == FALSE) {
new_state = NM_MODEM_STATE_SEARCHING;
reason = "modem searching";
} else {
new_state = NM_MODEM_STATE_REGISTERED;
reason = "modem ready";
}
if (state != new_state)
nm_modem_set_state(NM_MODEM(self), new_state, reason);
}
/* Disconnect */
typedef struct {
NMModemOfono * self;
_NMModemDisconnectCallback callback;
gpointer callback_user_data;
GCancellable * cancellable;
gboolean warn;
} DisconnectContext;
static void
disconnect_context_complete(DisconnectContext *ctx, GError *error)
{
if (ctx->callback)
ctx->callback(NM_MODEM(ctx->self), error, ctx->callback_user_data);
nm_g_object_unref(ctx->cancellable);
g_object_unref(ctx->self);
g_slice_free(DisconnectContext, ctx);
}
static void
disconnect_context_complete_on_idle(gpointer user_data, GCancellable *cancellable)
{
DisconnectContext *ctx = user_data;
gs_free_error GError *error = NULL;
if (!g_cancellable_set_error_if_cancelled(cancellable, &error)) {
g_set_error_literal(&error,
NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
("modem is currently not connected"));
}
disconnect_context_complete(ctx, error);
}
static void
disconnect_done(GObject *source, GAsyncResult *result, gpointer user_data)
{
DisconnectContext *ctx = user_data;
NMModemOfono * self = ctx->self;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *v = NULL;
v = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
disconnect_context_complete(ctx, error);
return;
}
if (error && ctx->warn)
_LOGW("failed to disconnect modem: %s", error->message);
_LOGD("modem disconnected");
update_modem_state(self);
disconnect_context_complete(ctx, error);
}
static void
disconnect(NMModem * modem,
gboolean warn,
GCancellable * cancellable,
_NMModemDisconnectCallback callback,
gpointer user_data)
{
NMModemOfono * self = NM_MODEM_OFONO(modem);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
DisconnectContext * ctx;
NMModemState state = nm_modem_get_state(NM_MODEM(self));
_LOGD("warn: %s modem_state: %s", warn ? "TRUE" : "FALSE", nm_modem_state_to_string(state));
ctx = g_slice_new0(DisconnectContext);
ctx->self = g_object_ref(self);
ctx->cancellable = nm_g_object_ref(cancellable);
ctx->warn = warn;
ctx->callback = callback;
ctx->callback_user_data = user_data;
if (state != NM_MODEM_STATE_CONNECTED || g_cancellable_is_cancelled(cancellable)) {
nm_utils_invoke_on_idle(cancellable, disconnect_context_complete_on_idle, ctx);
return;
}
nm_modem_set_state(NM_MODEM(self),
NM_MODEM_STATE_DISCONNECTING,
nm_modem_state_to_string(NM_MODEM_STATE_DISCONNECTING));
g_dbus_proxy_call(priv->context_proxy,
"SetProperty",
g_variant_new("(sv)", "Active", g_variant_new("b", warn)),
G_DBUS_CALL_FLAGS_NONE,
20000,
ctx->cancellable,
disconnect_done,
ctx);
}
static void
deactivate_cleanup(NMModem *modem, NMDevice *device, gboolean stop_ppp_manager)
{
NMModemOfono * self = NM_MODEM_OFONO(modem);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
/* TODO: cancel SimpleConnect() if any */
g_clear_object(&priv->ip4_config);
NM_MODEM_CLASS(nm_modem_ofono_parent_class)
->deactivate_cleanup(modem, device, stop_ppp_manager);
}
static gboolean
check_connection_compatible_with_modem(NMModem *modem, NMConnection *connection, GError **error)
{
NMModemOfono * self = NM_MODEM_OFONO(modem);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
const char * id;
if (!_nm_connection_check_main_setting(connection, NM_SETTING_GSM_SETTING_NAME, NULL)) {
nm_utils_error_set(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
"connection type %s is not supported by ofono modem",
nm_connection_get_connection_type(connection));
return FALSE;
}
if (!priv->imsi) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"modem has no IMSI");
return FALSE;
}
id = nm_connection_get_id(connection);
if (!strstr(id, "/context")) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"the connection ID has no context");
return FALSE;
}
if (!strstr(id, priv->imsi)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"the connection ID does not contain the IMSI");
return FALSE;
}
return TRUE;
}
static void
handle_sim_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
{
NMModemOfono * self = NM_MODEM_OFONO(user_data);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
if (g_strcmp0(property, "SubscriberIdentity") == 0 && VARIANT_IS_OF_TYPE_STRING(v)) {
gsize length;
const char *value_str = g_variant_get_string(v, &length);
_LOGD("SubscriberIdentify found");
/* Check for empty DBus string value */
if (length && g_strcmp0(value_str, "(null)") != 0
&& g_strcmp0(value_str, priv->imsi) != 0) {
if (priv->imsi != NULL) {
_LOGW("SimManager:'SubscriberIdentity' changed: %s", priv->imsi);
g_free(priv->imsi);
}
priv->imsi = g_strdup(value_str);
update_modem_state(self);
}
}
}
static void
sim_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
{
gs_unref_variant GVariant *v_child = g_variant_get_child_value(v, 0);
handle_sim_property(proxy, property, v_child, user_data);
}
static void
sim_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMModemOfono * self;
NMModemOfonoPrivate *priv;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *v_properties = NULL;
gs_unref_variant GVariant *v_dict = NULL;
gs_unref_variant GVariant *v = NULL;
GVariantIter i;
const char * property;
v_properties =
_nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error);
if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
self = NM_MODEM_OFONO(user_data);
priv = NM_MODEM_OFONO_GET_PRIVATE(self);
g_clear_object(&priv->sim_proxy_cancellable);
if (!v_properties) {
g_dbus_error_strip_remote_error(error);
_LOGW("error getting sim properties: %s", error->message);
return;
}
_LOGD("sim v_properties is type: %s", g_variant_get_type_string(v_properties));
v_dict = g_variant_get_child_value(v_properties, 0);
if (!v_dict) {
_LOGW("error getting sim properties: no v_dict");
return;
}
_LOGD("sim v_dict is type: %s", g_variant_get_type_string(v_dict));
/*
* TODO:
* 1) optimize by looking up properties ( Online, Interfaces ), instead
* of iterating
*
* 2) reduce code duplication between all of the get_properties_done
* functions in this class.
*/
g_variant_iter_init(&i, v_dict);
while (g_variant_iter_loop(&i, "{&sv}", &property, &v)) {
handle_sim_property(NULL, property, v, self);
}
}
static void
_sim_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMModemOfono * self;
NMModemOfonoPrivate *priv;
gs_free_error GError *error = NULL;
GDBusProxy * proxy;
proxy = g_dbus_proxy_new_for_bus_finish(result, &error);
if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
self = user_data;
priv = NM_MODEM_OFONO_GET_PRIVATE(self);
if (!proxy) {
_LOGW("failed to create SimManager proxy: %s", error->message);
g_clear_object(&priv->sim_proxy_cancellable);
return;
}
priv->sim_proxy = proxy;
/* Watch for custom ofono PropertyChanged signals */
_nm_dbus_signal_connect(priv->sim_proxy,
"PropertyChanged",
G_VARIANT_TYPE("(sv)"),
G_CALLBACK(sim_property_changed),
self);
g_dbus_proxy_call(priv->sim_proxy,
"GetProperties",
NULL,
G_DBUS_CALL_FLAGS_NONE,
20000,
priv->sim_proxy_cancellable,
sim_get_properties_done,
self);
}
static void
handle_sim_iface(NMModemOfono *self, gboolean found)
{
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
_LOGD("SimManager interface %sfound", found ? "" : "not ");
if (!found && (priv->sim_proxy || priv->sim_proxy_cancellable)) {
_LOGI("SimManager interface disappeared");
nm_clear_g_cancellable(&priv->sim_proxy_cancellable);
if (priv->sim_proxy) {
g_signal_handlers_disconnect_by_data(priv->sim_proxy, self);
g_clear_object(&priv->sim_proxy);
}
nm_clear_g_free(&priv->imsi);
update_modem_state(self);
} else if (found && (!priv->sim_proxy && !priv->sim_proxy_cancellable)) {
_LOGI("found new SimManager interface");
priv->sim_proxy_cancellable = g_cancellable_new();
g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
| G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
NULL, /* GDBusInterfaceInfo */
OFONO_DBUS_SERVICE,
nm_modem_get_path(NM_MODEM(self)),
OFONO_DBUS_INTERFACE_SIM_MANAGER,
priv->sim_proxy_cancellable, /* GCancellable */
_sim_proxy_new_cb,
self);
}
}
static void
handle_connman_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
{
NMModemOfono * self = NM_MODEM_OFONO(user_data);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
if (g_strcmp0(property, "Attached") == 0 && VARIANT_IS_OF_TYPE_BOOLEAN(v)) {
gboolean attached = g_variant_get_boolean(v);
gboolean old_attached = priv->gprs_attached;
_LOGD("Attached: %s", attached ? "True" : "False");
if (priv->gprs_attached != attached) {
priv->gprs_attached = attached;
_LOGI("Attached %s -> %s",
old_attached ? "true" : "false",
attached ? "true" : "false");
update_modem_state(self);
}
}
}
static void
connman_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
{
gs_unref_variant GVariant *v_child = g_variant_get_child_value(v, 0);
handle_connman_property(proxy, property, v_child, user_data);
}
static void
connman_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMModemOfono * self;
NMModemOfonoPrivate *priv;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *v_properties = NULL;
gs_unref_variant GVariant *v_dict = NULL;
gs_unref_variant GVariant *v = NULL;
GVariantIter i;
const char * property;
v_properties =
_nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error);
if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
self = NM_MODEM_OFONO(user_data);
priv = NM_MODEM_OFONO_GET_PRIVATE(self);
g_clear_object(&priv->connman_proxy_cancellable);
if (!v_properties) {
g_dbus_error_strip_remote_error(error);
_LOGW("error getting connman properties: %s", error->message);
return;
}
v_dict = g_variant_get_child_value(v_properties, 0);
/*
* TODO:
* 1) optimize by looking up properties ( Online, Interfaces ), instead
* of iterating
*
* 2) reduce code duplication between all of the get_properties_done
* functions in this class.
*/
g_variant_iter_init(&i, v_dict);
while (g_variant_iter_loop(&i, "{&sv}", &property, &v)) {
handle_connman_property(NULL, property, v, self);
}
}
static void
_connman_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMModemOfono * self;
NMModemOfonoPrivate *priv;
gs_free_error GError *error = NULL;
GDBusProxy * proxy;
proxy = g_dbus_proxy_new_for_bus_finish(result, &error);
if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
self = user_data;
priv = NM_MODEM_OFONO_GET_PRIVATE(self);
if (!proxy) {
_LOGW("failed to create ConnectionManager proxy: %s", error->message);
g_clear_object(&priv->connman_proxy_cancellable);
return;
}
priv->connman_proxy = proxy;
_nm_dbus_signal_connect(priv->connman_proxy,
"PropertyChanged",
G_VARIANT_TYPE("(sv)"),
G_CALLBACK(connman_property_changed),
self);
g_dbus_proxy_call(priv->connman_proxy,
"GetProperties",
NULL,
G_DBUS_CALL_FLAGS_NONE,
20000,
priv->connman_proxy_cancellable,
connman_get_properties_done,
self);
}
static void
handle_connman_iface(NMModemOfono *self, gboolean found)
{
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
_LOGD("ConnectionManager interface %sfound", found ? "" : "not ");
if (!found && (priv->connman_proxy || priv->connman_proxy_cancellable)) {
_LOGI("ConnectionManager interface disappeared");
nm_clear_g_cancellable(&priv->connman_proxy_cancellable);
if (priv->connman_proxy) {
g_signal_handlers_disconnect_by_data(priv->connman_proxy, self);
g_clear_object(&priv->connman_proxy);
}
/* The connection manager proxy disappeared, we should
* consider the modem disabled.
*/
priv->gprs_attached = FALSE;
update_modem_state(self);
} else if (found && (!priv->connman_proxy && !priv->connman_proxy_cancellable)) {
_LOGI("found new ConnectionManager interface");
priv->connman_proxy_cancellable = g_cancellable_new();
g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES
| G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
NULL, /* GDBusInterfaceInfo */
OFONO_DBUS_SERVICE,
nm_modem_get_path(NM_MODEM(self)),
OFONO_DBUS_INTERFACE_CONNECTION_MANAGER,
priv->connman_proxy_cancellable,
_connman_proxy_new_cb,
self);
}
}
static void
handle_modem_property(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
{
NMModemOfono * self = NM_MODEM_OFONO(user_data);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
if ((g_strcmp0(property, "Online") == 0) && VARIANT_IS_OF_TYPE_BOOLEAN(v)) {
gboolean online = g_variant_get_boolean(v);
_LOGD("Online: %s", online ? "True" : "False");
if (online != priv->modem_online) {
priv->modem_online = online;
_LOGI("modem is now %s", online ? "Online" : "Offline");
update_modem_state(self);
}
} else if ((g_strcmp0(property, "Interfaces") == 0) && VARIANT_IS_OF_TYPE_STRING_ARRAY(v)) {
const char **array, **iter;
gboolean found_connman = FALSE;
gboolean found_sim = FALSE;
_LOGD("Interfaces found");
array = g_variant_get_strv(v, NULL);
if (array) {
for (iter = array; *iter; iter++) {
if (g_strcmp0(OFONO_DBUS_INTERFACE_SIM_MANAGER, *iter) == 0)
found_sim = TRUE;
else if (g_strcmp0(OFONO_DBUS_INTERFACE_CONNECTION_MANAGER, *iter) == 0)
found_connman = TRUE;
}
g_free(array);
}
handle_sim_iface(self, found_sim);
handle_connman_iface(self, found_connman);
}
}
static void
modem_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
{
GVariant *v_child = g_variant_get_child_value(v, 0);
handle_modem_property(proxy, property, v_child, user_data);
g_variant_unref(v_child);
}
static void
modem_get_properties_done(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMModemOfono * self;
NMModemOfonoPrivate *priv;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *v_properties = NULL;
gs_unref_variant GVariant *v_dict = NULL;
GVariant * v;
GVariantIter i;
const char * property;
v_properties =
_nm_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, G_VARIANT_TYPE("(a{sv})"), &error);
if (!v_properties && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
self = NM_MODEM_OFONO(user_data);
priv = NM_MODEM_OFONO_GET_PRIVATE(self);
g_clear_object(&priv->modem_proxy_cancellable);
if (!v_properties) {
g_dbus_error_strip_remote_error(error);
_LOGW("error getting modem properties: %s", error->message);
return;
}
v_dict = g_variant_get_child_value(v_properties, 0);
if (!v_dict) {
_LOGW("error getting modem properties: no v_dict");
return;
}
/*
* TODO:
* 1) optimize by looking up properties ( Online, Interfaces ), instead
* of iterating
*
* 2) reduce code duplication between all of the get_properties_done
* functions in this class.
*/
g_variant_iter_init(&i, v_dict);
while (g_variant_iter_loop(&i, "{&sv}", &property, &v)) {
handle_modem_property(NULL, property, v, self);
}
}
static void
stage1_prepare_done(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMModemOfono * self;
NMModemOfonoPrivate *priv;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *v = NULL;
v = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), result, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
self = NM_MODEM_OFONO(user_data);
priv = NM_MODEM_OFONO_GET_PRIVATE(self);
g_clear_object(&priv->context_proxy_cancellable);
nm_clear_pointer(&priv->connect_properties, g_hash_table_destroy);
if (error) {
if (!g_strstr_len(error->message,
NM_STRLEN(OFONO_ERROR_IN_PROGRESS),
OFONO_ERROR_IN_PROGRESS)) {
nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY);
}
}
}
static void
handle_settings(GVariant *v_dict, gpointer user_data)
{
NMModemOfono * self = NM_MODEM_OFONO(user_data);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
NMPlatformIP4Address addr;
gboolean ret = FALSE;
const char * interface;
const char * s;
const char ** array, **iter;
guint32 address_network, gateway_network;
guint32 ip4_route_table, ip4_route_metric;
int ifindex;
GError * error = NULL;
//_LOGD("PropertyChanged: %s", property);
/*
* TODO: might be a good idea and re-factor this to mimic bluez-device,
* ie. have this function just check the key, and call a sub-func to
* handle the action.
*/
_LOGI("IPv4 static Settings:");
if (!g_variant_lookup(v_dict, "Interface", "&s", &interface)) {
_LOGW("Settings 'Interface' missing");
goto out;
}
_LOGD("Interface: %s", interface);
if (!nm_modem_set_data_port(NM_MODEM(self),
NM_PLATFORM_GET,
interface,
NM_MODEM_IP_METHOD_STATIC,
NM_MODEM_IP_METHOD_UNKNOWN,
0,
&error)) {
_LOGW("failed to connect to modem: %s", error->message);
g_clear_error(&error);
goto out;
}
ifindex = nm_modem_get_ip_ifindex(NM_MODEM(self));
nm_assert(ifindex > 0);
/* TODO: verify handling of ip4_config; check other places it's used... */
g_clear_object(&priv->ip4_config);
priv->ip4_config = nm_ip4_config_new(nm_platform_get_multi_idx(NM_PLATFORM_GET), ifindex);
if (!g_variant_lookup(v_dict, "Address", "&s", &s)) {
_LOGW("Settings 'Address' missing");
goto out;
}
if (!s || !nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) {
_LOGW("can't convert 'Address' %s to addr", s ?: "");
goto out;
}
memset(&addr, 0, sizeof(addr));
addr.ifindex = ifindex;
addr.address = address_network;
addr.addr_source = NM_IP_CONFIG_SOURCE_WWAN;
if (!g_variant_lookup(v_dict, "Netmask", "&s", &s)) {
_LOGW("Settings 'Netmask' missing");
goto out;
}
if (!s || !nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) {
_LOGW("invalid 'Netmask': %s", s ?: "");
goto out;
}
addr.plen = nm_utils_ip4_netmask_to_prefix(address_network);
_LOGI("Address: %s", nm_platform_ip4_address_to_string(&addr, NULL, 0));
nm_ip4_config_add_address(priv->ip4_config, &addr);
if (!g_variant_lookup(v_dict, "Gateway", "&s", &s) || !s) {
_LOGW("Settings 'Gateway' missing");
goto out;
}
if (!nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &gateway_network)) {
_LOGW("invalid 'Gateway': %s", s);
goto out;
}
nm_modem_get_route_parameters(NM_MODEM(self), &ip4_route_table, &ip4_route_metric, NULL, NULL);
{
const NMPlatformIP4Route r = {
.rt_source = NM_IP_CONFIG_SOURCE_WWAN,
.gateway = gateway_network,
.table_coerced = nm_platform_route_table_coerce(ip4_route_table),
.metric = ip4_route_metric,
};
_LOGI("Gateway: %s", s);
nm_ip4_config_add_route(priv->ip4_config, &r, NULL);
}
if (!g_variant_lookup(v_dict, "DomainNameServers", "^a&s", &array)) {
_LOGW("Settings 'DomainNameServers' missing");
goto out;
}
if (array) {
for (iter = array; *iter; iter++) {
if (nm_utils_parse_inaddr_bin(AF_INET, *iter, NULL, &address_network)
&& address_network) {
_LOGI("DNS: %s", *iter);
nm_ip4_config_add_nameserver(priv->ip4_config, address_network);
} else {
_LOGW("invalid NameServer: %s", *iter);
}
}
if (iter == array) {
_LOGW("Settings: 'DomainNameServers': none specified");
g_free(array);
goto out;
}
g_free(array);
}
if (g_variant_lookup(v_dict, "MessageProxy", "&s", &s)) {
_LOGI("MessageProxy: %s", s);
if (s && nm_utils_parse_inaddr_bin(AF_INET, s, NULL, &address_network)) {
nm_modem_get_route_parameters(NM_MODEM(self),
&ip4_route_table,
&ip4_route_metric,
NULL,
NULL);
{
const NMPlatformIP4Route mms_route = {
.network = address_network,
.plen = 32,
.gateway = gateway_network,
.table_coerced = nm_platform_route_table_coerce(ip4_route_table),
.metric = ip4_route_metric,
};
nm_ip4_config_add_route(priv->ip4_config, &mms_route, NULL);
}
} else {
_LOGW("invalid MessageProxy: %s", s);
}
}
ret = TRUE;
out:
if (nm_modem_get_state(NM_MODEM(self)) != NM_MODEM_STATE_CONNECTED) {
_LOGI("emitting PREPARE_RESULT: %s", ret ? "TRUE" : "FALSE");
nm_modem_emit_prepare_result(NM_MODEM(self),
ret,
ret ? NM_DEVICE_STATE_REASON_NONE
: NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
} else {
_LOGW("MODEM_PPP_FAILED");
nm_modem_emit_ppp_failed(NM_MODEM(self), NM_DEVICE_STATE_REASON_PPP_FAILED);
}
}
static void
context_property_changed(GDBusProxy *proxy, const char *property, GVariant *v, gpointer user_data)
{
NMModemOfono * self = NM_MODEM_OFONO(user_data);
gs_unref_variant GVariant *v_dict = NULL;
_LOGD("PropertyChanged: %s", property);
if (g_strcmp0(property, "Settings") != 0)
return;
v_dict = g_variant_get_child_value(v, 0);
if (!v_dict) {
_LOGW("ofono: (%s): error getting IPv4 Settings", nm_modem_get_uid(NM_MODEM(self)));
return;
}
g_assert(g_variant_is_of_type(v_dict, G_VARIANT_TYPE_VARDICT));
handle_settings(v_dict, user_data);
}
static NMActStageReturn
static_stage3_ip4_config_start(NMModem * modem,
NMActRequest * req,
NMDeviceStateReason *out_failure_reason)
{
NMModemOfono * self = NM_MODEM_OFONO(modem);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
GError * error = NULL;
if (!priv->ip4_config) {
_LOGD("IP4 config not ready(?)");
return NM_ACT_STAGE_RETURN_FAILURE;
}
_LOGD("IP4 config is done; setting modem_state -> CONNECTED");
g_signal_emit_by_name(self, NM_MODEM_IP4_CONFIG_RESULT, priv->ip4_config, error);
/* Signal listener takes ownership of the IP4Config */
priv->ip4_config = NULL;
nm_modem_set_state(NM_MODEM(self),
NM_MODEM_STATE_CONNECTED,
nm_modem_state_to_string(NM_MODEM_STATE_CONNECTED));
return NM_ACT_STAGE_RETURN_POSTPONE;
}
static void
context_properties_cb(GDBusProxy *proxy, GAsyncResult *result, gpointer user_data)
{
NMModemOfono * self;
NMModemOfonoPrivate *priv;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *properties = NULL;
gs_unref_variant GVariant *settings = NULL;
gs_unref_variant GVariant *v_dict = NULL;
gboolean active;
self = NM_MODEM_OFONO(user_data);
priv = NM_MODEM_OFONO_GET_PRIVATE(self);
properties = g_dbus_proxy_call_finish(proxy, result, &error);
if (!properties) {
_LOGW("ofono: connection failed: no context properties returned %s", error->message);
g_clear_error(&error);
goto error;
}
v_dict = g_variant_get_child_value(properties, 0);
if (!v_dict || !g_variant_is_of_type(v_dict, G_VARIANT_TYPE_VARDICT)) {
_LOGW("ofono: connection failed; could not read connection properties");
goto error;
}
if (!g_variant_lookup(v_dict, "Active", "b", &active)) {
_LOGW("ofono: connection failed; can not read 'Active' property");
goto error;
}
/* Watch for custom ofono PropertyChanged signals */
_nm_dbus_signal_connect(priv->context_proxy,
"PropertyChanged",
G_VARIANT_TYPE("(sv)"),
G_CALLBACK(context_property_changed),
self);
if (active) {
_LOGD("ofono: connection is already Active");
settings = g_variant_lookup_value(v_dict, "Settings", G_VARIANT_TYPE_VARDICT);
if (settings == NULL) {
_LOGW("ofono: connection failed; can not read 'Settings' property");
goto error;
}
handle_settings(settings, user_data);
} else {
g_dbus_proxy_call(priv->context_proxy,
"SetProperty",
g_variant_new("(sv)", "Active", g_variant_new("b", TRUE)),
G_DBUS_CALL_FLAGS_NONE,
20000,
NULL,
(GAsyncReadyCallback) stage1_prepare_done,
self);
}
return;
error:
nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY);
}
static void
context_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMModemOfono * self;
NMModemOfonoPrivate *priv;
gs_free_error GError *error = NULL;
GDBusProxy * proxy;
proxy = g_dbus_proxy_new_for_bus_finish(result, &error);
if (!proxy || g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
self = NM_MODEM_OFONO(user_data);
priv = NM_MODEM_OFONO_GET_PRIVATE(self);
if (!proxy) {
_LOGE("failed to create ofono ConnectionContext DBus proxy: %s", error->message);
g_clear_object(&priv->context_proxy_cancellable);
nm_modem_emit_prepare_result(NM_MODEM(self), FALSE, NM_DEVICE_STATE_REASON_MODEM_BUSY);
return;
}
priv->context_proxy = proxy;
if (!priv->gprs_attached) {
g_clear_object(&priv->context_proxy_cancellable);
nm_modem_emit_prepare_result(NM_MODEM(self),
FALSE,
NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
return;
}
/* We have an old copy of the settings from a previous activation,
* clear it so that we can gate getting the IP config from oFono
* on whether or not we have already received them
*/
g_clear_object(&priv->ip4_config);
/* We need to directly query ConnectionContextinteface to get the current
* property values */
g_dbus_proxy_call(priv->context_proxy,
"GetProperties",
NULL,
G_DBUS_CALL_FLAGS_NONE,
20000,
NULL,
(GAsyncReadyCallback) context_properties_cb,
self);
}
static void
do_context_activate(NMModemOfono *self)
{
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
g_return_if_fail(NM_IS_MODEM_OFONO(self));
nm_clear_g_cancellable(&priv->context_proxy_cancellable);
g_clear_object(&priv->context_proxy);
priv->context_proxy_cancellable = g_cancellable_new();
g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
NULL,
OFONO_DBUS_SERVICE,
priv->context_path,
OFONO_DBUS_INTERFACE_CONNECTION_CONTEXT,
priv->context_proxy_cancellable,
context_proxy_new_cb,
self);
}
static GHashTable *
create_connect_properties(NMConnection *connection)
{
NMSettingGsm *setting;
GHashTable * properties;
const char * str;
setting = nm_connection_get_setting_gsm(connection);
properties = g_hash_table_new(nm_str_hash, g_str_equal);
str = nm_setting_gsm_get_apn(setting);
if (str)
g_hash_table_insert(properties, "AccessPointName", g_strdup(str));
str = nm_setting_gsm_get_username(setting);
if (str)
g_hash_table_insert(properties, "Username", g_strdup(str));
str = nm_setting_gsm_get_password(setting);
if (str)
g_hash_table_insert(properties, "Password", g_strdup(str));
return properties;
}
static NMActStageReturn
modem_act_stage1_prepare(NMModem * modem,
NMConnection * connection,
NMDeviceStateReason *out_failure_reason)
{
NMModemOfono * self = NM_MODEM_OFONO(modem);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
const char * context_id;
char ** id = NULL;
context_id = nm_connection_get_id(connection);
id = g_strsplit(context_id, "/", 0);
g_return_val_if_fail(id[2], NM_ACT_STAGE_RETURN_FAILURE);
_LOGD("trying %s %s", id[1], id[2]);
g_free(priv->context_path);
priv->context_path = g_strdup_printf("%s/%s", nm_modem_get_path(modem), id[2]);
g_strfreev(id);
if (!priv->context_path) {
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_GSM_APN_FAILED);
return NM_ACT_STAGE_RETURN_FAILURE;
}
if (priv->connect_properties)
g_hash_table_destroy(priv->connect_properties);
priv->connect_properties = create_connect_properties(connection);
_LOGI("activating context %s", priv->context_path);
update_modem_state(self);
if (nm_modem_get_state(modem) == NM_MODEM_STATE_REGISTERED) {
do_context_activate(self);
} else {
_LOGW("could not activate context: modem is not registered.");
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER);
return NM_ACT_STAGE_RETURN_FAILURE;
}
return NM_ACT_STAGE_RETURN_POSTPONE;
}
static void
modem_proxy_new_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMModemOfono * self;
NMModemOfonoPrivate *priv;
gs_free_error GError *error = NULL;
GDBusProxy * proxy;
proxy = g_dbus_proxy_new_for_bus_finish(result, &error);
if (!proxy && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
self = NM_MODEM_OFONO(user_data);
priv = NM_MODEM_OFONO_GET_PRIVATE(self);
if (!proxy) {
_LOGE("failed to create ofono modem DBus proxy: %s", error->message);
g_clear_object(&priv->modem_proxy_cancellable);
return;
}
priv->modem_proxy = proxy;
_nm_dbus_signal_connect(priv->modem_proxy,
"PropertyChanged",
G_VARIANT_TYPE("(sv)"),
G_CALLBACK(modem_property_changed),
self);
g_dbus_proxy_call(priv->modem_proxy,
"GetProperties",
NULL,
G_DBUS_CALL_FLAGS_NONE,
20000,
priv->modem_proxy_cancellable,
modem_get_properties_done,
self);
}
/*****************************************************************************/
static void
nm_modem_ofono_init(NMModemOfono *self)
{}
static void
constructed(GObject *object)
{
NMModemOfono * self = NM_MODEM_OFONO(object);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
priv->modem_proxy_cancellable = g_cancellable_new();
g_dbus_proxy_new_for_bus(G_BUS_TYPE_SYSTEM,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
NULL,
OFONO_DBUS_SERVICE,
nm_modem_get_path(NM_MODEM(self)),
OFONO_DBUS_INTERFACE_MODEM,
priv->modem_proxy_cancellable,
modem_proxy_new_cb,
self);
G_OBJECT_CLASS(nm_modem_ofono_parent_class)->constructed(object);
}
NMModem *
nm_modem_ofono_new(const char *path)
{
gs_free char *basename = NULL;
g_return_val_if_fail(path != NULL, NULL);
nm_log_info(LOGD_MB, "ofono: creating new Ofono modem path %s", path);
/* Use short modem name (not its object path) as the NM device name (which
* comes from NM_MODEM_UID)and the device ID.
*/
basename = g_path_get_basename(path);
return g_object_new(NM_TYPE_MODEM_OFONO,
NM_MODEM_PATH,
path,
NM_MODEM_UID,
basename,
NM_MODEM_DEVICE_ID,
basename,
NM_MODEM_CONTROL_PORT,
"ofono", /* mandatory */
NM_MODEM_DRIVER,
"ofono",
NM_MODEM_STATE,
(int) NM_MODEM_STATE_INITIALIZING,
NULL);
}
static void
dispose(GObject *object)
{
NMModemOfono * self = NM_MODEM_OFONO(object);
NMModemOfonoPrivate *priv = NM_MODEM_OFONO_GET_PRIVATE(self);
nm_clear_g_cancellable(&priv->modem_proxy_cancellable);
nm_clear_g_cancellable(&priv->connman_proxy_cancellable);
nm_clear_g_cancellable(&priv->context_proxy_cancellable);
nm_clear_g_cancellable(&priv->sim_proxy_cancellable);
if (priv->connect_properties) {
g_hash_table_destroy(priv->connect_properties);
priv->connect_properties = NULL;
}
g_clear_object(&priv->ip4_config);
if (priv->modem_proxy) {
g_signal_handlers_disconnect_by_data(priv->modem_proxy, self);
g_clear_object(&priv->modem_proxy);
}
if (priv->connman_proxy) {
g_signal_handlers_disconnect_by_data(priv->connman_proxy, self);
g_clear_object(&priv->connman_proxy);
}
if (priv->context_proxy) {
g_signal_handlers_disconnect_by_data(priv->context_proxy, self);
g_clear_object(&priv->context_proxy);
}
if (priv->sim_proxy) {
g_signal_handlers_disconnect_by_data(priv->sim_proxy, self);
g_clear_object(&priv->sim_proxy);
}
g_free(priv->imsi);
priv->imsi = NULL;
G_OBJECT_CLASS(nm_modem_ofono_parent_class)->dispose(object);
}
static void
nm_modem_ofono_class_init(NMModemOfonoClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
NMModemClass *modem_class = NM_MODEM_CLASS(klass);
object_class->constructed = constructed;
object_class->dispose = dispose;
modem_class->get_capabilities = get_capabilities;
modem_class->disconnect = disconnect;
modem_class->deactivate_cleanup = deactivate_cleanup;
modem_class->check_connection_compatible_with_modem = check_connection_compatible_with_modem;
modem_class->modem_act_stage1_prepare = modem_act_stage1_prepare;
modem_class->static_stage3_ip4_config_start = static_stage3_ip4_config_start;
}