iproute2: backport L4S patches
authorStijn Tintel <stijn@linux-ipv6.be>
Tue, 22 Mar 2022 17:39:36 +0000 (19:39 +0200)
committerStijn Tintel <stijn@linux-ipv6.be>
Mon, 28 Mar 2022 11:58:41 +0000 (14:58 +0300)
This commit backports Low-Loss Low-Latency Scalable Throughput (L4S)
patches from https://github.com/L4STeam/iproute2.

Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
package/network/utils/iproute2/patches/990-0001-tc-add-dualpi2-scheduler-module.patch [new file with mode: 0644]
package/network/utils/iproute2/patches/990-0002-ss-Add-TCP-Prague-inet_diag-interface.patch [new file with mode: 0644]

diff --git a/package/network/utils/iproute2/patches/990-0001-tc-add-dualpi2-scheduler-module.patch b/package/network/utils/iproute2/patches/990-0001-tc-add-dualpi2-scheduler-module.patch
new file mode 100644 (file)
index 0000000..265bb58
--- /dev/null
@@ -0,0 +1,837 @@
+From 3dc5a64115b16acc6492f721927d8fd45471e95b Mon Sep 17 00:00:00 2001
+From: Olga Albisser <olga@albisser.org>
+Date: Mon, 12 Aug 2019 12:18:26 +0200
+Subject: [PATCH 1/2] tc: add dualpi2 scheduler module
+
+DualPI2 AQM is a combination of the DualQ Coupled-AQM with a PI2
+base-AQM, able to control scalable congestion controls like DCTCP
+and TCP-Prague, implemented as a Linux qdisc.
+
+This patch adds support to tc to configure it through its netlink
+interface.
+
+Signed-off-by: Olga Albisser <olga@albisser.org>
+Signed-off-by: Koen De Schepper <koen.de_schepper@nokia-bell-labs.com>
+Signed-off-by: Oliver Tilmans <olivier.tilmans@nokia-bell-labs.com>
+Signed-off-by: Bob Briscoe <research@bobbriscoe.net>
+Signed-off-by: Henrik Steen <henrist@henrist.net>
+[fix compile error due to undefined __uint32_t]
+Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
+---
+ bash-completion/tc             |   9 +-
+ include/uapi/linux/pkt_sched.h |  34 +++
+ include/utils.h                |   8 +
+ man/man8/tc-dualpi2.8          | 235 +++++++++++++++++
+ tc/Makefile                    |   1 +
+ tc/q_dualpi2.c                 | 456 +++++++++++++++++++++++++++++++++
+ 6 files changed, 742 insertions(+), 1 deletion(-)
+ create mode 100644 man/man8/tc-dualpi2.8
+ create mode 100644 tc/q_dualpi2.c
+
+diff --git a/bash-completion/tc b/bash-completion/tc
+index 086cb7f6..d31934bd 100644
+--- a/bash-completion/tc
++++ b/bash-completion/tc
+@@ -4,7 +4,7 @@
+ QDISC_KIND=' choke codel bfifo pfifo pfifo_head_drop fq fq_codel gred hhf \
+             mqprio multiq netem pfifo_fast pie fq_pie red rr sfb sfq tbf atm \
+-            cbq drr dsmark hfsc htb prio qfq '
++            cbq drr dsmark hfsc htb prio qfq dualpi2'
+ FILTER_KIND=' basic bpf cgroup flow flower fw route rsvp tcindex u32 matchall '
+ ACTION_KIND=' gact mirred bpf sample '
+@@ -374,6 +374,13 @@ _tc_qdisc_options()
+             _tc_once_attr 'default r2q direct_qlen debug'
+             return 0
+             ;;
++        dualpi2)
++            _tc_once_attr 'limit coupling_factor step_thresh classic_protection \
++                max_rtt typical_rtt target tupdate alpha beta'
++            _tc_one_of_list 'drop_on_overload overflow'
++            _tc_one_of_list 'drop_enqueue drop_dequeue'
++            _tc_one_of_list 'split_gso no_split_gso'
++            ;;
+         multiq|pfifo_fast|atm|drr|qfq)
+             return 0
+             ;;
+diff --git a/include/uapi/linux/pkt_sched.h b/include/uapi/linux/pkt_sched.h
+index ec88590b..f3ee0a86 100644
+--- a/include/uapi/linux/pkt_sched.h
++++ b/include/uapi/linux/pkt_sched.h
+@@ -1265,4 +1265,38 @@ enum {
+ #define TCA_ETS_MAX (__TCA_ETS_MAX - 1)
++/* DUALPI2 */
++enum {
++      TCA_DUALPI2_UNSPEC,
++      TCA_DUALPI2_LIMIT,              /* Packets */
++      TCA_DUALPI2_TARGET,             /* us */
++      TCA_DUALPI2_TUPDATE,            /* us */
++      TCA_DUALPI2_ALPHA,              /* Hz scaled up by 256 */
++      TCA_DUALPI2_BETA,               /* HZ scaled up by 256 */
++      TCA_DUALPI2_STEP_THRESH,        /* Packets or us */
++      TCA_DUALPI2_STEP_PACKETS,       /* Whether STEP_THRESH is in packets */
++      TCA_DUALPI2_COUPLING,           /* Coupling factor between queues */
++      TCA_DUALPI2_DROP_OVERLOAD,      /* Whether to drop on overload */
++      TCA_DUALPI2_DROP_EARLY,         /* Whether to drop on enqueue */
++      TCA_DUALPI2_C_PROTECTION,       /* Percentage */
++      TCA_DUALPI2_ECN_MASK,           /* L4S queue classification mask */
++      TCA_DUALPI2_SPLIT_GSO,          /* Split aggregated packets */
++      TCA_DUALPI2_PAD,
++      __TCA_DUALPI2_MAX
++};
++
++#define TCA_DUALPI2_MAX   (__TCA_DUALPI2_MAX - 1)
++
++struct tc_dualpi2_xstats {
++      __u32 prob;             /* current probability */
++      __u32 delay_c;          /* current delay in C queue */
++      __u32 delay_l;          /* current delay in L queue */
++      __s32 credit;           /* current c_protection credit */
++      __u32 packets_in_c;     /* number of packets enqueued in C queue */
++      __u32 packets_in_l;     /* number of packets enqueued in L queue */
++      __u32 maxq;             /* maximum queue size */
++      __u32 ecn_mark;         /* packets marked with ecn*/
++      __u32 step_marks;       /* ECN marks due to the step AQM */
++};
++
+ #endif
+diff --git a/include/utils.h b/include/utils.h
+index c9849461..4c1002ab 100644
+--- a/include/utils.h
++++ b/include/utils.h
+@@ -275,6 +275,14 @@ unsigned int print_name_and_link(const char *fmt,
+       _min1 < _min2 ? _min1 : _min2; })
+ #endif
++#ifndef max
++# define max(x, y) ({                 \
++      typeof(x) _max1 = (x);          \
++      typeof(y) _max2 = (y);          \
++      (void) (&_max1 == &_max2);      \
++      _max1 > _max2 ? _max1 : _max2; })
++#endif
++
+ #ifndef __check_format_string
+ # define __check_format_string(pos_str, pos_args) \
+       __attribute__ ((format (printf, (pos_str), (pos_args))))
+diff --git a/man/man8/tc-dualpi2.8 b/man/man8/tc-dualpi2.8
+new file mode 100644
+index 00000000..dcbf0bcf
+--- /dev/null
++++ b/man/man8/tc-dualpi2.8
+@@ -0,0 +1,235 @@
++.TH DUALPI2 8 "13 December 2018" "iproute2" "Linux"
++
++.SH NAME
++DUALPI2 \- Dual Queue Proportional Integral Controller AQM - Improved with a square
++.SH SYNOPSIS
++.sp
++.ad l
++.in +8
++.ti -8
++.BR tc " " qdisc " ... " dualpi2
++.br
++.RB "[ " limit
++.IR PACKETS " ]"
++.br
++.RB "[ " coupling_factor
++.IR NUMBER " ]"
++.br
++.RB "[ " step_thresh
++.IR TIME | PACKETS " ]"
++.br
++.RB "[ " drop_on_overload " | " overflow " ]"
++.br
++.RB "[ " drop_enqueue " | " drop_dequeue " ]"
++.br
++.RB "[ " l4s_ect " | " any_ect " ]"
++.br
++.RB "[ " classic_protection
++.IR PERCENTAGE " ] "
++.br
++.RB "[ " max_rtt
++.IR TIME 
++.RB " [ " typical_rtt 
++.IR TIME " ]] "
++.br
++.RB "[ " target
++.IR TIME " ]"
++.br
++.RB "[ " tupdate
++.IR TIME " ]"
++.br
++.RB "[ " alpha
++.IR float " ]"
++.br
++.RB "[ " beta
++.IR float " ] "
++.br
++.RB "[ " split_gso " | " no_split_gso " ]"
++
++.SH DESCRIPTION
++DUALPI2 AQM is a combination of the DUALQ Coupled-AQM with a PI2 base-AQM. The PI2 AQM (details can be found in the paper cited below) is in turn both an extension and a simplification of the PIE AQM. PI2 makes quite some PIE heuristics unnecessary, while being able to control scalable congestion controls like DCTCP and TCP-Prague. With PI2, both Reno/Cubic can be used in parallel with DCTCP, maintaining window fairness. DUALQ provides latency separation between low latency DCTCP flows and Reno/Cubic flows that need a bigger queue. The main design goals are:
++.PD 0
++.IP \(bu 4
++L4S - Low Loss, Low Latency and Scalable congestion control support
++.IP \(bu 4
++DualQ option to separate the L4S traffic in a low latency queue, without harming remaining traffic that is scheduled in classic queue due to congestion-coupling
++.IP \(bu 4
++Configurable overload strategies
++.IP \(bu 4
++Use of sojourn time to reliably estimate queue delay
++.IP \(bu 4
++Simple implementation
++.IP \(bu 4
++Guaranteed stability and fast responsiveness
++.PD
++
++.SH ALGORITHM
++DUALPI2 is designed to provide low loss and low latency to L4S traffic, without harming classic traffic. Every update interval a new internal base probability is calculated, based on queue delay. The base probability is updated with a delta based on the difference between the current queue delay and the 
++.I "" target
++delay, and the queue growth comparing with the queuing delay during the previous 
++.I "" tupdate
++interval. The integral gain factor 
++.RB "" alpha
++is used to correct slowly enough any persistent standing queue error to the user specified target delay, while the proportional gain factor
++.RB "" beta
++is used to quickly compensate for queue changes (growth or shrink).
++
++The updated base probability is used as input to decide to mark and drop packets. DUALPI2 scales the calculated probability for each of the two queues accordingly. For the L4S queue, the probability is multiplied by a 
++.RB "" coupling_factor
++, while for the classic queue, it is squared to compensate the squareroot rate equation of Reno/Cubic. The ECT identifier (
++.RB "" l4s_ect | any_ect
++) is used to classify traffic into respective queues.
++
++If DUALPI2 AQM has detected overload (when excessive non-responsive traffic is sent), it can signal congestion solely using 
++.RB "" drop
++, irrespective of the ECN field, or alternatively limit the drop probability and let the queue grow and eventually 
++.RB "" overflow
++(like tail-drop).
++
++Additional details can be found in the draft cited below.
++
++.SH PARAMETERS
++.TP
++.BI limit " PACKETS"
++Limit the number of packets that can be enqueued. Incoming packets are dropped when this limit
++is reached. This limit is common for the L4S and Classic queue. Defaults to
++.I 10000
++packets. This is about 125ms delay on a 1Gbps link.
++.TP
++.BI coupling_factor " NUMBER"
++Set the coupling rate factor between Classic and L4S. Defaults to
++.I 2
++.TP
++.B l4s_ect | any_ect
++Configures the ECT classifier. Packets whose ECT codepoint matches this are sent to the L4S queue where they receive a scalable marking. Defaults to
++.I l4s_ect
++, i.e., the L4S identifier ECT(1). Setting this to
++.I any_ect
++causes all packets whose ECN field is not zero to be sent to the L4S queue. This enables to be backward compatible with, e.g., DCTCP.
++.PD
++.BI step_thresh " TIME | PACKETS"
++Set the step threshold for the L4S queue. This will cause packets with a sojourn time exceeding the threshold to always be marked. This value can either be specified using time units (i.e., us, ms, s), or in packets (pkt, packet(s)). A velue without units is assumed to be in time (us). If defining the step in packets, be sure to disable GRO on the ingress interfaces. Defaults to
++.I 1ms
++.
++.TP
++.B drop_on_overload  |  overflow
++Control the overload strategy. 
++.I drop_on_overload
++preserves the delay in the L4S queue by dropping in both queues on overload.
++.I overflow
++sacrifices delay to avoid losses, eventually resulting in a taildrop behavior once
++.I limit
++is reached. Defaults to
++.I drop_on_overload.
++.PD
++.TP
++.B drop_enqueue | drop_dequeue
++Decide when packets are PI-based dropped or marked. The
++.I step_thresh 
++based L4S marking is always at dequeue. Defaults to
++.I drop_dequeue
++.PD
++.TP
++.BI classic_protection " PERCENTAGE
++Protects the classic queue from unresponsive traffic in the L4S queue. This bounds the maximal delay in the C queue to be
++.I (100 - PERCENTAGE)
++times greater than the one in the L queue. Defaults to
++.I 10
++.TP
++.BI typical_rtt " TIME"
++.PD 0
++.TP
++.PD
++.BI max_rtt " TIME"
++Specify the maximum round trip time (RTT) and/or the typical RTT of the traffic
++that will be controlled by dualpi2. If either of
++.I max_rtt
++or
++.I typical_rtt
++is not specified, the missing value will be computed from the following 
++relationship:
++.I max_rtt = typical_rtt * 6.
++If any of these parameters is given, it will be used to automatically compute
++suitable values for
++.I alpha, beta, target, and tupdate,
++according to the relationship from the appendix A.1 in the IETF draft, to
++achieve a stable control. Consequently, those derived values will override their
++eventual user-provided ones. The default range of operation for the qdisc uses
++.I max_rtt = 100ms
++and 
++.I typical_rtt = 15ms
++, which is suited to control internet traffic.
++.TP
++.BI target " TIME"
++Set the expected queue delay. Defaults to
++.I 15
++ms.
++.TP
++.BI tupdate " TIME"
++Set the frequency at which the system drop probability is calculated. Defaults to
++.I 16
++ms. This should be a third of the max RTT supported.
++.TP
++.BI alpha " float"
++.PD 0
++.TP
++.PD
++.BI beta " float"
++Set alpha and beta, the integral and proportional gain factors in Hz for the PI controller. These can be calculated based on control theory. Defaults are
++.I 0.16
++and
++.I 3.2
++Hz, which provide stable control for RTT's up to 100ms with tupdate of 16. Be aware, unlike with PIE, these are the real unscaled gain factors.
++.PD
++.TP
++.B split_gso | no_split_gso
++Decide how to handle aggregated packets. Either treat the aggregate as
++on single packet (thus all share fate wrt. marks and drops) with
++.I no_split_gso
++, trading some tail latency for CPU usage, or treat each packet individually
++(i.e. split them) with
++.I split_gso
++to finely mark/drop and control queueing latencies. Defaults to 
++.I split_gso
++
++.SH EXAMPLES
++Setting DUALPI2 for the Internet with default parameters:
++ # sudo tc qdisc add dev eth0 root dualpi2
++
++Setting DUALPI2 for datacenter with legacy DCTCP using ECT(0):
++ # sudo tc qdisc add dev eth0 root dualpi2 any_ect
++
++.SH FILTERS
++This qdisc can be used in conjunction with tc-filters. More precisely, it will
++honor filters "stealing packets", as well as accept other classification schemes.
++.BR
++.TP
++Packets whose priority/classid are set to
++.I 1
++will be enqueued in the L queue, alongside L4S traffic, and thus subject to the
++increase marking probability (or drops if they are marked not-ECT).
++.BR
++.TP
++Packet whose prioriy/classid are set to 
++.I 2
++will also be enqueued in the L queue, but will never be dropped if they are
++not-ECT (unless the qdisc is full and thus resorts to taildrop).
++.BR
++.TP
++Finally, all the other classid/priority map to the classic queue.
++
++.SH SEE ALSO
++.BR tc (8),
++.BR tc-pie (8)
++
++.SH SOURCES
++.IP \(bu 4
++IETF draft submission is at https://www.ietf.org/id/draft-ietf-tsvwg-aqm-dualq-coupled
++.IP \(bu 4
++CoNEXT '16 Proceedings of the 12th International on Conference on emerging Networking EXperiments and Technologies : "PI2: A
++Linearized AQM for both Classic and Scalable TCP"
++
++.SH AUTHORS
++DUALPI2 was implemented by Koen De Schepper, Olga Albisser, Henrik Steen, and Olivier Tilmans also the authors of
++this man page. Please report bugs and corrections to the Linux networking
++development mailing list at <netdev@vger.kernel.org>.
+diff --git a/tc/Makefile b/tc/Makefile
+index 5a517af2..ceed97d5 100644
+--- a/tc/Makefile
++++ b/tc/Makefile
+@@ -9,6 +9,7 @@ SHARED_LIBS ?= y
+ TCMODULES :=
+ TCMODULES += q_fifo.o
++TCMODULES += q_dualpi2.o
+ TCMODULES += q_sfq.o
+ TCMODULES += q_red.o
+ TCMODULES += q_prio.o
+diff --git a/tc/q_dualpi2.c b/tc/q_dualpi2.c
+new file mode 100644
+index 00000000..57b4ca82
+--- /dev/null
++++ b/tc/q_dualpi2.c
+@@ -0,0 +1,456 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/* Copyright (C) 2019 Nokia.
++ *
++ * DualQ PI Improved with a Square (dualpi2)
++ * Supports controlling scalable congestion controls (DCTCP, etc...)
++ * Supports DualQ with PI2
++ * Supports L4S ECN identifier
++ * Author: Koen De Schepper <koen.de_schepper@nokia-bell-labs.com>
++ * Author: Olga Albisser <olga@albisser.org>
++ * Author: Henrik Steen <henrist@henrist.net>
++ */
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include <syslog.h>
++#include <fcntl.h>
++#include <sys/socket.h>
++#include <netinet/in.h>
++#include <arpa/inet.h>
++#include <string.h>
++#include <math.h>
++#include <errno.h>
++
++#include "utils.h"
++#include "tc_util.h"
++
++#define MAX_PROB ((uint32_t)(~((uint32_t)0)))
++#define DEFAULT_ALPHA_BETA ((uint32_t)(~((uint32_t)0)))
++#define ALPHA_BETA_MAX ((2 << 23) - 1) /* see net/sched/sch_dualpi2.c */
++#define ALPHA_BETA_SCALE (1 << 8)
++#define RTT_TYP_TO_MAX 6
++
++enum {
++      INET_ECN_NOT_ECT = 0,
++      INET_ECN_ECT_1 = 1,
++      INET_ECN_ECT_0 = 2,
++      INET_ECN_CE = 3,
++      INET_ECN_MASK = 3,
++};
++
++static const char *get_ecn_type(uint8_t ect)
++{
++      switch (ect & INET_ECN_MASK) {
++              case INET_ECN_ECT_1: return "l4s_ect";
++              case INET_ECN_ECT_0:
++              case INET_ECN_MASK: return "any_ect";
++              default:
++                      fprintf(stderr,
++                              "Warning: Unexpected ecn type %u!\n", ect);
++                      return "";
++      }
++}
++
++static void explain(void)
++{
++      fprintf(stderr, "Usage: ... dualpi2\n");
++      fprintf(stderr, "               [limit PACKETS]\n");
++      fprintf(stderr, "               [coupling_factor NUMBER]\n");
++      fprintf(stderr, "               [step_thresh TIME|PACKETS]\n");
++      fprintf(stderr, "               [drop_on_overload|overflow]\n");
++      fprintf(stderr, "               [drop_enqueue|drop_dequeue]\n");
++      fprintf(stderr, "               [classic_protection PERCENTAGE]\n");
++      fprintf(stderr, "               [max_rtt TIME [typical_rtt TIME]]\n");
++      fprintf(stderr, "               [target TIME] [tupdate TIME]\n");
++      fprintf(stderr, "               [alpha ALPHA] [beta BETA]\n");
++      fprintf(stderr, "               [split_gso|no_split_gso]\n");
++}
++
++static int get_float(float *val, const char *arg, float min, float max)
++{
++        float res;
++        char *ptr;
++
++        if (!arg || !*arg)
++                return -1;
++        res = strtof(arg, &ptr);
++        if (!ptr || ptr == arg || *ptr)
++                return -1;
++      if (res < min || res > max)
++              return -1;
++        *val = res;
++        return 0;
++}
++
++static int get_packets(uint32_t *val, const char *arg)
++{
++      unsigned long res;
++      char *ptr;
++
++      if (!arg || !*arg)
++              return -1;
++      res = strtoul(arg, &ptr, 10);
++      if (!ptr || ptr == arg ||
++          (strcmp(ptr, "pkt") && strcmp(ptr, "packet") &&
++           strcmp(ptr, "packets")))
++              return -1;
++      if (res == ULONG_MAX && errno == ERANGE)
++              return -1;
++      if (res > 0xFFFFFFFFUL)
++              return -1;
++      *val = res;
++      return 0;
++}
++
++static int parse_alpha_beta(const char *name, char *argv, uint32_t *field)
++{
++
++      float field_f;
++
++      if (get_float(&field_f, argv, 0.0, ALPHA_BETA_MAX)) {
++              fprintf(stderr, "Illegal \"%s\"\n", name);
++              return -1;
++      }
++      else if (field_f < 1.0f / ALPHA_BETA_SCALE)
++              fprintf(stderr, "Warning: \"%s\" is too small and will be "
++                      "rounded to zero.\n", name);
++      *field = (uint32_t)(field_f * ALPHA_BETA_SCALE);
++      return 0;
++}
++
++static int try_get_percentage(int *val, const char *arg, int base)
++{
++      long res;
++      char *ptr;
++
++      if (!arg || !*arg)
++              return -1;
++      res = strtol(arg, &ptr, base);
++      if (!ptr || ptr == arg || (*ptr && strcmp(ptr, "%")))
++              return -1;
++      if (res == ULONG_MAX && errno == ERANGE)
++              return -1;
++      if (res < 0 || res > 100)
++              return -1;
++
++      *val = res;
++      return 0;
++}
++
++static int dualpi2_parse_opt(struct qdisc_util *qu, int argc, char **argv,
++                       struct nlmsghdr *n, const char* dev)
++{
++      uint32_t limit = 0;
++      uint32_t target = 0;
++      uint32_t tupdate = 0;
++      uint32_t alpha = DEFAULT_ALPHA_BETA;
++      uint32_t beta = DEFAULT_ALPHA_BETA;
++      int32_t coupling_factor = -1;
++      uint8_t ecn_mask = INET_ECN_NOT_ECT;
++      bool step_packets = false;
++      uint32_t step_thresh = 0;
++      int c_protection = -1;
++      int drop_early = -1;
++      int drop_overload = -1;
++      int split_gso = -1;
++      uint32_t rtt_max = 0;
++      uint32_t rtt_typ = 0;
++      struct rtattr *tail;
++
++      while (argc > 0) {
++              if (strcmp(*argv, "limit") == 0) {
++                      NEXT_ARG();
++                      if (get_u32(&limit, *argv, 10)) {
++                              fprintf(stderr, "Illegal \"limit\"\n");
++                              return -1;
++                      }
++              } else if (strcmp(*argv, "target") == 0) {
++                      NEXT_ARG();
++                      if (get_time(&target, *argv)) {
++                              fprintf(stderr, "Illegal \"target\"\n");
++                              return -1;
++                      }
++              } else if (strcmp(*argv, "tupdate") == 0) {
++                      NEXT_ARG();
++                      if (get_time(&tupdate, *argv)) {
++                              fprintf(stderr, "Illegal \"tupdate\"\n");
++                              return -1;
++                      }
++              } else if (strcmp(*argv, "alpha") == 0) {
++                      NEXT_ARG();
++                      if (parse_alpha_beta("alpha", *argv, &alpha))
++                              return -1;
++              } else if (strcmp(*argv, "beta") == 0) {
++                      NEXT_ARG();
++                      if (parse_alpha_beta("beta", *argv, &beta))
++                              return -1;
++              } else if (strcmp(*argv, "coupling_factor") == 0) {
++                      NEXT_ARG();
++                      if (get_s32(&coupling_factor, *argv, 0) ||
++                          coupling_factor > 0xFFUL ||coupling_factor < 0) {
++                              fprintf(stderr,
++                                      "Illegal \"coupling_factor\"\n");
++                              return -1;
++                      }
++              } else if (strcmp(*argv, "l4s_ect") == 0)
++                      ecn_mask = INET_ECN_ECT_1;
++              else if (strcmp(*argv, "any_ect") == 0)
++                      ecn_mask = INET_ECN_MASK;
++              else if (strcmp(*argv, "step_thresh") == 0) {
++                      NEXT_ARG();
++                      /* First assume that this is specified in time */
++                      if (get_time(&step_thresh, *argv)) {
++                              /* Then packets */
++                              if (get_packets(&step_thresh, *argv)) {
++                                      fprintf(stderr,
++                                              "Illegal \"step_thresh\"\n");
++                                      return -1;
++                              }
++                              step_packets = true;
++                      }
++              } else if (strcmp(*argv, "overflow") == 0) {
++                        drop_overload = 0;
++              } else if (strcmp(*argv, "drop_on_overload") == 0) {
++                        drop_overload = 1;
++              } else if (strcmp(*argv, "drop_enqueue") == 0) {
++                      drop_early = 1;
++              } else if (strcmp(*argv, "drop_dequeue") == 0) {
++                      drop_early = 0;
++              } else if (strcmp(*argv, "split_gso") == 0) {
++                      split_gso = 1;
++              } else if (strcmp(*argv, "no_split_gso") == 0) {
++                      split_gso = 0;
++              } else if (strcmp(*argv, "classic_protection") == 0) {
++                        NEXT_ARG();
++                        if (try_get_percentage(&c_protection, *argv, 10) ||
++                          c_protection > 100 ||
++                          c_protection < 0) {
++                                fprintf(stderr,
++                                      "Illegal \"classic_protection\"\n");
++                                return -1;
++                        }
++              } else if (strcmp(*argv, "max_rtt") == 0) {
++                      NEXT_ARG();
++                      if (get_time(&rtt_max, *argv)) {
++                              fprintf(stderr, "Illegal \"rtt_max\"\n");
++                              return -1;
++                      }
++              } else if (strcmp(*argv, "typical_rtt") == 0) {
++                      NEXT_ARG();
++                      if (get_time(&rtt_typ, *argv)) {
++                              fprintf(stderr, "Illegal \"rtt_typ\"\n");
++                              return -1;
++                      }
++              } else if (strcmp(*argv, "help") == 0) {
++                      explain();
++                      return -1;
++              } else {
++                      fprintf(stderr, "What is \"%s\"?\n", *argv);
++                      explain();
++                      return -1;
++              }
++              --argc;
++              ++argv;
++      }
++
++      if (rtt_max || rtt_typ) {
++              double alpha_f, beta_f;
++              SPRINT_BUF(max_rtt_t);
++              SPRINT_BUF(typ_rtt_t);
++              SPRINT_BUF(tupdate_t);
++              SPRINT_BUF(target_t);
++
++              if (!rtt_typ)
++                      rtt_typ = max(rtt_max / RTT_TYP_TO_MAX, 1U);
++              else if (!rtt_max)
++                      rtt_max = rtt_typ * RTT_TYP_TO_MAX;
++              else if (rtt_typ > rtt_max) {
++                      fprintf(stderr, "typical_rtt must be >= max_rtt!\n");
++                      return -1;
++              }
++              if (alpha != DEFAULT_ALPHA_BETA || beta != DEFAULT_ALPHA_BETA ||
++                  tupdate || target)
++                      fprintf(stderr, "rtt_max is specified, ignoring values "
++                              "specified for alpha/beta/tupdate/target\n");
++              target = rtt_typ;
++              tupdate = (double)rtt_typ < (double)rtt_max / 3.0f ?
++                      rtt_typ : (double)rtt_max / 3.0f;
++                tupdate = max(tupdate, 1U);
++              alpha_f = (double)tupdate / rtt_max / rtt_max
++                      * TIME_UNITS_PER_SEC * 0.1f;
++              beta_f = 0.3f / (double)rtt_max * TIME_UNITS_PER_SEC;
++              if (beta_f > ALPHA_BETA_MAX) {
++                      fprintf(stderr, "max_rtt=%s is too low and cause beta "
++                              "to overflow!\n",
++                              sprint_time(rtt_max, max_rtt_t));
++                      return -1;
++              }
++              if (alpha_f < 1.0f / ALPHA_BETA_SCALE ||
++                  beta_f < 1.0f / ALPHA_BETA_SCALE) {
++                      fprintf(stderr, "max_rtt=%s is too large and will "
++                              "cause alpha=%f and/or beta=%f to be rounded "
++                              "down to 0!\n", sprint_time(rtt_max, max_rtt_t),
++                              alpha_f, beta_f);
++                      return -1;
++              }
++              fprintf(stderr, "Auto-configuring parameters using "
++                      "[max_rtt: %s, typical_rtt: %s]: "
++                      "target=%s tupdate=%s alpha=%f beta=%f\n",
++                      sprint_time(rtt_max, max_rtt_t),
++                      sprint_time(rtt_typ, typ_rtt_t),
++                      sprint_time(target, target_t),
++                      sprint_time(tupdate, tupdate_t), alpha_f, beta_f);
++              alpha = alpha_f * ALPHA_BETA_SCALE;
++              beta = beta_f * ALPHA_BETA_SCALE;
++      }
++
++      tail = addattr_nest(n, 1024, TCA_OPTIONS);
++      if (limit)
++              addattr32(n, 1024, TCA_DUALPI2_LIMIT, limit);
++      if (tupdate)
++              addattr32(n, 1024, TCA_DUALPI2_TUPDATE, tupdate);
++      if (target)
++              addattr32(n, 1024, TCA_DUALPI2_TARGET, target);
++      if (alpha != DEFAULT_ALPHA_BETA)
++              addattr32(n, 1024, TCA_DUALPI2_ALPHA, alpha);
++      if (beta != DEFAULT_ALPHA_BETA)
++              addattr32(n, 1024, TCA_DUALPI2_BETA, beta);
++      if (ecn_mask != INET_ECN_NOT_ECT)
++              addattr8(n, 1024, TCA_DUALPI2_ECN_MASK, ecn_mask);
++      if (drop_overload != -1)
++              addattr8(n, 1024, TCA_DUALPI2_DROP_OVERLOAD, drop_overload);
++      if (coupling_factor != -1)
++              addattr8(n, 1024, TCA_DUALPI2_COUPLING, coupling_factor);
++      if (split_gso != -1)
++              addattr8(n, 1024, TCA_DUALPI2_SPLIT_GSO, split_gso);
++      if (step_thresh) {
++              addattr32(n, 1024, TCA_DUALPI2_STEP_THRESH, step_thresh);
++                addattr8(n, 1024, TCA_DUALPI2_STEP_PACKETS, step_packets);
++      }
++      if (drop_early != -1)
++              addattr8(n, 1024, TCA_DUALPI2_DROP_EARLY, drop_early);
++      if (c_protection != -1)
++              addattr8(n, 1024, TCA_DUALPI2_C_PROTECTION, c_protection);
++      addattr_nest_end(n, tail);
++      return 0;
++}
++
++static int dualpi2_print_opt(struct qdisc_util *qu, FILE *f, struct rtattr *opt)
++{
++      struct rtattr *tb[TCA_DUALPI2_MAX + 1];
++      uint32_t tupdate;
++      uint32_t target;
++      uint32_t step_thresh;
++      bool step_packets = false;
++      SPRINT_BUF(b1);
++
++      if (opt == NULL)
++              return 0;
++
++      parse_rtattr_nested(tb, TCA_DUALPI2_MAX, opt);
++
++      if (tb[TCA_DUALPI2_LIMIT] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_LIMIT]) >= sizeof(__u32))
++              fprintf(f, "limit %up ",
++                      rta_getattr_u32(tb[TCA_DUALPI2_LIMIT]));
++      if (tb[TCA_DUALPI2_TARGET] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_TARGET]) >= sizeof(__u32)) {
++              target = rta_getattr_u32(tb[TCA_DUALPI2_TARGET]);
++              fprintf(f, "target %s ", sprint_time(target, b1));
++      }
++      if (tb[TCA_DUALPI2_TUPDATE] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_TUPDATE]) >= sizeof(__u32)) {
++              tupdate = rta_getattr_u32(tb[TCA_DUALPI2_TUPDATE]);
++              fprintf(f, "tupdate %s ", sprint_time(tupdate, b1));
++      }
++      if (tb[TCA_DUALPI2_ALPHA] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_ALPHA]) >= sizeof(__u32)) {
++              fprintf(f, "alpha %f ",
++                      ((float)rta_getattr_u32(tb[TCA_DUALPI2_ALPHA])) /
++                      ALPHA_BETA_SCALE);
++      }
++      if (tb[TCA_DUALPI2_BETA] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_BETA]) >= sizeof(__u32)) {
++              fprintf(f, "beta %f ",
++                      ((float)rta_getattr_u32(tb[TCA_DUALPI2_BETA])) /
++                      ALPHA_BETA_SCALE);
++      }
++      if (tb[TCA_DUALPI2_ECN_MASK] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_ECN_MASK]) >= sizeof(__u8))
++              fprintf(f, "%s ",
++                      get_ecn_type(rta_getattr_u8(tb[TCA_DUALPI2_ECN_MASK])));
++      if (tb[TCA_DUALPI2_COUPLING] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_COUPLING]) >= sizeof(__u8))
++              fprintf(f, "coupling_factor %u ",
++                      rta_getattr_u8(tb[TCA_DUALPI2_COUPLING]));
++      if (tb[TCA_DUALPI2_DROP_OVERLOAD] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_DROP_OVERLOAD]) >= sizeof(__u8)) {
++              if (rta_getattr_u8(tb[TCA_DUALPI2_DROP_OVERLOAD]))
++                      fprintf(f, "drop_on_overload ");
++              else
++                      fprintf(f, "overflow ");
++      }
++      if (tb[TCA_DUALPI2_STEP_PACKETS] &&
++            RTA_PAYLOAD(tb[TCA_DUALPI2_STEP_PACKETS]) >= sizeof(__u8) &&
++          rta_getattr_u8(tb[TCA_DUALPI2_STEP_PACKETS]))
++                        step_packets = true;
++      if (tb[TCA_DUALPI2_STEP_THRESH] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_STEP_THRESH]) >= sizeof(__u32)) {
++              step_thresh = rta_getattr_u32(tb[TCA_DUALPI2_STEP_THRESH]);
++              if (step_packets)
++                      fprintf(f, "step_thresh %upkt ", step_thresh);
++              else
++                      fprintf(f, "step_thresh %s ",
++                              sprint_time(step_thresh, b1));
++      }
++      if (tb[TCA_DUALPI2_DROP_EARLY] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_DROP_EARLY]) >= sizeof(__u8)) {
++              if (rta_getattr_u8(tb[TCA_DUALPI2_DROP_EARLY]))
++                      fprintf(f, "drop_enqueue ");
++              else
++                      fprintf(f, "drop_dequeue ");
++      }
++      if (tb[TCA_DUALPI2_SPLIT_GSO] &&
++          RTA_PAYLOAD(tb[TCA_DUALPI2_SPLIT_GSO]) >= sizeof(__u8)) {
++              if (rta_getattr_u8(tb[TCA_DUALPI2_SPLIT_GSO]))
++                      fprintf(f, "split_gso ");
++              else
++                      fprintf(f, "no_split_gso ");
++      }
++      if (tb[TCA_DUALPI2_C_PROTECTION] &&
++            RTA_PAYLOAD(tb[TCA_DUALPI2_C_PROTECTION]) >= sizeof(__u8))
++                fprintf(f, "classic_protection %u%% ",
++                      rta_getattr_u8(tb[TCA_DUALPI2_C_PROTECTION]));
++
++      return 0;
++}
++
++static int dualpi2_print_xstats(struct qdisc_util *qu, FILE *f,
++                          struct rtattr *xstats)
++{
++      struct tc_dualpi2_xstats *st;
++
++      if (xstats == NULL)
++              return 0;
++
++      if (RTA_PAYLOAD(xstats) < sizeof(*st))
++              return -1;
++
++      st = RTA_DATA(xstats);
++      fprintf(f, "prob %f delay_c %uus delay_l %uus\n",
++              (double)st->prob / (double)MAX_PROB, st->delay_c, st->delay_l);
++      fprintf(f, "pkts_in_c %u pkts_in_l %u maxq %u\n",
++              st->packets_in_c, st->packets_in_l, st->maxq);
++      fprintf(f, "ecn_mark %u step_marks %u\n", st->ecn_mark, st->step_marks);
++      fprintf(f, "credit %d (%c)\n", st->credit, st->credit > 0 ? 'C' : 'L');
++      return 0;
++
++}
++
++struct qdisc_util dualpi2_qdisc_util = {
++      .id = "dualpi2",
++      .parse_qopt     = dualpi2_parse_opt,
++      .print_qopt     = dualpi2_print_opt,
++      .print_xstats   = dualpi2_print_xstats,
++};
+-- 
+2.34.1
+
diff --git a/package/network/utils/iproute2/patches/990-0002-ss-Add-TCP-Prague-inet_diag-interface.patch b/package/network/utils/iproute2/patches/990-0002-ss-Add-TCP-Prague-inet_diag-interface.patch
new file mode 100644 (file)
index 0000000..7a1f296
--- /dev/null
@@ -0,0 +1,157 @@
+From 2e2e9c54326b4990f3ebc8c205aee54f92cb792b Mon Sep 17 00:00:00 2001
+From: Olivier Tilmans <olivier.tilmans@nokia-bell-labs.com>
+Date: Thu, 27 Feb 2020 17:42:21 +0100
+Subject: [PATCH 2/2] ss: Add TCP Prague inet_diag interface
+
+Signed-off-by: Olivier Tilmans <olivier.tilmans@nokia-bell-labs.com>
+---
+ include/uapi/linux/inet_diag.h | 13 +++++++-
+ misc/ss.c                      | 56 ++++++++++++++++++++++++++++++++++
+ 2 files changed, 68 insertions(+), 1 deletion(-)
+
+diff --git a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h
+index ed1c3153..dc3d467f 100644
+--- a/include/uapi/linux/inet_diag.h
++++ b/include/uapi/linux/inet_diag.h
+@@ -76,7 +76,7 @@ enum {
+  * to offset cc+"yes" or to offset cc+"no". "yes" is supposed to be
+  * length of the command and its arguments.
+  */
+- 
++
+ struct inet_diag_bc_op {
+       unsigned char   code;
+       unsigned char   yes;
+@@ -158,6 +158,7 @@ enum {
+       INET_DIAG_CLASS_ID,     /* request as INET_DIAG_TCLASS */
+       INET_DIAG_MD5SIG,
+       INET_DIAG_ULP_INFO,
++      INET_DIAG_PRAGUEINFO, /* TODO(otilmans) this will need to be pushe down once we upgrade the kernel. */
+       INET_DIAG_SK_BPF_STORAGES,
+       INET_DIAG_CGROUP_ID,
+       INET_DIAG_SOCKOPT,
+@@ -231,9 +232,19 @@ struct tcp_bbr_info {
+       __u32   bbr_cwnd_gain;          /* cwnd gain shifted left 8 bits */
+ };
++struct tcp_prague_info {
++      __u64   prague_alpha;
++      __u64   prague_ai_ack_increase;
++      __u32   prague_max_burst;
++      __u32   prague_round;
++      __u32   prague_rtt_indep;
++      bool    prague_enabled;
++};
++
+ union tcp_cc_info {
+       struct tcpvegas_info    vegas;
+       struct tcp_dctcp_info   dctcp;
+       struct tcp_bbr_info     bbr;
++      struct tcp_prague_info  prague;
+ };
+ #endif /* _INET_DIAG_H_ */
+diff --git a/misc/ss.c b/misc/ss.c
+index b39f63fe..61308785 100644
+--- a/misc/ss.c
++++ b/misc/ss.c
+@@ -807,6 +807,15 @@ struct dctcpstat {
+       bool            enabled;
+ };
++struct praguestat {
++      uint64_t alpha;
++      uint64_t ai_ack_increase;
++      uint32_t max_burst;
++      uint32_t round;
++      uint32_t rtt_indep;
++      bool enabled;
++};
++
+ struct tcpstat {
+       struct sockstat     ss;
+       unsigned int        timer;
+@@ -865,6 +874,7 @@ struct tcpstat {
+       bool                app_limited;
+       struct dctcpstat    *dctcp;
+       struct tcp_bbr_info *bbr_info;
++      struct praguestat   *prague;
+ };
+ /* SCTP assocs share the same inode number with their parent endpoint. So if we
+@@ -942,6 +952,22 @@ static const char *tipc_netid_name(int type)
+       }
+ }
++static const char *prague_rtt_indep_mode(uint32_t rtt_indep)
++{
++      switch(rtt_indep) {
++      case 0:
++              return "disabled";
++      case 1:
++              return "rate";
++      case 2:
++              return "scalable";
++      case 3:
++              return "additive";
++      default:
++              return "???";
++      }
++}
++
+ /* Allocate and initialize a new buffer chunk */
+ static struct buf_chunk *buf_chunk_new(void)
+ {
+@@ -2568,6 +2594,19 @@ static void tcp_stats_print(struct tcpstat *s)
+               out(" dctcp:fallback_mode");
+       }
++      if (s->prague && s->prague->enabled) {
++              struct praguestat *prague = s->prague;
++
++              out(" prague:(alpha:%g%%,ai_ack_increase:%g%%,max_burst:%u,"
++                  "round:%u,rtt_indep:%s)",
++                  (double)prague->alpha / (double)(1ULL << 20U) * 100.0f,
++                  (double)prague->ai_ack_increase / (double)(1ULL << 20) *
++                  100.0f, prague->max_burst, prague->round,
++                  prague_rtt_indep_mode(prague->rtt_indep));
++      } else if (s->prague) {
++              out(" prague:reno-fallback-mode");
++      }
++
+       if (s->bbr_info) {
+               __u64 bw;
+@@ -3040,6 +3079,22 @@ static void tcp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r,
+                       s.dctcp         = dctcp;
+               }
++              if (tb[INET_DIAG_PRAGUEINFO]) {
++                      struct praguestat *prague = malloc(sizeof(struct
++                                                                praguestat));
++
++                      const struct tcp_prague_info *pinfo
++                              = RTA_DATA(tb[INET_DIAG_PRAGUEINFO]);
++
++                      prague->enabled = !!pinfo->prague_enabled;
++                      prague->alpha   = pinfo->prague_alpha;
++                      prague->ai_ack_increase = pinfo->prague_ai_ack_increase;
++                      prague->max_burst = pinfo->prague_max_burst;
++                      prague->round = pinfo->prague_round;
++                      prague->rtt_indep = pinfo->prague_rtt_indep;
++                      s.prague        = prague;
++              }
++
+               if (tb[INET_DIAG_BBRINFO]) {
+                       const void *bbr_info = RTA_DATA(tb[INET_DIAG_BBRINFO]);
+                       int len = min(RTA_PAYLOAD(tb[INET_DIAG_BBRINFO]),
+@@ -3086,6 +3141,7 @@ static void tcp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r,
+               tcp_stats_print(&s);
+               free(s.dctcp);
+               free(s.bbr_info);
++              free(s.prague);
+       }
+       if (tb[INET_DIAG_MD5SIG]) {
+               struct tcp_diag_md5sig *sig = RTA_DATA(tb[INET_DIAG_MD5SIG]);
+-- 
+2.34.1
+