summaryrefslogtreecommitdiff
path: root/src/core/dhcp/nm-dhcp-listener.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/dhcp/nm-dhcp-listener.c')
-rw-r--r--src/core/dhcp/nm-dhcp-listener.c318
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 */
+}