Skip to content

bind() takes ~10 ms for every socket family under WSL2, independent of networking mode #41470

Description

Windows Version

Microsoft Windows [Version 10.0.26200.9106]

WSL Version

2.7.10.0

Are you using WSL 1 or WSL 2?

  • WSL 2
  • WSL 1

Kernel Version

6.18.33.2-2

Distro Version

Ubuntu 24.04 (Noble Numbat)

Other Software

openvpn3-linux v26-3 and v27.1 (OpenVPN core 3.11.5 / 3.11.7) — affected, but only
as a symptom. The benchmark below reproduces the underlying behaviour with nothing
installed beyond Python 3.

Repro Steps

bind() takes ~10 ms on every socket family under WSL2, while socket() creation
is free. This is not specific to networking mode — a private (non-mirrored) network
namespace shows the same cost.

  1. .wslconfig:
[wsl2]
networkingMode=mirrored
dnsTunneling=true
firewall=false
autoProxy=true

[experimental]
hostAddressLoopback=true
  1. Save and run this benchmark. socket() is timed separately as a control:
import socket, time, statistics, os
N = 200
def bench(label, make, act):
    ts = []
    for _ in range(N):
        s = make()
        t0 = time.perf_counter(); act(s); t1 = time.perf_counter()
        ts.append((t1 - t0) * 1000); s.close()
    ts.sort()
    print(f"{label:36s} median {statistics.median(ts):7.3f} ms  min {ts[0]:6.3f}  max {ts[-1]:7.3f}")

bench("AF_NETLINK bind()",
      lambda: socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE),
      lambda s: s.bind((0, 0)))
bench("AF_NETLINK socket() only (control)",
      lambda: socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE),
      lambda s: None)
bench("AF_UNIX bind() (abstract)",
      lambda: socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM),
      lambda s: s.bind("\0b%d_%d" % (os.getpid(), time.time_ns())))
bench("AF_INET/UDP bind()",
      lambda: socket.socket(socket.AF_INET, socket.SOCK_DGRAM),
      lambda s: s.bind(("127.0.0.1", 0)))
  1. To separate mirrored-mode cost from baseline cost, run the same script again
    inside a private network namespace, which is not mirrored:
sudo unshare -n -- sh -c "ip link set lo up; exec python3 bench.py"

Expected Behavior

bind() on AF_UNIX and AF_NETLINK involves no network stack coordination and
should complete in microseconds, as it does on a conventional Linux kernel.
AF_INET bind() to an ephemeral port on loopback should likewise be
inexpensive.

Actual Behavior

                            host netns      private netns
AF_NETLINK bind()            10.098 ms       10.092 ms     <- unchanged
AF_UNIX bind()               10.150 ms       10.495 ms     <- unchanged
AF_INET/UDP bind()           21.141 ms       10.500 ms     <- halved
socket() only (control)       0.000 ms        0.000 ms

Two independent effects:

  1. A baseline ~10 ms bind() cost on every socket family, identical inside a
    private namespace. Not related to mirrored networking. This is the one with
    real impact.
  2. Mirrored mode adds a second ~10 ms increment, but only to AF_INET
    (21 ms vs 10.5 ms). Plausibly port coordination with the Windows host —
    AF_UNIX and AF_NETLINK have no ports and are unaffected.

socket() measuring ~0 ms rules out general syscall overhead.

Correction (see comment below). I originally wrote that the ~10 ms figure
looked like a scheduler tick. That is wrong — this kernel is CONFIG_HZ=250
(a 4 ms tick), and 10 ms is not a multiple of 4 ms. A 400-sample histogram is
sharply unimodal at 10–12 ms for AF_NETLINK (97% within 8–12 ms) and at
20–22 ms for AF_INET (90%), i.e. almost exactly 2×. That points at one and
two instances of a fixed ~10 ms synchronous cost rather than anything
timer-aligned. Confirmed independently of Python via strace -T on
openvpn3-service-netcfg (C++): 901 bind() calls, mean 9.1 ms, max 10.8 ms.

Real-world impact. openvpn3-linux cannot establish a tunnel at all. Its
openvpn3-service-netcfg opens a fresh AF_NETLINK socket per configuration
operation; with a split-tunnel profile pushing ~900 routes (a commonplace
corporate configuration) that is ~900 × ~10 ms ≈ 9 s, against a fixed 5 s D-Bus
timeout on its Establish() call:

901 bind() calls    mean 9.1 ms   max 10.8 ms   total 8.183 s

Error calling NetCfgDevice::Establish(): ... Timeout was reached
Failed configuring TUN device (TUN_IFACE_CREATE)

507 of the 900 operations had completed when the caller aborted. The identical
route set installed through a single reused socket (ip -batch) takes ~0.4 s, so
the per-bind() cost is the entire difference between working and not working.
Switching to NAT mode does not help, since the baseline cost is not
mirrored-mode related. The per-socket churn is also being reported to
openvpn3-linux separately.

Diagnostic Logs

Per-operation pattern from strace -f -tt -T on openvpn3-service-netcfg,
repeating once per route:

socket(AF_UNIX, SOCK_DGRAM) -> ioctl(SIOCGIFINDEX) -> close
socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)
  -> setsockopt(SO_SNDBUF) -> setsockopt(SO_RCVBUF)
  -> bind()          <-- ~9-10 ms, dominant cost
  -> getsockname -> sendmsg -> recvmsg -> close

Within the 5 s Establish() window: 507 bind() calls, 4.601 s in bind()
alone out of 4.939 s total worker syscall time — 93% of the budget.

Possibly related, though a different mechanism: #41443 (mirrored networking drops
replies from a TUN-based VPN adapter). Same kernel and distro; that one is about
packet delivery, this one about syscall latency.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions