[PR #202] fix: split handshake junk packets into bind-sized batches #201

Open
opened 2026-10-13 21:04:31 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/amnezia-vpn/amneziawg-go/pull/202
Author: @earmark-halt
Created: 10/8/2026
Status: 🔄 Open

Base: master ← Head: fix/handshake-junk-batch-size


📝 Commits (1)

  • d5bcc5c fix: split handshake junk packets into bind-sized batches

📊 Changes

2 files changed (+38 additions, -4 deletions)

View changed files

📝 device/device_test.go (+18 -0)
📝 device/peer.go (+20 -4)

📄 Description

Fixes #111.

Problem

SendHandshakeInitiation hands the I1–I5 packets, all Jc junk packets and the initiation itself to the bind in a single Send call. The conn.Bind contract caps one Send at BatchSize() buffers, and StdNetBind only has room for conn.IdealBatchSize (128) messages per call. Once Jc + <number of I-packets> + 1 > 128, the first handshake panics and takes the whole process down:

panic: runtime error: index out of range [128] with length 128

conn.(*StdNetBind).Send                  conn/bind_std.go:400   (without UDP GSO, e.g. macOS)
conn.coalesceMessages                    conn/bind_std.go:492   (with UDP GSO, e.g. Linux)
device.(*Peer).SendBuffers               device/peer.go:140
device.(*Peer).SendHandshakeInitiation   device/send.go:177

Junk and I-packets are allocated with cap == len, so GSO never coalesces them and the threshold is the same everywhere: Jc = 128 without I-packets, Jc = 123 with all of I1–I5 set.

Jc is a uint16 in the docs, in amneziawg-tools and in the kernel module (which sends junk packets one skb at a time). Such configs are accepted everywhere and work with the kernel module, but crash amneziawg-go.

Fix

Peer.SendBuffers now splits the buffers into chunks of at most bind.BatchSize(), as the Bind interface requires. The transport path never passes more than one batch, so it still makes exactly one Send call with the same error handling as before.

If the bind falls back from UDP GSO while sending a chunk (ErrUDPGSODisabled with a nil RetryErr), that chunk did go out, so the remaining chunks, including the initiation, are still sent.

Testing

  • New TestAWGDevicePingManyJunkPackets: ping/pong over real UDP sockets with jc=128. Without the fix it panics with the trace above on both send paths (macOS without UDP GSO, Linux with it). With the fix it passes (-count=5, also under -race).
  • go test ./... on macOS and Linux: everything passes except TestAWGDevicePing, which also fails on master (#168).
  • ./device and ./conn cross-compile for windows/{amd64,arm64}, linux/{amd64,mips}, freebsd, openbsd, android/arm64 and ios/arm64.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/amnezia-vpn/amneziawg-go/pull/202 **Author:** [@earmark-halt](https://github.com/earmark-halt) **Created:** 10/8/2026 **Status:** 🔄 Open **Base:** `master` ← **Head:** `fix/handshake-junk-batch-size` --- ### 📝 Commits (1) - [`d5bcc5c`](https://github.com/amnezia-vpn/amneziawg-go/commit/d5bcc5c30f82a72ab23954672a28fb90a55c6256) fix: split handshake junk packets into bind-sized batches ### 📊 Changes **2 files changed** (+38 additions, -4 deletions) <details> <summary>View changed files</summary> 📝 `device/device_test.go` (+18 -0) 📝 `device/peer.go` (+20 -4) </details> ### 📄 Description Fixes #111. ## Problem `SendHandshakeInitiation` hands the I1–I5 packets, all `Jc` junk packets and the initiation itself to the bind in a single `Send` call. The `conn.Bind` contract caps one `Send` at `BatchSize()` buffers, and `StdNetBind` only has room for `conn.IdealBatchSize` (128) messages per call. Once `Jc + <number of I-packets> + 1 > 128`, the first handshake panics and takes the whole process down: ``` panic: runtime error: index out of range [128] with length 128 conn.(*StdNetBind).Send conn/bind_std.go:400 (without UDP GSO, e.g. macOS) conn.coalesceMessages conn/bind_std.go:492 (with UDP GSO, e.g. Linux) device.(*Peer).SendBuffers device/peer.go:140 device.(*Peer).SendHandshakeInitiation device/send.go:177 ``` Junk and I-packets are allocated with `cap == len`, so GSO never coalesces them and the threshold is the same everywhere: `Jc = 128` without I-packets, `Jc = 123` with all of I1–I5 set. `Jc` is a `uint16` in the docs, in `amneziawg-tools` and in the kernel module (which sends junk packets one skb at a time). Such configs are accepted everywhere and work with the kernel module, but crash amneziawg-go. ## Fix `Peer.SendBuffers` now splits the buffers into chunks of at most `bind.BatchSize()`, as the `Bind` interface requires. The transport path never passes more than one batch, so it still makes exactly one `Send` call with the same error handling as before. If the bind falls back from UDP GSO while sending a chunk (`ErrUDPGSODisabled` with a nil `RetryErr`), that chunk did go out, so the remaining chunks, including the initiation, are still sent. ## Testing - New `TestAWGDevicePingManyJunkPackets`: ping/pong over real UDP sockets with `jc=128`. Without the fix it panics with the trace above on both send paths (macOS without UDP GSO, Linux with it). With the fix it passes (`-count=5`, also under `-race`). - `go test ./...` on macOS and Linux: everything passes except `TestAWGDevicePing`, which also fails on `master` (#168). - `./device` and `./conn` cross-compile for windows/{amd64,arm64}, linux/{amd64,mips}, freebsd, openbsd, android/arm64 and ios/arm64. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
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#201
No description provided.