Skip to content

feat: extend keepalive to support multiple WireGuard interfaces - #3340

Closed
MircoBarone wants to merge 2 commits into
liqotech:masterfrom
MircoBarone:PR9-multitunnel-keepalive-bis
Closed

MircoBarone wants to merge 2 commits into
liqotech:masterfrom
MircoBarone:PR9-multitunnel-keepalive-bis

Conversation

@MircoBarone

@MircoBarone MircoBarone commented Jul 25, 2026 •

Copy link
Copy Markdown
Contributor

Overview and Motivation

Part of the multi-tunnel WireGuard implementation (9th PR related to #3225).

Liqo currently monitors the health of a single WireGuard interface through its keepalive mechanism. Periodic keepalive packets allow measuring interface latency and detecting connectivity failures, exposing this information through the Connection resource.

With the introduction of multiple parallel WireGuard tunnels, the keepalive mechanism must be extended to monitor each tunnel independently. Since different tunnels may experience different network conditions, the Connection resource must expose the status of each individual tunnel while still providing a global view of the connection.

Connection resource changes

The Connection resource has been extended by introducing a status.tunnels field containing one status entry for each WireGuard interface.

Each entry stores:

  • the interface identifier;
  • the connection status;
  • the measured latency.

The global connection fields (status.value and status.latency) are still exposed, but they are now computed independently by the Connection Aggregator controller.

Keepalive changes

The keepalive subsystem is composed of two actors:

  • a Sender, periodically transmitting PING messages containing a timestamp;
  • a Receiver, which replies to PING messages with PONG messages and computes the latency when receiving a PONG.

Although the existing implementation already supported multiple senders, they were identified by the remote ClusterID, reflecting an older architecture where a gateway could communicate with multiple remote gateways.

This existing design has been reused by replacing the ClusterID with the WireGuard InterfaceID. As a result, every tunnel owns an independent sender.

To support this, the gateway container now receives a new --num-interfaces flag, populated automatically by the deployment templates according to the configured number of WireGuard interfaces. This determines how many keepalive senders are created. The introduction of this flag overlaps with PR #3338, where it is described in greater detail.

A new callback, UpdateTunnelStatus, is called when a PONG is received and updates the status of a single tunnel in the Connection resource. Updates are performed using Server-Side Apply (SSA), assigning the interface name as the field manager so that every tunnel independently owns its corresponding entry in status.tunnels.

Tunnel updates are throttled to avoid unnecessary API writes. An update is skipped if the tunnel status has not changed and the previous update was performed less than PingUpdateStatusInterval ago.

The keepalive implementation has also been improved to process received messages concurrently:

  • an independent receive buffer is allocated for each message to avoid shared-buffer races;
  • finer-grained synchronization is introduced by separating peer-map locking from per-peer state locking.

Connection aggregation

A new ConnectionAggregator controller computes the global connection status from the individual tunnel states.

The aggregation policy is intentionally conservative:

  • the global status is Error if at least one tunnel is in the Error state;
  • the global status is Connecting while one or more tunnels are still initializing;
  • the global status is Connected only when every tunnel is connected.

In the Connected state, the global latency is computed as the average latency across all tunnels.

The controller reconciles whenever the status.tunnels field of the Connection resource changes, either because a tunnel status is updated or because tunnels are added or removed. It computes the aggregated connection state and updates the global connection fields through the UpdateConnectionStatusAggregated callback.
Like tunnel updates, the aggregated status is written using Server-Side Apply (SSA), but with a dedicated field manager. This cleanly separates ownership between the per-tunnel entries and the aggregated connection fields.

Tunnel initialization and reconciliation

EnsureConnection(), called by the PublicKeys controller, invokes reconcileTunnelsStatus() to reconcile the status.tunnels field with the currently configured WireGuard interfaces.

Missing tunnel entries are initialized in the Connecting state while waiting for the first successful keepalive exchange. Conversely, stale entries left from previous gateway configurations (for example after reducing the number of tunnels) are removed from the Connection resource.

Note about SSA

client.Apply is deprecated, but migrating to client.SubResource("status").Apply() requires generated ApplyConfiguration types. Since introducing those generated types is outside the scope of this PR, the existing SSA implementation has been kept. Temporary //nolint:staticcheck directives have been added to document this decision and suppress the deprecation warnings until a dedicated migration is performed.

@adamjensenbot

Copy link
Copy Markdown
Collaborator

Hi @MircoBarone. Thanks for your PR!

I am @adamjensenbot.
You can interact with me issuing a slash command in the first line of a comment.
Currently, I understand the following commands:

  • /rebase: Rebase this PR onto the master branch (You can add the option test=true to launch the tests
    when the rebase operation is completed)
  • /merge: Merge this PR into the master branch
  • /build Build Liqo components
  • /test Launch the E2E and Unit tests
  • /hold, /unhold Add/remove the hold label to prevent merging with /merge

Make sure this PR appears in the liqo changelog, adding one of the following labels:

  • feat: 🚀 New Feature
  • fix: 🐛 Bug Fix
  • refactor: 🧹 Code Refactoring
  • docs: 📝 Documentation
  • style: 💄 Code Style
  • perf: 🐎 Performance Improvement
  • test: ✅ Tests
  • chore: 🚚 Dependencies Management
  • build: 📦 Builds Management
  • ci: 👷 CI/CD
  • revert: ⏪ Reverts Previous Changes

@github-actions github-actions Bot added the feat Adds a new feature to the codebase label Jul 25, 2026
@github-actions github-actions Bot added the fix Fixes a bug in the codebase. label Jul 25, 2026
@MircoBarone
MircoBarone force-pushed the PR9-multitunnel-keepalive-bis branch 2 times, most recently from ab7e387 to 24e9bff Compare July 25, 2026 23:11
@MircoBarone
MircoBarone marked this pull request as draft July 25, 2026 23:17
Keep using client.Status().Patch with client.Apply since migrating
to ApplyConfiguration requires generated types and is outside the scope of this PR.
@MircoBarone
MircoBarone force-pushed the PR9-multitunnel-keepalive-bis branch from 24e9bff to e10bd45 Compare July 27, 2026 14:44
@MircoBarone
MircoBarone marked this pull request as ready for review July 27, 2026 15:01
@MircoBarone
MircoBarone marked this pull request as draft August 19, 2026 10:27
@MircoBarone

Copy link
Copy Markdown
Contributor Author

Closed in favor of #3373

@MircoBarone MircoBarone closed this Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat Adds a new feature to the codebase fix Fixes a bug in the codebase. size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants