[GH-ISSUE #110] arm64: outbound H4 header corrupted in transport packets (AmneziaWG 1.5) #57

Open
opened 2026-10-13 21:03:09 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @FrankBacon24 on GitHub (Jan 26, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-go/issues/110

Originally assigned to: @ygurov on GitHub.

Summary

Outbound AmneziaWG transport packets have corrupted H4 (uint32) message type header on embedded Linux (arm64, CGO disabled).

Handshake works correctly, but transport packets are sent with incorrect H4 bytes, causing the server to drop all data traffic.

This blocks AmneziaWG 1.5 usage on embedded routers (Keenetic / Entware).


Environment

  • Repo: amneziawg-go
  • Version tested: v0.2.14-beta-awg-1.5-1
  • OS: Embedded Linux (Keenetic OS, Entware)
  • Arch: linux/arm64
  • CGO: CGO_ENABLED=0
  • Go: go1.25.6
  • Client: sing-box (custom build using amneziawg-go as WireGuard backend)
  • Server/config validation: same AmneziaWG config works on iOS AmneziaWG client

Build snippet (client side):

go get github.com/amnezia-vpn/amneziawg-go@v0.2.14-beta-awg-1.5-1
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build ...

Expected behavior

Transport UDP payload starts with correct little-endian uint32 H4 value from config.

Example:

Config H4 = 189347894 (0x0B4A0C36)
Expected little-endian bytes: 36 0C 4A 0B

Actual behavior

Transport packets are sent with corrupted H4 bytes:

Actual UDP payload[0:4]: 36 38 49 0B
Expected:               36 0C 4A 0B
                           ^^ ^^ corrupted

Bytes 0 and 3 are correct, bytes 1–2 are wrong (looks like partial write / stale bytes).


Symptoms

  • ✅ Handshake works (init/response exchanged)
  • ✅ Regular WireGuard (without Amnezia params) works
  • ❌ AmneziaWG transport/data fails (server drops packets due to wrong H4)
  • ❌ TCP connections stall/timeout after ~2 minutes

Reproduction steps

  1. Build any client that uses amneziawg-go with:
    • GOARCH=arm64
    • CGO_ENABLED=0
  2. Configure AmneziaWG 1.5 (H1–H4, etc.)
  3. Connect and capture traffic
  4. Observe correct handshake and corrupted H4 in outbound transport packets.

Notes

Inbound H4 parsing issue (previous “unknown type: 54”) was fixed in another project, but this report is about outbound transport packet encoding, where the uint32 header appears to be written incorrectly on arm64.


This issue was first investigated in sing-box-extended, then confirmed to persist with amneziawg-go v0.2.14-beta-awg-1.5-1, so it likely belongs here.

Also providing WireShark screenshot

Image

Hypothesis / where to look

The corruption pattern is stable: byte0 and byte3 match expected, while byte1–byte2 are wrong:

Expected: 36 0C 4A 0B
Actual: 36 38 49 0B

This looks like one of the following in the outbound transport path:

  1. Unsafe/unaligned uint32 write on arm64

    • e.g. writing header via *(*uint32)(unsafe.Pointer(&buf[i])) = h4
    • or casting a []byte to []uint32 / struct overlay without alignment guarantees
    • arm64 can behave differently vs amd64 when the slice start is not 4-byte aligned
  2. Partial overwrite / stale bytes due to buffer reuse

    • header bytes 1–2 may come from previous packet contents
    • e.g. writing only LSB/MSB or using incorrect copy length / offset
  3. Endian/offset bug limited to transport packets

    • handshake path likely uses a different encoder (and works)
    • transport path might build packet in-place and miscompute offset for the 4-byte type field

Suggested mitigation: avoid unsafe/unaligned writes and always write H4 with:

  • binary.LittleEndian.PutUint32(buf[offset:], h4)
    and ensure buf[offset:] has length ≥ 4 and offset matches the real payload start.
Originally created by @FrankBacon24 on GitHub (Jan 26, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-go/issues/110 Originally assigned to: @ygurov on GitHub. ## Summary Outbound AmneziaWG transport packets have **corrupted H4 (uint32) message type header** on embedded Linux (arm64, CGO disabled). Handshake works correctly, but **transport packets are sent with incorrect H4 bytes**, causing the server to drop all data traffic. This blocks AmneziaWG 1.5 usage on embedded routers (Keenetic / Entware). --- ## Environment - **Repo:** amneziawg-go - **Version tested:** `v0.2.14-beta-awg-1.5-1` - **OS:** Embedded Linux (Keenetic OS, Entware) - **Arch:** `linux/arm64` - **CGO:** `CGO_ENABLED=0` - **Go:** `go1.25.6` - **Client:** sing-box (custom build using amneziawg-go as WireGuard backend) - **Server/config validation:** same AmneziaWG config works on **iOS AmneziaWG client** Build snippet (client side): ```bash go get github.com/amnezia-vpn/amneziawg-go@v0.2.14-beta-awg-1.5-1 GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build ... ``` --- ## Expected behavior Transport UDP payload starts with correct **little-endian uint32 H4** value from config. Example: ``` Config H4 = 189347894 (0x0B4A0C36) Expected little-endian bytes: 36 0C 4A 0B ``` --- ## Actual behavior Transport packets are sent with **corrupted H4 bytes**: ``` Actual UDP payload[0:4]: 36 38 49 0B Expected: 36 0C 4A 0B ^^ ^^ corrupted ``` Bytes 0 and 3 are correct, bytes 1–2 are wrong (looks like partial write / stale bytes). --- ## Symptoms - ✅ Handshake works (init/response exchanged) - ✅ Regular WireGuard (without Amnezia params) works - ❌ AmneziaWG transport/data fails (server drops packets due to wrong H4) - ❌ TCP connections stall/timeout after ~2 minutes --- ## Reproduction steps 1. Build any client that uses amneziawg-go with: - `GOARCH=arm64` - `CGO_ENABLED=0` 2. Configure AmneziaWG 1.5 (H1–H4, etc.) 3. Connect and capture traffic 4. Observe correct handshake and corrupted H4 in outbound transport packets. --- ## Notes Inbound H4 parsing issue (previous “unknown type: 54”) was fixed in another project, but this report is about **outbound transport packet encoding**, where the uint32 header appears to be written incorrectly on arm64. --- ## Related reference This issue was first investigated in sing-box-extended, then confirmed to persist with amneziawg-go `v0.2.14-beta-awg-1.5-1`, so it likely belongs here. Also providing WireShark screenshot <img width="1923" height="983" alt="Image" src="https://github.com/user-attachments/assets/dfd31da3-aa92-49ce-9bea-cce59d11d472" /> ## Hypothesis / where to look The corruption pattern is stable: **byte0 and byte3 match expected**, while **byte1–byte2 are wrong**: Expected: `36 0C 4A 0B` Actual: `36 38 49 0B` This looks like one of the following in the outbound transport path: 1) **Unsafe/unaligned uint32 write on arm64** - e.g. writing header via `*(*uint32)(unsafe.Pointer(&buf[i])) = h4` - or casting a `[]byte` to `[]uint32` / struct overlay without alignment guarantees - arm64 can behave differently vs amd64 when the slice start is not 4-byte aligned 2) **Partial overwrite / stale bytes due to buffer reuse** - header bytes 1–2 may come from previous packet contents - e.g. writing only LSB/MSB or using incorrect copy length / offset 3) **Endian/offset bug limited to transport packets** - handshake path likely uses a different encoder (and works) - transport path might build packet in-place and miscompute offset for the 4-byte type field Suggested mitigation: avoid unsafe/unaligned writes and always write H4 with: - `binary.LittleEndian.PutUint32(buf[offset:], h4)` and ensure `buf[offset:]` has length ≥ 4 and `offset` matches the real payload start.
Author
Owner

@FrankBacon24 commented on GitHub (Jan 27, 2026):

I've may mistaken with the version whicn contains this issue. Probably this issue happens not in the beta version. I've cathed it in the base contained in singbox-extended. I'm sorry for the mistarget

<!-- gh-comment-id:3804235319 --> @FrankBacon24 commented on GitHub (Jan 27, 2026): I've may mistaken with the version whicn contains this issue. Probably this issue happens not in the beta version. I've cathed it in the base contained in singbox-extended. I'm sorry for the mistarget
Author
Owner

@websharik commented on GitHub (Mar 12, 2026):

Same issue in amd64 container. Handshake ok, but transfering data error.

sing-box version 1.13.2-extended-1.6.2

Environment: go1.25.8 linux/amd64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0
Revision: cb3131b3d490dd7b2cd537db5582e61ef8bd0c9d
CGO: disabled
<!-- gh-comment-id:4049555004 --> @websharik commented on GitHub (Mar 12, 2026): Same issue in amd64 container. Handshake ok, but transfering data error. ``` sing-box version 1.13.2-extended-1.6.2 Environment: go1.25.8 linux/amd64 Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0 Revision: cb3131b3d490dd7b2cd537db5582e61ef8bd0c9d CGO: disabled ```
Author
Owner

@remotetohome commented on GitHub (May 7, 2026):

Possibly related: amnezia-client#2582.

AmneziaVPN-Android (4.8.15.5 / awg-go v0.2.16, arm64) silently fails transport when the server uses non-zero S3/S4 - handshake completes, data plane stays broken. The on-the-wire mechanism: with non-zero S4, the H4 field's byte offset in transport packets shifts by S4 bytes, which would expose any alignment/offset bug in the H4 writer; setting S3=S4=0 hides the bug because no offset shift occurs.

Hypothesis only as I don't have a direct repro of the byte-1/byte-2 corruption you describe in this issue from my Android client's outbound packets. But the symptom shape matches (handshake OK, transport broken, server-side asymmetric counters), and your hypothesis-section comments about unsafe/unaligned uint32 writes on arm64 line up with what would happen if the H4 writer's destination offset is wrong by exactly S4 bytes. Cross-posting in case it helps narrow the root cause.

<!-- gh-comment-id:4401031178 --> @remotetohome commented on GitHub (May 7, 2026): Possibly related: [amnezia-client#2582](https://github.com/amnezia-vpn/amnezia-client/issues/2582). AmneziaVPN-Android (4.8.15.5 / awg-go v0.2.16, arm64) silently fails transport when the server uses non-zero S3/S4 - handshake completes, data plane stays broken. The on-the-wire mechanism: with non-zero S4, the H4 field's byte offset in transport packets shifts by S4 bytes, which would expose any alignment/offset bug in the H4 writer; setting S3=S4=0 hides the bug because no offset shift occurs. Hypothesis only as I don't have a direct repro of the byte-1/byte-2 corruption you describe in this issue from my Android client's outbound packets. But the symptom shape matches (handshake OK, transport broken, server-side asymmetric counters), and your hypothesis-section comments about unsafe/unaligned uint32 writes on arm64 line up with what would happen if the H4 writer's destination offset is wrong by exactly S4 bytes. Cross-posting in case it helps narrow the root cause.
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-go#57
No description provided.