[GH-ISSUE #191] Do upgraded amneziawg-tools + kernel module keep legacy (1.x) interfaces in 1.x mode, or force 2.0 ranged headers on all interfaces? #114

Open
opened 2026-10-13 21:06:40 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @iia-arg on GitHub (Jul 24, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/191

Hi, and thanks for AmneziaWG.

We run several self-hosted AmneziaWG servers using the raw amneziawg-tools + DKMS kernel module directly (wg-quick-style configs), not the AmneziaVPN desktop app. Our servers are currently on the 1.x protocol, and we have a number of active peers on 1.x configs plus server-to-server mesh links.

We're planning a gradual migration to AmneziaWG 2.0 and need to confirm one thing the app-level docs don't cover for raw-tools deployments:

When we upgrade amneziawg-tools and the kernel module to the 2.0-capable version, will existing interfaces whose configs use only 1.x parameters (no ranged H1-H4, no I1-I5) keep operating in legacy 1.x mode and stay interoperable with existing 1.x peers?

We ask because of #162: the 2.0 ranged-headers refactor appears to have made mh_genheader() unconditional and left the per-peer advanced_security flag as dead code. If header generation is now unconditional, upgrading the module might force 2.0-style headers on all interfaces and break our 1.x peers on software-upgrade alone — before we've migrated anyone.

Concretely:

  1. Can a single upgraded server run a legacy 1.x interface and a 2.0 interface in parallel (separate interfaces/ports) during migration?
  2. Is protocol mode selected per-interface by config parameters, or does the installed module version globally determine 1.x vs 2.0 behavior?
  3. Recommended migration path for raw-tools self-hosters (not app users) to avoid breaking existing 1.x peers.

Thanks in advance.

Originally created by @iia-arg on GitHub (Jul 24, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/191 Hi, and thanks for AmneziaWG. We run several self-hosted AmneziaWG servers using the **raw `amneziawg-tools` + DKMS kernel module** directly (wg-quick-style configs), **not** the AmneziaVPN desktop app. Our servers are currently on the 1.x protocol, and we have a number of active peers on 1.x configs plus server-to-server mesh links. We're planning a gradual migration to AmneziaWG 2.0 and need to confirm one thing the app-level docs don't cover for raw-tools deployments: **When we upgrade `amneziawg-tools` and the kernel module to the 2.0-capable version, will existing interfaces whose configs use only 1.x parameters (no ranged `H1-H4`, no `I1-I5`) keep operating in legacy 1.x mode and stay interoperable with existing 1.x peers?** We ask because of #162: the 2.0 ranged-headers refactor appears to have made `mh_genheader()` unconditional and left the per-peer `advanced_security` flag as dead code. If header generation is now unconditional, upgrading the module might force 2.0-style headers on *all* interfaces and break our 1.x peers on software-upgrade alone — before we've migrated anyone. Concretely: 1. Can a single upgraded server run a **legacy 1.x interface and a 2.0 interface in parallel** (separate interfaces/ports) during migration? 2. Is protocol mode selected **per-interface by config parameters**, or does the installed module version globally determine 1.x vs 2.0 behavior? 3. Recommended migration path for **raw-tools self-hosters** (not app users) to avoid breaking existing 1.x peers. Thanks in advance.
Author
Owner

@bivlked commented on GitHub (Aug 4, 2026):

I run raw tools plus DKMS self-hosted as well and had the same question before upgrading, so I built a test for it today. Everything below is on v3.0.20260731-04 from the PPA with tools v3.0.20260730, two namespaces on one host.

  1. Parallel interfaces work. I brought up four at once on the same module, each with a different generation of config:
  • nothing AWG-specific at all
  • 1.x style: Jc/Jmin/Jmax, S1, S2, scalar H1-H4, no S3/S4, no I1-I5
  • 2.0 style: S1-S4, ranged H1-H4, I1
  • 3.0 style: the above plus HeaderProtectionKey and the new timer keys

All four came up and passed traffic at the same time, 0% loss on each.

  1. It is the config that decides, not the module version. These parameters live on the device, so interfaces do not affect one another, and an interface with none of them set reports the stock WireGuard message types:
$ awg showconf awgs0
H1 = 1
H2 = 2
H3 = 3
H4 = 4

That is not only cosmetic. I captured the handshake initiation for two of the four:

  • the interface with no parameters: UDP payload is 148 bytes and starts 01 00 00 00, so MESSAGE_HANDSHAKE_INITIATION
  • the 1.x-style interface with S1 = 100 and H1 = 1234567: payload is 248 bytes (100 + 148), at offset 100 sits 87 d6 12 00, which is 1234567 little-endian, and ahead of it are four junk packets of 41 to 63 bytes from Jc = 4

So a scalar H puts that exact constant on the wire, at the offset S1 gives it. The values are ranges internally now, but the parser turns a value with no dash into hi = lo, and device.c initialises the unset ones to MESSAGE_HANDSHAKE_INITIATION through MESSAGE_DATA, so a one-element range has nothing to randomise.

  1. Migration. Two things shape it.

First, H1-H4 and S1-S4 are per-interface, not per-peer, so they cannot differ between peers on one interface. The S values have to be equal on both ends, since the receiver reads the message type at exactly that offset. The H values have to be compatible rather than identical, because the sender picks from its own range and the receiver only checks that the value falls inside its own, though with per-interface scoping that distinction rarely buys you anything.

Second, and this is the one that would have caught me out: awg setconf cannot unset these parameters. I set Jc, S4, H1, I1, ContentPaddingAddition and RekeyAfterTime on an interface, then applied a config with nothing in it but PrivateKey and ListenPort, and all six were still in place afterwards. So an interface cannot be walked back a generation in place, it has to be deleted and recreated.

Which makes the route you sketched the right one: a second interface on a second port, peers moved across one at a time, old interface removed once it is empty.

On #162, the per-peer field is gone from peer.h. The netlink attribute WGPEER_A_ADVANCED_SECURITY is still there and configs still accept AdvancedSecurity, but nothing reads it. I set AdvancedSecurity = on at both ends of one peer and awg showconf still reports off.

The mixed-mode case that issue is about does still fail, and I checked rather than assumed: I pointed a client with no AWG parameters at the 1.x-style interface above and got no handshake, 100% loss. That is the per-interface scoping again rather than anything the upgrade did to you.

What I did not test is a genuinely old 1.x build on the far end, since one kernel means one module. What I did show is that the current module puts 1.x bytes on the wire when it is given a 1.x config.

<!-- gh-comment-id:5175050062 --> @bivlked commented on GitHub (Aug 4, 2026): I run raw tools plus DKMS self-hosted as well and had the same question before upgrading, so I built a test for it today. Everything below is on `v3.0.20260731-04` from the PPA with tools `v3.0.20260730`, two namespaces on one host. 1. Parallel interfaces work. I brought up four at once on the same module, each with a different generation of config: - nothing AWG-specific at all - 1.x style: `Jc/Jmin/Jmax`, `S1`, `S2`, scalar `H1`-`H4`, no `S3/S4`, no `I1`-`I5` - 2.0 style: `S1`-`S4`, ranged `H1`-`H4`, `I1` - 3.0 style: the above plus `HeaderProtectionKey` and the new timer keys All four came up and passed traffic at the same time, 0% loss on each. 2. It is the config that decides, not the module version. These parameters live on the device, so interfaces do not affect one another, and an interface with none of them set reports the stock WireGuard message types: ``` $ awg showconf awgs0 H1 = 1 H2 = 2 H3 = 3 H4 = 4 ``` That is not only cosmetic. I captured the handshake initiation for two of the four: - the interface with no parameters: UDP payload is 148 bytes and starts `01 00 00 00`, so `MESSAGE_HANDSHAKE_INITIATION` - the 1.x-style interface with `S1 = 100` and `H1 = 1234567`: payload is 248 bytes (100 + 148), at offset 100 sits `87 d6 12 00`, which is 1234567 little-endian, and ahead of it are four junk packets of 41 to 63 bytes from `Jc = 4` So a scalar `H` puts that exact constant on the wire, at the offset `S1` gives it. The values are ranges internally now, but the parser turns a value with no dash into `hi = lo`, and `device.c` initialises the unset ones to `MESSAGE_HANDSHAKE_INITIATION` through `MESSAGE_DATA`, so a one-element range has nothing to randomise. 3. Migration. Two things shape it. First, `H1`-`H4` and `S1`-`S4` are per-interface, not per-peer, so they cannot differ between peers on one interface. The `S` values have to be equal on both ends, since the receiver reads the message type at exactly that offset. The `H` values have to be compatible rather than identical, because the sender picks from its own range and the receiver only checks that the value falls inside its own, though with per-interface scoping that distinction rarely buys you anything. Second, and this is the one that would have caught me out: `awg setconf` cannot unset these parameters. I set `Jc`, `S4`, `H1`, `I1`, `ContentPaddingAddition` and `RekeyAfterTime` on an interface, then applied a config with nothing in it but `PrivateKey` and `ListenPort`, and all six were still in place afterwards. So an interface cannot be walked back a generation in place, it has to be deleted and recreated. Which makes the route you sketched the right one: a second interface on a second port, peers moved across one at a time, old interface removed once it is empty. On #162, the per-peer field is gone from `peer.h`. The netlink attribute `WGPEER_A_ADVANCED_SECURITY` is still there and configs still accept `AdvancedSecurity`, but nothing reads it. I set `AdvancedSecurity = on` at both ends of one peer and `awg showconf` still reports `off`. The mixed-mode case that issue is about does still fail, and I checked rather than assumed: I pointed a client with no AWG parameters at the 1.x-style interface above and got no handshake, 100% loss. That is the per-interface scoping again rather than anything the upgrade did to you. What I did not test is a genuinely old 1.x build on the far end, since one kernel means one module. What I did show is that the current module puts 1.x bytes on the wire when it is given a 1.x config.
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#114
No description provided.