]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
metrics: add networkd related metrics 40619/head
authorYaping Li <202858510+YapingLi04@users.noreply.github.com>
Tue, 10 Feb 2026 01:08:01 +0000 (17:08 -0800)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 18 Feb 2026 08:58:33 +0000 (17:58 +0900)
src/network/meson.build
src/network/networkd-manager.c
src/network/networkd-manager.h
src/network/networkd-varlink-metrics.c [new file with mode: 0644]
src/network/networkd-varlink-metrics.h [new file with mode: 0644]
test/networkd-test.py
test/test-network/systemd-networkd-tests.py
test/units/TEST-74-AUX-UTILS.varlinkctl.sh
units/meson.build
units/systemd-networkd-varlink-metrics.socket [new file with mode: 0644]
units/systemd-networkd.service.in

index 4457e836444b004e7ef297063f30d29a056a0f85..89f3231c2907defefefd0a3278e2672ffd078bf5 100644 (file)
@@ -87,6 +87,7 @@ systemd_networkd_extract_sources = files(
         'networkd-state-file.c',
         'networkd-sysctl.c',
         'networkd-util.c',
+        'networkd-varlink-metrics.c',
         'networkd-wifi.c',
         'networkd-wiphy.c',
         'networkd-wwan.c',
index dbda6311d9a9b807f00d24b3065e3680ad74fd18..62e52717585c1bf1d7e55285c51583e13b4a2663 100644 (file)
@@ -44,6 +44,7 @@
 #include "networkd-serialize.h"
 #include "networkd-speed-meter.h"
 #include "networkd-state-file.h"
+#include "networkd-varlink-metrics.h"
 #include "networkd-wifi.h"
 #include "networkd-wiphy.h"
 #include "networkd-wwan-bus.h"
@@ -212,13 +213,14 @@ static int manager_connect_udev(Manager *m) {
         return 0;
 }
 
-static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd, int *ret_resolve_hook_fd) {
+static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd, int *ret_varlink_metrics_fd, int *ret_resolve_hook_fd) {
         _cleanup_strv_free_ char **names = NULL;
-        int n, rtnl_fd = -EBADF, varlink_fd = -EBADF, resolve_hook_fd = -EBADF;
+        int n, rtnl_fd = -EBADF, varlink_fd = -EBADF, varlink_metrics_fd = -EBADF, resolve_hook_fd = -EBADF;
 
         assert(m);
         assert(ret_rtnl_fd);
         assert(ret_varlink_fd);
+        assert(ret_varlink_metrics_fd);
         assert(ret_resolve_hook_fd);
 
         n = sd_listen_fds_with_names(/* unset_environment= */ true, &names);
@@ -243,6 +245,11 @@ static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd,
                         continue;
                 }
 
+                if (streq(names[i], "varlink-metrics")) {
+                        varlink_metrics_fd = fd;
+                        continue;
+                }
+
                 if (streq(names[i], "resolve-hook")) {
                         resolve_hook_fd = fd;
                         continue;
@@ -263,6 +270,7 @@ static int manager_listen_fds(Manager *m, int *ret_rtnl_fd, int *ret_varlink_fd,
 
         *ret_rtnl_fd = rtnl_fd;
         *ret_varlink_fd = varlink_fd;
+        *ret_varlink_metrics_fd = varlink_metrics_fd;
         *ret_resolve_hook_fd = resolve_hook_fd;
 
         return 0;
@@ -557,7 +565,7 @@ static int manager_set_keep_configuration(Manager *m) {
 }
 
 int manager_setup(Manager *m) {
-        _cleanup_close_ int rtnl_fd = -EBADF, varlink_fd = -EBADF, resolve_hook_fd = -EBADF;
+        _cleanup_close_ int rtnl_fd = -EBADF, varlink_fd = -EBADF, varlink_metrics_fd = -EBADF, resolve_hook_fd = -EBADF;
         int r;
 
         assert(m);
@@ -581,7 +589,7 @@ int manager_setup(Manager *m) {
         if (r < 0)
                 return r;
 
-        r = manager_listen_fds(m, &rtnl_fd, &varlink_fd, &resolve_hook_fd);
+        r = manager_listen_fds(m, &rtnl_fd, &varlink_fd, &varlink_metrics_fd, &resolve_hook_fd);
         if (r < 0)
                 return r;
 
@@ -604,6 +612,10 @@ int manager_setup(Manager *m) {
         if (r < 0)
                 return r;
 
+        r = manager_varlink_metrics_init(m, TAKE_FD(varlink_metrics_fd));
+        if (r < 0)
+                return r;
+
         r = manager_varlink_init_resolve_hook(m, TAKE_FD(resolve_hook_fd));
         if (r < 0)
                 return r;
@@ -760,6 +772,7 @@ Manager* manager_free(Manager *m) {
 
         m->varlink_server = sd_varlink_server_unref(m->varlink_server);
         m->varlink_resolve_hook_server = sd_varlink_server_unref(m->varlink_resolve_hook_server);
+        m->varlink_metrics_server = sd_varlink_server_unref(m->varlink_metrics_server);
         m->query_filter_subscriptions = set_free(m->query_filter_subscriptions);
         hashmap_free(m->polkit_registry);
         sd_bus_flush_close_unref(m->bus);
index 13467d963e58ab18d16d2b06838c15459ce83129..7b014017200ddae6e3021c8b31843c7def1d9d81 100644 (file)
@@ -23,6 +23,7 @@ typedef struct Manager {
         sd_bus *bus;
         sd_varlink_server *varlink_server;
         sd_varlink_server *varlink_resolve_hook_server;
+        sd_varlink_server *varlink_metrics_server;
         Set *query_filter_subscriptions;
         sd_device_monitor *device_monitor;
         Hashmap *polkit_registry;
diff --git a/src/network/networkd-varlink-metrics.c b/src/network/networkd-varlink-metrics.c
new file mode 100644 (file)
index 0000000..1853bfd
--- /dev/null
@@ -0,0 +1,200 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-varlink.h"
+
+#include "argv-util.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "hashmap.h"
+#include "metrics.h"
+#include "network-util.h"
+#include "networkd-link.h"
+#include "networkd-manager.h"
+#include "networkd-varlink-metrics.h"
+
+#define METRIC_IO_SYSTEMD_NETWORK_PREFIX "io.systemd.Network."
+
+typedef const char* (*link_metric_extractor_t)(const Link *link);
+
+static int link_metric_build_json(
+                MetricFamilyContext *context,
+                link_metric_extractor_t extractor,
+                void *userdata) {
+
+        Manager *manager = ASSERT_PTR(userdata);
+        Link *link;
+        int r;
+
+        assert(context);
+        assert(extractor);
+
+        HASHMAP_FOREACH(link, manager->links_by_index) {
+                r = metric_build_send_string(context, link->ifname, extractor(link), /* fields= */ NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static const char* link_get_address_state(const Link *l) {
+        return link_address_state_to_string(ASSERT_PTR(l)->address_state);
+}
+
+static const char* link_get_admin_state(const Link *l) {
+        return link_state_to_string(ASSERT_PTR(l)->state);
+}
+
+static const char* link_get_carrier_state(const Link *l) {
+        return link_carrier_state_to_string(ASSERT_PTR(l)->carrier_state);
+}
+
+static const char* link_get_ipv4_address_state(const Link *l) {
+        return link_address_state_to_string(ASSERT_PTR(l)->ipv4_address_state);
+}
+
+static const char* link_get_ipv6_address_state(const Link *l) {
+        return link_address_state_to_string(ASSERT_PTR(l)->ipv6_address_state);
+}
+
+static const char* link_get_oper_state(const Link *l) {
+        return link_operstate_to_string(ASSERT_PTR(l)->operstate);
+}
+
+static int link_address_state_build_json(MetricFamilyContext *ctx, void *userdata) {
+        return link_metric_build_json(ctx, link_get_address_state, userdata);
+}
+
+static int link_admin_state_build_json(MetricFamilyContext *ctx, void *userdata) {
+        return link_metric_build_json(ctx, link_get_admin_state, userdata);
+}
+
+static int link_carrier_state_build_json(MetricFamilyContext *ctx, void *userdata) {
+        return link_metric_build_json(ctx, link_get_carrier_state, userdata);
+}
+
+static int link_ipv4_address_state_build_json(MetricFamilyContext *ctx, void *userdata) {
+        return link_metric_build_json(ctx, link_get_ipv4_address_state, userdata);
+}
+
+static int link_ipv6_address_state_build_json(MetricFamilyContext *ctx, void *userdata) {
+        return link_metric_build_json(ctx, link_get_ipv6_address_state, userdata);
+}
+
+static int link_oper_state_build_json(MetricFamilyContext *ctx, void *userdata) {
+        return link_metric_build_json(ctx, link_get_oper_state, userdata);
+}
+
+static int managed_interfaces_build_json(MetricFamilyContext *context, void *userdata) {
+        Manager *manager = ASSERT_PTR(userdata);
+        Link *link;
+        uint64_t count = 0;
+
+        assert(context);
+
+        HASHMAP_FOREACH(link, manager->links_by_index)
+                if (link->network)
+                        count++;
+
+        return metric_build_send_unsigned(context, /* object= */ NULL, count, /* fields= */ NULL);
+}
+
+/* Keep metrics ordered alphabetically */
+static const MetricFamily network_metric_family_table[] = {
+        {
+                .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "addressState",
+                .description = "Per interface metric: address state",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = link_address_state_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "adminState",
+                .description = "Per interface metric: admin state",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = link_admin_state_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "carrierState",
+                .description = "Per interface metric: carrier state",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = link_carrier_state_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "ipv4AddressState",
+                .description = "Per interface metric: IPv4 address state",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = link_ipv4_address_state_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "ipv6AddressState",
+                .description = "Per interface metric: IPv6 address state",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = link_ipv6_address_state_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "managedInterfaces",
+                .description = "Number of network interfaces managed by systemd-networkd",
+                .type = METRIC_FAMILY_TYPE_GAUGE,
+                .generate = managed_interfaces_build_json,
+        },
+        {
+                .name = METRIC_IO_SYSTEMD_NETWORK_PREFIX "operationalState",
+                .description = "Per interface metric: operational state",
+                .type = METRIC_FAMILY_TYPE_STRING,
+                .generate = link_oper_state_build_json,
+        },
+        {}
+};
+
+static int vl_method_metrics_describe(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return metrics_method_describe(network_metric_family_table, link, parameters, flags, userdata);
+}
+
+static int vl_method_metrics_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+        return metrics_method_list(network_metric_family_table, link, parameters, flags, userdata);
+}
+
+int manager_varlink_metrics_init(Manager *m, int fd) {
+        _unused_ _cleanup_close_ int fd_close = fd; /* take possession */
+        int r;
+
+        assert(m);
+
+        if (m->varlink_metrics_server)
+                return 0;
+
+        if (fd < 0 && invoked_by_systemd()) {
+                log_debug("systemd-networkd-varlink-metrics.socket seems to be disabled, not installing metrics varlink server.");
+                return 0;
+        }
+
+        _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL;
+        r = metrics_setup_varlink_server(
+                        &s,
+                        SD_VARLINK_SERVER_INHERIT_USERDATA,
+                        m->event,
+                        SD_EVENT_PRIORITY_NORMAL,
+                        vl_method_metrics_list,
+                        vl_method_metrics_describe,
+                        m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set up metrics varlink server: %m");
+
+        if (fd < 0) {
+                r = sd_varlink_server_listen_address(
+                                s,
+                                "/run/systemd/report/io.systemd.Network",
+                                0666 | SD_VARLINK_SERVER_MODE_MKDIR_0755);
+                if (ERRNO_IS_NEG_PRIVILEGE(r)) {
+                        log_warning_errno(r, "Failed to bind to metrics varlink socket, ignoring: %m");
+                        return 0;
+                }
+        } else
+                r = sd_varlink_server_listen_fd(s, fd);
+        if (r < 0)
+                return log_error_errno(r, "Failed to bind to metrics varlink socket: %m");
+
+        TAKE_FD(fd_close);
+        m->varlink_metrics_server = TAKE_PTR(s);
+        return 0;
+}
diff --git a/src/network/networkd-varlink-metrics.h b/src/network/networkd-varlink-metrics.h
new file mode 100644 (file)
index 0000000..7933504
--- /dev/null
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "networkd-forward.h"
+
+int manager_varlink_metrics_init(Manager *m, int fd);
index 62041201ca629413bc07e62bffd03706f06247f2..d9f7af36782600fb858a8abfb08087ad0714c9d6 100755 (executable)
@@ -66,6 +66,7 @@ def setUpModule():
         'systemd-networkd.socket',
         'systemd-networkd-resolve-hook.socket',
         'systemd-networkd-varlink.socket',
+        'systemd-networkd-varlink-metrics.socket',
         'systemd-networkd.service',
         'systemd-resolved-monitor.socket',
         'systemd-resolved-varlink.socket',
@@ -91,7 +92,8 @@ def setUpModule():
         subprocess.call(['useradd', '--system', '--no-create-home', 'systemd-network'])
 
     for d in ['/etc/systemd/network', '/run/systemd/network',
-              '/run/systemd/netif', '/run/systemd/resolve', '/run/systemd/resolve.hook']:
+              '/run/systemd/netif', '/run/systemd/report',
+              '/run/systemd/resolve', '/run/systemd/resolve.hook']:
         subprocess.check_call(["mount", "-m", "-t", "tmpfs", "none", d])
         tmpmounts.append(d)
     if os.path.isdir('/run/systemd/resolve'):
@@ -281,6 +283,7 @@ Gateway=192.168.250.1
         subprocess.check_call(['systemctl', 'stop', 'systemd-networkd.socket'])
         subprocess.check_call(['systemctl', 'stop', 'systemd-networkd-resolve-hook.socket'])
         subprocess.check_call(['systemctl', 'stop', 'systemd-networkd-varlink.socket'])
+        subprocess.check_call(['systemctl', 'stop', 'systemd-networkd-varlink-metrics.socket'])
         subprocess.check_call(['systemctl', 'stop', 'systemd-networkd.service'])
         subprocess.check_call(['ip', 'link', 'del', 'mybridge'])
         subprocess.check_call(['ip', 'link', 'del', 'port1'])
@@ -378,6 +381,7 @@ class ClientTestBase(NetworkdTestingUtilities):
         subprocess.call(['systemctl', 'stop', 'systemd-networkd.socket'])
         subprocess.call(['systemctl', 'stop', 'systemd-networkd-resolve-hook.socket'])
         subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink.socket'])
+        subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink-metrics.socket'])
         subprocess.call(['systemctl', 'stop', 'systemd-networkd.service'])
         subprocess.call(['ip', 'link', 'del', 'dummy0'],
                         stderr=subprocess.DEVNULL)
@@ -935,10 +939,12 @@ class NetworkdClientTest(ClientTestBase, unittest.TestCase):
 set -eu
 mkdir -p /run/systemd/network
 mkdir -p /run/systemd/netif
+mkdir -p /run/systemd/report
 mkdir -p /run/systemd/resolve.hook
 mkdir -p /var/lib/systemd/network
 mount -t tmpfs none /run/systemd/network
 mount -t tmpfs none /run/systemd/netif
+mount -t tmpfs none /run/systemd/report
 mount -t tmpfs none /run/systemd/resolve.hook
 mount -t tmpfs none /var/lib/systemd/network
 [ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus
@@ -989,6 +995,7 @@ exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ {{ s/^.*=/
                                '-p', 'InaccessibleDirectories=-/etc/systemd/network',
                                '-p', 'InaccessibleDirectories=-/run/systemd/network',
                                '-p', 'InaccessibleDirectories=-/run/systemd/netif',
+                               '-p', 'InaccessibleDirectories=-/run/systemd/report',
                                '-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook',
                                '-p', 'InaccessibleDirectories=-/var/lib/systemd/network',
                                '--service-type=notify', script])
index 93e3ee626f53df8359ce322c2bc440f6e07711bc..bbbab8e3fe6361628ef26fa632c80e494ae67722 100755 (executable)
@@ -427,6 +427,7 @@ def save_active_units():
             'systemd-networkd.socket',
             'systemd-networkd-resolve-hook.socket',
             'systemd-networkd-varlink.socket',
+            'systemd-networkd-varlink-metrics.socket',
             'systemd-networkd.service',
             'systemd-resolved-monitor.socket',
             'systemd-resolved-varlink.socket',
@@ -455,6 +456,10 @@ def restore_active_units():
         call('systemctl stop systemd-networkd-varlink.socket')
         has_network_socket = True
 
+    if 'systemd-networkd-varlink-metrics.socket' in active_units:
+        call('systemctl stop systemd-networkd-varlink-metrics.socket')
+        has_network_socket = True
+
     if 'systemd-resolved-monitor.socket' in active_units:
         call('systemctl stop systemd-resolved-monitor.socket')
         has_resolve_socket = True
@@ -524,6 +529,7 @@ def setup_system_units():
                 'systemd-networkd-persistent-storage.service',
                 'systemd-networkd-resolve-hook.socket',
                 'systemd-networkd-varlink.socket',
+                'systemd-networkd-varlink-metrics.socket',
                 'systemd-resolved.service',
                 'systemd-timesyncd.service',
                 'systemd-udevd.service',
@@ -582,6 +588,13 @@ def setup_system_units():
             'StartLimitIntervalSec=0',
         ]
     )
+    create_unit_dropin(
+        'systemd-networkd-varlink-metrics.socket',
+        [
+            '[Unit]',
+            'StartLimitIntervalSec=0',
+        ]
+    )
     create_unit_dropin(
         'systemd-udevd.service',
         [
@@ -611,6 +624,7 @@ def clear_system_units():
     rm_unit('systemd-networkd-persistent-storage.service')
     rm_unit('systemd-networkd-resolve-hook.socket')
     rm_unit('systemd-networkd-varlink.socket')
+    rm_unit('systemd-networkd-varlink-metrics.socket')
     rm_unit('systemd-resolved.service')
     rm_unit('systemd-timesyncd.service')
     rm_unit('systemd-udevd.service')
@@ -997,11 +1011,13 @@ def stop_networkd(show_logs=True, check_failed=True):
         check_output('systemctl stop systemd-networkd.socket')
         check_output('systemctl stop systemd-networkd-resolve-hook.socket')
         check_output('systemctl stop systemd-networkd-varlink.socket')
+        check_output('systemctl stop systemd-networkd-varlink-metrics.socket')
         check_output('systemctl stop systemd-networkd.service')
     else:
         call('systemctl stop systemd-networkd.socket')
         call('systemctl stop systemd-networkd-resolve-hook.socket')
         call('systemctl stop systemd-networkd-varlink.socket')
+        call('systemctl stop systemd-networkd-varlink-metrics.socket')
         call('systemctl stop systemd-networkd.service')
 
     if show_logs:
index 9d3e6a0bbc603168e048eb5954ef737cc1d60aaf..377c4734e06cb3ecb4fed103ec7d07aea869fb71 100755 (executable)
@@ -245,3 +245,9 @@ systemd-run --wait --pipe --user --machine testuser@ \
 
 # test report
 systemd-report
+
+# test io.systemd.Network Metrics
+varlinkctl info /run/systemd/report/io.systemd.Network
+varlinkctl list-methods /run/systemd/report/io.systemd.Network
+varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.List {}
+varlinkctl --more call /run/systemd/report/io.systemd.Network io.systemd.Metrics.Describe {}
index 8030675498147fec684721ab8d8b72817d6e5937..ebefaf87f06a1cde3b597b9853522f4375da500b 100644 (file)
@@ -505,6 +505,10 @@ units = [
           'file' : 'systemd-networkd-varlink.socket',
           'conditions' : ['ENABLE_NETWORKD'],
         },
+        {
+          'file' : 'systemd-networkd-varlink-metrics.socket',
+          'conditions' : ['ENABLE_NETWORKD'],
+        },
         {
           'file' : 'systemd-networkd-wait-online.service.in',
           'conditions' : ['ENABLE_NETWORKD'],
diff --git a/units/systemd-networkd-varlink-metrics.socket b/units/systemd-networkd-varlink-metrics.socket
new file mode 100644 (file)
index 0000000..562cc6b
--- /dev/null
@@ -0,0 +1,25 @@
+#  SPDX-License-Identifier: LGPL-2.1-or-later
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Network Management Metrics Varlink Socket
+Documentation=man:systemd-networkd.service(8)
+ConditionCapability=CAP_NET_ADMIN
+DefaultDependencies=no
+Before=sockets.target shutdown.target
+Conflicts=shutdown.target
+
+[Socket]
+ListenStream=/run/systemd/report/io.systemd.Network
+FileDescriptorName=varlink-metrics
+SocketMode=0666
+Service=systemd-networkd.service
+
+[Install]
+WantedBy=sockets.target
index 589782effcd6d2d284fac94b9d7be7dd1e94c80e..17e7425f28286d28bcd3106e0a4ff9e0f7b3f34a 100644 (file)
@@ -46,7 +46,7 @@ RestrictRealtime=yes
 RestrictSUIDSGID=yes
 RuntimeDirectory=systemd/netif
 RuntimeDirectoryPreserve=yes
-Sockets=systemd-networkd.socket systemd-networkd-varlink.socket systemd-networkd-resolve-hook.socket
+Sockets=systemd-networkd.socket systemd-networkd-varlink.socket systemd-networkd-varlink-metrics.socket systemd-networkd-resolve-hook.socket
 SystemCallArchitectures=native
 SystemCallErrorNumber=EPERM
 SystemCallFilter=@system-service bpf
@@ -56,7 +56,7 @@ User=systemd-network
 
 [Install]
 WantedBy=multi-user.target
-Also=systemd-networkd.socket systemd-networkd-varlink.socket systemd-networkd-resolve-hook.socket
+Also=systemd-networkd.socket systemd-networkd-varlink.socket systemd-networkd-varlink-metrics.socket systemd-networkd-resolve-hook.socket
 Alias=dbus-org.freedesktop.network1.service
 
 # The output from this generator is used by udevd and networkd. Enable it by