From a879e1a46eb42b3468ff85c62a3d20582b760be4 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 25 May 2019 05:08:13 +0900 Subject: [PATCH] network: monitor link bit rates --- src/libsystemd/sd-bus/bus-common-errors.c | 2 + src/libsystemd/sd-bus/bus-common-errors.h | 2 + src/network/meson.build | 2 + src/network/networkd-conf.c | 26 ++++- src/network/networkd-gperf.gperf | 6 +- src/network/networkd-link-bus.c | 46 +++++++++ src/network/networkd-link.h | 4 + src/network/networkd-manager.c | 13 ++- src/network/networkd-manager.h | 8 ++ src/network/networkd-speed-meter.c | 113 ++++++++++++++++++++++ src/network/networkd-speed-meter.h | 12 +++ src/network/networkd.conf | 4 + 12 files changed, 230 insertions(+), 8 deletions(-) create mode 100644 src/network/networkd-speed-meter.c create mode 100644 src/network/networkd-speed-meter.h diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 6e5fe00e06..ef53d0a450 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -101,5 +101,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRODUCT_UUID, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_SPEED_METER_INACTIVE, EOPNOTSUPP), + SD_BUS_ERROR_MAP_END }; diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 8339feb768..2544bdebc1 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -78,4 +78,6 @@ #define BUS_ERROR_NO_PRODUCT_UUID "org.freedesktop.hostname1.NoProductUUID" +#define BUS_ERROR_SPEED_METER_INACTIVE "org.freedesktop.network1.SpeedMeterInactive" + BUS_ERROR_MAP_ELF_USE(bus_common_errors); diff --git a/src/network/meson.build b/src/network/meson.build index feeb98cb07..959421fc5c 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -85,6 +85,8 @@ sources = files(''' networkd-route.h networkd-routing-policy-rule.c networkd-routing-policy-rule.h + networkd-speed-meter.c + networkd-speed-meter.h networkd-util.c networkd-util.h '''.split()) diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c index 334ffc3c57..1ef5beb203 100644 --- a/src/network/networkd-conf.c +++ b/src/network/networkd-conf.c @@ -11,17 +11,33 @@ #include "extract-word.h" #include "hexdecoct.h" #include "networkd-conf.h" +#include "networkd-manager.h" #include "networkd-network.h" +#include "networkd-speed-meter.h" #include "string-table.h" int manager_parse_config_file(Manager *m) { + int r; + assert(m); - return config_parse_many_nulstr(PKGSYSCONFDIR "/networkd.conf", - CONF_PATHS_NULSTR("systemd/networkd.conf.d"), - "DHCP\0", - config_item_perf_lookup, networkd_gperf_lookup, - CONFIG_PARSE_WARN, m); + r = config_parse_many_nulstr(PKGSYSCONFDIR "/networkd.conf", + CONF_PATHS_NULSTR("systemd/networkd.conf.d"), + "Network\0DHCP\0", + config_item_perf_lookup, networkd_gperf_lookup, + CONFIG_PARSE_WARN, m); + if (r < 0) + return r; + + if (m->use_speed_meter && m->speed_meter_interval_usec < SPEED_METER_MINIMUM_TIME_INTERVAL) { + char buf[FORMAT_TIMESPAN_MAX]; + + log_warning("SpeedMeterIntervalSec= is too small, using %s.", + format_timespan(buf, sizeof buf, SPEED_METER_MINIMUM_TIME_INTERVAL, USEC_PER_SEC)); + m->speed_meter_interval_usec = SPEED_METER_MINIMUM_TIME_INTERVAL; + } + + return 0; } static const char* const duid_type_table[_DUID_TYPE_MAX] = { diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index cc88fc0add..d74d375599 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -18,5 +18,7 @@ struct ConfigPerfItem; %struct-type %includes %% -DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Manager, duid) -DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, duid) +Network.SpeedMeter, config_parse_bool, 0, offsetof(Manager, use_speed_meter) +Network.SpeedMeterIntervalSec, config_parse_sec, 0, offsetof(Manager, speed_meter_interval_usec) +DHCP.DUIDType, config_parse_duid_type, 0, offsetof(Manager, duid) +DHCP.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, duid) diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index 0dbcd86d9e..2f414cb116 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #include "alloc-util.h" +#include "bus-common-errors.h" #include "bus-util.h" #include "networkd-link.h" #include "networkd-manager.h" @@ -10,11 +11,56 @@ static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_operational_state, link_operstate, LinkOperationalState); static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_administrative_state, link_state, LinkState); +static int property_get_bit_rates( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Link *link = userdata; + Manager *manager; + double tx, rx, interval_sec; + + assert(bus); + assert(reply); + assert(userdata); + + manager = link->manager; + + if (!manager->use_speed_meter) + return sd_bus_error_set(error, BUS_ERROR_SPEED_METER_INACTIVE, "Speed meter is disabled."); + + if (manager->speed_meter_usec_old == 0) + return sd_bus_error_set(error, BUS_ERROR_SPEED_METER_INACTIVE, "Speed meter is not active."); + + if (!link->stats_updated) + return sd_bus_error_set(error, BUS_ERROR_SPEED_METER_INACTIVE, "Failed to measure bit-rates."); + + assert(manager->speed_meter_usec_new > manager->speed_meter_usec_old); + interval_sec = (double) (manager->speed_meter_usec_new - manager->speed_meter_usec_old) / USEC_PER_SEC; + + if (link->stats_new.tx_bytes > link->stats_old.tx_bytes) + tx = (link->stats_new.tx_bytes - link->stats_old.tx_bytes) / interval_sec; + else + tx = (UINT64_MAX - (link->stats_old.tx_bytes - link->stats_new.tx_bytes)) / interval_sec; + + if (link->stats_new.rx_bytes > link->stats_old.rx_bytes) + rx = (link->stats_new.rx_bytes - link->stats_old.rx_bytes) / interval_sec; + else + rx = (UINT64_MAX - (link->stats_old.rx_bytes - link->stats_new.rx_bytes)) / interval_sec; + + return sd_bus_message_append(reply, "(dd)", tx, rx); +} + const sd_bus_vtable link_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("OperationalState", "s", property_get_operational_state, offsetof(Link, operstate), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("AdministrativeState", "s", property_get_administrative_state, offsetof(Link, state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("BitRates", "(dd)", property_get_bit_rates, 0, 0), SD_BUS_VTABLE_END }; diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index f8121180c0..d0290f7c7d 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -122,6 +122,10 @@ typedef struct Link { Hashmap *bound_by_links; Hashmap *bound_to_links; Set *slaves; + + /* For speed meter */ + struct rtnl_link_stats64 stats_old, stats_new; + bool stats_updated; } Link; typedef int (*link_netlink_message_handler_t)(sd_netlink*, sd_netlink_message*, Link*); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index de177e6d1a..6984c5b967 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -22,6 +22,7 @@ #include "netlink-util.h" #include "network-internal.h" #include "networkd-manager.h" +#include "networkd-speed-meter.h" #include "ordered-set.h" #include "path-util.h" #include "set.h" @@ -1369,10 +1370,14 @@ int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; - m = new0(Manager, 1); + m = new(Manager, 1); if (!m) return -ENOMEM; + *m = (Manager) { + .speed_meter_interval_usec = SPEED_METER_DEFAULT_TIME_INTERVAL, + }; + m->state_file = strdup("/run/systemd/netif/state"); if (!m->state_file) return -ENOMEM; @@ -1469,6 +1474,7 @@ void manager_free(Manager *m) { sd_netlink_unref(m->genl); sd_resolve_unref(m->resolve); + sd_event_source_unref(m->speed_meter_event_source); sd_event_unref(m->event); sd_device_monitor_unref(m->device_monitor); @@ -1484,9 +1490,14 @@ void manager_free(Manager *m) { int manager_start(Manager *m) { Link *link; Iterator i; + int r; assert(m); + r = manager_start_speed_meter(m); + if (r < 0) + return log_error_errno(r, "Failed to initialize speed meter: %m"); + /* The dirty handler will deal with future serialization, but the first one must be done explicitly. */ diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index 06fa9d8d32..c816d86d18 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -11,6 +11,7 @@ #include "dhcp-identifier.h" #include "hashmap.h" #include "list.h" +#include "time-util.h" #include "networkd-address-pool.h" #include "networkd-link.h" @@ -55,6 +56,13 @@ struct Manager { Set *rules_saved; int sysctl_ipv6_enabled; + + /* For link speed meter*/ + bool use_speed_meter; + sd_event_source *speed_meter_event_source; + usec_t speed_meter_interval_usec; + usec_t speed_meter_usec_new; + usec_t speed_meter_usec_old; }; extern const sd_bus_vtable manager_vtable[]; diff --git a/src/network/networkd-speed-meter.c b/src/network/networkd-speed-meter.c new file mode 100644 index 0000000000..5fd30f3df8 --- /dev/null +++ b/src/network/networkd-speed-meter.c @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#include "sd-event.h" +#include "sd-netlink.h" + +#include "networkd-link.h" +#include "networkd-manager.h" +#include "networkd-speed-meter.h" + +static int process_message(Manager *manager, sd_netlink_message *message) { + uint16_t type; + int ifindex, r; + Link *link; + + r = sd_netlink_message_get_type(message, &type); + if (r < 0) + return r; + + if (type != RTM_NEWLINK) + return 0; + + r = sd_rtnl_message_link_get_ifindex(message, &ifindex); + if (r < 0) + return r; + + link = hashmap_get(manager->links, INT_TO_PTR(ifindex)); + if (!link) + return -ENODEV; + + link->stats_old = link->stats_new; + + r = sd_netlink_message_read(message, IFLA_STATS64, sizeof link->stats_new, &link->stats_new); + if (r < 0) + return r; + + link->stats_updated = true; + + return 0; +} + +static int speed_meter_handler(sd_event_source *s, uint64_t usec, void *userdata) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + Manager *manager = userdata; + sd_netlink_message *i; + usec_t usec_now; + Iterator j; + Link *link; + int r; + + assert(s); + assert(userdata); + + r = sd_event_now(sd_event_source_get_event(s), CLOCK_MONOTONIC, &usec_now); + if (r < 0) + return r; + + r = sd_event_source_set_time(s, usec_now + manager->speed_meter_interval_usec); + if (r < 0) + return r; + + manager->speed_meter_usec_old = manager->speed_meter_usec_new; + manager->speed_meter_usec_new = usec_now; + + HASHMAP_FOREACH(link, manager->links, j) + link->stats_updated = false; + + r = sd_rtnl_message_new_link(manager->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) { + log_warning_errno(r, "Failed to allocate RTM_GETLINK netlink message, ignoring: %m"); + return 0; + } + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) { + log_warning_errno(r, "Failed to set dump flag, ignoring: %m"); + return 0; + } + + r = sd_netlink_call(manager->rtnl, req, 0, &reply); + if (r < 0) { + log_warning_errno(r, "Failed to call RTM_GETLINK, ignoring: %m"); + return 0; + } + + for (i = reply; i; i = sd_netlink_message_next(i)) + (void) process_message(manager, i); + + return 0; +} + +int manager_start_speed_meter(Manager *manager) { + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + int r; + + assert(manager); + assert(manager->event); + + if (!manager->use_speed_meter) + return 0; + + r = sd_event_add_time(manager->event, &s, CLOCK_MONOTONIC, 0, 0, speed_meter_handler, manager); + if (r < 0) + return r; + + r = sd_event_source_set_enabled(s, SD_EVENT_ON); + if (r < 0) + return r; + + manager->speed_meter_event_source = TAKE_PTR(s); + return 0; +} diff --git a/src/network/networkd-speed-meter.h b/src/network/networkd-speed-meter.h new file mode 100644 index 0000000000..f5727784a9 --- /dev/null +++ b/src/network/networkd-speed-meter.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +/* Default interval is 10sec. The speed meter periodically make networkd + * to be woke up. So, too small interval value is not desired. + * We set the minimum value 100msec = 0.1sec. */ +#define SPEED_METER_DEFAULT_TIME_INTERVAL (10 * USEC_PER_SEC) +#define SPEED_METER_MINIMUM_TIME_INTERVAL (100 * USEC_PER_MSEC) + +typedef struct Manager Manager; + +int manager_start_speed_meter(Manager *m); diff --git a/src/network/networkd.conf b/src/network/networkd.conf index 8dc5676166..c5667da9fa 100644 --- a/src/network/networkd.conf +++ b/src/network/networkd.conf @@ -11,6 +11,10 @@ # # See networkd.conf(5) for details +[Network] +#SpeedMeter=no +#SpeedMeterIntervalSec=10sec + [DHCP] #DUIDType=vendor #DUIDRawData=