]> git.ipfire.org Git - thirdparty/wireguard-go.git/commitdiff
conn: set SO_{SND,RCV}BUF to 7MB on the Bind UDP socket
authorJordan Whited <jordan@tailscale.com>
Thu, 2 Mar 2023 23:25:19 +0000 (15:25 -0800)
committerJason A. Donenfeld <Jason@zx2c4.com>
Fri, 10 Mar 2023 13:52:20 +0000 (14:52 +0100)
The conn.Bind UDP sockets' send and receive buffers are now being sized
to 7MB, whereas they were previously inheriting the system defaults.
The system defaults are considerably small and can result in dropped
packets on high speed links. By increasing the size of these buffers we
are able to achieve higher throughput in the aforementioned case.

The iperf3 results below demonstrate the effect of this commit between
two Linux computers with 32-core Xeon Platinum CPUs @ 2.9Ghz. There is
roughly ~125us of round trip latency between them.

The first result is from commit 792b49c which uses the system defaults,
e.g. net.core.{r,w}mem_max = 212992. The TCP retransmits are correlated
with buffer full drops on both sides.

Starting Test: protocol: TCP, 1 streams, 131072 byte blocks
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-10.00  sec  4.74 GBytes  4.08 Gbits/sec  2742   285 KBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
Test Complete. Summary Results:
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  4.74 GBytes  4.08 Gbits/sec  2742   sender
[  5]   0.00-10.04  sec  4.74 GBytes  4.06 Gbits/sec         receiver

The second result is after increasing SO_{SND,RCV}BUF to 7MB, i.e.
applying this commit.

Starting Test: protocol: TCP, 1 streams, 131072 byte blocks
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-10.00  sec  6.14 GBytes  5.27 Gbits/sec    0   3.15 MBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
Test Complete. Summary Results:
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  6.14 GBytes  5.27 Gbits/sec    0    sender
[  5]   0.00-10.04  sec  6.14 GBytes  5.25 Gbits/sec         receiver

The specific value of 7MB is chosen as it is the max supported by a
default configuration of macOS. A value greater than 7MB may further
benefit throughput for environments with higher network latency and
lower CPU clocks, but will also increase latency under load
(bufferbloat). Some platforms will silently clamp the value to other
maximums. On Linux, we use SO_{SND,RCV}BUFFORCE in case 7MB is beyond
net.core.{r,w}mem_max.

Co-authored-by: James Tucker <james@tailscale.com>
Signed-off-by: James Tucker <james@tailscale.com>
Signed-off-by: Jordan Whited <jordan@tailscale.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
conn/controlfns.go
conn/controlfns_linux.go
conn/controlfns_unix.go
conn/controlfns_windows.go [new file with mode: 0644]

index fe32871a804af73b0fc7269faccb0f7f9848e5fb..4f7d90fa10f040a228f71ec347cdc9609186a05a 100644 (file)
@@ -10,6 +10,13 @@ import (
        "syscall"
 )
 
+// UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it is
+// the max supported by a default configuration of macOS. Some platforms will
+// silently clamp the value to other maximums, such as linux clamping to
+// net.core.{r,w}mem_max (see _linux.go for additional implementation that works
+// around this limitation)
+const socketBufferSize = 7 << 20
+
 // controlFn is the callback function signature from net.ListenConfig.Control.
 // It is used to apply platform specific configuration to the socket prior to
 // bind.
index 9e26d952753937d04025812500b4a9e69ed0efb7..aff62456f38647b269d4940e79ba779b34eae285 100644 (file)
@@ -15,6 +15,21 @@ import (
 func init() {
        controlFns = append(controlFns,
 
+               // Attempt to set the socket buffer size beyond net.core.{r,w}mem_max by
+               // using SO_*BUFFORCE. This requires CAP_NET_ADMIN, and is allowed here to
+               // fail silently - the result of failure is lower performance on very fast
+               // links or high latency links.
+               func(network, address string, c syscall.RawConn) error {
+                       return c.Control(func(fd uintptr) {
+                               // Set up to *mem_max
+                               _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUF, socketBufferSize)
+                               _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUF, socketBufferSize)
+                               // Set beyond *mem_max if CAP_NET_ADMIN
+                               _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, socketBufferSize)
+                               _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, socketBufferSize)
+                       })
+               },
+
                // Enable receiving of the packet information (IP_PKTINFO for IPv4,
                // IPV6_PKTINFO for IPv6) that is used to implement sticky socket support.
                func(network, address string, c syscall.RawConn) error {
index 9738c73d30452d5988dae8eca6e417cf79e26dba..c4536d4bb4828989b7f7dabce5643a80cbf592ef 100644 (file)
@@ -15,6 +15,13 @@ import (
 
 func init() {
        controlFns = append(controlFns,
+               func(network, address string, c syscall.RawConn) error {
+                       return c.Control(func(fd uintptr) {
+                               _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUF, socketBufferSize)
+                               _ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUF, socketBufferSize)
+                       })
+               },
+
                func(network, address string, c syscall.RawConn) error {
                        var err error
                        if network == "udp6" {
diff --git a/conn/controlfns_windows.go b/conn/controlfns_windows.go
new file mode 100644 (file)
index 0000000..c3bdf7d
--- /dev/null
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
+ */
+
+package conn
+
+import (
+       "syscall"
+
+       "golang.org/x/sys/windows"
+)
+
+func init() {
+       controlFns = append(controlFns,
+               func(network, address string, c syscall.RawConn) error {
+                       return c.Control(func(fd uintptr) {
+                               _ = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_RCVBUF, socketBufferSize)
+                               _ = windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_SNDBUF, socketBufferSize)
+                       })
+               },
+       )
+}