Blame src/core/dhcp/nm-dhcp-helper.c

Packit Service 5ffa24
/* SPDX-License-Identifier: GPL-2.0-or-later */
Packit Service 5ffa24
/*
Packit Service 5ffa24
 * Copyright (C) 2007 - 2013 Red Hat, Inc.
Packit Service 5ffa24
 */
Packit Service 5ffa24
Packit Service dff8e4
#include "libnm-glib-aux/nm-default-glib.h"
Packit Service 5ffa24
Packit Service 5ffa24
#include <unistd.h>
Packit Service 5ffa24
#include <stdlib.h>
Packit Service 5ffa24
#include <signal.h>
Packit Service 5ffa24
Packit Service dff8e4
#include "libnm-glib-aux/nm-logging-syslog.h"
Packit Service 5ffa24
Packit Service 5ffa24
#include "nm-dhcp-helper-api.h"
Packit Service 5ffa24
Packit Service 5ffa24
/*****************************************************************************/
Packit Service 5ffa24
Packit Service 5ffa24
#if NM_MORE_LOGGING
Packit Service 5ffa24
    #define _NMLOG_ENABLED(level) TRUE
Packit Service 5ffa24
#else
Packit Service 5ffa24
    #define _NMLOG_ENABLED(level) ((level) <= LOG_ERR)
Packit Service 5ffa24
#endif
Packit Service 5ffa24
Packit Service 5ffa24
#define _NMLOG(always_enabled, level, ...)                                                       \
Packit Service 5ffa24
    G_STMT_START                                                                                 \
Packit Service 5ffa24
    {                                                                                            \
Packit Service 5ffa24
        if ((always_enabled) || _NMLOG_ENABLED(level)) {                                         \
Packit Service 5ffa24
            GTimeVal _tv;                                                                        \
Packit Service 5ffa24
                                                                                                 \
Packit Service 5ffa24
            g_get_current_time(&_tv);                                                            \
Packit Service 5ffa24
            g_print(                                                                             \
Packit Service 5ffa24
                "nm-dhcp-helper[%ld] %-7s [%ld.%04ld] " _NM_UTILS_MACRO_FIRST(__VA_ARGS__) "\n", \
Packit Service 5ffa24
                (long) getpid(),                                                                 \
Packit Service 5ffa24
                nm_utils_syslog_to_str(level),                                                   \
Packit Service 5ffa24
                _tv.tv_sec,                                                                      \
Packit Service 5ffa24
                _tv.tv_usec / 100 _NM_UTILS_MACRO_REST(__VA_ARGS__));                            \
Packit Service 5ffa24
        }                                                                                        \
Packit Service 5ffa24
    }                                                                                            \
Packit Service 5ffa24
    G_STMT_END
Packit Service 5ffa24
Packit Service 5ffa24
#define _LOGD(...) _NMLOG(TRUE, LOG_INFO, __VA_ARGS__)
Packit Service 5ffa24
#define _LOGI(...) _NMLOG(TRUE, LOG_NOTICE, __VA_ARGS__)
Packit Service 5ffa24
#define _LOGW(...) _NMLOG(TRUE, LOG_WARNING, __VA_ARGS__)
Packit Service 5ffa24
#define _LOGE(...) _NMLOG(TRUE, LOG_ERR, __VA_ARGS__)
Packit Service 5ffa24
Packit Service 5ffa24
#define _LOGd(...) _NMLOG(FALSE, LOG_INFO, __VA_ARGS__)
Packit Service 5ffa24
#define _LOGi(...) _NMLOG(FALSE, LOG_NOTICE, __VA_ARGS__)
Packit Service 5ffa24
#define _LOGw(...) _NMLOG(FALSE, LOG_WARNING, __VA_ARGS__)
Packit Service 5ffa24
Packit Service 5ffa24
/*****************************************************************************/
Packit Service 5ffa24
Packit Service 5ffa24
static GVariant *
Packit Service 5ffa24
build_signal_parameters(void)
Packit Service 5ffa24
{
Packit Service 5ffa24
    const char *const *environ_iter;
Packit Service 5ffa24
    GVariantBuilder    builder;
Packit Service 5ffa24
Packit Service 5ffa24
    g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
Packit Service 5ffa24
Packit Service 5ffa24
    /* List environment and format for dbus dict */
Packit Service 5ffa24
    for (environ_iter = (const char *const *) environ; *environ_iter; environ_iter++) {
Packit Service 5ffa24
        static const char *const ignore_with_prefix_list[] =
Packit Service 5ffa24
            {"PATH", "SHLVL", "_", "PWD", "dhc_dbus", NULL};
Packit Service 5ffa24
        const char *       item = *environ_iter;
Packit Service 5ffa24
        gs_free char *     name = NULL;
Packit Service 5ffa24
        const char *       val;
Packit Service 5ffa24
        const char *const *p;
Packit Service 5ffa24
Packit Service 5ffa24
        val = strchr(item, '=');
Packit Service 5ffa24
        if (!val || item == val)
Packit Service 5ffa24
            continue;
Packit Service 5ffa24
Packit Service 5ffa24
        name = g_strndup(item, val - item);
Packit Service 5ffa24
        val += 1;
Packit Service 5ffa24
Packit Service 5ffa24
        /* Ignore non-DHCP-related environment variables */
Packit Service 5ffa24
        for (p = ignore_with_prefix_list; *p; p++) {
Packit Service 5ffa24
            if (strncmp(name, *p, strlen(*p)) == 0)
Packit Service 5ffa24
                goto next;
Packit Service 5ffa24
        }
Packit Service 5ffa24
Packit Service 5ffa24
        if (!g_utf8_validate(name, -1, NULL))
Packit Service 5ffa24
            continue;
Packit Service 5ffa24
Packit Service 5ffa24
        /* Value passed as a byte array rather than a string, because there are
Packit Service 5ffa24
         * no character encoding guarantees with DHCP, and D-Bus requires
Packit Service 5ffa24
         * strings to be UTF-8.
Packit Service 5ffa24
         *
Packit Service 5ffa24
         * Note that we can't use g_variant_new_bytestring() here, because that
Packit Service 5ffa24
         * includes the trailing '\0'. (??!?)
Packit Service 5ffa24
         */
Packit Service 5ffa24
        g_variant_builder_add(&builder,
Packit Service 5ffa24
                              "{sv}",
Packit Service 5ffa24
                              name,
Packit Service 5ffa24
                              g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, val, strlen(val), 1));
Packit Service 5ffa24
Packit Service 5ffa24
next:;
Packit Service 5ffa24
    }
Packit Service 5ffa24
Packit Service 5ffa24
    return g_variant_ref_sink(g_variant_new("(a{sv})", &builder));
Packit Service 5ffa24
}
Packit Service 5ffa24
Packit Service 5ffa24
static void
Packit Service 5ffa24
kill_pid(void)
Packit Service 5ffa24
{
Packit Service 5ffa24
    const char *pid_str;
Packit Service 5ffa24
    pid_t       pid = 0;
Packit Service 5ffa24
Packit Service 5ffa24
    pid_str = getenv("pid");
Packit Service 5ffa24
    if (pid_str)
Packit Service 5ffa24
        pid = strtol(pid_str, NULL, 10);
Packit Service 5ffa24
    if (pid) {
Packit Service 5ffa24
        _LOGI("a fatal error occurred, kill dhclient instance with pid %d", pid);
Packit Service 5ffa24
        kill(pid, SIGTERM);
Packit Service 5ffa24
    }
Packit Service 5ffa24
}
Packit Service 5ffa24
Packit Service 5ffa24
int
Packit Service 5ffa24
main(int argc, char *argv[])
Packit Service 5ffa24
{
Packit Service 5ffa24
    gs_unref_object GDBusConnection *connection = NULL;
Packit Service 5ffa24
    gs_free_error GError *error                 = NULL;
Packit Service 5ffa24
    gs_unref_variant GVariant *parameters       = NULL;
Packit Service 5ffa24
    gs_unref_variant GVariant *result           = NULL;
Packit Service 5ffa24
    gboolean                   success          = FALSE;
Packit Service 5ffa24
    guint                      try_count;
Packit Service 5ffa24
    gint64                     time_start;
Packit Service 5ffa24
    gint64                     time_end;
Packit Service 5ffa24
Packit Service 5ffa24
    /* Connecting to the unix socket can fail with EAGAIN if there are too
Packit Service 5ffa24
     * many pending connections and the server can't accept them in time
Packit Service 5ffa24
     * before reaching backlog capacity. Ideally the server should increase
Packit Service 5ffa24
     * the backlog length, but GLib doesn't provide a way to change it for a
Packit Service 5ffa24
     * GDBus server. Retry for up to 5 seconds in case of failure. */
Packit Service 5ffa24
    time_start = g_get_monotonic_time();
Packit Service 5ffa24
    time_end   = time_start + (5000 * 1000L);
Packit Service 5ffa24
    try_count  = 0;
Packit Service 5ffa24
Packit Service 5ffa24
do_connect:
Packit Service 5ffa24
    try_count++;
Packit Service 5ffa24
    connection =
Packit Service 5ffa24
        g_dbus_connection_new_for_address_sync("unix:path=" NMRUNDIR "/private-dhcp",
Packit Service 5ffa24
                                               G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
Packit Service 5ffa24
                                               NULL,
Packit Service 5ffa24
                                               NULL,
Packit Service 5ffa24
                                               &error);
Packit Service 5ffa24
    if (!connection) {
Packit Service 5ffa24
        if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
Packit Service 5ffa24
            gint64 time_remaining = time_end - g_get_monotonic_time();
Packit Service 5ffa24
            gint64 interval;
Packit Service 5ffa24
Packit Service 5ffa24
            if (time_remaining > 0) {
Packit Service 5ffa24
                _LOGi("failure to connect: %s (retry %u, waited %lld ms)",
Packit Service 5ffa24
                      error->message,
Packit Service 5ffa24
                      try_count,
Packit Service 5ffa24
                      (long long) (time_end - time_remaining - time_start) / 1000);
Packit Service 5ffa24
                interval = NM_CLAMP((gint64)(100L * (1L << NM_MIN(try_count, 31))), 5000, 100000);
Packit Service 5ffa24
                g_usleep(NM_MIN(interval, time_remaining));
Packit Service 5ffa24
                g_clear_error(&error);
Packit Service 5ffa24
                goto do_connect;
Packit Service 5ffa24
            }
Packit Service 5ffa24
        }
Packit Service 5ffa24
Packit Service 5ffa24
        g_dbus_error_strip_remote_error(error);
Packit Service 5ffa24
        _LOGE("could not connect to NetworkManager D-Bus socket: %s", error->message);
Packit Service 5ffa24
        goto out;
Packit Service 5ffa24
    }
Packit Service 5ffa24
Packit Service 5ffa24
    parameters = build_signal_parameters();
Packit Service 5ffa24
    time_end   = g_get_monotonic_time() + (200 * 1000L); /* retry for at most 200 milliseconds */
Packit Service 5ffa24
    try_count  = 0;
Packit Service 5ffa24
Packit Service 5ffa24
do_notify:
Packit Service 5ffa24
    try_count++;
Packit Service 5ffa24
    result = g_dbus_connection_call_sync(connection,
Packit Service 5ffa24
                                         NULL,
Packit Service 5ffa24
                                         NM_DHCP_HELPER_SERVER_OBJECT_PATH,
Packit Service 5ffa24
                                         NM_DHCP_HELPER_SERVER_INTERFACE_NAME,
Packit Service 5ffa24
                                         NM_DHCP_HELPER_SERVER_METHOD_NOTIFY,
Packit Service 5ffa24
                                         parameters,
Packit Service 5ffa24
                                         NULL,
Packit Service 5ffa24
                                         G_DBUS_CALL_FLAGS_NONE,
Packit Service 5ffa24
                                         1000,
Packit Service 5ffa24
                                         NULL,
Packit Service 5ffa24
                                         &error);
Packit Service 5ffa24
Packit Service 5ffa24
    if (!result) {
Packit Service 5ffa24
        gs_free char *s_err = NULL;
Packit Service 5ffa24
Packit Service 5ffa24
        s_err = g_dbus_error_get_remote_error(error);
Packit Service 5ffa24
        if (NM_IN_STRSET(s_err, "org.freedesktop.DBus.Error.UnknownMethod")) {
Packit Service 5ffa24
            gint64 remaining_time = time_end - g_get_monotonic_time();
Packit Service 5ffa24
            gint64 interval;
Packit Service 5ffa24
Packit Service 5ffa24
            /* I am not sure that a race can actually happen, as we register the object
Packit Service 5ffa24
             * on the server side during GDBusServer:new-connection signal.
Packit Service 5ffa24
             *
Packit Service 5ffa24
             * However, there was also a race for subscribing to an event, so let's just
Packit Service 5ffa24
             * do some retry. */
Packit Service 5ffa24
            if (remaining_time > 0) {
Packit Service 5ffa24
                _LOGi("failure to call notify: %s (retry %u)", error->message, try_count);
Packit Service 5ffa24
                interval = NM_CLAMP((gint64)(100L * (1L << NM_MIN(try_count, 31))), 5000, 25000);
Packit Service 5ffa24
                g_usleep(NM_MIN(interval, remaining_time));
Packit Service 5ffa24
                g_clear_error(&error);
Packit Service 5ffa24
                goto do_notify;
Packit Service 5ffa24
            }
Packit Service 5ffa24
        }
Packit Service 5ffa24
        _LOGW("failure to call notify: %s (try signal via Event)", error->message);
Packit Service 5ffa24
        g_clear_error(&error);
Packit Service 5ffa24
Packit Service 5ffa24
        /* for backward compatibility, try to emit the signal. There is no stable
Packit Service 5ffa24
         * API between the dhcp-helper and NetworkManager. However, while upgrading
Packit Service 5ffa24
         * the NetworkManager package, a newer helper might want to notify an
Packit Service 5ffa24
         * older server, which still uses the "Event". */
Packit Service 5ffa24
        if (!g_dbus_connection_emit_signal(connection,
Packit Service 5ffa24
                                           NULL,
Packit Service 5ffa24
                                           "/",
Packit Service 5ffa24
                                           NM_DHCP_CLIENT_DBUS_IFACE,
Packit Service 5ffa24
                                           "Event",
Packit Service 5ffa24
                                           parameters,
Packit Service 5ffa24
                                           &error)) {
Packit Service 5ffa24
            g_dbus_error_strip_remote_error(error);
Packit Service 5ffa24
            _LOGE("could not send DHCP Event signal: %s", error->message);
Packit Service 5ffa24
            goto out;
Packit Service 5ffa24
        }
Packit Service 5ffa24
        /* We were able to send the asynchronous Event. Consider that a success. */
Packit Service 5ffa24
        success = TRUE;
Packit Service 5ffa24
    } else
Packit Service 5ffa24
        success = TRUE;
Packit Service 5ffa24
Packit Service 5ffa24
    if (!g_dbus_connection_flush_sync(connection, NULL, &error)) {
Packit Service 5ffa24
        g_dbus_error_strip_remote_error(error);
Packit Service 5ffa24
        _LOGE("could not flush D-Bus connection: %s", error->message);
Packit Service 5ffa24
        success = FALSE;
Packit Service 5ffa24
        goto out;
Packit Service 5ffa24
    }
Packit Service 5ffa24
Packit Service 5ffa24
out:
Packit Service 5ffa24
    if (!success)
Packit Service 5ffa24
        kill_pid();
Packit Service 5ffa24
    return success ? EXIT_SUCCESS : EXIT_FAILURE;
Packit Service 5ffa24
}