[GH-ISSUE #2997] Linux: endpoint exclusion route skipped after switching servers due to failed default-gateway detection #1376

Open
opened 2026-10-13 20:41:11 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @samdark on GitHub (Aug 17, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/2997

Related to #2048, but not fixed by #2840.

I have two AWG2 server connections in the Linux client:

  • The first server works normally.
  • The second server reports Connected and completes the handshake, but no traffic passes.
  • The same second connection works on a phone connected to the same LAN.
  • Recreating the second server from scratch did not help.

The client log shows that it fails to install the endpoint exclusion route:

Amnezia LinuxRouteMonitor : Adding exclusion route for <VPN_SERVER_IP>/32
Amnezia LinuxRouteMonitor : No default gateway available, skipping exclusion route

It then installs the full-tunnel routes:

Adding route to 0.0.0.0/1
Adding route to 128.0.0.0/1

Consequently, traffic to the VPN endpoint itself is routed into amn0. The initial handshake succeeds before the full-tunnel routes are installed, so the UI misleadingly reports that the connection is active.

The working server already had a correct endpoint route through the physical interface. Adding the missing route manually fixed the second connection immediately:

sudo ip route replace <VPN_SERVER_IP>/32 via <LAN_GATEWAY> dev <PHYSICAL_INTERFACE>

After this command, AWG2 traffic through the second server works normally.

The logs also contain:

Error: argument "amnvpnrt" is wrong: invalid table ID
mkdir: No such file or directory: /sys/fs/cgroup/net_cls/amnvpnexclusions/

Environment

  • AmneziaVPN: 5.0.0.5 (4d28f81, built July 26, 2026)
  • OS: Ubuntu 26.04 LTS (Resolute Raccoon)
  • Kernel: 7.0.0-29-generic
  • Architecture: x86_64
  • VPN interface: amn0
  • Protocol: AmneziaWG v2

This appears to be a Linux client bug in default-gateway detection or endpoint exclusion-route management, particularly when multiple server connections are configured or switched between.

#2840 does not cover the missing endpoint exclusion-route variant.

Originally created by @samdark on GitHub (Aug 17, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/2997 Related to #2048, but not fixed by #2840. I have two AWG2 server connections in the Linux client: - The first server works normally. - The second server reports `Connected` and completes the handshake, but no traffic passes. - The same second connection works on a phone connected to the same LAN. - Recreating the second server from scratch did not help. The client log shows that it fails to install the endpoint exclusion route: ``` Amnezia LinuxRouteMonitor : Adding exclusion route for <VPN_SERVER_IP>/32 Amnezia LinuxRouteMonitor : No default gateway available, skipping exclusion route ``` It then installs the full-tunnel routes: ``` Adding route to 0.0.0.0/1 Adding route to 128.0.0.0/1 ``` Consequently, traffic to the VPN endpoint itself is routed into `amn0`. The initial handshake succeeds before the full-tunnel routes are installed, so the UI misleadingly reports that the connection is active. The working server already had a correct endpoint route through the physical interface. Adding the missing route manually fixed the second connection immediately: ``` sudo ip route replace <VPN_SERVER_IP>/32 via <LAN_GATEWAY> dev <PHYSICAL_INTERFACE> ``` After this command, AWG2 traffic through the second server works normally. The logs also contain: ``` Error: argument "amnvpnrt" is wrong: invalid table ID mkdir: No such file or directory: /sys/fs/cgroup/net_cls/amnvpnexclusions/ ``` Environment - AmneziaVPN: `5.0.0.5` (`4d28f81`, built July 26, 2026) - OS: Ubuntu 26.04 LTS (Resolute Raccoon) - Kernel: `7.0.0-29-generic` - Architecture: x86_64 - VPN interface: `amn0` - Protocol: AmneziaWG v2 This appears to be a Linux client bug in default-gateway detection or endpoint exclusion-route management, particularly when multiple server connections are configured or switched between. #2840 does not cover the missing endpoint exclusion-route variant.
Author
Owner

@cucumberian commented on GitHub (Aug 25, 2026):

Похоже я тоже словил этот баг в новой версии, вот репорт от квена:

Related to #2997 and #2048 — I was able to pin down the exact trigger for the "No default gateway available" failure on my machine.

Environment

  • AmneziaVPN: 5.0.1.5 (linux x64, .run installer)
  • OS: Linux Mint 22.3 (Zena), kernel 6.8.0-138-generic, x86_64
  • Two AWG2 servers configured; full-tunnel mode ("all traffic")

Symptom

Intermittent: after connecting the UI shows "Connected" but no traffic passes at all — DNS and TCP hang (connections stuck in SYN-SENT to the tunnel's DNS server). Reconnecting sometimes fixes it, which makes it look random. It correlates with how many routes are in the table at connect time (I run Docker + Tailscale, so this fluctuates).

Root cause

NetworkUtilities::getGatewayAndIface() (client/core/utils/networkUtilities.cpp, Linux branch) dumps the routing table via netlink RTM_GETROUTE into a fixed 8192-byte buffer:

  • L294: constexpr int BUFFER_SIZE = 8192;
  • L302: char msgbuf[100], buffer[BUFFER_SIZE];
  • L337–L352 (recv loop): when the accumulated dump reaches 8192 bytes, subsequent recv() calls are made with length 0 (L338) and return 0; nlh then points one past the end of buffer[], the header check fails (L347–L351), L350 logs perror("Error in received packet") (with a stale errno — ENOENT/EDOM etc.) and the function returns {} — an empty gateway.

On my host the RTM_GETROUTE dump is 8644 bytes, so this triggers deterministically whenever the Docker/Tailscale routes are present:

chunk 0: received=2476   (RTM_NEWROUTE ...)
chunk 1: received=5716   (buffer now full)
chunk 2: recv(len = 8192 - 8192 = 0) -> returns 0, nlh points past buffer end
         NLMSG_OK fails -> "Error in received packet" -> return {}

With a larger buffer the same code completes and correctly finds gateway 192.168.0.1 on enp6s0, so the only problem is the fixed buffer size / missing handling of dumps that do not fit.

When the function returns empty, LinuxRouteMonitor::rtmSendRoute() (client/platforms/linux/daemon/linuxroutemonitor.cpp L166–L171) skips installing the endpoint exclusion route:

Amnezia LinuxRouteMonitor : Adding exclusion route for <SERVER_IP>/32
Amnezia LinuxRouteMonitor : No default gateway available, skipping exclusion route

and then installs the full-tunnel routes (0.0.0.0/1 + 128.0.0.0/1 via amn0). Without the /32 exclusion for the server endpoint, all traffic — including the WireGuard handshake and keepalive packets to the server itself — is routed into amn0 instead of out through the physical interface. The tunnel therefore carries no usable traffic while the UI still reports "Connected".

Reproduction

Any host whose netlink RTM_GETROUTE dump exceeds 8192 bytes (many interfaces/routes: Docker bridges, Tailscale, multiple VPNs). Connect AmneziaVPN in full-tunnel mode and check journalctl -u AmneziaVPN for the two lines above.

Workaround

After connecting, add the exclusion route manually:

sudo ip route replace <SERVER_IP>/32 via <LAN_GATEWAY> dev <PHYSICAL_IFACE>

or simply disconnect/reconnect until the dump happens to fit under 8 KiB (unreliable).

Suggested fix

  • Grow the buffer and/or accumulate recv() chunks until NLMSG_DONE instead of assuming the whole dump fits in one fixed buffer;
  • as a fallback when gateway detection fails, still install the exclusion route (e.g. plain dev <iface> unicast) or surface an error in the UI — silently skipping it makes full-tunnel mode dead-on-arrival with no visible indication.

(Line numbers refer to the dev branch and may shift slightly between releases.)

<!-- gh-comment-id:5412547404 --> @cucumberian commented on GitHub (Aug 25, 2026): Похоже я тоже словил этот баг в новой версии, вот репорт от квена: Related to #2997 and #2048 — I was able to pin down the exact trigger for the "No default gateway available" failure on my machine. ## Environment - AmneziaVPN: 5.0.1.5 (linux x64, .run installer) - OS: Linux Mint 22.3 (Zena), kernel 6.8.0-138-generic, x86_64 - Two AWG2 servers configured; full-tunnel mode ("all traffic") ## Symptom Intermittent: after connecting the UI shows "Connected" but no traffic passes at all — DNS and TCP hang (connections stuck in SYN-SENT to the tunnel's DNS server). Reconnecting sometimes fixes it, which makes it look random. It correlates with how many routes are in the table at connect time (I run Docker + Tailscale, so this fluctuates). ## Root cause `NetworkUtilities::getGatewayAndIface()` (`client/core/utils/networkUtilities.cpp`, Linux branch) dumps the routing table via netlink RTM_GETROUTE into a fixed 8192-byte buffer: - L294: `constexpr int BUFFER_SIZE = 8192;` - L302: `char msgbuf[100], buffer[BUFFER_SIZE];` - L337–L352 (recv loop): when the accumulated dump reaches 8192 bytes, subsequent `recv()` calls are made with length 0 (L338) and return 0; `nlh` then points one past the end of `buffer[]`, the header check fails (L347–L351), L350 logs `perror("Error in received packet")` (with a stale errno — ENOENT/EDOM etc.) and the function returns `{}` — an empty gateway. On my host the RTM_GETROUTE dump is 8644 bytes, so this triggers deterministically whenever the Docker/Tailscale routes are present: chunk 0: received=2476 (RTM_NEWROUTE ...) chunk 1: received=5716 (buffer now full) chunk 2: recv(len = 8192 - 8192 = 0) -> returns 0, nlh points past buffer end NLMSG_OK fails -> "Error in received packet" -> return {} With a larger buffer the same code completes and correctly finds `gateway 192.168.0.1 on enp6s0`, so the only problem is the fixed buffer size / missing handling of dumps that do not fit. When the function returns empty, `LinuxRouteMonitor::rtmSendRoute()` (`client/platforms/linux/daemon/linuxroutemonitor.cpp` L166–L171) skips installing the endpoint exclusion route: Amnezia LinuxRouteMonitor : Adding exclusion route for <SERVER_IP>/32 Amnezia LinuxRouteMonitor : No default gateway available, skipping exclusion route and then installs the full-tunnel routes (0.0.0.0/1 + 128.0.0.0/1 via amn0). Without the /32 exclusion for the server endpoint, all traffic — including the WireGuard handshake and keepalive packets to the server itself — is routed into amn0 instead of out through the physical interface. The tunnel therefore carries no usable traffic while the UI still reports "Connected". ## Reproduction Any host whose netlink RTM_GETROUTE dump exceeds 8192 bytes (many interfaces/routes: Docker bridges, Tailscale, multiple VPNs). Connect AmneziaVPN in full-tunnel mode and check `journalctl -u AmneziaVPN` for the two lines above. ## Workaround After connecting, add the exclusion route manually: sudo ip route replace <SERVER_IP>/32 via <LAN_GATEWAY> dev <PHYSICAL_IFACE> or simply disconnect/reconnect until the dump happens to fit under 8 KiB (unreliable). ## Suggested fix - Grow the buffer and/or accumulate recv() chunks until NLMSG_DONE instead of assuming the whole dump fits in one fixed buffer; - as a fallback when gateway detection fails, still install the exclusion route (e.g. plain `dev <iface>` unicast) or surface an error in the UI — silently skipping it makes full-tunnel mode dead-on-arrival with no visible indication. (Line numbers refer to the `dev` branch and may shift slightly between releases.)
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#1376
No description provided.