summary refs log tree commit diff
path: root/src/libnm-client-impl/nm-libnm-utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-client-impl/nm-libnm-utils.c')
-rw-r--r--src/libnm-client-impl/nm-libnm-utils.c916
1 files changed, 916 insertions, 0 deletions
diff --git a/src/libnm-client-impl/nm-libnm-utils.c b/src/libnm-client-impl/nm-libnm-utils.c
new file mode 100644
index 00000000..671466cf
--- /dev/null
+++ b/src/libnm-client-impl/nm-libnm-utils.c
@@ -0,0 +1,916 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2007 - 2008 Novell, Inc.
+ * Copyright (C) 2007 - 2018 Red Hat, Inc.
+ */
+
+#include "libnm-client-impl/nm-default-libnm.h"
+
+#include "nm-libnm-utils.h"
+
+#include "libnm-glib-aux/nm-time-utils.h"
+#include "libnm-core-aux-intern/nm-common-macros.h"
+#include "nm-object.h"
+
+/*****************************************************************************/
+
+volatile int _nml_dbus_log_level = 0;
+
+int
+_nml_dbus_log_level_init(void)
+{
+    const GDebugKey keys[] = {
+        {"trace", _NML_DBUS_LOG_LEVEL_TRACE},
+        {"debug", _NML_DBUS_LOG_LEVEL_DEBUG},
+        {"warning", _NML_DBUS_LOG_LEVEL_WARN},
+        {"error", _NML_DBUS_LOG_LEVEL_ERROR},
+        {"stdout", NML_DBUS_LOG_STDOUT},
+    };
+    int l;
+
+    l = _NML_DBUS_LOG_LEVEL_INITIALIZED
+        | nm_utils_parse_debug_string(g_getenv("LIBNM_CLIENT_DEBUG"), keys, G_N_ELEMENTS(keys));
+
+    if (!g_atomic_int_compare_and_exchange(&_nml_dbus_log_level, 0, l))
+        l = g_atomic_int_get(&_nml_dbus_log_level);
+
+    nm_assert(l & _NML_DBUS_LOG_LEVEL_INITIALIZED);
+    return l;
+}
+
+void
+_nml_dbus_log(NMLDBusLogLevel level, gboolean use_stdout, const char *fmt, ...)
+{
+    NMLDBusLogLevel configured_log_level;
+    gs_free char *  msg = NULL;
+    va_list         args;
+    const char *    prefix = "";
+    gint64          ts;
+    pid_t           pid;
+
+    /* we only call _nml_dbus_log() after nml_dbus_log_enabled(), which already does
+     * an atomic access to the variable. Since the value is only initialized once and
+     * never changes, we can just access it without additional locking. */
+    configured_log_level = _nml_dbus_log_level;
+
+    nm_assert(level & configured_log_level);
+
+    va_start(args, fmt);
+    msg = g_strdup_vprintf(fmt, args);
+    va_end(args);
+
+    switch (level) {
+    case NML_DBUS_LOG_LEVEL_TRACE:
+        prefix = "<trace> ";
+        break;
+    case NML_DBUS_LOG_LEVEL_DEBUG:
+        prefix = "<debug> ";
+        break;
+    case NML_DBUS_LOG_LEVEL_WARN:
+        prefix = "<warn > ";
+        if (NM_FLAGS_HAS(configured_log_level, _NML_DBUS_LOG_LEVEL_WARN)) {
+            g_warning("libnm-dbus: %s%s", prefix, msg);
+            return;
+        }
+        break;
+    case NML_DBUS_LOG_LEVEL_ERROR:
+        prefix = "<error> ";
+        if (NM_FLAGS_HAS(configured_log_level, _NML_DBUS_LOG_LEVEL_ERROR)) {
+            g_critical("libnm-dbus: %s%s", prefix, msg);
+            return;
+        }
+        if (NM_FLAGS_HAS(configured_log_level, _NML_DBUS_LOG_LEVEL_WARN)) {
+            g_warning("libnm-dbus: %s%s", prefix, msg);
+            return;
+        }
+        break;
+    default:
+        break;
+    }
+
+    ts = nm_utils_clock_gettime_nsec(CLOCK_BOOTTIME);
+
+    pid = getpid();
+
+    if (use_stdout) {
+        g_print("libnm-dbus[%lld]: %s[%" G_GINT64_FORMAT ".%05" G_GINT64_FORMAT "] %s\n",
+                (long long) pid,
+                prefix,
+                ts / NM_UTILS_NSEC_PER_SEC,
+                (ts / (NM_UTILS_NSEC_PER_SEC / 10000)) % 10000,
+                msg);
+    } else {
+        g_printerr("libnm-dbus[%lld]: %s[%" G_GINT64_FORMAT ".%05" G_GINT64_FORMAT "] %s\n",
+                   (long long) pid,
+                   prefix,
+                   ts / NM_UTILS_NSEC_PER_SEC,
+                   (ts / (NM_UTILS_NSEC_PER_SEC / 10000)) % 10000,
+                   msg);
+    }
+}
+
+/*****************************************************************************/
+
+/* Stolen from dbus-glib */
+char *
+nm_utils_wincaps_to_dash(const char *caps)
+{
+    const char *p;
+    GString *   str;
+
+    str = g_string_new(NULL);
+    p   = caps;
+    while (*p) {
+        if (g_ascii_isupper(*p)) {
+            if (str->len > 0 && (str->len < 2 || str->str[str->len - 2] != '-'))
+                g_string_append_c(str, '-');
+            g_string_append_c(str, g_ascii_tolower(*p));
+        } else
+            g_string_append_c(str, *p);
+        ++p;
+    }
+
+    return g_string_free(str, FALSE);
+}
+
+/*****************************************************************************/
+
+static char *
+_fixup_string(const char *       desc,
+              const char *const *ignored_phrases,
+              const char *const *ignored_words,
+              gboolean           square_brackets_sensible)
+{
+    char *   desc_full;
+    gboolean in_paren = FALSE;
+    char *   p, *q;
+    int      i;
+
+    if (!desc || !desc[0])
+        return NULL;
+
+    /* restore original non-UTF-8-safe text. */
+    desc_full = nm_utils_str_utf8safe_unescape_cp(desc, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE);
+
+    /* replace all invalid UTF-8 bytes with space. */
+    p = desc_full;
+    while (!g_utf8_validate(p, -1, (const char **) &q)) {
+        /* the byte is invalid UTF-8. Replace it with space and proceed. */
+        *q = ' ';
+        p  = q + 1;
+    }
+
+    /* replace '_', ',', ASCII control characters and everything inside parentheses, with space. */
+    for (p = desc_full; p[0]; p++) {
+        if (*p == '(')
+            in_paren = TRUE;
+        else if (*p == ')')
+            in_paren = FALSE;
+        else if (NM_IN_SET(*p, '_', ',') || nm_ascii_is_ctrl_or_del(*p) || in_paren) {
+            /* pass */
+        } else
+            continue;
+        *p = ' ';
+    }
+
+    /* Attempt to shorten ID by ignoring certain phrases */
+    for (i = 0; ignored_phrases[i]; i++) {
+        p = strstr(desc_full, ignored_phrases[i]);
+        if (p) {
+            const char *eow = &p[strlen(ignored_phrases[i])];
+
+            /* require that the phrase is delimited by space, or
+             * at the beginning or end of the description. */
+            if ((p == desc_full || p[-1] == ' ') && NM_IN_SET(eow[0], '\0', ' '))
+                memmove(p, eow, strlen(eow) + 1); /* +1 for the \0 */
+        }
+    }
+
+    /* Attempt to shorten ID by ignoring certain individual words.
+     * - word-split the description at spaces
+     * - coalesce multiple spaces
+     * - skip over ignored_words */
+    p = desc_full;
+    q = desc_full;
+    for (;;) {
+        char *eow;
+        gsize l;
+
+        /* skip leading spaces. */
+        while (p[0] == ' ')
+            p++;
+
+        if (!p[0])
+            break;
+
+        /* split leading word on first space */
+        eow = strchr(p, ' ');
+        if (eow)
+            *eow = '\0';
+
+        if (nm_utils_strv_find_first((char **) ignored_words, -1, p) >= 0)
+            goto next;
+
+        l = strlen(p);
+        if (q != p) {
+            if (q != desc_full)
+                *q++ = ' ';
+            memmove(q, p, l);
+        }
+        q += l;
+
+next:
+        if (!eow)
+            break;
+        p = eow + 1;
+    }
+
+    *q++ = '\0';
+
+    p = strchr(desc_full, '[');
+    if (p == desc_full) {
+        /* All we're left with is in square brackets.
+         * Always prefer that to a blank string.*/
+        square_brackets_sensible = TRUE;
+    }
+    if (square_brackets_sensible) {
+        /* If there's a [<string>] that survived the substitution, then the string
+         * is a short form that is generally preferable. */
+        q = strchr(desc_full, ']');
+        if (p && q > p) {
+            p++;
+            memmove(desc_full, p, q - p);
+            desc_full[q - p] = '\0';
+        }
+    } else {
+        /* [<string>] sometimes contains the preferred human-readable name, but
+         * mostly it's utterly useless. Sigh. Drop it. */
+        if (p) {
+            if (p > desc_full && p[-1] == ' ')
+                p--;
+            *p = '\0';
+        }
+    }
+
+    if (!desc_full[0]) {
+        g_free(desc_full);
+        return NULL;
+    }
+
+    return desc_full;
+}
+
+char *
+nm_utils_fixup_vendor_string(const char *desc)
+{
+    static const char *const IGNORED_PHRASES[] = {
+        "Access Systems",
+        "Business Mobile Networks BV",
+        "Communications & Multimedia",
+        "Company of Japan",
+        "Computer Co.",
+        "Computer Corp.",
+        "Computer Corporation",
+        "Computer Inc.",
+        "Computer, Inc.",
+        "Information and Communication Products",
+        "Macao Commercial Offshore",
+        "Mobile Phones",
+        "(M) Son",
+        "Multimedia Internet Technology",
+        "Technology Group Ltd.",
+        "Wireless Networks",
+        "Wireless Solutions",
+        NULL,
+    };
+    static const char *const IGNORED_WORDS[] = {
+        "AB",
+        "AG",
+        "A/S",
+        "ASA",
+        "B.V.",
+        "Chips",
+        "Co.",
+        "Co",
+        "Communications",
+        "Components",
+        "Computers",
+        "Computertechnik",
+        "corp.",
+        "Corp.",
+        "Corp",
+        "Corporation",
+        "Design",
+        "Electronics",
+        "Enterprise",
+        "Enterprises",
+        "Europe",
+        "GmbH",
+        "Hardware",
+        "[hex]",
+        "Holdings",
+        "Inc.",
+        "Inc",
+        "INC.",
+        "Incorporated",
+        "Instruments",
+        "International",
+        "Intl.",
+        "Labs",
+        "Limited.",
+        "Limited",
+        "Ltd.",
+        "Ltd",
+        "Microelectronics",
+        "Microsystems",
+        "MSM",
+        "Multimedia",
+        "Networks",
+        "Norway",
+        "Optical",
+        "PCS",
+        "Semiconductor",
+        "Systems",
+        "Systemtechnik",
+        "Techcenter",
+        "Technik",
+        "Technologies",
+        "Technology",
+        "TECHNOLOGY",
+        "Telephonics",
+        "USA",
+        "WCDMA",
+        NULL,
+    };
+    char *desc_full;
+    char *p;
+
+    desc_full = _fixup_string(desc, IGNORED_PHRASES, IGNORED_WORDS, TRUE);
+    if (!desc_full)
+        return NULL;
+
+    /* Chop off everything after a slash. */
+    for (p = desc_full; *p; p++) {
+        if ((p[0] == ' ' && p[1] == '/') || p[0] == '/') {
+            p[0] = '\0';
+            break;
+        }
+    }
+
+    nm_assert(g_utf8_validate(desc_full, -1, NULL));
+
+    return desc_full;
+}
+
+char *
+nm_utils_fixup_product_string(const char *desc)
+{
+    static const char *const IGNORED_PHRASES[] = {
+        "100/10 MBit",
+        "10/100 Mbps",
+        "1.0 GbE",
+        "10 GbE",
+        "10 Gigabit",
+        "10 Mbps",
+        "1/10 Gigabit",
+        "150 Mbps",
+        "2.5 GbE",
+        "54 Mbps",
+        "Attached Port",
+        "+ BT",
+        "\"CDC Subset\"",
+        "CE Media Processor",
+        "Controller Area Network",
+        "Converged Network",
+        "DEC-Tulip compatible",
+        "Dish Adapter",
+        "Double 108 Mbps",
+        "Dual Band",
+        "Dual Port",
+        "Embedded UTP",
+        "Ethernet Connection",
+        "Ethernet Pro 100",
+        "Express Module",
+        "Fabric Adapter",
+        "Fast Ethernet",
+        "for 10GBASE-T",
+        "for 10GbE backplane",
+        "for 10GbE QSFP+",
+        "for 10GbE SFP+",
+        "for 1GbE",
+        "for 20GbE backplane",
+        "for 25GbE backplane",
+        "for 25GbE SFP28",
+        "for 40GbE backplane",
+        "for 40GbE QSFP+",
+        "G Adapter",
+        "Gigabit Desktop Network",
+        "Gigabit Ethernet",
+        "Gigabit or",
+        "Host Interface",
+        "Host Virtual Interface",
+        "IEEE 802.11a/b/g",
+        "IEEE 802.11g",
+        "IEEE 802.11G",
+        "IEEE 802.11n",
+        "MAC + PHY",
+        "Mini Card",
+        "Mini Wireless",
+        "multicore SoC",
+        "Multi Function",
+        "N Draft 11n Wireless",
+        "Network Connection",
+        "Network Everywhere",
+        "N Wireless",
+        "N+ Wireless",
+        "OCT To Fast Ethernet Converter",
+        "PC Card",
+        "PCI Express",
+        "Platform Controller Hub",
+        "Plus Bluetooth",
+        "Quad Gigabit",
+        "rev 1",
+        "rev 17",
+        "rev 2",
+        "rev A",
+        "rev B",
+        "rev F",
+        "TO Ethernet",
+        "Turbo Wireless Adapter",
+        "Unified Wire",
+        "USB 1.1",
+        "USB 2.0",
+        "Virtual media for",
+        "WiFi Link",
+        "+ WiMAX",
+        "WiMAX/WiFi Link",
+        "Wireless G",
+        "Wireless G+",
+        "Wireless Lan",
+        "Wireless Mini adapter",
+        "Wireless Mini Adapter",
+        "Wireless N",
+        "with 1000-BASE-T interface",
+        "with CX4 copper interface",
+        "with Range Amplifier",
+        "with SR-XFP optical interface",
+        "w/ Upgradable Antenna",
+        NULL,
+    };
+    static const char *const IGNORED_WORDS[] = {
+        "1000BaseSX",
+        "1000BASE-T",
+        "1000Base-ZX",
+        "100/10M",
+        "100baseFx",
+        "100Base-MII",
+        "100Base-T",
+        "100BaseT4",
+        "100Base-TX",
+        "100BaseTX",
+        "100GbE",
+        "100Mbps",
+        "100MBps",
+        "10/100",
+        "10/100/1000",
+        "10/100/1000Base-T",
+        "10/100/1000BASE-T",
+        "10/100BaseT",
+        "10/100baseTX",
+        "10/100BaseTX",
+        "10/100/BNC",
+        "10/100M",
+        "10/20-Gigabit",
+        "10/25/40/50GbE",
+        "10/40G",
+        "10base-FL",
+        "10BaseT",
+        "10BASE-T",
+        "10G",
+        "10Gb",
+        "10Gb/25Gb",
+        "10Gb/25Gb/40Gb/50Gb",
+        "10Gbase-T",
+        "10GBase-T",
+        "10GBASE-T",
+        "10GbE",
+        "10Gbps",
+        "10-Giga",
+        "10-Gigabit",
+        "10mbps",
+        "10Mbps",
+        "1/10GbE",
+        "1/10-Gigabit",
+        "11b/g/n",
+        "11g",
+        "150Mbps",
+        "16Gbps/10Gbps",
+        "1GbE",
+        "1x2:2",
+        "20GbE",
+        "25Gb",
+        "25GbE",
+        "2-Port",
+        "2x3:3",
+        "3G",
+        "3G/4G",
+        "3x3:3",
+        "40GbE",
+        "4G",
+        "54g",
+        "54M",
+        "54Mbps",
+        "56k",
+        "5G",
+        "802.11",
+        "802.11a/b/g",
+        "802.11abg",
+        "802.11a/b/g/n",
+        "802.11abgn",
+        "802.11ac",
+        "802.11ad",
+        "802.11a/g",
+        "802.11b",
+        "802.11b/g",
+        "802.11bg",
+        "802.11b/g/n",
+        "802.11bgn",
+        "802.11b/g/n-draft",
+        "802.11g",
+        "802.11n",
+        "802.11N",
+        "802.11n/b/g",
+        "802.11ng",
+        "802AIN",
+        "802UIG-1",
+        "adapter",
+        "Adapter",
+        "adaptor",
+        "ADSL",
+        "Basic",
+        "CAN-Bus",
+        "card",
+        "Card",
+        "Cardbus",
+        "CardBus",
+        "CDMA",
+        "CNA",
+        "Composite",
+        "controller",
+        "Controller",
+        "Copper",
+        "DB",
+        "Desktop",
+        "device",
+        "Device",
+        "dongle",
+        "driver",
+        "Dual-band",
+        "Dual-Protocol",
+        "EISA",
+        "Enhanced",
+        "ethernet.",
+        "ethernet",
+        "Ethernet",
+        "Ethernet/RNDIS",
+        "ExpressModule",
+        "family",
+        "Family",
+        "Fast/Gigabit",
+        "Fiber",
+        "gigabit",
+        "Gigabit",
+        "G-NIC",
+        "Hi-Gain",
+        "Hi-Speed",
+        "HSDPA",
+        "HSUPA",
+        "integrated",
+        "Integrated",
+        "interface",
+        "LAN",
+        "LAN+Winmodem",
+        "Laptop",
+        "LTE",
+        "LTE/UMTS/GSM",
+        "MAC",
+        "Micro",
+        "Mini-Card",
+        "Mini-USB",
+        "misprogrammed",
+        "modem",
+        "Modem",
+        "Modem/Networkcard",
+        "Module",
+        "Multimode",
+        "Multithreaded",
+        "Name:",
+        "net",
+        "network",
+        "Network",
+        "n/g/b",
+        "NIC",
+        "Notebook",
+        "OEM",
+        "PCI",
+        "PCI64",
+        "PCIe",
+        "PCI-E",
+        "PCI-Express",
+        "PCI-X",
+        "PCMCIA",
+        "PDA",
+        "PnP",
+        "RDMA",
+        "RJ-45",
+        "Series",
+        "Server",
+        "SoC",
+        "Switch",
+        "Technologies",
+        "TOE",
+        "USB",
+        "USB2.0",
+        "USB/Ethernet",
+        "UTP",
+        "UTP/Coax",
+        "v1",
+        "v1.1",
+        "v2",
+        "V2.0",
+        "v3",
+        "v4",
+        "wifi",
+        "Wi-Fi",
+        "WiFi",
+        "wireless",
+        "Wireless",
+        "Wireless-150N",
+        "Wireless-300N",
+        "Wireless-G",
+        "Wireless-N",
+        "WLAN",
+        NULL,
+    };
+    char *desc_full;
+    char *p;
+
+    desc_full = _fixup_string(desc, IGNORED_PHRASES, IGNORED_WORDS, FALSE);
+    if (!desc_full)
+        return NULL;
+
+    /* Chop off everything after a '-'. */
+    for (p = desc_full; *p; p++) {
+        if (p[0] == ' ' && p[1] == '-' && p[2] == ' ') {
+            p[0] = '\0';
+            break;
+        }
+    }
+
+    nm_assert(g_utf8_validate(desc_full, -1, NULL));
+
+    return desc_full;
+}
+
+/*****************************************************************************/
+
+const NMLDBusMetaIface *const _nml_dbus_meta_ifaces[] = {
+    &_nml_dbus_meta_iface_nm,
+    &_nml_dbus_meta_iface_nm_accesspoint,
+    &_nml_dbus_meta_iface_nm_agentmanager,
+    &_nml_dbus_meta_iface_nm_checkpoint,
+    &_nml_dbus_meta_iface_nm_connection_active,
+    &_nml_dbus_meta_iface_nm_dhcp4config,
+    &_nml_dbus_meta_iface_nm_dhcp6config,
+    &_nml_dbus_meta_iface_nm_device,
+    &_nml_dbus_meta_iface_nm_device_adsl,
+    &_nml_dbus_meta_iface_nm_device_bluetooth,
+    &_nml_dbus_meta_iface_nm_device_bond,
+    &_nml_dbus_meta_iface_nm_device_bridge,
+    &_nml_dbus_meta_iface_nm_device_dummy,
+    &_nml_dbus_meta_iface_nm_device_generic,
+    &_nml_dbus_meta_iface_nm_device_iptunnel,
+    &_nml_dbus_meta_iface_nm_device_infiniband,
+    &_nml_dbus_meta_iface_nm_device_lowpan,
+    &_nml_dbus_meta_iface_nm_device_macsec,
+    &_nml_dbus_meta_iface_nm_device_macvlan,
+    &_nml_dbus_meta_iface_nm_device_modem,
+    &_nml_dbus_meta_iface_nm_device_olpcmesh,
+    &_nml_dbus_meta_iface_nm_device_ovsbridge,
+    &_nml_dbus_meta_iface_nm_device_ovsinterface,
+    &_nml_dbus_meta_iface_nm_device_ovsport,
+    &_nml_dbus_meta_iface_nm_device_ppp,
+    &_nml_dbus_meta_iface_nm_device_statistics,
+    &_nml_dbus_meta_iface_nm_device_team,
+    &_nml_dbus_meta_iface_nm_device_tun,
+    &_nml_dbus_meta_iface_nm_device_veth,
+    &_nml_dbus_meta_iface_nm_device_vlan,
+    &_nml_dbus_meta_iface_nm_device_vrf,
+    &_nml_dbus_meta_iface_nm_device_vxlan,
+    &_nml_dbus_meta_iface_nm_device_wifip2p,
+    &_nml_dbus_meta_iface_nm_device_wireguard,
+    &_nml_dbus_meta_iface_nm_device_wired,
+    &_nml_dbus_meta_iface_nm_device_wireless,
+    &_nml_dbus_meta_iface_nm_device_wpan,
+    &_nml_dbus_meta_iface_nm_dnsmanager,
+    &_nml_dbus_meta_iface_nm_ip4config,
+    &_nml_dbus_meta_iface_nm_ip6config,
+    &_nml_dbus_meta_iface_nm_settings,
+    &_nml_dbus_meta_iface_nm_settings_connection,
+    &_nml_dbus_meta_iface_nm_vpn_connection,
+    &_nml_dbus_meta_iface_nm_wifip2ppeer,
+};
+
+#define COMMON_PREFIX "org.freedesktop.NetworkManager"
+
+static int
+_strcmp_common_prefix(gconstpointer a, gconstpointer b, gpointer user_data)
+{
+    const NMLDBusMetaIface *iface           = a;
+    const char *            dbus_iface_name = b;
+
+    nm_assert(g_str_has_prefix(iface->dbus_iface_name, COMMON_PREFIX));
+
+    return strcmp(&iface->dbus_iface_name[NM_STRLEN(COMMON_PREFIX)], dbus_iface_name);
+}
+
+const NMLDBusMetaIface *
+nml_dbus_meta_iface_get(const char *dbus_iface_name)
+{
+    gssize idx;
+
+    nm_assert(dbus_iface_name);
+
+    G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMLDBusMetaIface, dbus_iface_name) == 0);
+
+    /* we assume that NetworkManager only uses unique interface names. E.g. one
+     * interface name always has one particular meaning (and offers one set of
+     * properties, signals and methods). This is a convenient assumption, and
+     * we sure would never violate it when extending NM's D-Bus API. */
+
+    if (NM_STR_HAS_PREFIX(dbus_iface_name, COMMON_PREFIX)) {
+        /* optimize, that in fact all our interfaces have the same prefix. */
+        idx = nm_utils_ptrarray_find_binary_search((gconstpointer *) _nml_dbus_meta_ifaces,
+                                                   G_N_ELEMENTS(_nml_dbus_meta_ifaces),
+                                                   &dbus_iface_name[NM_STRLEN(COMMON_PREFIX)],
+                                                   _strcmp_common_prefix,
+                                                   NULL);
+    } else
+        return NULL;
+
+    if (idx < 0)
+        return NULL;
+    return _nml_dbus_meta_ifaces[idx];
+}
+
+const NMLDBusMetaProperty *
+nml_dbus_meta_property_get(const NMLDBusMetaIface *meta_iface,
+                           const char *            dbus_property_name,
+                           guint *                 out_idx)
+{
+    gssize idx;
+
+    nm_assert(meta_iface);
+    nm_assert(dbus_property_name);
+
+    idx = nm_utils_array_find_binary_search(meta_iface->dbus_properties,
+                                            sizeof(meta_iface->dbus_properties[0]),
+                                            meta_iface->n_dbus_properties,
+                                            &dbus_property_name,
+                                            nm_strcmp_p_with_data,
+                                            NULL);
+    if (idx < 0) {
+        NM_SET_OUT(out_idx, meta_iface->n_dbus_properties);
+        return NULL;
+    }
+    NM_SET_OUT(out_idx, idx);
+    return &meta_iface->dbus_properties[idx];
+}
+
+void
+_nml_dbus_meta_class_init_with_properties_impl(GObjectClass *                 object_class,
+                                               const NMLDBusMetaIface *const *meta_ifaces)
+{
+    int i_iface;
+
+    nm_assert(G_IS_OBJECT_CLASS(object_class));
+    nm_assert(meta_ifaces);
+    nm_assert(meta_ifaces[0]);
+
+    for (i_iface = 0; meta_ifaces[i_iface]; i_iface++) {
+        const NMLDBusMetaIface *meta_iface = meta_ifaces[i_iface];
+        guint8 *                reverse_idx;
+        guint8                  i;
+
+        nm_assert(g_type_is_a(meta_iface->get_type_fcn(), G_OBJECT_CLASS_TYPE(object_class)));
+        nm_assert(meta_iface->n_obj_properties > 0);
+        nm_assert(meta_iface->obj_properties);
+        nm_assert(meta_iface->obj_properties_reverse_idx[0] == 0);
+        nm_assert(meta_iface->obj_properties == meta_ifaces[0]->obj_properties);
+
+        if (i_iface == 0)
+            g_object_class_install_properties(object_class,
+                                              meta_iface->n_obj_properties,
+                                              (GParamSpec **) meta_iface->obj_properties);
+
+        reverse_idx = (guint8 *) meta_iface->obj_properties_reverse_idx;
+
+        for (i = 0; i < meta_iface->n_obj_properties; i++)
+            reverse_idx[i] = 0xFFu;
+        for (i = 0; i < meta_iface->n_dbus_properties; i++) {
+            const NMLDBusMetaProperty *mpr = &meta_iface->dbus_properties[i];
+
+            if (mpr->obj_properties_idx != 0 && !mpr->obj_property_no_reverse_idx) {
+                nm_assert(mpr->obj_properties_idx < meta_iface->n_obj_properties);
+                nm_assert(reverse_idx[mpr->obj_properties_idx] == 0xFFu);
+
+                reverse_idx[mpr->obj_properties_idx] = i;
+            }
+        }
+    }
+}
+
+gboolean
+nm_utils_g_param_spec_is_default(const GParamSpec *pspec)
+{
+    g_return_val_if_fail(pspec, FALSE);
+
+    if (pspec->value_type == G_TYPE_BOOLEAN)
+        return ((((GParamSpecBoolean *) pspec)->default_value) == FALSE);
+    if (pspec->value_type == G_TYPE_UCHAR)
+        return ((((GParamSpecUChar *) pspec)->default_value) == 0u);
+    if (pspec->value_type == G_TYPE_INT)
+        return ((((GParamSpecInt *) pspec)->default_value) == 0);
+    if (pspec->value_type == G_TYPE_UINT)
+        return ((((GParamSpecUInt *) pspec)->default_value) == 0u);
+    if (pspec->value_type == G_TYPE_INT64)
+        return ((((GParamSpecInt64 *) pspec)->default_value) == 0);
+    if (pspec->value_type == G_TYPE_UINT64)
+        return ((((GParamSpecUInt64 *) pspec)->default_value) == 0u);
+    if (g_type_is_a(pspec->value_type, G_TYPE_ENUM))
+        return ((((GParamSpecEnum *) pspec)->default_value) == 0);
+    if (g_type_is_a(pspec->value_type, G_TYPE_FLAGS))
+        return ((((GParamSpecFlags *) pspec)->default_value) == 0u);
+    if (pspec->value_type == G_TYPE_STRING)
+        return ((((GParamSpecString *) pspec)->default_value) == NULL);
+    if (NM_IN_SET(pspec->value_type,
+                  G_TYPE_BYTES,
+                  G_TYPE_PTR_ARRAY,
+                  G_TYPE_ARRAY,
+                  G_TYPE_HASH_TABLE,
+                  G_TYPE_STRV)) {
+        /* boxed types have NULL default. */
+        g_return_val_if_fail(G_IS_PARAM_SPEC_BOXED(pspec), FALSE);
+        g_return_val_if_fail(G_TYPE_IS_BOXED(pspec->value_type), FALSE);
+        return TRUE;
+    }
+    if (g_type_is_a(pspec->value_type, NM_TYPE_OBJECT)) {
+        /* object types have NULL default. */
+        g_return_val_if_fail(G_IS_PARAM_SPEC_OBJECT(pspec), FALSE);
+        g_return_val_if_fail(G_TYPE_IS_OBJECT(pspec->value_type), FALSE);
+        return TRUE;
+    }
+
+    /* This function is only used for asserting/testing. It thus
+     * strictly asserts and only support argument types that we expect. */
+    g_return_val_if_reached(FALSE);
+}
+
+/*****************************************************************************/
+
+/**
+ * nm_utils_print:
+ * @output_mode: if 1 it uses g_print(). If 2, it uses g_printerr().
+ *   If 0, it uses either g_print() or g_printerr(), depending
+ *   on LIBNM_CLIENT_DEBUG (and the "stdout" flag).
+ * @msg: the message to print. The function does not append
+ *   a trailing newline.
+ *
+ * The only purpose of this function is to give access to g_print()
+ * or g_printerr() from pygobject. libnm can do debug logging by
+ * setting LIBNM_CLIENT_DEBUG and uses thereby g_printerr() or
+ * g_print(). A plain "print()" function in python is not in sync
+ * with these functions (it implements additional buffering). By
+ * using nm_utils_print(), the same logging mechanisms can be used.
+ *
+ * Since: 1.30
+ */
+void
+nm_utils_print(int output_mode, const char *msg)
+{
+    gboolean use_stdout;
+
+    g_return_if_fail(msg);
+
+    if (output_mode == 0) {
+        nml_dbus_log_enabled_full(NML_DBUS_LOG_LEVEL_ANY, &use_stdout);
+        output_mode = use_stdout ? 1 : 2;
+    }
+
+    if (output_mode == 1)
+        g_print("%s", msg);
+    else if (output_mode == 2)
+        g_printerr("%s", msg);
+    else
+        g_return_if_reached();
+}