mirror of
https://github.com/amnezia-vpn/amneziawg-linux-kernel-module.git
synced 2026-10-13 21:34:34 +00:00
[GH-ISSUE #163] AWG 2.0 server with ranged H1-H4 sends random headers to AWG 1.0 clients that expect fixed values #97
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
amnezia-vpn/amneziawg-linux-kernel-module#97
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @otternetwork on GitHub (Apr 5, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/163
An AmneziaWG 2.0 server configured with ranged H1-H4 values (e.g.,
H1=1568029198-1568094733) cannot communicate with AmneziaWG 1.0 clients configured with fixed H1-H4 values matching the range starts (e.g.,H1=1568029198).The server correctly accepts the client's handshake initiation (since the fixed value falls within the range), but responds with a random value from the range via
mh_genheader(). The AWG 1.0 client only recognizes the exact fixed value it was configured with and rejects the response.Root cause
mh_genheader()callsget_random_u32_inclusive(mh->start, mh->end), which returns a random value from the full range. This is correct for AWG 2.0 peers that validate against the same range, but breaks AWG 1.0 peers that perform exact-match validation against a single value.The issue affects all four header types — the server generates random values from its H2 range for the handshake response, H3 range for cookies, and H4 range for transport data. The AWG 1.0 client rejects all of them because none match its configured fixed values (except by rare coincidence when the random value happens to equal the range start).
Steps to reproduce
Attempt to connect — handshake fails. The server's response uses a random H2 value that the client does not recognize.
Change the server to use the same fixed values (H1=1568029198, etc.) — connection succeeds.
Expected behavior
An AWG 2.0 server should be backward-compatible with AWG 1.0 clients whose fixed header values fall within the server's ranges. The server could detect that the peer used the range start value in its initiation (suggesting a fixed-header client) and respond with range start values rather than random ones.
Affected code
src/send.c— all calls tomh_genheader()for handshake initiation, response, cookie, and transport headers. There is no per-peer distinction between AWG 1.0 (fixed) and AWG 2.0 (ranged) peers; all AWG peers unconditionally receive random headers from the configured ranges.Relationship to other issues
This is a separate concern from the legacy WireGuard client support regression described in #160, though both stem from the same AWG 2.0 refactoring (PR #88, commit
e939553f72). That issue is about standard WireGuard clients being unable to connect; this issue is about AWG 1.0 clients being unable to connect to AWG 2.0 servers with ranged headers.@rufus20145 commented on GitHub (May 19, 2026):
Why should it be so? You can easily create separate WG interface for peers fully compatible with AWG 2.0 and let old peers to connect to AWG 1.0-configured interface (as it will assume new parameters to be 0 on both server and client sides).
I think, it's a bad heuristic. What if my AWG 2.0 client purely randomly selects this value from it's range for handshake initiation? Why should it stop receiving random headers from range?
Also, I suppose that such an effect on the mh_genheader() function could badly impact the connection speed, since it's the main hotpath.
@otternetwork commented on GitHub (May 23, 2026):
Even in such a rare case nothing will break, the client will still be able to connect, just with random headers turned off.
And if random headers are actually required for connection and the handshake fails, the client will retry, land on a different random number, and proceed with random headers. Actually, regardless of the first handshake success, on the next handshake the client will almost certainly pick a different random value and get classified correctly.
The PR does not add overhead to mh_genheader() itself - it's untouched. The new mh_peerheader() is a static inline wrapper:
static inline u32 mh_peerheader(struct magic_header *mh, bool ranged)
{
return ranged ? mh_genheader(mh) : mh->start;
}
For AWG 2.0 peers (ranged=true), the code path is identical to today - it calls mh_genheader() as before, with one extra predictable branch. For AWG 1.0/legacy peers, it's actually faster because it returns mh->start directly, skipping the get_random_u32_inclusive() call entirely.
The PR replaces this with a branch on peer->advanced_security (a per-peer boolean constant set at handshake time). Both advanced_security and ranged_headers are stable per-peer values, so the branch predictor will get them right nearly 100% of the time - cost is effectively zero (~1 ns). Meanwhile the encryption that follows each packet dwarfs this by orders of magnitude.