Attention is currently required from: Arne Schwabe.

Hello Arne Schwabe,

I'd like you to do a code review.
Please visit

    https://gerrit.openvpn.net/c/openvpn/+/1991?usp=email

to review the following change.


Change subject: dco_linux: enable RPS on newly created DCO interfaces
......................................................................

dco_linux: enable RPS on newly created DCO interfaces

When multiple TCP streams share an OpenVPN tunnel, their ACKs arrive
through the same outer flow and can be processed on a single NIC RX CPU.
Delivering the decrypted ACKs can also drain TCP transmit queues inline,
concentrating receive processing, transmission and crypto work on that
CPU despite the parallelism available between inner flows.

Enable Receive Packet Steering (RPS) on newly created DCO interfaces to
distribute inner flow processing across CPUs, after decryption. Build a
mask covering all possible CPU IDs and let the kernel filter out offline
CPUs.

Leave pre-existing interfaces and nonzero RPS masks unchanged, including
masks inherited from net.core.rps_default_mask. Failure to configure RPS
is nonfatal.

Tested on a Ryzen 9 9950X with Fedora's stock ovpn module from kernel
7.2.5-200.fc44.x86_64 and two ConnectX-4 Lx ports were cabled at 25
Gb/s, The tunnel used IPv4/UDP, AES-256-GCM and MTU 1448. Values are
medians across three 20 seconds iperf3 runs, after a 3-second warmup:

flows    RPS off     RPS on      change
  1         9.01      12.06      +33.9%
  4         9.41      16.90      +79.6%

At four streams, the sender NIC IRQ CPU's average softirq utilization
was 98.7-99.5% off and 4.7-47.2% on.

Change-Id: I97c55e211f9113d090808724ab43cb3c94099fb0
Signed-off-by: Ralf Lici <[email protected]>
---
M README.dco.md
M src/openvpn/dco_linux.c
2 files changed, 154 insertions(+), 0 deletions(-)



  git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/91/1991/1

diff --git a/README.dco.md b/README.dco.md
index a93dc94..47c4ae3 100644
--- a/README.dco.md
+++ b/README.dco.md
@@ -52,6 +52,11 @@

 in your log.

+On Linux, OpenVPN enables Receive Packet Steering (RPS) on DCO interfaces it
+creates, distributing inner-flow processing across online CPUs. Existing
+nonzero RPS masks, including masks inherited from `net.core.rps_default_mask`,
+are preserved.
+

 Getting started (Windows)
 -------------------------
diff --git a/src/openvpn/dco_linux.c b/src/openvpn/dco_linux.c
index e6e12b4..66543b9 100644
--- a/src/openvpn/dco_linux.c
+++ b/src/openvpn/dco_linux.c
@@ -33,6 +33,7 @@
 #include "buffer.h"
 #include "networking.h"
 #include "openvpn.h"
+#include "options_util.h"

 #include "socket.h"
 #include "tun.h"
@@ -553,6 +554,149 @@
     return "unknown";
 }

+/**
+ * Build an RPS mask covering all possible CPU IDs. The kernel filters out
+ * offline CPUs when installing the mask, so only the last ID in the sorted
+ * sysfs CPU list is needed, even when the list has gaps.
+ * Removes the trailing newline from cpus before parsing the last ID.
+ */
+static bool
+ovpn_rps_mask_full(char *cpus, struct buffer *mask)
+{
+    /* the cpus string contains range formats like "0-31" or "0-15,32-47" */
+    size_t len = strspn(cpus, "0123456789,-");
+    if (!len || cpus[len] != '\n' || cpus[len + 1] != '\0')
+    {
+        return false;
+    }
+
+    /* find the last separator (either '-' or ',') */
+    const char *last = cpus + len;
+    while (last > cpus && last[-1] >= '0' && last[-1] <= '9')
+    {
+        --last;
+    }
+    if (last == cpus + len)
+    {
+        return false;
+    }
+
+    cpus[len] = '\0';
+    int last_cpu;
+    if (!atoi_constrained(last, &last_cpu, "last possible CPU", 0, INT_MAX, 
M_WARN))
+    {
+        return false;
+    }
+
+    /* the most significant group may contain fewer than 32 bits */
+    unsigned int word = UINT32_MAX >> (31 - last_cpu % 32);
+    /* first handle the most significant, potentially partial group */
+    if (!buf_printf(mask, "%x", word))
+    {
+        return false;
+    }
+    /* then write the other chunks, comma-separated as required by bitmap_parse
+     * in linux/lib/bitmap-str.c
+     */
+    for (int i = last_cpu / 32; i > 0; --i)
+    {
+        if (!buf_printf(mask, ",ffffffff"))
+        {
+            return false;
+        }
+    }
+    return buf_printf(mask, "\n");
+}
+
+static void
+ovpn_enable_rps(const char *dev)
+{
+    char rps_path[PATH_MAX];
+    /* ovpn interfaces have a single RX queue */
+    if (!checked_snprintf(rps_path, sizeof(rps_path),
+                          "/sys/class/net/%s/queues/rx-0/rps_cpus", dev))
+    {
+        msg(M_WARN, "DCO: cannot build RPS path for %s", dev);
+        return;
+    }
+
+    struct gc_arena gc = gc_new();
+    struct buffer current = buffer_read_from_file(rps_path, &gc);
+    /* buffer_read_from_file counts the terminating NUL as content */
+    buf_rmtail(&current, '\0');
+    if (!buf_defined(&current) || BLEN(&current) < 2 || *BLAST(&current) != 
'\n')
+    {
+        /* RPS may be unavailable, for example with CONFIG_RPS disabled */
+        msg(D_DCO_DEBUG, "DCO: cannot read RPS mask from %s", rps_path);
+        goto out;
+    }
+    if (strspn(BSTR(&current), "0,") != (size_t)BLEN(&current) - 1)
+    {
+        /* new devices can still inherit an administrator's rps_default_mask */
+        msg(D_DCO_DEBUG, "DCO: preserving existing RPS mask in %s: %.*s", 
rps_path,
+            BLEN(&current) - 1, BSTR(&current));
+        goto out;
+    }
+
+    const char *cpus_path = "/sys/devices/system/cpu/possible";
+    struct buffer cpus = buffer_read_from_file(cpus_path, &gc);
+    if (!buf_defined(&cpus))
+    {
+        msg(M_WARN, "DCO: cannot read possible CPUs from %s", cpus_path);
+        goto out;
+    }
+
+    /* a sysfs attribute must fit in one page and be written in one call
+     * (docs.kernel.org/filesystems/sysfs.html#reading-writing-attribute-data)
+     */
+    long page_size = sysconf(_SC_PAGESIZE);
+    if (page_size <= 0 || page_size > INT_MAX)
+    {
+        msg(M_WARN, "DCO: cannot determine page size for RPS configuration");
+        goto out;
+    }
+    struct buffer mask = alloc_buf_gc((size_t)page_size, &gc);
+    if (!ovpn_rps_mask_full(BSTR(&cpus), &mask))
+    {
+        msg(M_WARN, "DCO: cannot build RPS mask from %s", cpus_path);
+        goto out;
+    }
+
+    int fd = open(rps_path, O_WRONLY);
+    if (fd < 0)
+    {
+        msg(M_WARN | M_ERRNO, "DCO: cannot open %s for enabling RPS", 
rps_path);
+        goto out;
+    }
+    /* submit the complete mask in one write and retry only on interruption */
+    ssize_t written;
+    do
+    {
+        written = write(fd, BPTR(&mask), (size_t)BLEN(&mask));
+    } while (written < 0 && errno == EINTR);
+
+    if (written < 0)
+    {
+        msg(M_WARN | M_ERRNO, "DCO: cannot enable RPS on %s", dev);
+    }
+    else if (written != BLEN(&mask))
+    {
+        msg(M_WARN, "DCO: incomplete RPS mask write to %s", rps_path);
+    }
+    else
+    {
+        msg(D_DCO, "DCO: RPS enabled on %s with CPU mask %.*s", dev, 
BLEN(&mask) - 1,
+            BSTR(&mask));
+    }
+    if (close(fd) < 0)
+    {
+        msg(M_WARN | M_ERRNO, "DCO: cannot close %s", rps_path);
+    }
+
+out:
+    gc_free(&gc);
+}
+
 int
 open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev)
 {
@@ -584,6 +728,11 @@
             return -EINVAL;
         }
     }
+    /* configure RPS on the device only if it was not pre-created */
+    if (ret == 0)
+    {
+        ovpn_enable_rps(dev);
+    }

     tt->dco.ifindex = if_nametoindex(dev);
     if (!tt->dco.ifindex)

--
To view, visit https://gerrit.openvpn.net/c/openvpn/+/1991?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.openvpn.net/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: openvpn
Gerrit-Branch: master
Gerrit-Change-Id: I97c55e211f9113d090808724ab43cb3c94099fb0
Gerrit-Change-Number: 1991
Gerrit-PatchSet: 1
Gerrit-Owner: Ralf Lici <[email protected]>
Gerrit-Reviewer: Arne Schwabe <[email protected]>
Gerrit-CC: openvpn-devel <[email protected]>
Gerrit-Attention: Arne Schwabe <[email protected]>
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to