diff options
Diffstat (limited to 'src/core/dhcp/nm-dhcp-listener.c')
-rw-r--r-- | src/core/dhcp/nm-dhcp-listener.c | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/src/core/dhcp/nm-dhcp-listener.c b/src/core/dhcp/nm-dhcp-listener.c new file mode 100644 index 0000000000..279ce4984c --- /dev/null +++ b/src/core/dhcp/nm-dhcp-listener.c @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2014 - 2016 Red Hat, Inc. + */ + +#include "nm-default.h" + +#include "nm-dhcp-listener.h" + +#include <sys/socket.h> +#include <sys/wait.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> + +#include "nm-dhcp-helper-api.h" +#include "nm-dhcp-client.h" +#include "nm-dhcp-manager.h" +#include "nm-core-internal.h" +#include "nm-dbus-manager.h" +#include "NetworkManagerUtils.h" + +#define PRIV_SOCK_PATH NMRUNDIR "/private-dhcp" +#define PRIV_SOCK_TAG "dhcp" + +/*****************************************************************************/ + +const NMDhcpClientFactory *const _nm_dhcp_manager_factories[6] = { +/* the order here matters, as we will try the plugins in this order to find + * the first available plugin. */ + +#if WITH_DHCPCANON + &_nm_dhcp_client_factory_dhcpcanon, +#endif +#if WITH_DHCLIENT + &_nm_dhcp_client_factory_dhclient, +#endif +#if WITH_DHCPCD + &_nm_dhcp_client_factory_dhcpcd, +#endif + &_nm_dhcp_client_factory_internal, + &_nm_dhcp_client_factory_systemd, + &_nm_dhcp_client_factory_nettools, +}; + +/*****************************************************************************/ + +typedef struct { + NMDBusManager *dbus_mgr; + gulong new_conn_id; + gulong dis_conn_id; + GHashTable * connections; +} NMDhcpListenerPrivate; + +struct _NMDhcpListener { + GObject parent; + NMDhcpListenerPrivate _priv; +}; + +struct _NMDhcpListenerClass { + GObjectClass parent; +}; + +enum { EVENT, LAST_SIGNAL }; +static guint signals[LAST_SIGNAL] = {0}; + +G_DEFINE_TYPE(NMDhcpListener, nm_dhcp_listener, G_TYPE_OBJECT) + +#define NM_DHCP_LISTENER_GET_PRIVATE(self) \ + _NM_GET_PRIVATE(self, NMDhcpListener, NM_IS_DHCP_LISTENER) + +NM_DEFINE_SINGLETON_GETTER(NMDhcpListener, nm_dhcp_listener_get, NM_TYPE_DHCP_LISTENER); + +/*****************************************************************************/ + +#define _NMLOG_PREFIX_NAME "dhcp-listener" +#define _NMLOG_DOMAIN LOGD_DHCP +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + const NMDhcpListener *_self = (self); \ + char _prefix[64]; \ + \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + "%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + (_self != singleton_instance \ + ? nm_sprintf_buf(_prefix, "%s[%p]", _NMLOG_PREFIX_NAME, _self) \ + : _NMLOG_PREFIX_NAME) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static char * +get_option(GVariant *options, const char *key) +{ + GVariant * value; + const guchar *bytes, *s; + gsize len; + char * converted, *d; + + if (!g_variant_lookup(options, key, "@ay", &value)) + return NULL; + + bytes = g_variant_get_fixed_array(value, &len, 1); + + /* Since the DHCP options come through environment variables, they should + * already be UTF-8 safe, but just make sure. + */ + converted = g_malloc(len + 1); + for (s = bytes, d = converted; s < bytes + len; s++, d++) { + /* Convert NULLs to spaces and non-ASCII characters to ? */ + if (*s == '\0') + *d = ' '; + else if (*s > 127) + *d = '?'; + else + *d = *s; + } + *d = '\0'; + g_variant_unref(value); + + return converted; +} + +static void +_method_call_handle(NMDhcpListener *self, GVariant *parameters) +{ + gs_free char * iface = NULL; + gs_free char * pid_str = NULL; + gs_free char * reason = NULL; + gs_unref_variant GVariant *options = NULL; + int pid; + gboolean handled = FALSE; + + g_variant_get(parameters, "(@a{sv})", &options); + + iface = get_option(options, "interface"); + if (iface == NULL) { + _LOGW("dhcp-event: didn't have associated interface."); + return; + } + + pid_str = get_option(options, "pid"); + pid = _nm_utils_ascii_str_to_int64(pid_str, 10, 0, G_MAXINT32, -1); + if (pid == -1) { + _LOGW("dhcp-event: couldn't convert PID '%s' to an integer", pid_str ?: "(null)"); + return; + } + + reason = get_option(options, "reason"); + if (reason == NULL) { + _LOGW("dhcp-event: (pid %d) DHCP event didn't have a reason", pid); + return; + } + + g_signal_emit(self, signals[EVENT], 0, iface, pid, options, reason, &handled); + if (!handled) { + if (g_ascii_strcasecmp(reason, "RELEASE") == 0) { + /* Ignore event when the dhcp client gets killed and we receive its last message */ + _LOGD("dhcp-event: (pid %d) unhandled RELEASE DHCP event for interface %s", pid, iface); + } else + _LOGW("dhcp-event: (pid %d) unhandled DHCP event for interface %s", pid, iface); + } +} + +static void +_method_call(GDBusConnection * connection, + const char * sender, + const char * object_path, + const char * interface_name, + const char * method_name, + GVariant * parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + NMDhcpListener *self = NM_DHCP_LISTENER(user_data); + + if (!nm_streq(interface_name, NM_DHCP_HELPER_SERVER_INTERFACE_NAME) + || !nm_streq(method_name, NM_DHCP_HELPER_SERVER_METHOD_NOTIFY)) { + g_dbus_method_invocation_return_error(invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_UNKNOWN_METHOD, + "Unknown method %s", + method_name); + return; + } + + _method_call_handle(self, parameters); + g_dbus_method_invocation_return_value(invocation, NULL); +} + +static GDBusInterfaceInfo *const interface_info = NM_DEFINE_GDBUS_INTERFACE_INFO( + NM_DHCP_HELPER_SERVER_INTERFACE_NAME, + .methods = NM_DEFINE_GDBUS_METHOD_INFOS( + NM_DEFINE_GDBUS_METHOD_INFO(NM_DHCP_HELPER_SERVER_METHOD_NOTIFY, + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("data", "a{sv}"), ), ), ), ); + +static guint +_dbus_connection_register_object(NMDhcpListener *self, GDBusConnection *connection, GError **error) +{ + static const GDBusInterfaceVTable interface_vtable = { + .method_call = _method_call, + }; + + return g_dbus_connection_register_object( + connection, + NM_DHCP_HELPER_SERVER_OBJECT_PATH, + interface_info, + NM_UNCONST_PTR(GDBusInterfaceVTable, &interface_vtable), + self, + NULL, + error); +} + +static void +new_connection_cb(NMDBusManager * mgr, + GDBusConnection * connection, + GDBusObjectManager *manager, + NMDhcpListener * self) +{ + NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE(self); + guint registration_id; + GError * error = NULL; + + /* it is important to register the object during the new-connection signal, + * as this avoids races with the connecting object. */ + registration_id = _dbus_connection_register_object(self, connection, &error); + if (!registration_id) { + _LOGE("failure to register %s for connection %p: %s", + NM_DHCP_HELPER_SERVER_OBJECT_PATH, + connection, + error->message); + g_error_free(error); + return; + } + + g_hash_table_insert(priv->connections, connection, GUINT_TO_POINTER(registration_id)); +} + +static void +dis_connection_cb(NMDBusManager *mgr, GDBusConnection *connection, NMDhcpListener *self) +{ + NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE(self); + guint id; + + id = GPOINTER_TO_UINT(g_hash_table_lookup(priv->connections, connection)); + if (id) { + g_dbus_connection_unregister_object(connection, id); + g_hash_table_remove(priv->connections, connection); + } +} + +/*****************************************************************************/ + +static void +nm_dhcp_listener_init(NMDhcpListener *self) +{ + NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE(self); + + /* Maps GDBusConnection :: signal-id */ + priv->connections = g_hash_table_new(nm_direct_hash, NULL); + + priv->dbus_mgr = g_object_ref(nm_dbus_manager_get()); + + /* Register the socket our DHCP clients will return lease info on */ + nm_dbus_manager_private_server_register(priv->dbus_mgr, PRIV_SOCK_PATH, PRIV_SOCK_TAG); + priv->new_conn_id = g_signal_connect(priv->dbus_mgr, + NM_DBUS_MANAGER_PRIVATE_CONNECTION_NEW "::" PRIV_SOCK_TAG, + G_CALLBACK(new_connection_cb), + self); + priv->dis_conn_id = + g_signal_connect(priv->dbus_mgr, + NM_DBUS_MANAGER_PRIVATE_CONNECTION_DISCONNECTED "::" PRIV_SOCK_TAG, + G_CALLBACK(dis_connection_cb), + self); +} + +static void +dispose(GObject *object) +{ + NMDhcpListenerPrivate *priv = NM_DHCP_LISTENER_GET_PRIVATE(object); + + nm_clear_g_signal_handler(priv->dbus_mgr, &priv->new_conn_id); + nm_clear_g_signal_handler(priv->dbus_mgr, &priv->dis_conn_id); + + nm_clear_pointer(&priv->connections, g_hash_table_destroy); + + g_clear_object(&priv->dbus_mgr); + + G_OBJECT_CLASS(nm_dhcp_listener_parent_class)->dispose(object); +} + +static void +nm_dhcp_listener_class_init(NMDhcpListenerClass *listener_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS(listener_class); + + object_class->dispose = dispose; + + signals[EVENT] = g_signal_new(NM_DHCP_LISTENER_EVENT, + G_OBJECT_CLASS_TYPE(object_class), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, + NULL, + NULL, + G_TYPE_BOOLEAN, /* listeners return TRUE if handled */ + 4, + G_TYPE_STRING, /* iface */ + G_TYPE_INT, /* pid */ + G_TYPE_VARIANT, /* options */ + G_TYPE_STRING); /* reason */ +} |