diff --git a/man/networkctl.xml b/man/networkctl.xml index 77f1e1fdfc..7f68f249e4 100644 --- a/man/networkctl.xml +++ b/man/networkctl.xml @@ -58,6 +58,17 @@ + + + + + + + + Show link statistics with status. + + + diff --git a/shell-completion/bash/networkctl b/shell-completion/bash/networkctl index c4a51df6db..62beae140d 100644 --- a/shell-completion/bash/networkctl +++ b/shell-completion/bash/networkctl @@ -32,7 +32,7 @@ _networkctl() { local i verb comps local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} local -A OPTS=( - [STANDALONE]='-a --all -h --help --version --no-pager --no-legend' + [STANDALONE]='-a --all -h --help --version --no-pager --no-legend -s --stats' [ARG]='' ) 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/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c index 35ca843569..0ac3d1664b 100644 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ b/src/libsystemd/sd-netlink/netlink-types.c @@ -519,7 +519,9 @@ static const NLType rtnl_link_types[] = { [IFLA_LINK] = { .type = NETLINK_TYPE_U32 }, /* [IFLA_QDISC], - [IFLA_STATS], +*/ + [IFLA_STATS] = { .size = sizeof(struct rtnl_link_stats) }, +/* [IFLA_COST], [IFLA_PRIORITY], */ @@ -541,7 +543,9 @@ static const NLType rtnl_link_types[] = { /* [IFLA_NUM_VF], [IFLA_VFINFO_LIST] = {. type = NETLINK_TYPE_NESTED, }, - [IFLA_STATS64], +*/ + [IFLA_STATS64] = { .size = sizeof(struct rtnl_link_stats64) }, +/* [IFLA_VF_PORTS] = { .type = NETLINK_TYPE_NESTED }, [IFLA_PORT_SELF] = { .type = NETLINK_TYPE_NESTED }, */ 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/networkctl.c b/src/network/networkctl.c index b924247af7..9c23672ffd 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -8,6 +8,7 @@ #include #include +#include "sd-bus.h" #include "sd-device.h" #include "sd-hwdb.h" #include "sd-lldp.h" @@ -16,6 +17,9 @@ #include "alloc-util.h" #include "arphrd-list.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-util.h" #include "device-util.h" #include "ether-addr-util.h" #include "fd-util.h" @@ -45,6 +49,7 @@ static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; static bool arg_all = false; +static bool arg_stats = false; static char *link_get_type_string(unsigned short iftype, sd_device *d) { const char *t, *devtype; @@ -109,9 +114,20 @@ typedef struct LinkInfo { uint32_t tx_queues; uint32_t rx_queues; + union { + struct rtnl_link_stats64 stats64; + struct rtnl_link_stats stats; + }; + + double tx_bitrate; + double rx_bitrate; + bool has_mac_address:1; bool has_tx_queues:1; bool has_rx_queues:1; + bool has_stats64:1; + bool has_stats:1; + bool has_bitrates:1; } LinkInfo; static int link_info_compare(const LinkInfo *a, const LinkInfo *b) { @@ -173,13 +189,65 @@ static int decode_link(sd_netlink_message *m, LinkInfo *info, char **patterns) { sd_netlink_message_read_u32(m, IFLA_NUM_TX_QUEUES, &info->tx_queues) >= 0 && info->tx_queues > 0; + if (sd_netlink_message_read(m, IFLA_STATS64, sizeof info->stats64, &info->stats64) >= 0) + info->has_stats64 = true; + else if (sd_netlink_message_read(m, IFLA_STATS, sizeof info->stats, &info->stats) >= 0) + info->has_stats = true; + return 1; } -static int acquire_link_info(sd_netlink *rtnl, char **patterns, LinkInfo **ret) { +static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *path = NULL, *ifindex_str = NULL; + int r; + + if (asprintf(&ifindex_str, "%i", link->ifindex) < 0) + return -ENOMEM; + + r = sd_bus_path_encode("/org/freedesktop/network1/link", ifindex_str, &path); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.network1", + path, + "org.freedesktop.DBus.Properties", + "Get", + &error, + &reply, + "ss", + "org.freedesktop.network1.Link", + "BitRates"); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_SPEED_METER_INACTIVE)) + return 0; + return log_error_errno(r, "%s", bus_error_message(&error, r)); + } + + r = sd_bus_message_enter_container(reply, 'v', "(dd)"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "(dd)", &link->tx_bitrate, &link->rx_bitrate); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + link->has_bitrates = link->tx_bitrate >= 0 && link->rx_bitrate >= 0; + + return 0; +} + +static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, LinkInfo **ret) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; _cleanup_free_ LinkInfo *links = NULL; - size_t allocated = 0, c = 0; + size_t allocated = 0, c = 0, j; sd_netlink_message *i; int r; @@ -211,6 +279,10 @@ static int acquire_link_info(sd_netlink *rtnl, char **patterns, LinkInfo **ret) typesafe_qsort(links, c, link_info_compare); + if (bus) + for (j = 0; j < c; j++) + (void) acquire_link_bitrates(bus, links + j); + *ret = TAKE_PTR(links); return (int) c; @@ -227,7 +299,7 @@ static int list_links(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to connect to netlink: %m"); - c = acquire_link_info(rtnl, argc > 1 ? argv + 1 : NULL, &links); + c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links); if (c < 0) return c; @@ -242,20 +314,11 @@ static int list_links(int argc, char *argv[], void *userdata) { assert_se(cell = table_get_cell(table, 0, 0)); (void) table_set_minimum_width(table, cell, 3); (void) table_set_weight(table, cell, 0); - (void) table_set_ellipsize_percent(table, cell, 0); + (void) table_set_ellipsize_percent(table, cell, 100); (void) table_set_align_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); - (void) table_set_minimum_width(table, cell, 16); - - assert_se(cell = table_get_cell(table, 0, 2)); - (void) table_set_minimum_width(table, cell, 18); - - assert_se(cell = table_get_cell(table, 0, 3)); - (void) table_set_minimum_width(table, cell, 16); - - assert_se(cell = table_get_cell(table, 0, 4)); - (void) table_set_minimum_width(table, cell, 10); + (void) table_set_ellipsize_percent(table, cell, 100); for (i = 0; i < c; i++) { _cleanup_free_ char *setup_state = NULL, *operational_state = NULL; @@ -278,7 +341,7 @@ static int list_links(int argc, char *argv[], void *userdata) { t = link_get_type_string(links[i].iftype, d); - r = table_add_cell_full(table, NULL, TABLE_INT, &links[i].ifindex, SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_INT, &links[i].ifindex); if (r < 0) return r; @@ -473,7 +536,7 @@ static int dump_gateways( if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, i == 0 ? "Gateway:" : "", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, i == 0 ? "Gateway:" : ""); if (r < 0) return r; @@ -531,7 +594,7 @@ static int dump_addresses( if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, i == 0 ? "Address:" : "", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, i == 0 ? "Address:" : ""); if (r < 0) return r; @@ -586,6 +649,7 @@ static int dump_address_labels(sd_netlink *rtnl) { assert_se(cell = table_get_cell(table, 0, 0)); (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); assert_se(cell = table_get_cell(table, 0, 1)); (void) table_set_align_percent(table, cell, 100); @@ -620,15 +684,13 @@ static int dump_address_labels(sd_netlink *rtnl) { if (r < 0) continue; - r = table_add_cell_full(table, NULL, TABLE_UINT32, &label, SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_UINT32, &label); if (r < 0) return r; r = table_add_cell_stringf(table, &cell, "%s/%u", pretty, prefixlen); if (r < 0) return r; - - (void) table_set_align_percent(table, cell, 100); } return table_print(table, NULL); @@ -724,7 +786,7 @@ static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) { if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, c == 0 ? prefix : "", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, c == 0 ? prefix : ""); if (r < 0) return r; @@ -763,7 +825,7 @@ static int dump_ifindexes(Table *table, const char *prefix, const int *ifindexes if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, c == 0 ? prefix : "", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, c == 0 ? prefix : ""); if (r < 0) return r; @@ -787,7 +849,7 @@ static int dump_list(Table *table, const char *prefix, char **l) { if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, i == l ? prefix : "", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, i == l ? prefix : ""); if (r < 0) return r; @@ -799,6 +861,67 @@ static int dump_list(Table *table, const char *prefix, char **l) { return 0; } +#define DUMP_STATS_ONE(name, val_name) \ + r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); \ + if (r < 0) \ + return r; \ + r = table_add_cell(table, NULL, TABLE_STRING, name ":"); \ + if (r < 0) \ + return r; \ + r = table_add_cell(table, NULL, info->has_stats64 ? TABLE_UINT64 : TABLE_UINT32, \ + info->has_stats64 ? (void*) &info->stats64.val_name : (void*) &info->stats.val_name); \ + if (r < 0) \ + return r; + +static int dump_statistics(Table *table, const LinkInfo *info) { + int r; + + if (!arg_stats) + return 0; + + if (!info->has_stats64 && !info->has_stats) + return 0; + + DUMP_STATS_ONE("Rx Packets", rx_packets); + DUMP_STATS_ONE("Tx Packets", tx_packets); + DUMP_STATS_ONE("Rx Bytes", rx_bytes); + DUMP_STATS_ONE("Tx Bytes", tx_bytes); + DUMP_STATS_ONE("Rx Errors", rx_errors); + DUMP_STATS_ONE("Tx Errors", tx_errors); + DUMP_STATS_ONE("Rx Dropped", rx_dropped); + DUMP_STATS_ONE("Tx Dropped", tx_dropped); + DUMP_STATS_ONE("Multicast Packets", multicast); + DUMP_STATS_ONE("Collisions", collisions); + + return 0; +} + +static const struct { + double val; + const char *str; +} prefix_table[] = { + { .val = 1e15, .str = "P" }, + { .val = 1e12, .str = "T" }, + { .val = 1e9, .str = "G" }, + { .val = 1e6, .str = "M" }, + { .val = 1e3, .str = "k" }, +}; + +static void get_prefix(double val, double *ret_div, const char **ret_prefix) { + assert(ret_div); + assert(ret_prefix); + + for (size_t i = 0; i < ELEMENTSOF(prefix_table); i++) + if (val > prefix_table[i].val) { + *ret_div = prefix_table[i].val; + *ret_prefix = prefix_table[i].str; + return; + } + + *ret_div = 1; + *ret_prefix = NULL; +} + static int link_status_one( sd_netlink *rtnl, sd_hwdb *hwdb, @@ -860,16 +983,22 @@ static int link_status_one( if (!table) return -ENOMEM; + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_ellipsize_percent(table, cell, 100); + + assert_se(cell = table_get_cell(table, 0, 1)); + (void) table_set_ellipsize_percent(table, cell, 100); + table_set_header(table, false); r = table_add_cell(table, &cell, TABLE_STRING, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE)); if (r < 0) return r; - (void) table_set_ellipsize_percent(table, cell, 0); (void) table_set_color(table, cell, on_color_operational); - r = table_add_cell_stringf(table, NULL, "%i: %s", info->ifindex, info->name); + r = table_add_cell_stringf(table, &cell, "%i: %s", info->ifindex, info->name); if (r < 0) return r; + (void) table_set_align_percent(table, cell, 0); r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; @@ -877,9 +1006,10 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "Link File:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, &cell, TABLE_STRING, "Link File:"); if (r < 0) return r; + (void) table_set_align_percent(table, cell, 100); r = table_add_cell(table, NULL, TABLE_STRING, strna(link)); if (r < 0) return r; @@ -887,7 +1017,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "Network File:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "Network File:"); if (r < 0) return r; r = table_add_cell(table, NULL, TABLE_STRING, strna(network)); @@ -897,7 +1027,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "Type:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "Type:"); if (r < 0) return r; r = table_add_cell(table, NULL, TABLE_STRING, strna(t)); @@ -907,7 +1037,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "State:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "State:"); if (r < 0) return r; r = table_add_cell_stringf(table, NULL, "%s%s%s (%s%s%s)", @@ -920,7 +1050,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "Path:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "Path:"); if (r < 0) return r; r = table_add_cell(table, NULL, TABLE_STRING, path); @@ -931,7 +1061,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "Driver:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "Driver:"); if (r < 0) return r; r = table_add_cell(table, NULL, TABLE_STRING, driver); @@ -942,7 +1072,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "Vendor:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "Vendor:"); if (r < 0) return r; r = table_add_cell(table, NULL, TABLE_STRING, vendor); @@ -953,7 +1083,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "Model:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "Model:"); if (r < 0) return r; r = table_add_cell(table, NULL, TABLE_STRING, model); @@ -970,7 +1100,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "HW Address:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "HW Address:"); if (r < 0) return r; r = table_add_cell_stringf(table, NULL, "%s%s%s%s", @@ -991,7 +1121,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "MTU:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "MTU:"); if (r < 0) return r; r = table_add_cell_stringf(table, NULL, "%" PRIu32 "%s%s%s%s%s%s%s", @@ -1007,11 +1137,32 @@ static int link_status_one( return r; } + if (info->has_bitrates) { + const char *tx_prefix, *rx_prefix; + double tx_div, rx_div; + + get_prefix(info->tx_bitrate, &tx_div, &tx_prefix); + get_prefix(info->rx_bitrate, &rx_div, &rx_prefix); + + r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); + if (r < 0) + return r; + r = table_add_cell(table, NULL, TABLE_STRING, "Bit Rate (Tx/Rx):"); + if (r < 0) + return r; + + r = table_add_cell_stringf(table, NULL, "%.4g %sbps/%.4g %sbps", + info->tx_bitrate / tx_div, strempty(tx_prefix), + info->rx_bitrate / rx_div, strempty(rx_prefix)); + if (r < 0) + return r; + } + if (info->has_tx_queues || info->has_rx_queues) { r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "Queue Length (Tx/Rx):", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "Queue Length (Tx/Rx):"); if (r < 0) return r; r = table_add_cell_stringf(table, NULL, "%" PRIu32 "/%" PRIu32, info->tx_queues, info->rx_queues); @@ -1049,7 +1200,7 @@ static int link_status_one( r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); if (r < 0) return r; - r = table_add_cell_full(table, NULL, TABLE_STRING, "Time Zone:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "Time Zone:"); if (r < 0) return r; r = table_add_cell(table, NULL, TABLE_STRING, tz); @@ -1061,6 +1212,10 @@ static int link_status_one( if (r < 0) return r; + r = dump_statistics(table, info); + if (r < 0) + return r; + return table_print(table, NULL); } @@ -1081,15 +1236,21 @@ static int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) { if (!table) return -ENOMEM; + assert_se(cell = table_get_cell(table, 0, 0)); + (void) table_set_ellipsize_percent(table, cell, 100); + + assert_se(cell = table_get_cell(table, 0, 1)); + (void) table_set_align_percent(table, cell, 100); + (void) table_set_ellipsize_percent(table, cell, 100); + table_set_header(table, false); r = table_add_cell(table, &cell, TABLE_STRING, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE)); if (r < 0) return r; (void) table_set_color(table, cell, on_color_operational); - (void) table_set_ellipsize_percent(table, cell, 0); - r = table_add_cell_full(table, NULL, TABLE_STRING, "State:", SIZE_MAX, SIZE_MAX, 0, 100, 0); + r = table_add_cell(table, NULL, TABLE_STRING, "State:"); if (r < 0) return r; @@ -1129,6 +1290,7 @@ static int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) { } static int link_status(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; _cleanup_free_ LinkInfo *links = NULL; @@ -1136,6 +1298,10 @@ static int link_status(int argc, char *argv[], void *userdata) { (void) pager_open(arg_pager_flags); + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect system bus: %m"); + r = sd_netlink_open(&rtnl); if (r < 0) return log_error_errno(r, "Failed to connect to netlink: %m"); @@ -1145,11 +1311,11 @@ static int link_status(int argc, char *argv[], void *userdata) { log_debug_errno(r, "Failed to open hardware database: %m"); if (arg_all) - c = acquire_link_info(rtnl, NULL, &links); + c = acquire_link_info(bus, rtnl, NULL, &links); else if (argc <= 1) return system_status(rtnl, hwdb); else - c = acquire_link_info(rtnl, argv + 1, &links); + c = acquire_link_info(bus, rtnl, argv + 1, &links); if (c < 0) return c; @@ -1225,7 +1391,7 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to connect to netlink: %m"); - c = acquire_link_info(rtnl, argc > 1 ? argv + 1 : NULL, &links); + c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links); if (c < 0) return c; @@ -1415,8 +1581,9 @@ static int help(void) { " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" " --no-legend Do not show the headers and footers\n" - " -a --all Show status for all links\n\n" - "Commands:\n" + " -a --all Show status for all links\n" + " -s --stats Show detailed link statics\n" + "\nCommands:\n" " list [PATTERN...] List links\n" " status [PATTERN...] Show link status\n" " lldp [PATTERN...] Show LLDP neighbors\n" @@ -1444,6 +1611,7 @@ static int parse_argv(int argc, char *argv[]) { { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, { "all", no_argument, NULL, 'a' }, + { "stats", no_argument, NULL, 's' }, {} }; @@ -1452,7 +1620,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "has", options, NULL)) >= 0) { switch (c) { @@ -1474,6 +1642,10 @@ static int parse_argv(int argc, char *argv[]) { arg_all = true; break; + case 's': + arg_stats = true; + break; + case '?': return -EINVAL; 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=