• Bug#1106602: unblock: iproute2/6.15.0-1 (2/2)

    From Sebastian Ramacher@21:1/5 to Luca Boccassi on Tue May 27 16:00:01 2025
    [continued from previous message]

    --- iproute2-6.14.0/ip/ipmonitor.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/ip/ipmonitor.c 2025-05-26 16:19:09.000000000 +0100
    @@ -5,6 +5,7 @@
    * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
    */

    +#include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    @@ -328,38 +329,46 @@

    if (lmask & IPMON_LNEXTHOP &&
    rtnl_add_nl_group(&rth, RTNLGRP_NEXTHOP) < 0) {
    - fprintf(stderr, "Failed to add nexthop group to list\n");
    - exit(1);
    + if (errno != EINVAL) {
    + fprintf(stderr, "Failed to add nexthop group to list\n");
    + exit(1);
    + }
    }

    if (lmask & IPMON_LSTATS &&
    rtnl_add_nl_group(&rth, RTNLGRP_STATS) < 0 &&
    nmask & IPMON_LSTATS) {
    - fprintf(stderr, "Failed to add stats group to list\n");
    - exit(1);
    + if (errno != EINVAL) {
    + fprintf(stderr, "Failed to add stats group to list\n"); + exit(1);
    + }
    }

    if (lmask & IPMON_LMADDR) {
    if ((!preferred_family || preferred_family == AF_INET) &&
    rtnl_add_nl_group(&rth, RTNLGRP_IPV4_MCADDR) < 0) {
    - fprintf(stderr,
    - "Failed to add ipv4 mcaddr group to list\n");
    - exit(1);
    + if (errno != EINVAL) {
    + fprintf(stderr, "Failed to add ipv4 mcaddr group to list\n");
    + exit(1);
    + }
    }
    if ((!preferred_family || preferred_family == AF_INET6) &&
    rtnl_add_nl_group(&rth, RTNLGRP_IPV6_MCADDR) < 0) {
    - fprintf(stderr,
    - "Failed to add ipv6 mcaddr group to list\n");
    - exit(1);
    + if (errno != EINVAL) {
    + fprintf(stderr,
    + "Failed to add ipv6 mcaddr group to list\n");
    + exit(1);
    + }
    }
    }

    if (lmask & IPMON_LACADDR) {
    if ((!preferred_family || preferred_family == AF_INET6) &&
    rtnl_add_nl_group(&rth, RTNLGRP_IPV6_ACADDR) < 0) {
    - fprintf(stderr,
    - "Failed to add ipv6 acaddr group to list\n");
    - exit(1);
    + if (errno != EINVAL) {
    + fprintf(stderr, "Failed to add ipv6 acaddr group to list\n");
    + exit(1);
    + }
    }
    }

    diff -Nru iproute2-6.14.0/ip/iproute.c iproute2-6.15.0/ip/iproute.c
    --- iproute2-6.14.0/ip/iproute.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/ip/iproute.c 2025-05-26 16:19:09.000000000 +0100
    @@ -1729,7 +1729,10 @@

    if (filter.cloned) {
    if (family != AF_INET6) {
    - iproute_flush_cache();
    + ret = iproute_flush_cache();
    + if (ret < 0)
    + return ret;
    +
    if (show_stats)
    printf("*** IPv4 routing cache is flushed.\n");
    }
    diff -Nru iproute2-6.14.0/ip/iprule.c iproute2-6.15.0/ip/iprule.c
    --- iproute2-6.14.0/ip/iprule.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/ip/iprule.c 2025-05-26 16:19:09.000000000 +0100
    @@ -23,6 +23,9 @@
    #include "ip_common.h"
    #include "json_print.h"

    +#define PORT_MAX_MASK 0xFFFF
    +#define DSCP_MAX_MASK 0x3F
    +
    enum list_action {
    IPRULE_LIST,
    IPRULE_FLUSH,
    @@ -44,9 +47,9 @@
    " [ iif STRING ] [ oif STRING ] [ pref NUMBER ] [ l3mdev ]\n"
    " [ uidrange NUMBER-NUMBER ]\n"
    " [ ipproto PROTOCOL ]\n"
    - " [ sport [ NUMBER | NUMBER-NUMBER ]\n"
    - " [ dport [ NUMBER | NUMBER-NUMBER ] ]\n"
    - " [ dscp DSCP ] [ flowlabel FLOWLABEL[/MASK] ]\n"
    + " [ sport [ NUMBER[/MASK] | NUMBER-NUMBER ]\n"
    + " [ dport [ NUMBER[/MASK] | NUMBER-NUMBER ] ]\n"
    + " [ dscp DSCP[/MASK] ] [ flowlabel FLOWLABEL[/MASK] ]\n"
    "ACTION := [ table TABLE_ID ]\n"
    " [ protocol PROTO ]\n"
    " [ nat ADDRESS ]\n"
    @@ -80,6 +83,7 @@
    int protocolmask;
    struct fib_rule_port_range sport;
    struct fib_rule_port_range dport;
    + __u16 sport_mask, dport_mask;
    __u8 ipproto;
    } filter;

    @@ -186,8 +190,9 @@
    return false;
    }

    - if (filter.sport.start) {
    + if (filter.sport_mask) {
    const struct fib_rule_port_range *r;
    + __u16 sport_mask = PORT_MAX_MASK;

    if (!tb[FRA_SPORT_RANGE])
    return false;
    @@ -196,10 +201,16 @@
    if (r->start != filter.sport.start ||
    r->end != filter.sport.end)
    return false;
    +
    + if (tb[FRA_SPORT_MASK])
    + sport_mask = rta_getattr_u16(tb[FRA_SPORT_MASK]);
    + if (filter.sport_mask != sport_mask)
    + return false;
    }

    - if (filter.dport.start) {
    + if (filter.dport_mask) {
    const struct fib_rule_port_range *r;
    + __u16 dport_mask = PORT_MAX_MASK;

    if (!tb[FRA_DPORT_RANGE])
    return false;
    @@ -208,6 +219,11 @@
    if (r->start != filter.dport.start ||
    r->end != filter.dport.end)
    return false;
    +
    + if (tb[FRA_DPORT_MASK])
    + dport_mask = rta_getattr_u16(tb[FRA_DPORT_MASK]);
    + if (filter.dport_mask != dport_mask)
    + return false;
    }

    if (filter.tun_id) {
    @@ -223,14 +239,21 @@
    }

    if (filter.dscpmask) {
    - if (tb[FRA_DSCP]) {
    - __u8 dscp = rta_getattr_u8(tb[FRA_DSCP]);
    + __u8 dscp_mask = DSCP_MAX_MASK;
    + __u8 dscp;

    - if (filter.dscp != dscp)
    - return false;
    - } else {
    + if (!tb[FRA_DSCP])
    + return false;
    +
    + dscp = rta_getattr_u8(tb[FRA_DSCP]);
    + if (filter.dscp != dscp)
    + return false;
    +
    + if (tb[FRA_DSCP_MASK])
    + dscp_mask = rta_getattr_u8(tb[FRA_DSCP_MASK]);
    +
    + if (filter.dscpmask != dscp_mask)
    return false;
    - }
    }

    if (filter.flowlabel_mask) {
    @@ -390,7 +413,26 @@
    struct fib_rule_port_range *r = RTA_DATA(tb[FRA_SPORT_RANGE]);

    if (r->start == r->end) {
    - print_uint(PRINT_ANY, "sport", " sport %u", r->start); + if (tb[FRA_SPORT_MASK]) {
    + __u16 mask;
    +
    + mask = rta_getattr_u16(tb[FRA_SPORT_MASK]);
    + print_uint(PRINT_JSON, "sport", NULL, r->start);
    + print_0xhex(PRINT_JSON, "sport_mask", NULL,
    + mask);
    + if (mask == PORT_MAX_MASK) {
    + print_uint(PRINT_FP, NULL, " sport %u", + r->start);
    + } else {
    + print_0xhex(PRINT_FP, NULL,
    + " sport %#x", r->start);
    + print_0xhex(PRINT_FP, NULL, "/%#x",
    + mask);
    + }
    + } else {
    + print_uint(PRINT_ANY, "sport", " sport %u",
    + r->start);
    + }
    } else {
    print_uint(PRINT_ANY, "sport_start", " sport %u",
    r->start);
    @@ -402,7 +444,26 @@
    struct fib_rule_port_range *r = RTA_DATA(tb[FRA_DPORT_RANGE]);

    if (r->start == r->end) {
    - print_uint(PRINT_ANY, "dport", " dport %u", r->start); + if (tb[FRA_DPORT_MASK]) {
    + __u16 mask;
    +
    + mask = rta_getattr_u16(tb[FRA_DPORT_MASK]);
    + print_uint(PRINT_JSON, "dport", NULL, r->start);
    + print_0xhex(PRINT_JSON, "dport_mask", NULL,
    + mask);
    + if (mask == 0xFFFF) {
    + print_uint(PRINT_FP, NULL, " dport %u", + r->start);
    + } else {
    + print_0xhex(PRINT_FP, NULL,
    + " dport %#x", r->start);
    + print_0xhex(PRINT_FP, NULL, "/%#x",
    + mask);
    + }
    + } else {
    + print_uint(PRINT_ANY, "dport", " dport %u",
    + r->start);
    + }
    } else {
    print_uint(PRINT_ANY, "dport_start", " dport %u",
    r->start);
    @@ -499,8 +560,24 @@
    if (tb[FRA_DSCP]) {
    __u8 dscp = rta_getattr_u8(tb[FRA_DSCP]);

    - print_string(PRINT_ANY, "dscp", " dscp %s",
    - rtnl_dscp_n2a(dscp, b1, sizeof(b1)));
    + if (tb[FRA_DSCP_MASK]) {
    + __u8 mask = rta_getattr_u8(tb[FRA_DSCP_MASK]);
    +
    + print_string(PRINT_JSON, "dscp", NULL,
    + rtnl_dscp_n2a(dscp, b1, sizeof(b1)));
    + print_0xhex(PRINT_JSON, "dscp_mask", NULL, mask);
    + if (mask == DSCP_MAX_MASK) {
    + print_string(PRINT_FP, NULL, " dscp %s",
    + rtnl_dscp_n2a(dscp, b1,
    + sizeof(b1)));
    + } else {
    + print_0xhex(PRINT_FP, NULL, " dscp %#x", dscp); + print_0xhex(PRINT_FP, NULL, "/%#x", mask);
    + }
    + } else {
    + print_string(PRINT_ANY, "dscp", " dscp %s",
    + rtnl_dscp_n2a(dscp, b1, sizeof(b1)));
    + }
    }

    /* The kernel will either provide both attributes, or none */
    @@ -600,6 +677,55 @@
    return 0;
    }

    +static void iprule_port_parse(char *arg, struct fib_rule_port_range *r,
    + __u16 *mask)
    +{
    + char *sep;
    +
    + *mask = PORT_MAX_MASK;
    +
    + sep = strchr(arg, '-');
    + if (sep) {
    + *sep = '\0';
    +
    + if (get_u16(&r->start, arg, 0))
    + invarg("invalid port range start", arg);
    +
    + if (get_u16(&r->end, sep + 1, 0))
    + invarg("invalid port range end", sep + 1);
    +
    + return;
    + }
    +
    + sep = strchr(arg, '/');
    + if (sep) {
    + *sep = '\0';
    +
    + if (get_u16(mask, sep + 1, 0))
    + invarg("invalid mask", sep + 1);
    + }
    +
    + if (get_u16(&r->start, arg, 0))
    + invarg("invalid port", arg);
    +
    + r->end = r->start;
    +}
    +
    +static void iprule_dscp_parse(char *arg, __u32 *dscp, __u32 *mask)
    +{
    + char *slash;
    +
    + *mask = DSCP_MAX_MASK;
    +
    + slash = strchr(arg, '/');
    + if (slash != NULL)
    + *slash = '\0';
    + if (rtnl_dscp_a2n(dscp, arg))
    + invarg("invalid dscp", arg);
    + if (slash && get_u32(mask, slash + 1, 0))
    + invarg("invalid dscp mask", slash + 1);
    +}
    +
    static void iprule_flowlabel_parse(char *arg, __u32 *flowlabel,
    __u32 *flowlabel_mask)
    {
    @@ -746,35 +872,17 @@
    invarg("Invalid \"ipproto\" value\n", *argv);
    filter.ipproto = ipproto;
    } else if (strcmp(*argv, "sport") == 0) {
    - struct fib_rule_port_range r;
    - int ret;
    -
    NEXT_ARG();
    - ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
    - if (ret == 1)
    - r.end = r.start;
    - else if (ret != 2)
    - invarg("invalid port range\n", *argv);
    - filter.sport = r;
    + iprule_port_parse(*argv, &filter.sport,
    + &filter.sport_mask);
    } else if (strcmp(*argv, "dport") == 0) {
    - struct fib_rule_port_range r;
    - int ret;
    -
    NEXT_ARG();
    - ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
    - if (ret == 1)
    - r.end = r.start;
    - else if (ret != 2)
    - invarg("invalid dport range\n", *argv);
    - filter.dport = r;
    + iprule_port_parse(*argv, &filter.dport,
    + &filter.dport_mask);
    } else if (strcmp(*argv, "dscp") == 0) {
    - __u32 dscp;
    -
    NEXT_ARG();
    - if (rtnl_dscp_a2n(&dscp, *argv))
    - invarg("invalid dscp\n", *argv);
    - filter.dscp = dscp;
    - filter.dscpmask = 1;
    + iprule_dscp_parse(*argv, &filter.dscp,
    + &filter.dscpmask);
    } else if (strcmp(*argv, "flowlabel") == 0) {
    NEXT_ARG();

    @@ -1036,35 +1144,35 @@
    addattr8(&req.n, sizeof(req), FRA_IP_PROTO, ipproto);
    } else if (strcmp(*argv, "sport") == 0) {
    struct fib_rule_port_range r;
    - int ret = 0;
    + __u16 sport_mask;

    NEXT_ARG();
    - ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
    - if (ret == 1)
    - r.end = r.start;
    - else if (ret != 2)
    - invarg("invalid port range\n", *argv);
    + iprule_port_parse(*argv, &r, &sport_mask);
    addattr_l(&req.n, sizeof(req), FRA_SPORT_RANGE, &r,
    sizeof(r));
    + if (sport_mask != PORT_MAX_MASK)
    + addattr16(&req.n, sizeof(req), FRA_SPORT_MASK, + sport_mask);
    } else if (strcmp(*argv, "dport") == 0) {
    struct fib_rule_port_range r;
    - int ret = 0;
    + __u16 dport_mask;

    NEXT_ARG();
    - ret = sscanf(*argv, "%hu-%hu", &r.start, &r.end);
    - if (ret == 1)
    - r.end = r.start;
    - else if (ret != 2)
    - invarg("invalid dport range\n", *argv);
    + iprule_port_parse(*argv, &r, &dport_mask);
    addattr_l(&req.n, sizeof(req), FRA_DPORT_RANGE, &r,
    sizeof(r));
    + if (dport_mask != PORT_MAX_MASK)
    + addattr16(&req.n, sizeof(req), FRA_DPORT_MASK, + dport_mask);
    } else if (strcmp(*argv, "dscp") == 0) {
    - __u32 dscp;
    + __u32 dscp, dscp_mask;

    NEXT_ARG();
    - if (rtnl_dscp_a2n(&dscp, *argv))
    - invarg("invalid dscp\n", *argv);
    + iprule_dscp_parse(*argv, &dscp, &dscp_mask);
    addattr8(&req.n, sizeof(req), FRA_DSCP, dscp);
    + if (dscp_mask != DSCP_MAX_MASK)
    + addattr8(&req.n, sizeof(req), FRA_DSCP_MASK,
    + dscp_mask);
    } else if (strcmp(*argv, "flowlabel") == 0) {
    __u32 flowlabel, flowlabel_mask;

    diff -Nru iproute2-6.14.0/ip/ipxfrm.c iproute2-6.15.0/ip/ipxfrm.c
    --- iproute2-6.14.0/ip/ipxfrm.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/ip/ipxfrm.c 2025-05-26 16:19:09.000000000 +0100
    @@ -351,7 +351,10 @@
    t = (long)time;
    tp = localtime(&t);

    - strftime(str, sizeof(str), "%Y-%m-%d %T", tp);
    + if (!tp)
    + strcpy(str, "invalid-time");
    + else
    + strftime(str, sizeof(str), "%Y-%m-%d %T", tp);
    }

    return str;
    diff -Nru iproute2-6.14.0/lib/color.c iproute2-6.15.0/lib/color.c
    --- iproute2-6.14.0/lib/color.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/lib/color.c 2025-05-26 16:19:09.000000000 +0100
    @@ -81,6 +81,18 @@
    set_color_palette();
    }

    +int default_color_opt(void)
    +{
    + const char *no_color;
    +
    + /* If NO_COLOR has a non-empty value, coloured output is never wanted */
    + no_color = getenv("NO_COLOR");
    + if (no_color && *no_color)
    + return COLOR_OPT_NEVER;
    +
    + return CONF_COLOR;
    +}
    +
    bool check_enable_color(int color, int json)
    {
    if (json || color == COLOR_OPT_NEVER)
    diff -Nru iproute2-6.14.0/lib/utils.c iproute2-6.15.0/lib/utils.c
    --- iproute2-6.14.0/lib/utils.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/lib/utils.c 2025-05-26 16:19:09.000000000 +0100
    @@ -304,10 +304,6 @@
    if (res == ULLONG_MAX && errno == ERANGE)
    return -1;

    - /* in case ULL is 128 bits */
    - if (res > 0xFFFFFFFFFFFFFFFFULL)
    - return -1;
    -
    *val = res;
    return 0;
    }
    @@ -399,8 +395,6 @@
    return -1;
    if ((res == LLONG_MIN || res == LLONG_MAX) && errno == ERANGE)
    return -1;
    - if (res > INT64_MAX || res < INT64_MIN)
    - return -1;

    *val = res;
    return 0;
    diff -Nru iproute2-6.14.0/MAINTAINERS iproute2-6.15.0/MAINTAINERS
    --- iproute2-6.14.0/MAINTAINERS 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/MAINTAINERS 2025-05-26 16:19:09.000000000 +0100
    @@ -26,7 +26,7 @@
    L: netdev@vger.kernel.org

    Ethernet Bridging - bridge
    -M: Roopa Prabhu <roopa@nvidia.com>
    +M: Ido Schimmel <idosch@nvidia.com>
    M: Nikolay Aleksandrov <razor@blackwall.org>
    L: bridge@lists.linux-foundation.org (moderated for non-subscribers)
    F: bridge/*
    diff -Nru iproute2-6.14.0/man/man8/ip-link.8.in iproute2-6.15.0/man/man8/ip-link.8.in
    --- iproute2-6.14.0/man/man8/ip-link.8.in 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/man/man8/ip-link.8.in 2025-05-26 16:19:09.000000000 +0100
    @@ -882,10 +882,14 @@
    [
    .BI mode " MODE "
    ] [
    +.BI scrub " SCRUB "
    +] [
    .I "POLICY "
    ] [
    .BR peer
    [
    +.BI scrub " SCRUB "
    +] [
    .I "POLICY "
    ] [
    .I "NAME "
    @@ -899,6 +903,17 @@
    as possible values. Default option is "l3".

    .sp
    +.BI scrub " SCRUB"
    +- specifies the scrub behavior of the netkit device with "default" and +"none" as possible values. With "default" the device zeroes the +skb->{mark,priority} fields before invoking the attached BPF program
    +when its peer device resides in a different network namespace. With
    +"none" the device leaves clearing skb->{mark,priority} up to the BPF +program. Default option is "default". Specifying scrub before the peer +option refers to the primary device, after the peer option refers to
    +the peer device.
    +
    +.sp
    .I "POLICY"
    - specifies the default device policy when no BPF programs are attached
    with "forward" and "blackhole" as possible values. Default option is
    diff -Nru iproute2-6.14.0/man/man8/ip-rule.8.in iproute2-6.15.0/man/man8/ip-rule.8.in
    --- iproute2-6.14.0/man/man8/ip-rule.8.in 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/man/man8/ip-rule.8.in 2025-05-26 16:19:09.000000000 +0100
    @@ -37,7 +37,7 @@
    .B tos
    .IR TOS " ] [ "
    .B dscp
    -.IR DSCP " ] [ "
    +.IR DSCP\fR[\fB/\fIMASK "] ] [ "
    .B fwmark
    .IR FWMARK\fR[\fB/\fIMASK "] ] [ "
    .B iif
    @@ -52,10 +52,10 @@
    .B ipproto
    .IR PROTOCOL " ] [ "
    .BR sport " [ "
    -.IR NUMBER " | "
    +.IR NUMBER\fR[\fB/\fIMASK "] | "
    .IR NUMBER "-" NUMBER " ] ] [ "
    .BR dport " [ "
    -.IR NUMBER " | "
    +.IR NUMBER\fR[\fB/\fIMASK "] | "
    .IR NUMBER "-" NUMBER " ] ] [ "
    .B tun_id
    .IR TUN_ID " ] [ "
    @@ -239,9 +239,10 @@
    select the TOS value to match.

    .TP
    -.BI dscp " DSCP"
    -select the DSCP value to match. DSCP values can be written either directly as
    -numeric values (valid values are 0-63), or using symbolic names specified in +.BI dscp " DSCP\fR[\fB/\fIMASK\fR]"
    +select the DSCP value to match with an optional mask. DSCP values can be +written either directly as numeric values (valid values are 0-63), or using +symbolic names specified in
    .BR @SYSCONF_USR_DIR@/rt_dsfield " or " @SYSCONF_ETC_DIR@/rt_dsfield
    (has precedence if exists).
    However, note that the file specifies full 8-bit dsfield values, whereas
    @@ -270,12 +271,14 @@
    select the ip protocol value to match.

    .TP
    -.BI sport " NUMBER | NUMBER-NUMBER"
    -select the source port value to match. supports port range.
    +.BI sport " NUMBER\fR[\fB/\fIMASK\fR] | NUMBER-NUMBER"
    +select the source port value to match with an optional mask. Supports port +range.

    .TP
    -.BI dport " NUMBER | NUMBER-NUMBER"
    -select the destination port value to match. supports port range.
    +.BI dport " NUMBER\fR[\fB/\fIMASK\fR] | NUMBER-NUMBER"
    +select the destination port value to match with an optional mask. Supports port
    +range.

    .TP
    .BI priority " PREFERENCE"
    diff -Nru iproute2-6.14.0/man/man8/rdma-statistic.8 iproute2-6.15.0/man/man8/rdma-statistic.8
    --- iproute2-6.14.0/man/man8/rdma-statistic.8 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/man/man8/rdma-statistic.8 2025-05-26 16:19:09.000000000 +0100
    @@ -39,6 +39,7 @@
    .B auto
    .RI "{ " CRITERIA " | "
    .BR off " }"
    +.B [ optional-counters | on/off ]

    .ti -8
    .B rdma statistic
    @@ -180,6 +181,11 @@
    On device mlx5_2 port 1, for each new user QP bind it with a counter automatically. Per counter for QPs with same qp type.
    .RE
    .PP
    +rdma statistic qp set link mlx5_2/1 auto type on optional-counters on
    +.RS 4
    +On device mlx5_2 port 1, for each new user QP bind it with a counter automatically. Per counter for QPs with same qp type. Whilst also binding the currently enabled optional-counters.
    +.RE
    +.PP
    rdma statistic qp set link mlx5_2/1 auto pid on
    .RS 4
    On device mlx5_2 port 1, for each new user QP bind it with a counter automatically. Per counter for QPs with same pid.
    diff -Nru iproute2-6.14.0/misc/nstat.c iproute2-6.15.0/misc/nstat.c
    --- iproute2-6.14.0/misc/nstat.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/misc/nstat.c 2025-05-26 16:19:09.000000000 +0100
    @@ -218,6 +218,10 @@
    p = next;
    }
    n = db;
    + if (n == NULL) {
    + fprintf(stderr, "Error: Invalid input – line has ':' but no entries. Add values after ':'.\n");
    + exit(-2);
    + }
    nread = getline(&buf, &buflen, fp);
    if (nread == -1) {
    fprintf(stderr, "%s:%d: error parsing history file\n", diff -Nru iproute2-6.14.0/rdma/include/uapi/rdma/rdma_netlink.h iproute2-6.15.0/rdma/include/uapi/rdma/rdma_netlink.h
    --- iproute2-6.14.0/rdma/include/uapi/rdma/rdma_netlink.h 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/rdma/include/uapi/rdma/rdma_netlink.h 2025-05-26 16:19:09.000000000 +0100
    @@ -580,6 +580,8 @@
    RDMA_NLDEV_ATTR_EVENT_TYPE, /* u8 */

    RDMA_NLDEV_SYS_ATTR_MONITOR_MODE, /* u8 */
    +
    + RDMA_NLDEV_ATTR_STAT_OPCOUNTER_ENABLED, /* u8 */
    /*
    * Always the end
    */
    diff -Nru iproute2-6.14.0/rdma/stat.c iproute2-6.15.0/rdma/stat.c
    --- iproute2-6.14.0/rdma/stat.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/rdma/stat.c 2025-05-26 16:19:09.000000000 +0100
    @@ -7,6 +7,7 @@
    #include "rdma.h"
    #include "res.h"
    #include "stat.h"
    +#include "utils.h"
    #include <inttypes.h>

    static int stat_help(struct rd *rd)
    @@ -62,7 +63,8 @@
    { NULL },
    };

    -static int prepare_auto_mode_str(uint32_t mask, char *output, int len) +static int prepare_auto_mode_str(uint32_t mask, bool opcnt, char *output,
    + int len)
    {
    char s[] = "qp auto";
    int i, outlen = strlen(s);
    @@ -90,6 +92,10 @@
    if (outlen + strlen(" on") >= len)
    return -EINVAL;
    strcat(output, " on");
    +
    + strcat(output, " optional-counters ");
    + strcat(output, (opcnt) ? "on" : "off");
    +
    } else {
    if (outlen + strlen(" off") >= len)
    return -EINVAL;
    @@ -104,6 +110,7 @@
    struct nlattr *tb[RDMA_NLDEV_ATTR_MAX] = {};
    uint32_t mode = 0, mask = 0;
    char output[128] = {};
    + bool opcnt = false;
    uint32_t idx, port;
    const char *name;

    @@ -126,7 +133,10 @@
    if (!tb[RDMA_NLDEV_ATTR_STAT_AUTO_MODE_MASK])
    return MNL_CB_ERROR;
    mask = mnl_attr_get_u32(tb[RDMA_NLDEV_ATTR_STAT_AUTO_MODE_MASK]);
    - prepare_auto_mode_str(mask, output, sizeof(output));
    + if (tb[RDMA_NLDEV_ATTR_STAT_OPCOUNTER_ENABLED])
    + opcnt = mnl_attr_get_u8(
    + tb[RDMA_NLDEV_ATTR_STAT_OPCOUNTER_ENABLED]);
    + prepare_auto_mode_str(mask, opcnt, output, sizeof(output));
    } else {
    snprintf(output, sizeof(output), "qp auto off");
    }
    @@ -351,6 +361,7 @@
    { .name = "lqpn", .is_number = true },
    { .name = "pid", .is_number = true },
    { .name = "qp-type", .is_number = false },
    + { .name = "optional-counters", .is_number = false },
    };

    static int stat_qp_show_one_link(struct rd *rd)
    @@ -395,9 +406,37 @@
    return rd_exec_cmd(rd, cmds, "parameter");
    }

    +static bool stat_get_on_off(struct rd *rd, const char *arg, int *ret)
    +{
    + bool value = false;
    +
    + if (strcmpx(rd_argv(rd), arg) != 0) {
    + *ret = -EINVAL;
    + return false;
    + }
    +
    + rd_arg_inc(rd);
    +
    + if (rd_is_multiarg(rd)) {
    + pr_err("The parameter %s shouldn't include range\n", arg);
    + *ret = EINVAL;
    + return false;
    + }
    +
    + value = parse_on_off(arg, rd_argv(rd), ret);
    + if (*ret)
    + return false;
    +
    + rd_arg_inc(rd);
    +
    + return value;
    +}
    +
    static int stat_qp_set_link_auto_sendmsg(struct rd *rd, uint32_t mask)
    {
    uint32_t seq;
    + bool opcnt;
    + int ret;

    rd_prepare_msg(rd, RDMA_NLDEV_CMD_STAT_SET,
    &seq, (NLM_F_REQUEST | NLM_F_ACK));
    @@ -408,6 +447,13 @@
    mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_MODE,
    RDMA_COUNTER_MODE_AUTO);
    mnl_attr_put_u32(rd->nlh, RDMA_NLDEV_ATTR_STAT_AUTO_MODE_MASK, mask);
    + if (rd_argc(rd)) {
    + opcnt = stat_get_on_off(rd, "optional-counters", &ret);
    + if (ret)
    + return ret;
    + mnl_attr_put_u8(rd->nlh, RDMA_NLDEV_ATTR_STAT_OPCOUNTER_ENABLED,
    + opcnt);
    + }

    return rd_sendrecv_msg(rd, seq);
    }
    diff -Nru iproute2-6.14.0/rdma/utils.c iproute2-6.15.0/rdma/utils.c
    --- iproute2-6.14.0/rdma/utils.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/rdma/utils.c 2025-05-26 16:19:09.000000000 +0100
    @@ -479,6 +479,7 @@
    [RDMA_NLDEV_ATTR_PARENT_NAME] = MNL_TYPE_STRING,
    [RDMA_NLDEV_ATTR_EVENT_TYPE] = MNL_TYPE_U8,
    [RDMA_NLDEV_SYS_ATTR_MONITOR_MODE] = MNL_TYPE_U8,
    + [RDMA_NLDEV_ATTR_STAT_OPCOUNTER_ENABLED] = MNL_TYPE_U8,
    };

    static int rd_attr_check(const struct nlattr *attr, int *typep)
    diff -Nru iproute2-6.14.0/tc/tc.c iproute2-6.15.0/tc/tc.c
    --- iproute2-6.14.0/tc/tc.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/tc/tc.c 2025-05-26 16:19:09.000000000 +0100
    @@ -254,7 +254,7 @@
    {
    const char *libbpf_version;
    char *batch_file = NULL;
    - int color = CONF_COLOR;
    + int color = default_color_opt();
    int ret;

    while (argc > 1) {
    diff -Nru iproute2-6.14.0/tc/tc_core.c iproute2-6.15.0/tc/tc_core.c
    --- iproute2-6.14.0/tc/tc_core.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/tc/tc_core.c 2025-05-26 16:19:09.000000000 +0100
    @@ -23,12 +23,12 @@
    static double tick_in_usec = 1;
    static double clock_factor = 1;

    -static unsigned int tc_core_time2tick(unsigned int time)
    +static double tc_core_time2tick(double time)
    {
    return time * tick_in_usec;
    }

    -unsigned int tc_core_tick2time(unsigned int tick)
    +double tc_core_tick2time(double tick)
    {
    return tick / tick_in_usec;
    }
    @@ -45,7 +45,7 @@

    unsigned int tc_calc_xmittime(__u64 rate, unsigned int size)
    {
    - return tc_core_time2tick(TIME_UNITS_PER_SEC*((double)size/(double)rate));
    + return ceil(tc_core_time2tick(TIME_UNITS_PER_SEC*((double)size/(double)rate)));
    }

    unsigned int tc_calc_xmitsize(__u64 rate, unsigned int ticks)
    diff -Nru iproute2-6.14.0/tc/tc_core.h iproute2-6.15.0/tc/tc_core.h
    --- iproute2-6.14.0/tc/tc_core.h 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/tc/tc_core.h 2025-05-26 16:19:09.000000000 +0100
    @@ -12,7 +12,7 @@
    };


    -unsigned tc_core_tick2time(unsigned tick);
    +double tc_core_tick2time(double tick);
    unsigned tc_core_time2ktime(unsigned time);
    unsigned tc_core_ktime2time(unsigned ktime);
    unsigned tc_calc_xmittime(__u64 rate, unsigned size);
    diff -Nru iproute2-6.14.0/tc/tc_util.c iproute2-6.15.0/tc/tc_util.c
    --- iproute2-6.14.0/tc/tc_util.c 2025-03-24 16:04:44.000000000 +0000
    +++ iproute2-6.15.0/tc/tc_util.c 2025-05-26 16:19:09.000000000 +0100
    @@ -665,7 +665,8 @@
    tm->expires / hz);
    }

    -static void print_tcstats_basic_hw(struct rtattr **tbs, const char *prefix) +static void print_tcstats_basic_hw(struct rtattr **tbs, const char *prefix, + __u64 packets64, __u64 packets64_hw)
    {
    struct gnet_stats_basic bs_hw;

    @@ -674,8 +675,9 @@

    memcpy(&bs_hw, RTA_DATA(tbs[TCA_STATS_BASIC_HW]),
    MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC_HW]), sizeof(bs_hw)));
    + packets64_hw = packets64_hw ? : bs_hw.packets;

    - if (bs_hw.bytes == 0 && bs_hw.packets == 0)
    + if (bs_hw.bytes == 0 && packets64_hw == 0)
    return;

    if (tbs[TCA_STATS_BASIC]) {
    @@ -684,15 +686,16 @@
    memcpy(&bs, RTA_DATA(tbs[TCA_STATS_BASIC]),
    MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC]),
    sizeof(bs)));
    + packets64 = packets64 ? : bs.packets;

    - if (bs.bytes >= bs_hw.bytes && bs.packets >= bs_hw.packets) {
    + if (bs.bytes >= bs_hw.bytes && packets64 >= packets64_hw) {
    print_nl();
    print_string(PRINT_FP, NULL, "%s", prefix);
    print_lluint(PRINT_ANY, "sw_bytes",
    "Sent software %llu bytes",
    bs.bytes - bs_hw.bytes);
    - print_uint(PRINT_ANY, "sw_packets", " %u pkt",
    - bs.packets - bs_hw.packets);
    + print_lluint(PRINT_ANY, "sw_packets", " %llu pkt",
    + packets64 - packets64_hw);
    }
    }

    @@ -700,21 +703,40 @@
    print_string(PRINT_FP, NULL, "%s", prefix);
    print_lluint(PRINT_ANY, "hw_bytes", "Sent hardware %llu bytes",
    bs_hw.bytes);
    - print_uint(PRINT_ANY, "hw_packets", " %u pkt", bs_hw.packets);
    + print_lluint(PRINT_ANY, "hw_packets", " %llu pkt", packets64_hw);
    +}
    +
    +static void parse_packets64(const struct rtattr *nest, __u64 *p_packets64,
    + __u64 *p_packets64_hw)
    +{
    + unsigned short prev_type = __TCA_STATS_MAX;
    + const struct rtattr *pos;
    +
    + /* 'TCA_STATS_PKT64' can appear twice in the 'TCA_ACT_STATS' nest.
    + * Whether the attribute carries the combined or hardware only
    + * statistics depends on the attribute that precedes it in the nest.
    + */
    + rtattr_for_each_nested(pos, nest) {
    + if (pos->rta_type == TCA_STATS_PKT64 &&
    + prev_type == TCA_STATS_BASIC)
    + *p_packets64 = rta_getattr_u64(pos);
    + else if (pos->rta_type == TCA_STATS_PKT64 &&
    + prev_type == TCA_STATS_BASIC_HW)
    + *p_packets64_hw = rta_getattr_u64(pos);
    + prev_type = pos->rta_type;
    + }
    }

    void print_tcstats2_attr(struct rtattr *rta, const char *prefix, struct rtattr **xstats)
    {
    struct rtattr *tbs[TCA_STATS_MAX + 1];
    + __u64 packets64 = 0, packets64_hw = 0;

    parse_rtattr_nested(tbs, TCA_STATS_MAX, rta);
    + parse_packets64(rta, &packets64, &packets64_hw);

    if (tbs[TCA_STATS_BASIC]) {
    struct gnet_stats_basic bs = {0};
    - __u64 packets64 = 0;
    -
    - if (tbs[TCA_STATS_PKT64])
    - packets64 = rta_getattr_u64(tbs[TCA_STATS_PKT64]);

    memcpy(&bs, RTA_DATA(tbs[TCA_STATS_BASIC]),
    MIN(RTA_PAYLOAD(tbs[TCA_STATS_BASIC]), sizeof(bs)));
    @@ -740,7 +762,7 @@
    }

    if (tbs[TCA_STATS_BASIC_HW])
    - print_tcstats_basic_hw(tbs, prefix);
    + print_tcstats_basic_hw(tbs, prefix, packets64, packets64_hw);

    if (tbs[TCA_STATS_RATE_EST64]) {
    struct gnet_stats_rate_est64 re = {0};


    --
    Sebastian Ramacher

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)