summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYu Watanabe <watanabe.yu+github@gmail.com>2019-06-20 06:56:37 +0900
committerGitHub <noreply@github.com>2019-06-20 06:56:37 +0900
commitb19eab1f74f5105fc5737b3790071175ea1a9292 (patch)
treeaaf07ce7487b224143688e24b89bc9c88a821585
parentf9dc94408d70dd2f44915f4c6d67dc498c1c6243 (diff)
parentc967d2c7ce19a460a3d6b63ef6a0b752f5427a1a (diff)
downloadsystemd-b19eab1f74f5105fc5737b3790071175ea1a9292.tar.gz
Merge pull request #12806 from yuwata/networkctl-ethtool-12657
networkctl: show speed, duplex, auto negotiation, and port
-rw-r--r--src/basic/format-util.c60
-rw-r--r--src/basic/format-util.h13
-rw-r--r--src/basic/parse-util.c41
-rw-r--r--src/basic/parse-util.h3
-rw-r--r--src/import/import-fs.c2
-rw-r--r--src/import/pull-job.c1
-rw-r--r--src/journal/journal-file.c2
-rw-r--r--src/journal/journal-vacuum.c2
-rw-r--r--src/journal/journalctl.c1
-rw-r--r--src/network/networkctl.c122
-rw-r--r--src/network/networkd-link-bus.c17
-rw-r--r--src/shared/ethtool-util.c (renamed from src/udev/net/ethtool-util.c)255
-rw-r--r--src/shared/ethtool-util.h (renamed from src/udev/net/ethtool-util.h)11
-rw-r--r--src/shared/format-table.c23
-rw-r--r--src/shared/format-table.h1
-rw-r--r--src/shared/meson.build2
-rw-r--r--src/test/meson.build4
-rw-r--r--src/test/test-btrfs.c2
-rw-r--r--src/test/test-format-util.c38
-rw-r--r--src/test/test-procfs-util.c2
-rw-r--r--src/udev/meson.build2
-rw-r--r--src/udev/net/link-config-gperf.gperf10
-rw-r--r--src/udev/net/link-config.c4
-rw-r--r--src/udev/net/link-config.h2
24 files changed, 406 insertions, 214 deletions
diff --git a/src/basic/format-util.c b/src/basic/format-util.c
index 39ef2fceef..4231fa8d3f 100644
--- a/src/basic/format-util.c
+++ b/src/basic/format-util.c
@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1+ */
+#include <stdio.h>
+
#include "format-util.h"
#include "memory-util.h"
@@ -8,3 +10,61 @@ char *format_ifname(int ifindex, char buf[static IF_NAMESIZE + 1]) {
memzero(buf, IF_NAMESIZE + 1);
return if_indextoname(ifindex, buf);
}
+
+char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) {
+ typedef struct {
+ const char *suffix;
+ uint64_t factor;
+ } suffix_table;
+ static const suffix_table table_iec[] = {
+ { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+ { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+ { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+ { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
+ { "M", UINT64_C(1024)*UINT64_C(1024) },
+ { "K", UINT64_C(1024) },
+ }, table_non_iec[] = {
+ { "E", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) },
+ { "P", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) },
+ { "T", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) },
+ { "G", UINT64_C(1000)*UINT64_C(1000)*UINT64_C(1000) },
+ { "M", UINT64_C(1000)*UINT64_C(1000) },
+ { "K", UINT64_C(1000) },
+ };
+ const suffix_table *table;
+ size_t n, i;
+
+ assert_cc(ELEMENTSOF(table_iec) == ELEMENTSOF(table_non_iec));
+
+ if (t == (uint64_t) -1)
+ return NULL;
+
+ table = flag & FORMAT_BYTES_USE_IEC ? table_iec : table_non_iec;
+ n = ELEMENTSOF(table_iec);
+
+ for (i = 0; i < n; i++)
+ if (t >= table[i].factor) {
+ if (flag & FORMAT_BYTES_BELOW_POINT) {
+ snprintf(buf, l,
+ "%" PRIu64 ".%" PRIu64 "%s",
+ t / table[i].factor,
+ i != n - 1 ?
+ (t / table[i + 1].factor * UINT64_C(10) / table[n - 1].factor) % UINT64_C(10):
+ (t * UINT64_C(10) / table[i].factor) % UINT64_C(10),
+ table[i].suffix);
+ } else
+ snprintf(buf, l,
+ "%" PRIu64 "%s",
+ t / table[i].factor,
+ table[i].suffix);
+
+ goto finish;
+ }
+
+ snprintf(buf, l, "%" PRIu64 "%s", t, flag & FORMAT_BYTES_TRAILING_B ? "B" : "");
+
+finish:
+ buf[l-1] = 0;
+ return buf;
+
+}
diff --git a/src/basic/format-util.h b/src/basic/format-util.h
index 9925a5e991..e0d184a541 100644
--- a/src/basic/format-util.h
+++ b/src/basic/format-util.h
@@ -3,6 +3,7 @@
#include <inttypes.h>
#include <net/if.h>
+#include <stdbool.h>
#if SIZEOF_PID_T == 4
# define PID_PRI PRIi32
@@ -68,3 +69,15 @@
#endif
char *format_ifname(int ifindex, char buf[static IF_NAMESIZE + 1]);
+
+typedef enum {
+ FORMAT_BYTES_USE_IEC = 1 << 0,
+ FORMAT_BYTES_BELOW_POINT = 1 << 1,
+ FORMAT_BYTES_TRAILING_B = 1 << 2,
+} FormatBytesFlag;
+
+#define FORMAT_BYTES_MAX 8
+char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag);
+static inline char *format_bytes(char *buf, size_t l, uint64_t t) {
+ return format_bytes_full(buf, l, t, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | FORMAT_BYTES_TRAILING_B);
+}
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index 7774e794d4..115a1494a2 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -362,47 +362,6 @@ int parse_syscall_and_errno(const char *in, char **name, int *error) {
return 0;
}
-char *format_bytes(char *buf, size_t l, uint64_t t) {
- unsigned i;
-
- /* This only does IEC units so far */
-
- static const struct {
- const char *suffix;
- uint64_t factor;
- } table[] = {
- { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
- { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
- { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
- { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) },
- { "M", UINT64_C(1024)*UINT64_C(1024) },
- { "K", UINT64_C(1024) },
- };
-
- if (t == (uint64_t) -1)
- return NULL;
-
- for (i = 0; i < ELEMENTSOF(table); i++) {
-
- if (t >= table[i].factor) {
- snprintf(buf, l,
- "%" PRIu64 ".%" PRIu64 "%s",
- t / table[i].factor,
- ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10),
- table[i].suffix);
-
- goto finish;
- }
- }
-
- snprintf(buf, l, "%" PRIu64 "B", t);
-
-finish:
- buf[l-1] = 0;
- return buf;
-
-}
-
int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
char *x = NULL;
unsigned long l;
diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
index 5a05dfeac5..3a70b79276 100644
--- a/src/basic/parse-util.h
+++ b/src/basic/parse-util.h
@@ -22,9 +22,6 @@ int parse_range(const char *t, unsigned *lower, unsigned *upper);
int parse_errno(const char *t);
int parse_syscall_and_errno(const char *in, char **name, int *error);
-#define FORMAT_BYTES_MAX 8
-char *format_bytes(char *buf, size_t l, uint64_t t);
-
int safe_atou_full(const char *s, unsigned base, unsigned *ret_u);
static inline int safe_atou(const char *s, unsigned *ret_u) {
diff --git a/src/import/import-fs.c b/src/import/import-fs.c
index 04344492c8..abb4efac99 100644
--- a/src/import/import-fs.c
+++ b/src/import/import-fs.c
@@ -6,13 +6,13 @@
#include "alloc-util.h"
#include "btrfs-util.h"
#include "fd-util.h"
+#include "format-util.h"
#include "fs-util.h"
#include "hostname-util.h"
#include "import-common.h"
#include "import-util.h"
#include "machine-image.h"
#include "mkdir.h"
-#include "parse-util.h"
#include "ratelimit.h"
#include "rm-rf.h"
#include "string-util.h"
diff --git a/src/import/pull-job.c b/src/import/pull-job.c
index 9f759a77e0..881bba0eeb 100644
--- a/src/import/pull-job.c
+++ b/src/import/pull-job.c
@@ -6,6 +6,7 @@
#include "alloc-util.h"
#include "fd-util.h"
+#include "format-util.h"
#include "gcrypt-util.h"
#include "hexdecoct.h"
#include "import-util.h"
diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c
index c2dcf76deb..91ca53e5eb 100644
--- a/src/journal/journal-file.c
+++ b/src/journal/journal-file.c
@@ -17,13 +17,13 @@
#include "chattr-util.h"
#include "compress.h"
#include "fd-util.h"
+#include "format-util.h"
#include "fs-util.h"
#include "journal-authenticate.h"
#include "journal-def.h"
#include "journal-file.h"
#include "lookup3.h"
#include "memory-util.h"
-#include "parse-util.h"
#include "path-util.h"
#include "random-util.h"
#include "set.h"
diff --git a/src/journal/journal-vacuum.c b/src/journal/journal-vacuum.c
index a932314e19..87d65896c6 100644
--- a/src/journal/journal-vacuum.c
+++ b/src/journal/journal-vacuum.c
@@ -9,11 +9,11 @@
#include "alloc-util.h"
#include "dirent-util.h"
#include "fd-util.h"
+#include "format-util.h"
#include "fs-util.h"
#include "journal-def.h"
#include "journal-file.h"
#include "journal-vacuum.h"
-#include "parse-util.h"
#include "sort-util.h"
#include "string-util.h"
#include "time-util.h"
diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c
index 3d053c59f0..a3eb61e0f1 100644
--- a/src/journal/journalctl.c
+++ b/src/journal/journalctl.c
@@ -35,6 +35,7 @@
#include "device-private.h"
#include "fd-util.h"
#include "fileio.h"
+#include "format-util.h"
#include "fs-util.h"
#include "fsprg.h"
#include "glob-util.h"
diff --git a/src/network/networkctl.c b/src/network/networkctl.c
index 901e88cc98..5d91779395 100644
--- a/src/network/networkctl.c
+++ b/src/network/networkctl.c
@@ -22,6 +22,7 @@
#include "bus-util.h"
#include "device-util.h"
#include "ether-addr-util.h"
+#include "ethtool-util.h"
#include "fd-util.h"
#include "format-table.h"
#include "format-util.h"
@@ -119,8 +120,14 @@ typedef struct LinkInfo {
struct rtnl_link_stats stats;
};
- double tx_bitrate;
- double rx_bitrate;
+ uint64_t tx_bitrate;
+ uint64_t rx_bitrate;
+
+ /* ethtool info */
+ int autonegotiation;
+ size_t speed;
+ Duplex duplex;
+ NetDevPort port;
bool has_mac_address:1;
bool has_tx_queues:1;
@@ -128,6 +135,7 @@ typedef struct LinkInfo {
bool has_stats64:1;
bool has_stats:1;
bool has_bitrates:1;
+ bool has_ethtool_link_info:1;
} LinkInfo;
static int link_info_compare(const LinkInfo *a, const LinkInfo *b) {
@@ -229,11 +237,11 @@ static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) {
r, "Failed to query link bit rates: %s", bus_error_message(&error, r));
}
- r = sd_bus_message_enter_container(reply, 'v', "(dd)");
+ r = sd_bus_message_enter_container(reply, 'v', "(tt)");
if (r < 0)
return bus_log_parse_error(r);
- r = sd_bus_message_read(reply, "(dd)", &link->tx_bitrate, &link->rx_bitrate);
+ r = sd_bus_message_read(reply, "(tt)", &link->tx_bitrate, &link->rx_bitrate);
if (r < 0)
return bus_log_parse_error(r);
@@ -241,7 +249,7 @@ static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) {
if (r < 0)
return bus_log_parse_error(r);
- link->has_bitrates = link->tx_bitrate >= 0 && link->rx_bitrate >= 0;
+ link->has_bitrates = true;
return 0;
}
@@ -249,6 +257,7 @@ static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) {
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;
+ _cleanup_close_ int fd = -1;
size_t allocated = 0, c = 0, j;
sd_netlink_message *i;
int r;
@@ -275,8 +284,16 @@ static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, Lin
r = decode_link(i, links + c, patterns);
if (r < 0)
return r;
- if (r > 0)
- c++;
+ if (r == 0)
+ continue;
+
+ r = ethtool_get_link_info(&fd, links[c].name,
+ &links[c].autonegotiation, &links[c].speed,
+ &links[c].duplex, &links[c].port);
+ if (r >= 0)
+ links[c].has_ethtool_link_info = true;
+
+ c++;
}
typesafe_qsort(links, c, link_info_compare);
@@ -898,32 +915,6 @@ static int dump_statistics(Table *table, const LinkInfo *info) {
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,
@@ -1140,11 +1131,7 @@ static int link_status_one(
}
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);
+ char tx[FORMAT_BYTES_MAX], rx[FORMAT_BYTES_MAX];
r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
if (r < 0)
@@ -1153,9 +1140,9 @@ static int link_status_one(
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));
+ r = table_add_cell_stringf(table, NULL, "%sbps/%sbps",
+ format_bytes_full(tx, sizeof tx, info->tx_bitrate, 0),
+ format_bytes_full(rx, sizeof rx, info->rx_bitrate, 0));
if (r < 0)
return r;
}
@@ -1172,6 +1159,59 @@ static int link_status_one(
return r;
}
+ if (info->has_ethtool_link_info) {
+ const char *duplex = duplex_to_string(info->duplex);
+ const char *port = port_to_string(info->port);
+
+ if (IN_SET(info->autonegotiation, AUTONEG_DISABLE, AUTONEG_ENABLE)) {
+ r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+ if (r < 0)
+ return r;
+ r = table_add_cell(table, NULL, TABLE_STRING, "Auto negotiation:");
+ if (r < 0)
+ return r;
+ r = table_add_cell(table, NULL, TABLE_BOOLEAN, &info->autonegotiation);
+ if (r < 0)
+ return r;
+ }
+
+ if (info->speed > 0) {
+ r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+ if (r < 0)
+ return r;
+ r = table_add_cell(table, NULL, TABLE_STRING, "Speed:");
+ if (r < 0)
+ return r;
+ r = table_add_cell(table, NULL, TABLE_BPS, &info->speed);
+ if (r < 0)
+ return r;
+ }
+
+ if (duplex) {
+ r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+ if (r < 0)
+ return r;
+ r = table_add_cell(table, NULL, TABLE_STRING, "Duplex:");
+ if (r < 0)
+ return r;
+ r = table_add_cell(table, NULL, TABLE_STRING, duplex);
+ if (r < 0)
+ return r;
+ }
+
+ if (port) {
+ r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+ if (r < 0)
+ return r;
+ r = table_add_cell(table, NULL, TABLE_STRING, "Port:");
+ if (r < 0)
+ return r;
+ r = table_add_cell(table, NULL, TABLE_STRING, port);
+ if (r < 0)
+ return r;
+ }
+ }
+
r = dump_addresses(rtnl, table, info->ifindex);
if (r < 0)
return r;
diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c
index 2f414cb116..076845421c 100644
--- a/src/network/networkd-link-bus.c
+++ b/src/network/networkd-link-bus.c
@@ -22,7 +22,8 @@ static int property_get_bit_rates(
Link *link = userdata;
Manager *manager;
- double tx, rx, interval_sec;
+ double interval_sec;
+ uint64_t tx, rx;
assert(bus);
assert(reply);
@@ -40,19 +41,19 @@ static int property_get_bit_rates(
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;
+ interval_sec = (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;
+ tx = (uint64_t) ((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;
+ tx = (uint64_t) ((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;
+ rx = (uint64_t) ((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;
+ rx = (uint64_t) ((UINT64_MAX - (link->stats_old.rx_bytes - link->stats_new.rx_bytes)) / interval_sec);
- return sd_bus_message_append(reply, "(dd)", tx, rx);
+ return sd_bus_message_append(reply, "(tt)", tx, rx);
}
const sd_bus_vtable link_vtable[] = {
@@ -60,7 +61,7 @@ const sd_bus_vtable link_vtable[] = {
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_PROPERTY("BitRates", "(tt)", property_get_bit_rates, 0, 0),
SD_BUS_VTABLE_END
};
diff --git a/src/udev/net/ethtool-util.c b/src/shared/ethtool-util.c
index c94977e7be..b2a81e4215 100644
--- a/src/udev/net/ethtool-util.c
+++ b/src/shared/ethtool-util.c
@@ -7,7 +7,7 @@
#include "conf-parser.h"
#include "ethtool-util.h"
-#include "link-config.h"
+#include "extract-word.h"
#include "log.h"
#include "memory-util.h"
#include "missing.h"
@@ -111,18 +111,19 @@ static const char* const ethtool_link_mode_bit_table[] = {
[ETHTOOL_LINK_MODE_FEC_BASER_BIT] = "fec-baser",
};
/* Make sure the array is large enough to fit all bits */
-assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < ELEMENTSOF(((struct link_config){}).advertise));
+assert_cc((ELEMENTSOF(ethtool_link_mode_bit_table)-1) / 32 < N_ADVERTISE);
DEFINE_STRING_TABLE_LOOKUP(ethtool_link_mode_bit, enum ethtool_link_mode_bit_indices);
-int ethtool_connect(int *ret) {
+static int ethtool_connect_or_warn(int *ret, bool warn) {
int fd;
assert_return(ret, -EINVAL);
fd = socket_ioctl_fd();
if (fd < 0)
- return fd;
+ return log_full_errno(warn ? LOG_WARNING: LOG_DEBUG, fd,
+ "ethtool: could not create control socket: %m");
*ret = fd;
@@ -140,9 +141,9 @@ int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
int r;
if (*fd < 0) {
- r = ethtool_connect(fd);
+ r = ethtool_connect_or_warn(fd, true);
if (r < 0)
- return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ return r;
}
strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
@@ -159,6 +160,44 @@ int ethtool_get_driver(int *fd, const char *ifname, char **ret) {
return 0;
}
+int ethtool_get_link_info(int *fd, const char *ifname,
+ int *ret_autonegotiation, size_t *ret_speed,
+ Duplex *ret_duplex, NetDevPort *ret_port) {
+ struct ethtool_cmd ecmd = {
+ .cmd = ETHTOOL_GSET,
+ };
+ struct ifreq ifr = {
+ .ifr_data = (void*) &ecmd,
+ };
+ int r;
+
+ if (*fd < 0) {
+ r = ethtool_connect_or_warn(fd, false);
+ if (r < 0)
+ return r;
+ }
+
+ strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
+
+ r = ioctl(*fd, SIOCETHTOOL, &ifr);
+ if (r < 0)
+ return -errno;
+
+ if (ret_autonegotiation)
+ *ret_autonegotiation = ecmd.autoneg;
+
+ if (ret_speed)
+ *ret_speed = ethtool_cmd_speed(&ecmd) * 1000 * 1000;
+
+ if (ret_duplex)
+ *ret_duplex = ecmd.duplex;
+
+ if (ret_port)
+ *ret_port = ecmd.port;
+
+ return 0;
+}
+
int ethtool_set_speed(int *fd, const char *ifname, unsigned speed, Duplex duplex) {
struct ethtool_cmd ecmd = {
.cmd = ETHTOOL_GSET
@@ -173,9 +212,9 @@ int ethtool_set_speed(int *fd, const char *ifname, unsigned speed, Duplex duplex
return 0;
if (*fd < 0) {
- r = ethtool_connect(fd);
+ r = ethtool_connect_or_warn(fd, true);
if (r < 0)
- return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ return r;
}
strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
@@ -231,9 +270,9 @@ int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol) {
return 0;
if (*fd < 0) {
- r = ethtool_connect(fd);
+ r = ethtool_connect_or_warn(fd, true);
if (r < 0)
- return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ return r;
}
strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
@@ -368,16 +407,16 @@ int ethtool_set_features(int *fd, const char *ifname, int *features) {
struct ifreq ifr = {};
if (*fd < 0) {
- r = ethtool_connect(fd);
+ r = ethtool_connect_or_warn(fd, true);
if (r < 0)
- return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ return r;
}
strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
r = get_stringset(*fd, &ifr, ETH_SS_FEATURES, &strings);
if (r < 0)
- return log_warning_errno(r, "link_config: could not get ethtool features for %s", ifname);
+ return log_warning_errno(r, "ethtool: could not get ethtool features for %s", ifname);
sfeatures = alloca0(sizeof(struct ethtool_sfeatures) + DIV_ROUND_UP(strings->len, 32U) * sizeof(sfeatures->features[0]));
sfeatures->cmd = ETHTOOL_SFEATURES;
@@ -389,7 +428,7 @@ int ethtool_set_features(int *fd, const char *ifname, int *features) {
r = find_feature_index(strings, netdev_feature_table[i]);
if (r < 0) {
- log_warning_errno(r, "link_config: could not find feature: %s", netdev_feature_table[i]);
+ log_warning_errno(r, "ethtool: could not find feature: %s", netdev_feature_table[i]);
continue;
}
@@ -409,7 +448,7 @@ int ethtool_set_features(int *fd, const char *ifname, int *features) {
r = ioctl(*fd, SIOCETHTOOL, &ifr);
if (r < 0)
- return log_warning_errno(r, "link_config: could not set ethtool features for %s", ifname);
+ return log_warning_errno(r, "ethtool: could not set ethtool features for %s", ifname);
return 0;
}
@@ -453,11 +492,13 @@ static int get_glinksettings(int fd, struct ifreq *ifr, struct ethtool_link_uset
if (ecmd.req.link_mode_masks_nwords <= 0 || ecmd.req.cmd != ETHTOOL_GLINKSETTINGS)
return -EOPNOTSUPP;
- u = new0(struct ethtool_link_usettings , 1);
+ u = new(struct ethtool_link_usettings, 1);
if (!u)
return -ENOMEM;
- u->base = ecmd.req;
+ *u = (struct ethtool_link_usettings) {
+ .base = ecmd.req,
+ };
offset = 0;
memcpy(u->link_modes.supported, &ecmd.link_mode_data[offset], 4 * ecmd.req.link_mode_masks_nwords);
@@ -486,23 +527,24 @@ static int get_gset(int fd, struct ifreq *ifr, struct ethtool_link_usettings **u
if (r < 0)
return -errno;
- e = new0(struct ethtool_link_usettings, 1);
+ e = new(struct ethtool_link_usettings, 1);
if (!e)
return -ENOMEM;
- e->base.cmd = ETHTOOL_GSET;
-
- e->base.link_mode_masks_nwords = 1;
- e->base.speed = ethtool_cmd_speed(&ecmd);
- e->base.duplex = ecmd.duplex;
- e->base.port = ecmd.port;
- e->base.phy_address = ecmd.phy_address;
- e->base.autoneg = ecmd.autoneg;
- e->base.mdio_support = ecmd.mdio_support;
-
- e->link_modes.supported[0] = ecmd.supported;
- e->link_modes.advertising[0] = ecmd.advertising;
- e->link_modes.lp_advertising[0] = ecmd.lp_advertising;
+ *e = (struct ethtool_link_usettings) {
+ .base.cmd = ETHTOOL_GSET,
+ .base.link_mode_masks_nwords = 1,
+ .base.speed = ethtool_cmd_speed(&ecmd),
+ .base.duplex = ecmd.duplex,
+ .base.port = ecmd.port,
+ .base.phy_address = ecmd.phy_address,
+ .base.autoneg = ecmd.autoneg,
+ .base.mdio_support = ecmd.mdio_support,
+
+ .link_modes.supported[0] = ecmd.supported,
+ .link_modes.advertising[0] = ecmd.advertising,
+ .link_modes.lp_advertising[0] = ecmd.lp_advertising,
+ };
*u = e;
@@ -578,20 +620,27 @@ static int set_sset(int fd, struct ifreq *ifr, const struct ethtool_link_usettin
* link mode; if the link is down, the speed is 0, %SPEED_UNKNOWN or the highest
* enabled speed and @duplex is %DUPLEX_UNKNOWN or the best enabled duplex mode.
*/
-int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *link) {
+int ethtool_set_glinksettings(
+ int *fd,
+ const char *ifname,
+ int autonegotiation,
+ uint32_t advertise[static N_ADVERTISE],
+ size_t speed,
+ Duplex duplex,
+ NetDevPort port) {
_cleanup_free_ struct ethtool_link_usettings *u = NULL;
struct ifreq ifr = {};
int r;
- if (link->autonegotiation != AUTONEG_DISABLE && eqzero(link->advertise)) {
- log_info("link_config: autonegotiation is unset or enabled, the speed and duplex are not writable.");
+ if (autonegotiation != AUTONEG_DISABLE && memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
+ log_info("ethtool: autonegotiation is unset or enabled, the speed and duplex are not writable.");
return 0;
}
if (*fd < 0) {
- r = ethtool_connect(fd);
+ r = ethtool_connect_or_warn(fd, true);
if (r < 0)
- return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ return r;
}
strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
@@ -600,26 +649,26 @@ int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *l
if (r < 0) {
r = get_gset(*fd, &ifr, &u);
if (r < 0)
- return log_warning_errno(r, "link_config: Cannot get device settings for %s : %m", ifname);
+ return log_warning_errno(r, "ethtool: Cannot get device settings for %s : %m", ifname);
}
- if (link->speed)
- u->base.speed = DIV_ROUND_UP(link->speed, 1000000);
+ if (speed > 0)
+ u->base.speed = DIV_ROUND_UP(speed, 1000000);
- if (link->duplex != _DUP_INVALID)
- u->base.duplex = link->duplex;
+ if (duplex != _DUP_INVALID)
+ u->base.duplex = duplex;
- if (link->port != _NET_DEV_PORT_INVALID)
- u->base.port = link->port;
+ if (port != _NET_DEV_PORT_INVALID)
+ u->base.port = port;
- if (link->autonegotiation >= 0)
- u->base.autoneg = link->autonegotiation;
+ if (autonegotiation >= 0)
+ u->base.autoneg = autonegotiation;
- if (!eqzero(link->advertise)) {
+ if (!memeqzero(advertise, sizeof(uint32_t) * N_ADVERTISE)) {
u->base.autoneg = AUTONEG_ENABLE;
- memcpy(&u->link_modes.advertising, link->advertise, sizeof(link->advertise));
- memzero((uint8_t*) &u->link_modes.advertising + sizeof(link->advertise),
- ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(link->advertise));
+ memcpy(&u->link_modes.advertising, advertise, sizeof(uint32_t) * N_ADVERTISE);
+ memzero((uint8_t*) &u->link_modes.advertising + sizeof(uint32_t) * N_ADVERTISE,
+ ETHTOOL_LINK_MODE_MASK_MAX_KERNEL_NBYTES - sizeof(uint32_t) * N_ADVERTISE);
}
if (u->base.cmd == ETHTOOL_GLINKSETTINGS)
@@ -627,59 +676,11 @@ int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *l
else
r = set_sset(*fd, &ifr, u);
if (r < 0)
- return log_warning_errno(r, "link_config: Cannot set device settings for %s : %m", ifname);
+ return log_warning_errno(r, "ethtool: Cannot set device settings for %s : %m", ifname);
return r;
}
-int config_parse_channel(const char *unit,
- const char *filename,
- unsigned line,
- const char *section,
- unsigned section_line,
- const char *lvalue,
- int ltype,
- const char *rvalue,
- void *data,
- void *userdata) {
- link_config *config = data;
- uint32_t k;
- int r;
-
- assert(filename);
- assert(section);
- assert(lvalue);
- assert(rvalue);
- assert(data);
-
- r = safe_atou32(rvalue, &k);
- if (r < 0) {
- log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
- return 0;
- }
-
- if (k < 1) {
- log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
- return 0;
- }
-
- if (streq(lvalue, "RxChannels")) {
- config->channels.rx_count = k;
- config->channels.rx_count_set = true;
- } else if (streq(lvalue, "TxChannels")) {
- config->channels.tx_count = k;
- config->channels.tx_count_set = true;
- } else if (streq(lvalue, "OtherChannels")) {
- config->channels.other_count = k;
- config->channels.other_count_set = true;
- } else if (streq(lvalue, "CombinedChannels")) {
- config->channels.combined_count = k;
- config->channels.combined_count_set = true;
- }
-
- return 0;
-}
-
int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels) {
struct ethtool_channels ecmd = {
.cmd = ETHTOOL_GCHANNELS
@@ -692,9 +693,9 @@ int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels)
int r;
if (*fd < 0) {
- r = ethtool_connect(fd);
+ r = ethtool_connect_or_warn(fd, true);
if (r < 0)
- return log_warning_errno(r, "link_config: could not connect to ethtool: %m");
+ return r;
}
strscpy(ifr.ifr_name, IFNAMSIZ, ifname);
@@ -734,6 +735,54 @@ int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels)
return 0;
}
+int config_parse_channel(const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+ netdev_channels *channels = data;
+ uint32_t k;
+ int r;
+
+ assert(filename);
+ assert(section);
+ assert(lvalue);
+ assert(rvalue);
+ assert(data);
+
+ r = safe_atou32(rvalue, &k);
+ if (r < 0) {
+ log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse channel value, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ if (k < 1) {
+ log_syntax(unit, LOG_ERR, filename, line, -EINVAL, "Invalid %s value, ignoring: %s", lvalue, rvalue);
+ return 0;
+ }
+
+ if (streq(lvalue, "RxChannels")) {
+ channels->rx_count = k;
+ channels->rx_count_set = true;
+ } else if (streq(lvalue, "TxChannels")) {
+ channels->tx_count = k;
+ channels->tx_count_set = true;
+ } else if (streq(lvalue, "OtherChannels")) {
+ channels->other_count = k;
+ channels->other_count_set = true;
+ } else if (streq(lvalue, "CombinedChannels")) {
+ channels->combined_count = k;
+ channels->combined_count_set = true;
+ }
+
+ return 0;
+}
+
int config_parse_advertise(const char *unit,
const char *filename,
unsigned line,
@@ -744,7 +793,7 @@ int config_parse_advertise(const char *unit,
const char *rvalue,
void *data,
void *userdata) {
- link_config *config = data;
+ uint32_t *advertise = data;
const char *p;
int r;
@@ -756,7 +805,7 @@ int config_parse_advertise(const char *unit,
if (isempty(rvalue)) {
/* Empty string resets the value. */
- zero(config->advertise);
+ memzero(advertise, sizeof(uint32_t) * N_ADVERTISE);
return 0;
}
@@ -782,7 +831,7 @@ int config_parse_advertise(const char *unit,
continue;
}
- config->advertise[mode / 32] |= 1UL << (mode % 32);
+ advertise[mode / 32] |= 1UL << (mode % 32);
}
return 0;
diff --git a/src/udev/net/ethtool-util.h b/src/shared/ethtool-util.h
index 7ca703d22c..03976c6ee3 100644
--- a/src/udev/net/ethtool-util.h
+++ b/src/shared/ethtool-util.h
@@ -6,7 +6,7 @@
#include "conf-parser.h"
-struct link_config;
+#define N_ADVERTISE 2
/* we can't use DUPLEX_ prefix, as it
* clashes with <linux/ethtool.h> */
@@ -79,13 +79,16 @@ typedef struct netdev_channels {
bool combined_count_set;
} netdev_channels;
-int ethtool_connect(int *ret);
-
int ethtool_get_driver(int *fd, const char *ifname, char **ret);
+int ethtool_get_link_info(int *fd, const char *ifname,
+ int *ret_autonegotiation, size_t *ret_speed,
+ Duplex *ret_duplex, NetDevPort *ret_port);
int ethtool_set_speed(int *fd, const char *ifname, unsigned speed, Duplex duplex);
int ethtool_set_wol(int *fd, const char *ifname, WakeOnLan wol);
int ethtool_set_features(int *fd, const char *ifname, int *features);
-int ethtool_set_glinksettings(int *fd, const char *ifname, struct link_config *link);
+int ethtool_set_glinksettings(int *fd, const char *ifname,
+ int autonegotiation, uint32_t advertise[static N_ADVERTISE],
+ size_t speed, Duplex duplex, NetDevPort port);
int ethtool_set_channels(int *fd, const char *ifname, netdev_channels *channels);
const char *duplex_to_string(Duplex d) _const_;
diff --git a/src/shared/format-table.c b/src/shared/format-table.c
index d6612a76d3..54ca1972bd 100644
--- a/src/shared/format-table.c
+++ b/src/shared/format-table.c
@@ -15,6 +15,7 @@
#include "pretty-print.h"
#include "sort-util.h"
#include "string-util.h"
+#include "strxcpyx.h"
#include "terminal-util.h"
#include "time-util.h"
#include "utf8.h"
@@ -235,6 +236,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
case TABLE_SIZE:
case TABLE_INT64:
case TABLE_UINT64:
+ case TABLE_BPS:
return sizeof(uint64_t);
case TABLE_INT32:
@@ -723,6 +725,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
break;
case TABLE_SIZE:
+ case TABLE_BPS:
buffer.size = va_arg(ap, uint64_t);
data = &buffer.size;
break;
@@ -885,6 +888,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
return CMP(a->timespan, b->timespan);
case TABLE_SIZE:
+ case TABLE_BPS:
return CMP(a->size, b->size);
case TABLE_INT:
@@ -1023,6 +1027,24 @@ static const char *table_data_format(TableData *d) {
break;
}
+ case TABLE_BPS: {
+ _cleanup_free_ char *p;
+ size_t n;
+
+ p = new(char, FORMAT_BYTES_MAX+2);
+ if (!p)
+ return NULL;
+
+ if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0))
+ return "n/a";
+
+ n = strlen(p);
+ strscpy(p + n, FORMAT_BYTES_MAX + 2 - n, "bps");
+
+ d->formatted = TAKE_PTR(p);
+ break;
+ }
+
case TABLE_INT: {
_cleanup_free_ char *p;
@@ -1622,6 +1644,7 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
return json_variant_new_unsigned(ret, d->timespan);
case TABLE_SIZE:
+ case TABLE_BPS:
if (d->size == (size_t) -1)
return json_variant_new_null(ret);
diff --git a/src/shared/format-table.h b/src/shared/format-table.h
index ec2bbba292..ada59f3423 100644
--- a/src/shared/format-table.h
+++ b/src/shared/format-table.h
@@ -15,6 +15,7 @@ typedef enum TableDataType {
TABLE_TIMESTAMP,
TABLE_TIMESPAN,
TABLE_SIZE,
+ TABLE_BPS,
TABLE_INT,
TABLE_INT32,
TABLE_INT64,
diff --git a/src/shared/meson.build b/src/shared/meson.build
index aa0423ccad..59b50a754e 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -59,6 +59,8 @@ shared_sources = files('''
enable-mempool.c
env-file-label.c
env-file-label.h
+ ethtool-util.c
+ ethtool-util.h
exec-util.c
exec-util.h
exit-status.c
diff --git a/src/test/meson.build b/src/test/meson.build
index ee6cdb6d54..34dd5cbc45 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -216,6 +216,10 @@ tests += [
[],
[]],
+ [['src/test/test-format-util.c'],
+ [],
+ []],
+
[['src/test/test-ratelimit.c'],
[],
[]],
diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c
index 5e5638cd72..5bd0e3458c 100644
--- a/src/test/test-btrfs.c
+++ b/src/test/test-btrfs.c
@@ -5,8 +5,8 @@
#include "btrfs-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "format-util.h"
#include "log.h"
-#include "parse-util.h"
#include "string-util.h"
#include "util.h"
diff --git a/src/test/test-format-util.c b/src/test/test-format-util.c
new file mode 100644
index 0000000000..2986b1bd8d
--- /dev/null
+++ b/src/test/test-format-util.c
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "format-util.h"
+#include "macro.h"
+#include "string-util.h"
+
+static void test_format_bytes_one(size_t val, bool trailing_B, const char *iec_with_p, const char *iec_without_p,
+ const char *non_iec_with_p, const char *non_iec_without_p) {
+ char buf[FORMAT_BYTES_MAX];
+
+ assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), iec_with_p));
+ assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_USE_IEC | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), iec_without_p));
+ assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_BELOW_POINT | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), non_iec_with_p));
+ assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, trailing_B ? FORMAT_BYTES_TRAILING_B : 0), non_iec_without_p));
+}
+
+static void test_format_bytes(void) {
+ test_format_bytes_one(900, true, "900B", "900B", "900B", "900B");
+ test_format_bytes_one(900, false, "900", "900", "900", "900");
+ test_format_bytes_one(1023, true, "1023B", "1023B", "1.0K", "1K");
+ test_format_bytes_one(1023, false, "1023", "1023", "1.0K", "1K");
+ test_format_bytes_one(1024, true, "1.0K", "1K", "1.0K", "1K");
+ test_format_bytes_one(1024, false, "1.0K", "1K", "1.0K", "1K");
+ test_format_bytes_one(1100, true, "1.0K", "1K", "1.1K", "1K");
+ test_format_bytes_one(1500, true, "1.4K", "1K", "1.5K", "1K");
+ test_format_bytes_one((size_t) 3*1024*1024, true, "3.0M", "3M", "3.1M", "3M");
+ test_format_bytes_one((size_t) 3*1024*1024*1024, true, "3.0G", "3G", "3.2G", "3G");
+ test_format_bytes_one((size_t) 3*1024*1024*1024*1024, true, "3.0T", "3T", "3.2T", "3T");
+ test_format_bytes_one((size_t) 3*1024*1024*1024*1024*1024, true, "3.0P", "3P", "3.3P", "3P");
+ test_format_bytes_one((size_t) 3*1024*1024*1024*1024*1024*1024, true, "3.0E", "3E", "3.4E", "3E");
+ test_format_bytes_one(SIZE_MAX, true, NULL, NULL, NULL, NULL);
+}
+
+int main(void) {
+ test_format_bytes();
+
+ return 0;
+}
diff --git a/src/test/test-procfs-util.c b/src/test/test-procfs-util.c
index 1d0612985b..662688e0f0 100644
--- a/src/test/test-procfs-util.c
+++ b/src/test/test-procfs-util.c
@@ -2,8 +2,8 @@
#include <errno.h>
+#include "format-util.h"
#include "log.h"
-#include "parse-util.h"
#include "procfs-util.h"
int main(int argc, char *argv[]) {
diff --git a/src/udev/meson.build b/src/udev/meson.build
index 13068c039b..511fe428b9 100644
--- a/src/udev/meson.build
+++ b/src/udev/meson.build
@@ -40,8 +40,6 @@ libudev_core_sources = '''
udev-builtin-usb_id.c
net/link-config.c
net/link-config.h
- net/ethtool-util.c
- net/ethtool-util.h
net/naming-scheme.c
net/naming-scheme.h
'''.split()
diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf
index dff849a34a..9698211d1d 100644
--- a/src/udev/net/link-config-gperf.gperf
+++ b/src/udev/net/link-config-gperf.gperf
@@ -47,8 +47,8 @@ Link.TCP6SegmentationOffload, config_parse_tristate, 0,
Link.UDPSegmentationOffload, config_parse_warn_compat, DISABLED_LEGACY, 0
Link.GenericReceiveOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_GRO])
Link.LargeReceiveOffload, config_parse_tristate, 0, offsetof(link_config, features[NET_DEV_FEAT_LRO])
-Link.RxChannels, config_parse_channel, 0, 0
-Link.TxChannels, config_parse_channel, 0, 0
-Link.OtherChannels, config_parse_channel, 0, 0
-Link.CombinedChannels, config_parse_channel, 0, 0
-Link.Advertise, config_parse_advertise, 0, 0
+Link.RxChannels, config_parse_channel, 0, offsetof(link_config, channels)
+Link.TxChannels, config_parse_channel, 0, offsetof(link_config, channels)
+Link.OtherChannels, config_parse_channel, 0, offsetof(link_config, channels)
+Link.CombinedChannels, config_parse_channel, 0, offsetof(link_config, channels)
+Link.Advertise, config_parse_advertise, 0, offsetof(link_config, advertise)
diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c
index b983f28f2f..611add9ae0 100644
--- a/src/udev/net/link-config.c
+++ b/src/udev/net/link-config.c
@@ -354,7 +354,9 @@ int link_config_apply(link_config_ctx *ctx, link_config *config,
if (r < 0)
return r;
- r = ethtool_set_glinksettings(&ctx->ethtool_fd, old_name, config);
+ r = ethtool_set_glinksettings(&ctx->ethtool_fd, old_name,
+ config->autonegotiation, config->advertise,
+ config->speed, config->duplex, config->port);
if (r < 0) {
if (config->port != _NET_DEV_PORT_INVALID)
diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h
index efe5f2ce3a..a45a0e709a 100644
--- a/src/udev/net/link-config.h
+++ b/src/udev/net/link-config.h
@@ -52,7 +52,7 @@ struct link_config {
size_t speed;
Duplex duplex;
int autonegotiation;
- uint32_t advertise[2];
+ uint32_t advertise[N_ADVERTISE];
WakeOnLan wol;
NetDevPort port;
int features[_NET_DEV_FEAT_MAX];