Reporting a failure mode in openvpn3-linux where a split-tunnel profile
with a large pushed route set cannot connect. openvpn3-service-netcfg's
per-operation netlink socket churn exceeds the client's fixed
Establish() timeout. Reproduced identically on v26-3 and v27.1.

ENVIRONMENT

  openvpn3-linux    v26-3 and v27.1 (both affected)
  OpenVPN core      3.11.5 / 3.11.7
  OS                Ubuntu 24.04, systemd + D-Bus
  Kernel            6.18.33.2 (WSL2)
  Profile           split-tunnel, ~900 pushed IPv4 prefixes

SYMPTOM

Authentication succeeds, the tunnel negotiates, then:

    Virtual device '<id>' registered on /net/openvpn/v3/netcfg/<id>
    Adding IP Address 172.x.y.z/24 gw 172.x.y.1 ipv6: no
    Setting remote IP address to <server> ipv6: no
    Error calling NetCfgDevice::Establish(): [Proxy::Client(
      'net.openvpn.v3.netcfg', ..., 'Establish')] Timeout was reached
    Failed configuring TUN device (TUN_IFACE_CREATE)

Five seconds later netcfg finishes the work the caller already gave up
on and rolls it back, logging ~900 of:

    Error while executing NetlinkRoute4(add: 0) tun0: -2
    Error while executing NetlinkAddr4(add: 0) tun0: -2
    Error while executing NetlinkLinkSet tun0 mtu 1500: -1

Those are teardown, not the cause. The tun itself is created fine --
ioctl(TUNSETIFF) returns 0 in 221us.

ROOT CAUSE

netcfg allocates, binds, uses and closes a fresh AF_NETLINK socket for
every single operation. From strace, this unit repeats 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()          <-- dominant cost
      -> getsockname -> sendmsg -> recvmsg -> close

On a typical kernel bind() is microseconds here and nobody notices. In
this environment bind() costs ~10ms, so the fixed per-operation
overhead becomes the whole story:

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

    Within the 5s Establish() window:
      507 bind() calls
      4.601s in bind() alone
      4.939s total worker syscall time
      -> 93% of the budget is bind()

~900 routes x ~10ms is ~9s against a 5s deadline. netcfg completed 507
of them before the caller aborted.

Note this is not netlink-specific slowness in the environment. AF_UNIX
bind() measures the same ~10ms and AF_INET bind() ~21ms, while socket()
creation is free. The point is that netcfg pays a fixed per-socket
setup cost ~900 times where once would do.

For contrast, on the same host, installing the same route set through a
single reused socket (ip -batch) takes ~0.4s, roughly 20x faster.
Individually via separate "ip route add" invocations it is ~0.9s per 45
routes.

SUGGESTED FIXES

1. Reuse one netlink socket for the duration of an Establish(), or for
   the lifetime of the NetCfgDevice, instead of one per operation. This
   removes the failure entirely and is a straight win everywhere --
   ~900 socket create/bind/close cycles per connection is pure overhead
   even when bind() is cheap.

2. The Establish() D-Bus call timeout appears to be a fixed 5s with no
   override, so a slower-than-anticipated environment has no escape
   hatch. Consider scaling it with the number of queued networks, or
   making it configurable.

SEPARATE BUG: SEGFAULT ON TIMED-OUT CALLER (v27.1)

When the caller gives up, v27.1 netcfg dereferences the D-Bus unique
name of the process that has already exited, and crashes:

    ** ERROR ** Async call failed: [DBus::Credentials::GetPID] Failed
    to retrieve process ID for bus name ':1.75': Could not get PID of
    name ':1.75': no such name
      error domain: net.openvpn.gdbuspp
    kernel: pool[20958]: segfault at 5e2123c54d3d ip 00005e24841cca58
      sp 0000775b767fb250 error 4 in openvpn3-service-netcfg[...]
    kernel: openvpn3-service-netcfg: pool: potentially unexpected
      fatal signal 11

Looks like a use-after-free or missing existence check on the caller's
bus name in the async error path. v26-3 handles the same timeout
gracefully and only logs the netlink errors, which is why the crash is
v27.1-specific while the underlying timeout affects both.
libgdbuspp3 3-3.

WORKAROUND FOR ANYONE HITTING THIS

Append "route-nopull" to the profile so Establish() only creates the
tun and sets the address, MTU and link state -- about 5 netlink ops,
well inside 5s. Then install the pushed routes separately through one
batched netlink socket with ip -batch. Capture the route list from a
normal connect attempt with:

    journalctl --since -5min | grep -oE 'Adding network [0-9./]+' \
      | awk '{print $3}' | sort -u

Caution: check whether the VPN server's own address falls inside any
pushed prefix. If it does, a protective /32 host route via the physical
gateway must exist before the tunnel routes go in, or the transport
deadlocks.


_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to