[GH-ISSUE #163] AWG 2.0 server with ranged H1-H4 sends random headers to AWG 1.0 clients that expect fixed values #97

Open
opened 2026-10-13 21:06:30 +00:00 by bsakaguchi · 2 comments
Owner

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() calls get_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

  1. Configure an AWG 2.0 server with ranged headers:
H1 = 1568029198-1568094733
H2 = 1173948051-1174013586
H3 = 1244736618-1244802153
H4 = 1997741353-1997806888
  1. Configure an AWG 1.0 client with fixed headers matching the range starts:
H1 = 1568029198
H2 = 1173948051
H3 = 1244736618
H4 = 1997741353
  1. Attempt to connect — handshake fails. The server's response uses a random H2 value that the client does not recognize.

  2. 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 to mh_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.

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()` calls `get_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 1. Configure an AWG 2.0 server with ranged headers: ``` H1 = 1568029198-1568094733 H2 = 1173948051-1174013586 H3 = 1244736618-1244802153 H4 = 1997741353-1997806888 ``` 2. Configure an AWG 1.0 client with fixed headers matching the range starts: ``` H1 = 1568029198 H2 = 1173948051 H3 = 1244736618 H4 = 1997741353 ``` 3. Attempt to connect — handshake fails. The server's response uses a random H2 value that the client does not recognize. 4. 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 to `mh_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 e939553f7225). 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.
Author
Owner

@rufus20145 commented on GitHub (May 19, 2026):

An AWG 2.0 server should be backward-compatible with AWG 1.0 clients

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).

peer used the range start value in its initiation

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.

<!-- gh-comment-id:4485590996 --> @rufus20145 commented on GitHub (May 19, 2026): > An AWG 2.0 server should be backward-compatible with AWG 1.0 clients 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). > peer used the range start value in its initiation 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.
Author
Owner

@otternetwork commented on GitHub (May 23, 2026):

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).

  1. Separate interfaces work, but it's an operational burden - two ports, separate routing and firewall rules. Transparent backward compatibility in a single interface is much better UX when it can be done safely.
  2. Not to mention the deployment complication on already existing infrastructure where multiple interfaces were not initially architectured
  3. Multiple local IPs must be allocated per user - if same user ends up having configs in multiple instances, multiple local IP will be taken
  4. Not configurable from user side - turning obfuscation off would require user to know the different instance port
  5. Initial backward compatibility was introduced in PR #16, so it's not a new concept but rather logical continuation

What if my AWG 2.0 client purely randomly selects this value from it's range for handshake initiation?

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.

could badly impact the connection speed, since it's the main hotpath

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.

<!-- gh-comment-id:4525530861 --> @otternetwork commented on GitHub (May 23, 2026): >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). 1. Separate interfaces work, but it's an operational burden - two ports, separate routing and firewall rules. Transparent backward compatibility in a single interface is much better UX when it can be done safely. 2. Not to mention the deployment complication on already existing infrastructure where multiple interfaces were not initially architectured 3. Multiple local IPs must be allocated per user - if same user ends up having configs in multiple instances, multiple local IP will be taken 4. Not configurable from user side - turning obfuscation off would require user to know the different instance port 5. Initial backward compatibility was introduced in PR #16, so it's not a new concept but rather logical continuation >What if my AWG 2.0 client purely randomly selects this value from it's range for handshake initiation? 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. >could badly impact the connection speed, since it's the main hotpath 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.
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/amneziawg-linux-kernel-module#97
No description provided.