From 916843c373bc653bc472e6353631134b42d490f2 Mon Sep 17 00:00:00 2001 From: Pantelis Antoniou Date: Thu, 8 Mar 2018 20:39:36 +0200 Subject: initial network service binding The AGL network service binding exposes connman apis via the AGL application framework. All network connectivity technologies are supported via the binding, limited only by the underlying connman daemon. Bug-AGL: SPEC-1540 Change-Id: Id73cfc98c7abe97cb655a4fc40d440422fa75803 Signed-off-by: Pantelis Antoniou [Cleanups to whitespace and naming to match other services] Signed-off-by: Matt Porter --- binding/network-connman.c | 555 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 555 insertions(+) create mode 100644 binding/network-connman.c (limited to 'binding/network-connman.c') diff --git a/binding/network-connman.c b/binding/network-connman.c new file mode 100644 index 0000000..a9e0b74 --- /dev/null +++ b/binding/network-connman.c @@ -0,0 +1,555 @@ +/* + * Copyright 2018 Konsulko Group + * Author: Pantelis Antoniou + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define AFB_BINDING_VERSION 2 +#include + +#include "network-api.h" +#include "network-common.h" + +static const struct property_info manager_props[] = { + { .name = "State", .fmt = "s", }, + { .name = "OfflineMode", .fmt = "b", }, + { .name = "SessionMode", .fmt = "b", }, + { }, +}; + +static const struct property_info technology_props[] = { + { .name = "Name", .fmt = "s", }, + { .name = "Type", .fmt = "s", }, + { .name = "Powered", .fmt = "b", }, + { .name = "Connected", .fmt = "b", }, + { .name = "Tethering", .fmt = "b", }, + { .name = "TetheringIdentifier",.fmt = "s", }, + { .name = "TetheringPassphrase",.fmt = "s", }, + { }, +}; + +static const struct property_info service_props[] = { + /* simple types */ + { .name = "State", .fmt = "s", }, + { .name = "Error", .fmt = "s", }, + { .name = "Type", .fmt = "s", }, + { .name = "Name", .fmt = "s", }, + { .name = "Favorite", .fmt = "b", }, + { .name = "Immutable", .fmt = "b", }, + { .name = "AutoConnect", .fmt = "b", }, + { .name = "Strength", .fmt = "y", }, + { .name = "Security", .fmt = "as", }, + { .name = "Roaming", .fmt = "b", }, + + /* complex types with configuration (but no subtype) */ + { + .name = "Nameservers", + .fmt = "as", + .flags = PI_CONFIG, + }, { + .name = "Timeservers", + .fmt = "as", + .flags = PI_CONFIG, + }, { + .name = "Domains", + .fmt = "as", + .flags = PI_CONFIG, + }, { + .name = "mDNS", + .fmt = "b", + .flags = PI_CONFIG, + }, + + /* complex types with subtypes */ + { + .name = "IPv4", + .fmt = "{sv}", + .flags = PI_CONFIG, + .sub = (const struct property_info []) { + { .name = "Method", .fmt = "s", }, + { .name = "Address", .fmt = "s", }, + { .name = "Netmask", .fmt = "s", }, + { .name = "Gateway", .fmt = "s", }, + { }, + }, + }, { + .name = "IPv6", + .fmt = "{sv}", + .flags = PI_CONFIG, + .sub = (const struct property_info []) { + { .name = "Method", .fmt = "s", }, + { .name = "Address", .fmt = "s", }, + { .name = "PrefixLength", .fmt = "y", }, + { .name = "Gateway", .fmt = "s", }, + { .name = "Privacy", .fmt = "s", }, + { }, + }, + }, { + .name = "Proxy", + .fmt = "{sv}", + .flags = PI_CONFIG, + .sub = (const struct property_info []) { + { .name = "Method", .fmt = "s", }, + { .name = "URL", .fmt = "s", }, + { .name = "Servers", .fmt = "as", }, + { .name = "Excludes", .fmt = "as", }, + { }, + }, + }, { + .name = "Ethernet", + .fmt = "{sv}", + .sub = (const struct property_info []) { + { .name = "Method", .fmt = "s", }, + { .name = "Interface", .fmt = "s", }, + { .name = "Address", .fmt = "s", }, + { .name = "MTU", .fmt = "q", }, + { }, + }, + }, { + .name = "Provider", + .fmt = "{sv}", + .flags = PI_CONFIG, + .sub = (const struct property_info []) { + { .name = "Host", .fmt = "s", }, + { .name = "Domain", .fmt = "s", }, + { .name = "Name", .fmt = "s", }, + { .name = "Type", .fmt = "s", }, + { }, + }, + }, + { } +}; + +const struct property_info *connman_get_property_info( + const char *access_type, GError **error) +{ + const struct property_info *pi = NULL; + + if (!strcmp(access_type, CONNMAN_AT_MANAGER)) + pi = manager_props; + else if (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY)) + pi = technology_props; + else if (!strcmp(access_type, CONNMAN_AT_SERVICE)) + pi = service_props; + else + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "illegal %s argument", access_type); + return pi; +} + +gboolean connman_property_dbus2json(const char *access_type, + json_object *jprop, const gchar *key, GVariant *var, + gboolean *is_config, + GError **error) +{ + const struct property_info *pi; + gboolean ret; + + *is_config = FALSE; + pi = connman_get_property_info(access_type, error); + if (!pi) + return FALSE; + + ret = root_property_dbus2json(jprop, pi, key, var, is_config); + if (!ret) + g_set_error(error, NB_ERROR, NB_ERROR_UNKNOWN_PROPERTY, + "unknown %s property %s", + access_type, key); + + return ret; +} + + +void connman_decode_call_error(struct network_state *ns, + const char *access_type, const char *type_arg, + const char *method, + GError **error) +{ + if (!error || !*error) + return; + + if (strstr((*error)->message, + "org.freedesktop.DBus.Error.UnknownObject")) { + + if (!strcmp(method, "SetProperty") || + !strcmp(method, "GetProperty") || + !strcmp(method, "ClearProperty")) { + + g_clear_error(error); + g_set_error(error, NB_ERROR, + NB_ERROR_UNKNOWN_PROPERTY, + "unknown %s property on %s", + access_type, type_arg); + + } else if (!strcmp(method, "Connect") || + !strcmp(method, "Disconnect") || + !strcmp(method, "Remove") || + !strcmp(method, "ResetCounters") || + !strcmp(method, "MoveAfter") || + !strcmp(method, "MoveBefore")) { + + g_clear_error(error); + g_set_error(error, NB_ERROR, + NB_ERROR_UNKNOWN_SERVICE, + "unknown service %s", + type_arg); + + } else if (!strcmp(method, "Scan")) { + + g_clear_error(error); + g_set_error(error, NB_ERROR, + NB_ERROR_UNKNOWN_TECHNOLOGY, + "unknown technology %s", + type_arg); + } + } +} + +GVariant *connman_call(struct network_state *ns, + const char *access_type, const char *type_arg, + const char *method, GVariant *params, GError **error) +{ + const char *path; + const char *interface; + GVariant *reply; + + if (!type_arg && (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY) || + !strcmp(access_type, CONNMAN_AT_SERVICE))) { + g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT, + "missing %s argument", + access_type); + return NULL; + } + + if (!strcmp(access_type, CONNMAN_AT_MANAGER)) { + path = CONNMAN_MANAGER_PATH; + interface = CONNMAN_MANAGER_INTERFACE; + } else if (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY)) { + path = CONNMAN_TECHNOLOGY_PATH(type_arg); + interface = CONNMAN_TECHNOLOGY_INTERFACE; + } else if (!strcmp(access_type, CONNMAN_AT_SERVICE)) { + path = CONNMAN_SERVICE_PATH(type_arg); + interface = CONNMAN_SERVICE_INTERFACE; + } else { + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "illegal %s argument", + access_type); + return NULL; + } + + reply = g_dbus_connection_call_sync(ns->conn, + CONNMAN_SERVICE, path, interface, method, params, + NULL, G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT, + NULL, error); + connman_decode_call_error(ns, access_type, type_arg, method, + error); + if (!reply) { + if (error && *error) + g_dbus_error_strip_remote_error(*error); + AFB_ERROR("Error calling %s%s%s %s method %s", + access_type, + type_arg ? "/" : "", + type_arg ? type_arg : "", + method, + error && *error ? (*error)->message : + "unspecified"); + } + + return reply; +} + +static void connman_call_async_ready(GObject *source_object, + GAsyncResult *res, gpointer user_data) +{ + struct connman_pending_work *cpw = user_data; + struct network_state *ns = cpw->ns; + GVariant *result; + GError *error = NULL; + + result = g_dbus_connection_call_finish(ns->conn, res, &error); + + cpw->callback(cpw->user_data, result, &error); + + g_clear_error(&error); + g_cancellable_reset(cpw->cancel); + g_free(cpw); +} + +void connman_cancel_call(struct network_state *ns, + struct connman_pending_work *cpw) +{ + g_cancellable_cancel(cpw->cancel); +} + +struct connman_pending_work * +connman_call_async(struct network_state *ns, + const char *access_type, const char *type_arg, + const char *method, GVariant *params, GError **error, + void (*callback)(void *user_data, GVariant *result, GError **error), + void *user_data) +{ + const char *path; + const char *interface; + struct connman_pending_work *cpw; + + if (!type_arg && (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY) || + !strcmp(access_type, CONNMAN_AT_SERVICE))) { + g_set_error(error, NB_ERROR, NB_ERROR_MISSING_ARGUMENT, + "missing %s argument", + access_type); + return NULL; + } + + if (!strcmp(access_type, CONNMAN_AT_MANAGER)) { + path = CONNMAN_MANAGER_PATH; + interface = CONNMAN_MANAGER_INTERFACE; + } else if (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY)) { + path = CONNMAN_TECHNOLOGY_PATH(type_arg); + interface = CONNMAN_TECHNOLOGY_INTERFACE; + } else if (!strcmp(access_type, CONNMAN_AT_SERVICE)) { + path = CONNMAN_SERVICE_PATH(type_arg); + interface = CONNMAN_SERVICE_INTERFACE; + } else { + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "illegal %s argument", + access_type); + return NULL; + } + + cpw = g_malloc(sizeof(*cpw)); + if (!cpw) { + g_set_error(error, NB_ERROR, NB_ERROR_OUT_OF_MEMORY, + "out of memory"); + return NULL; + } + cpw->ns = ns; + cpw->user_data = user_data; + cpw->cancel = g_cancellable_new(); + if (!cpw->cancel) { + g_free(cpw); + g_set_error(error, NB_ERROR, NB_ERROR_OUT_OF_MEMORY, + "out of memory"); + return NULL; + } + cpw->callback = callback; + + g_dbus_connection_call(ns->conn, + CONNMAN_SERVICE, path, interface, method, params, + NULL, /* reply type */ + G_DBUS_CALL_FLAGS_NONE, DBUS_REPLY_TIMEOUT, + cpw->cancel, /* cancellable? */ + connman_call_async_ready, + cpw); + + return cpw; +} + +json_object *connman_get_properties(struct network_state *ns, + const char *access_type, const char *type_arg, + GError **error) +{ + const struct property_info *pi = NULL; + const char *method = NULL; + GVariant *reply = NULL; + GVariantIter *array, *array2; + GVariant *var = NULL; + const gchar *path = NULL; + const gchar *key = NULL; + const gchar *basename; + json_object *jprop = NULL, *jresp = NULL, *jtype = NULL; + gboolean is_config; + + pi = connman_get_property_info(access_type, error); + if (!pi) + return NULL; + + method = NULL; + if (!strcmp(access_type, CONNMAN_AT_MANAGER)) + method = "GetProperties"; + else if (!strcmp(access_type, CONNMAN_AT_TECHNOLOGY)) + method = "GetTechnologies"; + else if (!strcmp(access_type, CONNMAN_AT_SERVICE)) + method = "GetServices"; + + if (!method) { + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "illegal %s argument", + access_type); + return NULL; + } + + reply = connman_call(ns, CONNMAN_AT_MANAGER, NULL, + method, NULL, error); + if (!reply) + return NULL; + + if (!strcmp(access_type, CONNMAN_AT_MANAGER)) { + jprop = json_object_new_object(); + g_variant_get(reply, "(a{sv})", &array); + while (g_variant_iter_loop(array, "{sv}", &key, &var)) { + root_property_dbus2json(jprop, pi, + key, var, &is_config); + } + g_variant_iter_free(array); + g_variant_unref(reply); + jresp = jprop; + } else { + if (!type_arg) + jresp = json_object_new_array(); + + g_variant_get(reply, "(a(oa{sv}))", &array); + while (g_variant_iter_loop(array, "(oa{sv})", &path, &array2)) { + + /* a basename must exist and be at least 1 character wide */ + basename = strrchr(path, '/'); + if (!basename || strlen(basename) <= 1) + continue; + basename++; + + if (!type_arg) { + jtype = json_object_new_object(); + json_object_object_add(jtype, access_type, + json_object_new_string(basename)); + } else if (g_strcmp0(basename, type_arg)) + continue; + + jprop = json_object_new_object(); + while (g_variant_iter_loop(array2, "{sv}", &key, &var)) { + root_property_dbus2json(jprop, pi, + key, var, &is_config); + } + + if (!type_arg) { + json_object_object_add(jtype, "properties", jprop); + json_object_array_add(jresp, jtype); + } + } + g_variant_iter_free(array); + g_variant_unref(reply); + + if (type_arg && jprop) + jresp = jprop; + + } + + if (!jresp) { + if (type_arg) + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "Bad %s %s", access_type, type_arg); + else + g_set_error(error, NB_ERROR, NB_ERROR_ILLEGAL_ARGUMENT, + "No %s", access_type); + } + + return jresp; +} + +json_object *connman_get_property(struct network_state *ns, + const char *access_type, const char *type_arg, + gboolean is_json_name, const char *name, GError **error) +{ + const struct property_info *pi; + json_object *jprop, *jval; + + pi = connman_get_property_info(access_type, error); + if (!pi) + return NULL; + + jprop = connman_get_properties(ns, access_type, type_arg, error); + if (!jprop) + return NULL; + + jval = get_named_property(pi, is_json_name, name, jprop); + json_object_put(jprop); + + if (!jval) + g_set_error(error, NB_ERROR, NB_ERROR_BAD_PROPERTY, + "Bad property %s on %s%s%s", name, + access_type, + type_arg ? "/" : "", + type_arg ? type_arg : ""); + return jval; +} + +/* NOTE: jval is consumed */ +gboolean connman_set_property(struct network_state *ns, + const char *access_type, const char *type_arg, + gboolean is_json_name, const char *name, json_object *jval, + GError **error) +{ + const struct property_info *pi; + GVariant *reply, *arg; + gboolean is_config; + gchar *propname; + + /* get start of properties */ + pi = connman_get_property_info(access_type, error); + if (!pi) + return FALSE; + + /* get actual property */ + pi = property_by_name(pi, is_json_name, name, &is_config); + if (!pi) { + g_set_error(error, NB_ERROR, NB_ERROR_UNKNOWN_PROPERTY, + "unknown property with name %s", name); + json_object_put(jval); + return FALSE; + } + + /* convert to gvariant */ + arg = property_json_to_gvariant(pi, NULL, NULL, jval, error); + + /* we don't need this anymore */ + json_object_put(jval); + jval = NULL; + + /* no variant? error */ + if (!arg) + return FALSE; + + if (!is_config) + propname = g_strdup(pi->name); + else + propname = configuration_dbus_name(pi->name); + + reply = connman_call(ns, access_type, type_arg, + "SetProperty", + g_variant_new("(sv)", propname, arg), + error); + + g_free(propname); + + if (!reply) + return FALSE; + + g_variant_unref(reply); + + return TRUE; +} -- cgit 1.2.3-korg