]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/networkctl.c
Merge pull request #13298 from RPigott/busctl
[thirdparty/systemd.git] / src / network / networkctl.c
index 2038571c0c6c94796cc15579bb2e08b5c46b9509..654955ba087f72020db3c6df9d1e85b8a86f5afa 100644 (file)
@@ -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"
@@ -46,6 +47,9 @@
 #include "terminal-util.h"
 #include "verbs.h"
 
+/* Kernel defines MODULE_NAME_LEN as 64 - sizeof(unsigned long). So, 64 is enough. */
+#define NETDEV_KIND_MAX 64
+
 static PagerFlags arg_pager_flags = 0;
 static bool arg_legend = true;
 static bool arg_all = false;
@@ -103,8 +107,23 @@ static void setup_state_to_color(const char *state, const char **on, const char
                 *on = *off = "";
 }
 
+typedef struct VxLanInfo {
+        uint32_t vni;
+        uint32_t link;
+
+        int local_family;
+        int group_family;
+
+        union in_addr_union local;
+        union in_addr_union group;
+
+        uint16_t dest_port;
+
+} VxLanInfo;
+
 typedef struct LinkInfo {
         char name[IFNAMSIZ+1];
+        char netdev_kind[NETDEV_KIND_MAX];
         int ifindex;
         unsigned short iftype;
         struct ether_addr mac_address;
@@ -119,8 +138,26 @@ typedef struct LinkInfo {
                 struct rtnl_link_stats stats;
         };
 
-        double tx_bitrate;
-        double rx_bitrate;
+        uint64_t tx_bitrate;
+        uint64_t rx_bitrate;
+
+        /* bridge info */
+        uint32_t forward_delay;
+        uint32_t hello_time;
+        uint32_t max_age;
+        uint32_t ageing_time;
+        uint32_t stp_state;
+        uint16_t priority;
+        uint8_t mcast_igmp_version;
+
+        /* vxlan info */
+        VxLanInfo vxlan_info;
+
+        /* ethtool info */
+        int autonegotiation;
+        size_t speed;
+        Duplex duplex;
+        NetDevPort port;
 
         bool has_mac_address:1;
         bool has_tx_queues:1;
@@ -128,16 +165,78 @@ 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) {
         return CMP(a->ifindex, b->ifindex);
 }
 
+static int decode_netdev(sd_netlink_message *m, LinkInfo *info) {
+        const char *received_kind;
+        int r;
+
+        assert(m);
+        assert(info);
+
+        r = sd_netlink_message_enter_container(m, IFLA_LINKINFO);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_read_string(m, IFLA_INFO_KIND, &received_kind);
+        if (r < 0)
+                return r;
+
+        r = sd_netlink_message_enter_container(m, IFLA_INFO_DATA);
+        if (r < 0)
+                return r;
+
+        if (streq(received_kind, "bridge")) {
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_FORWARD_DELAY, &info->forward_delay);
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_HELLO_TIME, &info->hello_time);
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_MAX_AGE, &info->max_age);
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_AGEING_TIME, &info->ageing_time);
+                (void) sd_netlink_message_read_u32(m, IFLA_BR_STP_STATE, &info->stp_state);
+                (void) sd_netlink_message_read_u16(m, IFLA_BR_PRIORITY, &info->priority);
+                (void) sd_netlink_message_read_u8(m, IFLA_BR_MCAST_IGMP_VERSION, &info->mcast_igmp_version);
+
+        } else if (streq(received_kind, "vxlan")) {
+                (void) sd_netlink_message_read_u32(m, IFLA_VXLAN_ID, &info->vxlan_info.vni);
+
+                r = sd_netlink_message_read_in_addr(m, IFLA_VXLAN_GROUP, &info->vxlan_info.group.in);
+                if (r >= 0)
+                        info->vxlan_info.group_family = AF_INET;
+                else {
+                        r = sd_netlink_message_read_in6_addr(m, IFLA_VXLAN_GROUP6, &info->vxlan_info.group.in6);
+                        if (r >= 0)
+                                info->vxlan_info.group_family = AF_INET6;
+                }
+
+                r = sd_netlink_message_read_in_addr(m, IFLA_VXLAN_LOCAL, &info->vxlan_info.local.in);
+                if (r >= 0)
+                        info->vxlan_info.local_family = AF_INET;
+                else {
+                        r = sd_netlink_message_read_in6_addr(m, IFLA_VXLAN_LOCAL6, &info->vxlan_info.local.in6);
+                        if (r >= 0)
+                                info->vxlan_info.local_family = AF_INET6;
+                }
+
+                (void) sd_netlink_message_read_u32(m, IFLA_VXLAN_LINK, &info->vxlan_info.link);
+                (void) sd_netlink_message_read_u16(m, IFLA_VXLAN_PORT, &info->vxlan_info.dest_port);
+        }
+
+        strncpy(info->netdev_kind, received_kind, IFNAMSIZ);
+
+        (void) sd_netlink_message_exit_container(m);
+        (void) sd_netlink_message_exit_container(m);
+
+        return 0;
+}
+
 static int decode_link(sd_netlink_message *m, LinkInfo *info, char **patterns) {
         const char *name;
-        uint16_t type;
         int ifindex, r;
+        uint16_t type;
 
         assert(m);
         assert(info);
@@ -194,6 +293,9 @@ static int decode_link(sd_netlink_message *m, LinkInfo *info, char **patterns) {
         else if (sd_netlink_message_read(m, IFLA_STATS, sizeof info->stats, &info->stats) >= 0)
                 info->has_stats = true;
 
+        /* fill kind info */
+        (void) decode_netdev(m, info);
+
         return 1;
 }
 
@@ -222,16 +324,18 @@ static int acquire_link_bitrates(sd_bus *bus, LinkInfo *link) {
                         "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));
+                bool quiet = sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY) ||
+                             sd_bus_error_has_name(&error, BUS_ERROR_SPEED_METER_INACTIVE);
+
+                return log_full_errno(quiet ? LOG_DEBUG : LOG_WARNING,
+                                      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);
 
@@ -239,7 +343,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;
 }
@@ -247,6 +351,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;
@@ -267,14 +372,22 @@ static int acquire_link_info(sd_bus *bus, sd_netlink *rtnl, char **patterns, Lin
                 return log_error_errno(r, "Failed to enumerate links: %m");
 
         for (i = reply; i; i = sd_netlink_message_next(i)) {
-                if (!GREEDY_REALLOC(links, allocated, c+1))
+                if (!GREEDY_REALLOC0(links, allocated, c+1))
                         return -ENOMEM;
 
                 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);
@@ -305,7 +418,7 @@ static int list_links(int argc, char *argv[], void *userdata) {
 
         (void) pager_open(arg_pager_flags);
 
-        table = table_new("IDX", "LINK", "TYPE", "OPERATIONAL", "SETUP");
+        table = table_new("idx", "link", "type", "operational", "setup");
         if (!table)
                 return log_oom();
 
@@ -314,20 +427,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;
@@ -350,27 +454,16 @@ 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);
-                if (r < 0)
-                        return r;
-
                 r = table_add_many(table,
+                                   TABLE_INT, links[i].ifindex,
                                    TABLE_STRING, links[i].name,
-                                   TABLE_STRING, strna(t));
+                                   TABLE_STRING, strna(t),
+                                   TABLE_STRING, strna(operational_state),
+                                   TABLE_SET_COLOR, on_color_operational,
+                                   TABLE_STRING, strna(setup_state),
+                                   TABLE_SET_COLOR, on_color_setup);
                 if (r < 0)
                         return r;
-
-                r = table_add_cell(table, &cell, TABLE_STRING, strna(operational_state));
-                if (r < 0)
-                        return r;
-
-                (void) table_set_color(table, cell, on_color_operational);
-
-                r = table_add_cell(table, &cell, TABLE_STRING, strna(setup_state));
-                if (r < 0)
-                        return r;
-
-                (void) table_set_color(table, cell, on_color_setup);
         }
 
         r = table_print(table, NULL);
@@ -509,7 +602,7 @@ static int get_gateway_description(
                 if (!in_addr_equal(fam, &gw, gateway))
                         continue;
 
-                r = sd_netlink_message_read_ether_addr(m, NDA_LLADDR, &mac);
+                r = sd_netlink_message_read(m, NDA_LLADDR, sizeof(mac), &mac);
                 if (r < 0)
                         continue;
 
@@ -541,11 +634,9 @@ static int dump_gateways(
         for (i = 0; i < n; i++) {
                 _cleanup_free_ char *gateway = NULL, *description = NULL, *with_description = NULL;
 
-                r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
-                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_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, i == 0 ? "Gateway:" : "");
                 if (r < 0)
                         return r;
 
@@ -599,11 +690,9 @@ static int dump_addresses(
         for (i = 0; i < n; i++) {
                 _cleanup_free_ char *pretty = NULL;
 
-                r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
-                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_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, i == 0 ? "Address:" : "");
                 if (r < 0)
                         return r;
 
@@ -648,7 +737,7 @@ static int dump_address_labels(sd_netlink *rtnl) {
         if (r < 0)
                 return r;
 
-        table = table_new("Label", "Prefix/Prefixlen");
+        table = table_new("label", "prefix/prefixlen");
         if (!table)
                 return -ENOMEM;
 
@@ -658,6 +747,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);
@@ -692,15 +782,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);
+                r = table_add_cell_stringf(table, NULL, "%s/%u", pretty, prefixlen);
                 if (r < 0)
                         return r;
-
-                (void) table_set_align_percent(table, cell, 100);
         }
 
         return table_print(table, NULL);
@@ -784,7 +872,6 @@ static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) {
         for (;;) {
                 const char *system_name = NULL, *port_id = NULL, *port_description = NULL;
                 _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
-                _cleanup_free_ char *str = NULL;
 
                 r = next_lldp_neighbor(f, &n);
                 if (r < 0)
@@ -792,11 +879,9 @@ static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) {
                 if (r == 0)
                         break;
 
-                r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
-                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_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, c == 0 ? prefix : "");
                 if (r < 0)
                         return r;
 
@@ -804,14 +889,12 @@ static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) {
                 (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id);
                 (void) sd_lldp_neighbor_get_port_description(n, &port_description);
 
-                if (asprintf(&str, "%s on port %s%s%s%s",
-                             strna(system_name), strna(port_id),
-                             isempty(port_description) ? "" : " (",
-                             port_description,
-                             isempty(port_description) ? "" : ")") < 0)
-                        return -ENOMEM;
-
-                r = table_add_cell(table, NULL, TABLE_STRING, str);
+                r = table_add_cell_stringf(table, NULL,
+                                           "%s on port %s%s%s%s",
+                                           strna(system_name), strna(port_id),
+                                           isempty(port_description) ? "" : " (",
+                                           port_description,
+                                           isempty(port_description) ? "" : ")");
                 if (r < 0)
                         return r;
 
@@ -831,15 +914,10 @@ static int dump_ifindexes(Table *table, const char *prefix, const int *ifindexes
                 return 0;
 
         for (c = 0; ifindexes[c] > 0; c++) {
-                r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
-                if (r < 0)
-                        return r;
-
-                r = table_add_cell_full(table, NULL, TABLE_STRING, c == 0 ? prefix : "", SIZE_MAX, SIZE_MAX, 0, 100, 0);
-                if (r < 0)
-                        return r;
-
-                r = table_add_cell(table, NULL, TABLE_IFINDEX, ifindexes + c);
+                r = table_add_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, c == 0 ? prefix : "",
+                                   TABLE_IFINDEX, ifindexes[c]);
                 if (r < 0)
                         return r;
         }
@@ -855,15 +933,10 @@ static int dump_list(Table *table, const char *prefix, char **l) {
                 return 0;
 
         STRV_FOREACH(i, l) {
-                r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
-                if (r < 0)
-                        return r;
-
-                r = table_add_cell_full(table, NULL, TABLE_STRING, i == l ? prefix : "", SIZE_MAX, SIZE_MAX, 0, 100, 0);
-                if (r < 0)
-                        return r;
-
-                r = table_add_cell(table, NULL, TABLE_STRING, *i);
+                r = table_add_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, i == l ? prefix : "",
+                                   TABLE_STRING, *i);
                 if (r < 0)
                         return r;
         }
@@ -872,14 +945,13 @@ static int dump_list(Table *table, const char *prefix, char **l) {
 }
 
 #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_full(table, NULL, TABLE_STRING, name ":",    \
-                                SIZE_MAX, SIZE_MAX, 0, 100, 0);         \
+        r = table_add_many(table,                                       \
+                           TABLE_EMPTY,                                 \
+                           TABLE_STRING, name ":");                     \
         if (r < 0)                                                      \
                 return r;                                               \
-        r = table_add_cell(table, NULL, info->has_stats64 ? TABLE_UINT64 : TABLE_UINT32, \
+        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;
@@ -907,32 +979,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,
@@ -990,107 +1036,79 @@ static int link_status_one(
         (void) sd_network_link_get_carrier_bound_to(info->ifindex, &carrier_bound_to);
         (void) sd_network_link_get_carrier_bound_by(info->ifindex, &carrier_bound_by);
 
-        table = table_new("DOT", "KEY", "VALUE");
+        table = table_new("dot", "key", "value");
         if (!table)
                 return -ENOMEM;
 
-        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);
-        if (r < 0)
-                return r;
-        r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
-        if (r < 0)
-                return r;
+        assert_se(cell = table_get_cell(table, 0, 0));
+        (void) table_set_ellipsize_percent(table, cell, 100);
 
-        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);
-        if (r < 0)
-                return r;
-        r = table_add_cell(table, NULL, TABLE_STRING, strna(link));
-        if (r < 0)
-                return r;
+        assert_se(cell = table_get_cell(table, 0, 1));
+        (void) table_set_ellipsize_percent(table, cell, 100);
 
-        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);
-        if (r < 0)
-                return r;
-        r = table_add_cell(table, NULL, TABLE_STRING, strna(network));
-        if (r < 0)
-                return r;
+        table_set_header(table, false);
 
-        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);
-        if (r < 0)
-                return r;
-        r = table_add_cell(table, NULL, TABLE_STRING, strna(t));
+        r = table_add_many(table,
+                           TABLE_STRING, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE),
+                           TABLE_SET_COLOR, on_color_operational);
         if (r < 0)
                 return r;
-
-        r = table_add_cell(table, NULL, TABLE_EMPTY, NULL);
+        r = table_add_cell_stringf(table, &cell, "%i: %s", info->ifindex, info->name);
         if (r < 0)
                 return r;
-        r = table_add_cell_full(table, NULL, TABLE_STRING, "State:", SIZE_MAX, SIZE_MAX, 0, 100, 0);
+        (void) table_set_align_percent(table, cell, 0);
+
+        r = table_add_many(table,
+                           TABLE_EMPTY,
+                           TABLE_EMPTY,
+                           TABLE_STRING, "Link File:",
+                           TABLE_SET_ALIGN_PERCENT, 100,
+                           TABLE_STRING, strna(link),
+                           TABLE_EMPTY,
+                           TABLE_STRING, "Network File:",
+                           TABLE_STRING, strna(network),
+                           TABLE_EMPTY,
+                           TABLE_STRING, "Type:",
+                           TABLE_STRING, strna(t),
+                           TABLE_EMPTY,
+                           TABLE_STRING, "State:");
         if (r < 0)
                 return r;
         r = table_add_cell_stringf(table, NULL, "%s%s%s (%s%s%s)",
-                                  on_color_operational, strna(operational_state), off_color_operational,
-                                  on_color_setup, strna(setup_state), off_color_setup);
+                                   on_color_operational, strna(operational_state), off_color_operational,
+                                   on_color_setup, strna(setup_state), off_color_setup);
         if (r < 0)
                 return r;
 
         if (path) {
-                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);
-                if (r < 0)
-                        return r;
-                r = table_add_cell(table, NULL, TABLE_STRING, path);
+                r = table_add_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Path:",
+                                   TABLE_STRING, path);
                 if (r < 0)
                         return r;
         }
         if (driver) {
-                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);
-                if (r < 0)
-                        return r;
-                r = table_add_cell(table, NULL, TABLE_STRING, driver);
+                r = table_add_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Driver:",
+                                   TABLE_STRING, driver);
                 if (r < 0)
                         return r;
         }
         if (vendor) {
-                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);
-                if (r < 0)
-                        return r;
-                r = table_add_cell(table, NULL, TABLE_STRING, vendor);
+                r = table_add_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Vendor:",
+                                   TABLE_STRING, vendor);
                 if (r < 0)
                         return r;
         }
         if (model) {
-                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);
-                if (r < 0)
-                        return r;
-                r = table_add_cell(table, NULL, TABLE_STRING, model);
+                r = table_add_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Model:",
+                                   TABLE_STRING, model);
                 if (r < 0)
                         return r;
         }
@@ -1101,17 +1119,16 @@ static int link_status_one(
 
                 (void) ieee_oui(hwdb, &info->mac_address, &description);
 
-                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_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "HW Address:");
                 if (r < 0)
                         return r;
                 r = table_add_cell_stringf(table, NULL, "%s%s%s%s",
-                                          ether_addr_to_string(&info->mac_address, ea),
-                                          description ? " (" : "",
-                                          description,
-                                          description ? ")" : "");
+                                           ether_addr_to_string(&info->mac_address, ea),
+                                           description ? " (" : "",
+                                           strempty(description),
+                                           description ? ")" : "");
                 if (r < 0)
                         return r;
         }
@@ -1122,10 +1139,9 @@ static int link_status_one(
                 xsprintf(min_str, "%" PRIu32, info->min_mtu);
                 xsprintf(max_str, "%" PRIu32, info->max_mtu);
 
-                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_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "MTU:");
                 if (r < 0)
                         return r;
                 r = table_add_cell_stringf(table, NULL, "%" PRIu32 "%s%s%s%s%s%s%s",
@@ -1141,32 +1157,100 @@ 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 (streq_ptr(info->netdev_kind, "bridge")) {
+                r = table_add_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Forward Delay:",
+                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->forward_delay),
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Hello Time:",
+                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->hello_time),
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Max Age:",
+                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->max_age),
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Ageing Time:",
+                                   TABLE_TIMESPAN_MSEC, jiffies_to_usec(info->ageing_time),
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Priority:",
+                                   TABLE_UINT16, info->priority,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "STP:",
+                                   TABLE_BOOLEAN, info->stp_state > 0,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Multicast IGMP Version:",
+                                   TABLE_UINT8, info->mcast_igmp_version);
                 if (r < 0)
                         return r;
-                r = table_add_cell_full(table, NULL, TABLE_STRING, "Bit Rate (Tx/Rx):", SIZE_MAX, SIZE_MAX, 0, 100, 0);
+
+        } else if (streq_ptr(info->netdev_kind, "vxlan")) {
+                if (info->vxlan_info.vni > 0) {
+                        r = table_add_many(table,
+                                           TABLE_EMPTY,
+                                           TABLE_STRING, "VNI:",
+                                           TABLE_UINT32, info->vxlan_info.vni);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (IN_SET(info->vxlan_info.group_family, AF_INET, AF_INET6)) {
+                        r = table_add_many(table,
+                                           TABLE_EMPTY,
+                                           TABLE_STRING, "Group:",
+                                           info->vxlan_info.group_family == AF_INET ? TABLE_IN_ADDR : TABLE_IN6_ADDR,
+                                           &info->vxlan_info.group);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (IN_SET(info->vxlan_info.local_family, AF_INET, AF_INET6)) {
+                        r = table_add_many(table,
+                                           TABLE_EMPTY,
+                                           TABLE_STRING, "Local:",
+                                           info->vxlan_info.local_family == AF_INET ? TABLE_IN_ADDR : TABLE_IN6_ADDR,
+                                           &info->vxlan_info.local);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (info->vxlan_info.dest_port > 0) {
+                        r = table_add_many(table,
+                                           TABLE_EMPTY,
+                                           TABLE_STRING, "Destination Port:",
+                                           TABLE_UINT16, be16toh(info->vxlan_info.dest_port));
+                        if (r < 0)
+                                return r;
+                }
+
+                if (info->vxlan_info.link > 0) {
+                        r = table_add_many(table,
+                                           TABLE_EMPTY,
+                                           TABLE_STRING, "Underlying Device:",
+                                           TABLE_IFINDEX, info->vxlan_info.link);
+                        if (r < 0)
+                                 return r;
+                }
+        }
+
+        if (info->has_bitrates) {
+                char tx[FORMAT_BYTES_MAX], rx[FORMAT_BYTES_MAX];
+
+                r = table_add_many(table,
+                                   TABLE_EMPTY,
+                                   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));
+                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;
         }
 
         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_many(table,
+                                   TABLE_EMPTY,
+                                   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);
@@ -1174,6 +1258,47 @@ 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_many(table,
+                                           TABLE_EMPTY,
+                                           TABLE_STRING, "Auto negotiation:",
+                                           TABLE_BOOLEAN, info->autonegotiation == AUTONEG_ENABLE);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (info->speed > 0) {
+                        r = table_add_many(table,
+                                           TABLE_EMPTY,
+                                           TABLE_STRING, "Speed:",
+                                           TABLE_BPS, (uint64_t) info->speed);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (duplex) {
+                        r = table_add_many(table,
+                                           TABLE_EMPTY,
+                                           TABLE_STRING, "Duplex:",
+                                           TABLE_STRING, duplex);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (port) {
+                        r = table_add_many(table,
+                                           TABLE_EMPTY,
+                                           TABLE_STRING, "Port:",
+                                           TABLE_STRING, port);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
         r = dump_addresses(rtnl, table, info->ifindex);
         if (r < 0)
                 return r;
@@ -1201,13 +1326,10 @@ static int link_status_one(
 
         (void) sd_network_link_get_timezone(info->ifindex, &tz);
         if (tz) {
-                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);
-                if (r < 0)
-                        return r;
-                r = table_add_cell(table, NULL, TABLE_STRING, tz);
+                r = table_add_many(table,
+                                   TABLE_EMPTY,
+                                   TABLE_STRING, "Time Zone:",
+                                   TABLE_STRING, tz);
                 if (r < 0)
                         return r;
         }
@@ -1236,26 +1358,25 @@ static int system_status(sd_netlink *rtnl, sd_hwdb *hwdb) {
         (void) sd_network_get_operational_state(&operational_state);
         operational_state_to_color(operational_state, &on_color_operational, &off_color_operational);
 
-        table = table_new("DOT", "KEY", "VALUE");
+        table = table_new("dot", "key", "value");
         if (!table)
                 return -ENOMEM;
 
-        table_set_header(table, false);
+        assert_se(cell = table_get_cell(table, 0, 0));
+        (void) table_set_ellipsize_percent(table, cell, 100);
 
-        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);
+        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);
 
-        r = table_add_cell_full(table, NULL, TABLE_STRING, "State:", SIZE_MAX, SIZE_MAX, 0, 100, 0);
-        if (r < 0)
-                return r;
+        table_set_header(table, false);
 
-        r = table_add_cell(table, &cell, TABLE_STRING, strna(operational_state));
-        if (r < 0)
-                return r;
-        (void) table_set_color(table, cell, on_color_operational);
+        r = table_add_many(table,
+                           TABLE_STRING, special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE),
+                           TABLE_SET_COLOR, on_color_operational,
+                           TABLE_STRING, "State:",
+                           TABLE_STRING, strna(operational_state),
+                           TABLE_SET_COLOR, on_color_operational);
 
         r = dump_addresses(rtnl, table, 0);
         if (r < 0)
@@ -1395,12 +1516,12 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) {
 
         (void) pager_open(arg_pager_flags);
 
-        table = table_new("LINK",
-                          "CHASSIS ID",
-                          "SYSTEM NAME",
-                          "CAPS",
-                          "PORT ID",
-                          "PORT DESCRIPTION");
+        table = table_new("link",
+                          "chassis id",
+                          "system name",
+                          "caps",
+                          "port id",
+                          "port description");
         if (!table)
                 return -ENOMEM;
 
@@ -1531,6 +1652,7 @@ static int link_delete(int argc, char *argv[], void *userdata) {
         _cleanup_set_free_ Set *indexes = NULL;
         int index, r, i;
         Iterator j;
+        void *p;
 
         r = sd_netlink_open(&rtnl);
         if (r < 0)
@@ -1550,8 +1672,8 @@ static int link_delete(int argc, char *argv[], void *userdata) {
                         return log_oom();
         }
 
-        SET_FOREACH(index, indexes, j) {
-                r = link_delete_send_message(rtnl, index);
+        SET_FOREACH(p, indexes, j) {
+                r = link_delete_send_message(rtnl, PTR_TO_INT(p));
                 if (r < 0) {
                         char ifname[IF_NAMESIZE + 1];