/* 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 #include #include #include #include #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 */ }