[PR #3170] fix: handle large netlink route dumps in getGatewayAndIface on Linux #3117

Open
opened 2026-10-14 21:07:36 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/amnezia-vpn/amnezia-client/pull/3170
Author: @Ramurio
Created: 9/14/2026
Status: 🔄 Open

Base: dev ← Head: fix/linux-netlink-route-dump


📝 Commits (1)

  • 1678143 fix: handle large netlink route dumps in getGatewayAndIface on Linux

📊 Changes

1 file changed (+48 additions, -11 deletions)

View changed files

📝 client/core/utils/networkUtilities.cpp (+48 -11)

📄 Description

Problem

On Linux, NetworkUtilities::getGatewayAndIface() fails to read the kernel routing table whenever the netlink route dump exceeds the preallocated 8 KiB buffer — e.g. on a machine running Docker (bridge networks, veth links with IPv6 addresses). With ~20 containers the dump measures ~9.6 KiB.

The consequences cascade:

  1. recv() silently truncates datagrams that don't fit the remaining capacity;
  2. once the buffer is full, recv(sock, ptr, 0, ...) is called, which returns 0 — the code treats this as a protocol error and bails out;
  3. getGatewayAndIface() returns an empty gateway;
  4. LinuxRouteMonitor logs No default gateway available, skipping exclusion route and never installs the endpoint exclusion route;
  5. WireGuard/AmneziaWG traffic to the VPN server gets routed into the tunnel itself and dies with sendmmsg: message too long (tunnel MTU < packet size);
  6. the client shows connected but no traffic flows — on every server, every network, any client version.

Additional issues fixed along the way:

  • the receive loop condition compared the reply against nlmsg_pid = getpid() from the request header; the kernel assigns the real port id to the (unbound) socket, and that pid may already be taken by another netlink socket in the same process (e.g. the one owned by LinuxRouteMonitor), so the loop could never terminate correctly;
  • perror("Error in received packet") printed a stale, unrelated errno (e.g. Numerical argument out of domain), misleading debugging; now the actual netlink error is logged for NLMSG_ERROR;
  • the netlink socket leaked on early returns.

Evidence

Reproduced standalone while restarting 21 Docker containers:

  • old code: first recv() fails once the dump exceeds the buffer (empty datagram returned by recv(sock, ptr, 0, ...));
  • dump arrives as 3 datagrams: 1036 + 8584 + 20 (NLMSG_DONE) bytes — the second one alone is larger than the old buffer;
  • patched code: 75/75 successful gateway lookups during a full container-restart storm, and the VPN connects with Docker running (previously: No default gateway available → route loop → dead tunnel).

Changes

  • dump buffer: 8 KiB stack → 128 KiB heap, with an explicit 'dump too large' bailout;
  • never call recv() with zero capacity; treat an empty datagram as end of transfer;
  • nlmsg_pid = 0 in the request (let the kernel fill the real port id); rely on NLMSG_DONE / NLM_F_MULTI to end the loop;
  • log the real netlink error instead of stale errno;
  • close the socket on all early returns.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/amnezia-vpn/amnezia-client/pull/3170 **Author:** [@Ramurio](https://github.com/Ramurio) **Created:** 9/14/2026 **Status:** 🔄 Open **Base:** `dev` ← **Head:** `fix/linux-netlink-route-dump` --- ### 📝 Commits (1) - [`1678143`](https://github.com/amnezia-vpn/amnezia-client/commit/16781433fa6045fb71c893070ad47b0a13dcf9e7) fix: handle large netlink route dumps in getGatewayAndIface on Linux ### 📊 Changes **1 file changed** (+48 additions, -11 deletions) <details> <summary>View changed files</summary> 📝 `client/core/utils/networkUtilities.cpp` (+48 -11) </details> ### 📄 Description ## Problem On Linux, `NetworkUtilities::getGatewayAndIface()` fails to read the kernel routing table whenever the netlink route dump exceeds the preallocated 8 KiB buffer — e.g. on a machine running Docker (bridge networks, veth links with IPv6 addresses). With ~20 containers the dump measures ~9.6 KiB. The consequences cascade: 1. `recv()` silently truncates datagrams that don't fit the remaining capacity; 2. once the buffer is full, `recv(sock, ptr, 0, ...)` is called, which returns **0** — the code treats this as a protocol error and bails out; 3. `getGatewayAndIface()` returns an empty gateway; 4. `LinuxRouteMonitor` logs `No default gateway available, skipping exclusion route` and never installs the endpoint exclusion route; 5. WireGuard/AmneziaWG traffic to the VPN server gets routed **into the tunnel itself** and dies with `sendmmsg: message too long` (tunnel MTU < packet size); 6. the client shows *connected* but no traffic flows — on every server, every network, any client version. Additional issues fixed along the way: - the receive loop condition compared the reply against `nlmsg_pid = getpid()` from the request header; the kernel assigns the real port id to the (unbound) socket, and that pid may already be taken by another netlink socket in the same process (e.g. the one owned by `LinuxRouteMonitor`), so the loop could never terminate correctly; - `perror("Error in received packet")` printed a stale, unrelated errno (e.g. `Numerical argument out of domain`), misleading debugging; now the actual netlink error is logged for `NLMSG_ERROR`; - the netlink socket leaked on early returns. ## Evidence Reproduced standalone while restarting 21 Docker containers: - old code: first `recv()` fails once the dump exceeds the buffer (empty datagram returned by `recv(sock, ptr, 0, ...)`); - dump arrives as 3 datagrams: `1036 + 8584 + 20 (NLMSG_DONE)` bytes — the second one alone is larger than the old buffer; - patched code: 75/75 successful gateway lookups during a full container-restart storm, and the VPN connects with Docker running (previously: `No default gateway available` → route loop → dead tunnel). ## Changes - dump buffer: 8 KiB stack → 128 KiB heap, with an explicit 'dump too large' bailout; - never call `recv()` with zero capacity; treat an empty datagram as end of transfer; - `nlmsg_pid = 0` in the request (let the kernel fill the real port id); rely on `NLMSG_DONE` / `NLM_F_MULTI` to end the loop; - log the real netlink error instead of stale errno; - close the socket on all early returns. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
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#3117
No description provided.