Compare commits
No commits in common. "0c0c10b5d3236796bd3832a6813223d6dc7d0bb1" and "e7ef4339e718641fc7bc1b0ea41b538108de77cc" have entirely different histories.
0c0c10b5d3
...
e7ef4339e7
88 changed files with 7632 additions and 3470 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1 @@
|
|||
wireguard-go
|
||||
/.idea/
|
||||
.DS_Store
|
||||
amneziawg-go
|
||||
168
README.md
168
README.md
|
|
@ -1,93 +1,113 @@
|
|||
**English** · [Русский](README.ru.md)
|
||||
# Go Implementation of AmneziaWG
|
||||
|
||||
# wireguard-go (lx fork) — sagernet + AmneziaWG 2.0
|
||||
AmneziaWG is a contemporary version of the WireGuard protocol. It's a fork of WireGuard-Go and offers protection against detection by Deep Packet Inspection (DPI) systems. At the same time, it retains the simplified architecture and high performance of the original.
|
||||
|
||||
The WireGuard-Go runtime used by **[sing-box-lx](https://github.com/Leadaxe/sing-box-lx)**:
|
||||
**[sagernet/wireguard-go](https://github.com/sagernet/wireguard-go)** (the fork sing-box builds on) **+ AmneziaWG 2.0 obfuscation**, merged together.
|
||||
The precursor, WireGuard, is known for its efficiency but had issues with detection due to its distinctive packet signatures.
|
||||
AmneziaWG addresses this problem by employing advanced obfuscation methods, allowing its traffic to blend seamlessly with regular internet traffic.
|
||||
As a result, AmneziaWG maintains high performance while adding an extra layer of stealth, making it a superb choice for those seeking a fast and discreet VPN connection.
|
||||
|
||||
This is **not** a general-purpose project. It exists for one reason — see below — and lives on the **`lx`** branch.
|
||||
## Usage
|
||||
|
||||
---
|
||||
|
||||
## Why this fork exists
|
||||
|
||||
sing-box's WireGuard endpoint needs **sagernet/wireguard-go**'s additions (the `conn.Bind.Send(…, offset)` contract, `device.InputPacket`, reserved/control). AmneziaWG's DPI-evasion obfuscation lives in **[amnezia-vpn/amneziawg-go](https://github.com/amnezia-vpn/amneziawg-go)**, which is a fork of *upstream* wireguard-go and therefore **lacks** those sagernet additions.
|
||||
|
||||
So neither fork alone works for sing-box-lx:
|
||||
|
||||
| | sing-box-compat API | AmneziaWG obfuscation |
|
||||
|---|:---:|:---:|
|
||||
| `sagernet/wireguard-go` | ✅ | ❌ |
|
||||
| `amnezia-vpn/amneziawg-go` | ❌ | ✅ |
|
||||
| **this fork** | ✅ | ✅ |
|
||||
|
||||
Each existing fork gives exactly **half** of what's needed:
|
||||
|
||||
- **Take `sagernet/wireguard-go`** → sing-box-lx compiles and runs, but the AWG fields (`jc`/`h1`/`i1`…) do nothing → **no obfuscation**; AmneziaWG doesn't actually work.
|
||||
- **Take `amnezia-vpn/amneziawg-go`** → the obfuscation is there, but sing-box-lx **won't even compile** (the sagernet functions are missing).
|
||||
|
||||
We need **both** ✅ at once, and no ready-made fork has them — so we built one by **merging**: sagernet (for the API) + amnezia (for the obfuscation). That is exactly the **"this fork"** row above.
|
||||
|
||||
The approach: **keep the sagernet base and graft the obfuscation onto it** — rather than the reverse (adding sagernet's APIs to amneziawg-go, which would route even plain WireGuard through a foreign device). This way sing-box compiles unchanged, the obfuscation is additive and off by default, and a config without AWG fields behaves exactly like plain WireGuard.
|
||||
|
||||
## How the merge works
|
||||
|
||||
Both `sagernet/wireguard-go` and `amneziawg-go` descend from the same upstream `git.zx2c4.com/wireguard-go`, so they share git history — which makes a real **3-way merge** possible (not a hand-port).
|
||||
|
||||
- **Base:** `sagernet/wireguard-go` (the exact commit sing-box pins — currently `506b7631853c`).
|
||||
- **Merged in:** `amnezia-vpn/amneziawg-go` (a tip with AWG2 / I1–I5 + the S4-keepalive fix).
|
||||
- **Key trick:** `MessageEncapsulatingTransportSize` is set to **`0`** in `device/noise-protocol.go`. sing-box-lx does not use sagernet's 8-byte `Bind.Send` headroom, and zeroing it makes the AmneziaWG obfuscation compose cleanly with no weave conflicts in the packet send path.
|
||||
- **Isolation:** the obfuscation is confined to `device/` — new files `device/obf*.go`, `device/magic-header.go`, plus grafts in `device/{send,receive,device,uapi}.go`. **`conn/`, `tun/`, `ipc/` stay pure sagernet.**
|
||||
- **Module path is unchanged** (`module github.com/sagernet/wireguard-go`) so the consumer plugs it in with a `replace` directive and needs **no import edits**.
|
||||
|
||||
## Consumed by
|
||||
|
||||
[sing-box-lx](https://github.com/Leadaxe/sing-box-lx) wires this in as a git submodule + a `replace`:
|
||||
Simply run:
|
||||
|
||||
```
|
||||
# sing-box-lx/.gitmodules
|
||||
[submodule "submodules/wireguard-go"]
|
||||
url = https://github.com/Leadaxe/wireguard-go-awg2-lx
|
||||
branch = lx
|
||||
|
||||
# sing-box-lx/go.mod (// lx)
|
||||
replace github.com/sagernet/wireguard-go => ./submodules/wireguard-go
|
||||
$ amneziawg-go wg0
|
||||
```
|
||||
|
||||
Built with the `with_awg` tag, it has been **live-validated** against a real AmneziaWG 2.0 server (handshake + keepalive + outbound traffic) and cross-compiles on linux/darwin/windows × amd64/arm64.
|
||||
This will create an interface and fork into the background. To remove the interface, use the usual `ip link del wg0`, or if your system does not support removing interfaces directly, you may instead remove the control socket via `rm -f /var/run/amneziawg/wg0.sock`, which will result in amneziawg-go shutting down.
|
||||
|
||||
## Maintaining it (rebase onto a new sagernet tag)
|
||||
To run amneziawg-go without forking to the background, pass `-f` or `--foreground`:
|
||||
|
||||
When sing-box bumps `sagernet/wireguard-go`, redo the merge:
|
||||
```
|
||||
$ amneziawg-go -f wg0
|
||||
```
|
||||
When an interface is running, you may use [`amneziawg-tools `](https://github.com/amnezia-vpn/amneziawg-tools) to configure it, as well as the usual `ip(8)` and `ifconfig(8)` commands.
|
||||
|
||||
```sh
|
||||
git remote add origin https://github.com/sagernet/wireguard-go # base
|
||||
git remote add amnezia https://github.com/amnezia-vpn/amneziawg-go # obfuscation source
|
||||
git fetch --all
|
||||
git checkout -b lx <new-sagernet-commit>
|
||||
git merge amnezia/master # real 3-way merge via the shared upstream ancestor
|
||||
To run with more logging you may set the environment variable `LOG_LEVEL=debug`.
|
||||
|
||||
## Platforms
|
||||
|
||||
### Linux
|
||||
|
||||
This will run on Linux; you should run amnezia-wg instead of using default linux kernel module.
|
||||
|
||||
### macOS
|
||||
|
||||
This runs on macOS using the utun driver. It does not yet support sticky sockets, and won't support fwmarks because of Darwin limitations. Since the utun driver cannot have arbitrary interface names, you must either use `utun[0-9]+` for an explicit interface name or `utun` to have the kernel select one for you. If you choose `utun` as the interface name, and the environment variable `WG_TUN_NAME_FILE` is defined, then the actual name of the interface chosen by the kernel is written to the file specified by that variable.
|
||||
This runs on MacOS, you should use it from [amneziawg-apple](https://github.com/amnezia-vpn/amneziawg-apple)
|
||||
|
||||
### Windows
|
||||
|
||||
This runs on Windows, you should use it from [amneziawg-windows](https://github.com/amnezia-vpn/amneziawg-windows), which uses this as a module.
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
This requires an installation of the latest version of [Go](https://go.dev/).
|
||||
|
||||
```
|
||||
$ git clone https://github.com/amnezia-vpn/amneziawg-go
|
||||
$ cd amneziawg-go
|
||||
$ make
|
||||
```
|
||||
|
||||
Conflict resolution recipe:
|
||||
## Configuration
|
||||
|
||||
1. New `device/obf*.go` + `device/magic-header.go` come in clean.
|
||||
2. **Mechanical** conflicts (amnezia → sagernet import paths, `queueconstants*`, `sticky*`, `tun.go`) → take **ours** (sagernet).
|
||||
3. Remove amnezia-added infra duplicates: `conn/gso_*.go`, `outline/*`, `tun/*_test.go`.
|
||||
4. `device/device.go` → **union** (sagernet `pauseManager` + amnezia obf fields).
|
||||
5. `device/send.go` / `receive.go` → take amnezia's obfuscation, set `MessageEncapsulatingTransportSize = 0`, and keep the 3-arg `bind.Send(…, 0)` calls.
|
||||
6. `conn/`, `tun/`, `ipc/`, `go.mod` module path → **ours** (sagernet).
|
||||
> [!NOTE]
|
||||
> If there is no value specified (for any param), AWG treats it as 0
|
||||
|
||||
Then in sing-box-lx: bump the submodule, `make -f Makefile.lx lx-build`, and re-test against an AWG2 server.
|
||||
### Junk packets
|
||||
|
||||
## Links
|
||||
The amount of junk packets specified in `Jc` with a random size between `Jmin` and `Jmax` would be generated and sent prior every handshake
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Consumer | [Leadaxe/sing-box-lx](https://github.com/Leadaxe/sing-box-lx) |
|
||||
| Base | [sagernet/wireguard-go](https://github.com/sagernet/wireguard-go) |
|
||||
| Obfuscation source | [amnezia-vpn/amneziawg-go](https://github.com/amnezia-vpn/amneziawg-go) · [docs.amnezia.org](https://docs.amnezia.org/documentation/amnezia-wg/) |
|
||||
| Original | [WireGuard/wireguard-go](https://git.zx2c4.com/wireguard-go/about/) |
|
||||
- `Jc: int`, recommended range is 4-12
|
||||
- `Jmin: int` <= `Jmax:int`
|
||||
|
||||
## License
|
||||
> [!TIP]
|
||||
> Junk packets do not carry any actual data, so there is no need to specify it on both sides. General recommendation is to use it on the client side only
|
||||
|
||||
MIT, inherited from WireGuard-Go (see [`LICENSE`](LICENSE)). The AmneziaWG obfuscation is likewise MIT (from amneziawg-go). This is an unofficial fork, not affiliated with WireGuard, SagerNet, or Amnezia.
|
||||
> [!IMPORTANT]
|
||||
> If Jmax >= system MTU (not the one specified in AWG), then the system can fracture this packet into fragments, which looks suspicious from the censor side
|
||||
|
||||
### Message paddings
|
||||
|
||||
- `S1: int` - padding of handshake initial message
|
||||
- `S2: int` - padding of handshake response message
|
||||
- `S3: int` - padding of handshake cookie message
|
||||
- `S4: int` - padding of transport messages
|
||||
|
||||
### Message headers
|
||||
|
||||
Every message in wireguard has `int32` type at the beginning of the packet. This field could be controlled by specifying the params below:
|
||||
|
||||
- `H1: string` - header range of handshake initial message
|
||||
- `H2: string` - header range of handshake initial message
|
||||
- `H3: string` - header range of handshake cookie message
|
||||
- `H4: string` - header range of transport message
|
||||
|
||||
Values could be specified as:
|
||||
- range: `x-y`, x <= y; e.g. `123-456`
|
||||
- single value `1234`
|
||||
|
||||
### Custom signature packets
|
||||
|
||||
These packets are being send prior to every handshake, in the same way as Junk packets do. The sending order is `I1`, `I2`, `I3`, `I4`, `I5`. If there is no value specified, the packet is skipped.
|
||||
|
||||
- `I1: string`
|
||||
- `I2: string`
|
||||
- `I3: string`
|
||||
- `I4: string`
|
||||
- `I5: string`
|
||||
|
||||
Value is a sequence of tags specified below:
|
||||
- `<b 0x[seq]>` - static bytes tag. Dumps `[seq]` as-is to the packet. `[seq]` is hex-encoded sequence which represents bytes sequence (2 hex numbers per byte) and is always even-sized
|
||||
- `<r [size]>` - random bytes tag. Dumps `[size]` amount of randomly-generated bytes to the packet
|
||||
- `<rd [size]>` - random digits tag. Dumps `[size]` amount of randomly-generated bytes from `[0-9]` set to the packet
|
||||
- `<rc [size]>` - random chars tag. Dumps `[size]` amount of randomly-generated bytes from `[a-zA-Z] set to the packet
|
||||
- `<t>` - timestamp tag. Dumps 4-bytes long current system time in UNIX format
|
||||
|
||||
> [!TIP]
|
||||
> Custom signature packets does not carry any actual data, so there is no need to specify it on both sides. General recommendation is to use it on the client side only
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If the final size of any packet exceeds system MTU, it would be fractured into fragments, which looks suspicious
|
||||
93
README.ru.md
93
README.ru.md
|
|
@ -1,93 +0,0 @@
|
|||
[English](README.md) · **Русский**
|
||||
|
||||
# wireguard-go (lx-форк) — sagernet + AmneziaWG 2.0
|
||||
|
||||
Рантайм WireGuard-Go для **[sing-box-lx](https://github.com/Leadaxe/sing-box-lx)**:
|
||||
**[sagernet/wireguard-go](https://github.com/sagernet/wireguard-go)** (форк, на котором собирается sing-box) **+ обфускация AmneziaWG 2.0**, слитые вместе.
|
||||
|
||||
Это **не** универсальный проект. Он существует ради одной задачи (см. ниже) и живёт на ветке **`lx`**.
|
||||
|
||||
---
|
||||
|
||||
## Зачем этот форк
|
||||
|
||||
WireGuard-endpoint sing-box нуждается в добавках **sagernet/wireguard-go** (контракт `conn.Bind.Send(…, offset)`, `device.InputPacket`, reserved/control). Обфускация против DPI у AmneziaWG живёт в **[amnezia-vpn/amneziawg-go](https://github.com/amnezia-vpn/amneziawg-go)**, который форкнут от *upstream* wireguard-go и потому этих sagernet-добавок **не имеет**.
|
||||
|
||||
Значит для sing-box-lx ни один форк по отдельности не подходит:
|
||||
|
||||
| | API под sing-box | обфускация AmneziaWG |
|
||||
|---|:---:|:---:|
|
||||
| `sagernet/wireguard-go` | ✅ | ❌ |
|
||||
| `amnezia-vpn/amneziawg-go` | ❌ | ✅ |
|
||||
| **этот форк** | ✅ | ✅ |
|
||||
|
||||
Каждый существующий форк даёт ровно **половину** нужного:
|
||||
|
||||
- **возьмёшь `sagernet/wireguard-go`** → sing-box-lx соберётся и запустится, но AWG-поля (`jc`/`h1`/`i1`…) ничего не сделают → **обфускации нет**; AmneziaWG не работает;
|
||||
- **возьмёшь `amnezia-vpn/amneziawg-go`** → обфускация есть, но sing-box-lx **не скомпилируется** (нет sagernet-функций).
|
||||
|
||||
Нужны **обе** ✅ сразу, а готового форка с двумя галочками не существует — поэтому мы собрали его **слиянием**: sagernet (за API) + amnezia (за обфускацию). Это ровно строка **«этот форк»** выше.
|
||||
|
||||
Подход: **берём sagernet-базу и граффтим обфускацию на неё** — а не наоборот (не дотачиваем sagernet-API к amneziawg-go, иначе даже обычный WireGuard шёл бы через чужой device). Так sing-box компилируется без изменений, обфускация аддитивна и выключена по умолчанию, а конфиг без AWG-полей ведёт себя как обычный WireGuard.
|
||||
|
||||
## Как устроен merge
|
||||
|
||||
И `sagernet/wireguard-go`, и `amneziawg-go` происходят от одного upstream `git.zx2c4.com/wireguard-go`, поэтому делят git-историю — а значит возможен настоящий **3-way merge** (а не ручной перенос).
|
||||
|
||||
- **База:** `sagernet/wireguard-go` (тот коммит, что пинит sing-box — сейчас `506b7631853c`).
|
||||
- **Вливаем:** `amnezia-vpn/amneziawg-go` (тип с AWG2 / I1–I5 + фикс S4-keepalive).
|
||||
- **Ключевой трюк:** `MessageEncapsulatingTransportSize` выставлен в **`0`** в `device/noise-protocol.go`. sing-box-lx не использует 8-байтный headroom sagernet для `Bind.Send`, и обнуление позволяет обфускации AmneziaWG встать чисто, без конфликтов в send-пути.
|
||||
- **Изоляция:** обфускация замкнута в `device/` — новые файлы `device/obf*.go`, `device/magic-header.go` + графты в `device/{send,receive,device,uapi}.go`. **`conn/`, `tun/`, `ipc/` остаются чистым sagernet.**
|
||||
- **Module-path не меняется** (`module github.com/sagernet/wireguard-go`), поэтому потребитель подключает форк через `replace` без правки импортов.
|
||||
|
||||
## Кто потребляет
|
||||
|
||||
[sing-box-lx](https://github.com/Leadaxe/sing-box-lx) подключает это как git submodule + `replace`:
|
||||
|
||||
```
|
||||
# sing-box-lx/.gitmodules
|
||||
[submodule "submodules/wireguard-go"]
|
||||
url = https://github.com/Leadaxe/wireguard-go-awg2-lx
|
||||
branch = lx
|
||||
|
||||
# sing-box-lx/go.mod (// lx)
|
||||
replace github.com/sagernet/wireguard-go => ./submodules/wireguard-go
|
||||
```
|
||||
|
||||
Собранный с тегом `with_awg`, он **проверен живым** сервером AmneziaWG 2.0 (handshake + keepalive + трафик наружу) и кросс-компилируется на linux/darwin/windows × amd64/arm64.
|
||||
|
||||
## Сопровождение (ребейз на новый sagernet-тег)
|
||||
|
||||
Когда sing-box бампит `sagernet/wireguard-go`, повторяем merge:
|
||||
|
||||
```sh
|
||||
git remote add origin https://github.com/sagernet/wireguard-go # база
|
||||
git remote add amnezia https://github.com/amnezia-vpn/amneziawg-go # источник обфускации
|
||||
git fetch --all
|
||||
git checkout -b lx <новый-sagernet-коммит>
|
||||
git merge amnezia/master # настоящий 3-way merge через общего upstream-предка
|
||||
```
|
||||
|
||||
Рецепт разрешения конфликтов:
|
||||
|
||||
1. Новые `device/obf*.go` + `device/magic-header.go` приходят чисто.
|
||||
2. **Механические** конфликты (import-path amnezia → sagernet, `queueconstants*`, `sticky*`, `tun.go`) → берём **наши** (sagernet).
|
||||
3. Удаляем amnezia-инфра-дубликаты: `conn/gso_*.go`, `outline/*`, `tun/*_test.go`.
|
||||
4. `device/device.go` → **union** (sagernet `pauseManager` + obf-поля amnezia).
|
||||
5. `device/send.go` / `receive.go` → берём обфускацию amnezia, ставим `MessageEncapsulatingTransportSize = 0`, сохраняем 3-арг `bind.Send(…, 0)`.
|
||||
6. `conn/`, `tun/`, `ipc/`, module-path в `go.mod` → **наши** (sagernet).
|
||||
|
||||
Затем в sing-box-lx: бампим submodule, `make -f Makefile.lx lx-build` и пере-тест против AWG2-сервера.
|
||||
|
||||
## Ссылки
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Потребитель | [Leadaxe/sing-box-lx](https://github.com/Leadaxe/sing-box-lx) |
|
||||
| База | [sagernet/wireguard-go](https://github.com/sagernet/wireguard-go) |
|
||||
| Источник обфускации | [amnezia-vpn/amneziawg-go](https://github.com/amnezia-vpn/amneziawg-go) · [docs.amnezia.org](https://docs.amnezia.org/documentation/amnezia-wg/) |
|
||||
| Оригинал | [WireGuard/wireguard-go](https://git.zx2c4.com/wireguard-go/about/) |
|
||||
|
||||
## Лицензия
|
||||
|
||||
MIT, унаследована от WireGuard-Go (см. [`LICENSE`](LICENSE)). Обфускация AmneziaWG — тоже MIT (из amneziawg-go). Это неофициальный форк, не аффилирован с WireGuard, SagerNet или Amnezia.
|
||||
104
conn/bind_std.go
104
conn/bind_std.go
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
|
@ -16,16 +16,12 @@ import (
|
|||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Bind = (*StdNetBind)(nil)
|
||||
_ Endpoint = (*StdNetEndpoint)(nil)
|
||||
)
|
||||
|
||||
// StdNetBind implements Bind for all platforms. While Windows has its own Bind
|
||||
|
|
@ -34,9 +30,6 @@ var (
|
|||
// methods for sending and receiving multiple datagrams per-syscall. See the
|
||||
// proposal in https://github.com/golang/go/issues/45886#issuecomment-1218301564.
|
||||
type StdNetBind struct {
|
||||
externalControl control.Func
|
||||
reservedForEndpoint map[netip.AddrPort][3]uint8
|
||||
|
||||
mu sync.Mutex // protects all fields except as specified
|
||||
ipv4 *net.UDPConn
|
||||
ipv6 *net.UDPConn
|
||||
|
|
@ -55,11 +48,8 @@ type StdNetBind struct {
|
|||
blackhole6 bool
|
||||
}
|
||||
|
||||
func NewStdNetBind(externalControl control.Func) Bind {
|
||||
func NewStdNetBind() Bind {
|
||||
return &StdNetBind{
|
||||
externalControl: externalControl,
|
||||
reservedForEndpoint: make(map[netip.AddrPort][3]uint8),
|
||||
|
||||
udpAddrPool: sync.Pool{
|
||||
New: func() any {
|
||||
return &net.UDPAddr{
|
||||
|
|
@ -70,10 +60,12 @@ func NewStdNetBind(externalControl control.Func) Bind {
|
|||
|
||||
msgsPool: sync.Pool{
|
||||
New: func() any {
|
||||
// ipv6.Message and ipv4.Message are interchangeable as they are
|
||||
// both aliases for x/net/internal/socket.Message.
|
||||
msgs := make([]ipv6.Message, IdealBatchSize)
|
||||
for i := range msgs {
|
||||
msgs[i].Buffers = make(net.Buffers, 1)
|
||||
msgs[i].OOB = make([]byte, controlSize)
|
||||
msgs[i].OOB = make([]byte, 0, stickyControlSize+gsoControlSize)
|
||||
}
|
||||
return &msgs
|
||||
},
|
||||
|
|
@ -116,7 +108,7 @@ func (e *StdNetEndpoint) DstIP() netip.Addr {
|
|||
return e.AddrPort.Addr()
|
||||
}
|
||||
|
||||
// See sticky_default,linux, etc for implementations of SrcIP and SrcIfidx.
|
||||
// See control_default,linux, etc for implementations of SrcIP and SrcIfidx.
|
||||
|
||||
func (e *StdNetEndpoint) DstToBytes() []byte {
|
||||
b, _ := e.AddrPort.MarshalBinary()
|
||||
|
|
@ -127,29 +119,8 @@ func (e *StdNetEndpoint) DstToString() string {
|
|||
return e.AddrPort.String()
|
||||
}
|
||||
|
||||
func listenNet(externalControl control.Func, network string, port int) (*net.UDPConn, int, error) {
|
||||
var listenerAddr string
|
||||
if network == "udp6" {
|
||||
listenerAddr = "[::]:" + strconv.Itoa(port)
|
||||
} else {
|
||||
listenerAddr = ":" + strconv.Itoa(port)
|
||||
}
|
||||
|
||||
var listener net.ListenConfig
|
||||
listener.Control = func(network, address string, conn syscall.RawConn) error {
|
||||
for _, wgControlFn := range controlFns {
|
||||
err := wgControlFn(network, address, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if externalControl != nil {
|
||||
return externalControl(network, address, conn)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
conn, err := listener.ListenPacket(context.Background(), network, listenerAddr)
|
||||
func listenNet(network string, port int) (*net.UDPConn, int, error) {
|
||||
conn, err := listenConfig().ListenPacket(context.Background(), network, ":"+strconv.Itoa(port))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
|
@ -166,11 +137,6 @@ func listenNet(externalControl control.Func, network string, port int) (*net.UDP
|
|||
return conn.(*net.UDPConn), uaddr.Port, nil
|
||||
}
|
||||
|
||||
// errEADDRINUSE is syscall.EADDRINUSE, boxed into an interface once
|
||||
// in erraddrinuse.go on almost all platforms. For other platforms,
|
||||
// it's at least non-nil.
|
||||
var errEADDRINUSE error = errors.New("")
|
||||
|
||||
func (s *StdNetBind) Open(uport uint16) ([]ReceiveFunc, uint16, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
|
@ -190,14 +156,14 @@ again:
|
|||
var v4pc *ipv4.PacketConn
|
||||
var v6pc *ipv6.PacketConn
|
||||
|
||||
v4conn, port, err = listenNet(s.externalControl, "udp4", port)
|
||||
v4conn, port, err = listenNet("udp4", port)
|
||||
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Listen on the same port as we're using for ipv4.
|
||||
v6conn, port, err = listenNet(s.externalControl, "udp6", port)
|
||||
if uport == 0 && errors.Is(err, errEADDRINUSE) && tries < 100 {
|
||||
v6conn, port, err = listenNet("udp6", port)
|
||||
if uport == 0 && errors.Is(err, syscall.EADDRINUSE) && tries < 100 {
|
||||
v4conn.Close()
|
||||
tries++
|
||||
goto again
|
||||
|
|
@ -209,7 +175,7 @@ again:
|
|||
var fns []ReceiveFunc
|
||||
if v4conn != nil {
|
||||
s.ipv4TxOffload, s.ipv4RxOffload = supportsUDPOffload(v4conn)
|
||||
if runtime.GOOS == "linux" {
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||
v4pc = ipv4.NewPacketConn(v4conn)
|
||||
s.ipv4PC = v4pc
|
||||
}
|
||||
|
|
@ -218,7 +184,7 @@ again:
|
|||
}
|
||||
if v6conn != nil {
|
||||
s.ipv6TxOffload, s.ipv6RxOffload = supportsUDPOffload(v6conn)
|
||||
if runtime.GOOS == "linux" {
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||
v6pc = ipv6.NewPacketConn(v6conn)
|
||||
s.ipv6PC = v6pc
|
||||
}
|
||||
|
|
@ -234,6 +200,7 @@ again:
|
|||
|
||||
func (s *StdNetBind) putMessages(msgs *[]ipv6.Message) {
|
||||
for i := range *msgs {
|
||||
(*msgs)[i].OOB = (*msgs)[i].OOB[:0]
|
||||
(*msgs)[i] = ipv6.Message{Buffers: (*msgs)[i].Buffers, OOB: (*msgs)[i].OOB}
|
||||
}
|
||||
s.msgsPool.Put(msgs)
|
||||
|
|
@ -243,8 +210,10 @@ func (s *StdNetBind) getMessages() *[]ipv6.Message {
|
|||
return s.msgsPool.Get().(*[]ipv6.Message)
|
||||
}
|
||||
|
||||
// If compilation fails here these are no longer the same underlying type.
|
||||
var _ ipv6.Message = ipv4.Message{}
|
||||
var (
|
||||
// If compilation fails here these are no longer the same underlying type.
|
||||
_ ipv6.Message = ipv4.Message{}
|
||||
)
|
||||
|
||||
type batchReader interface {
|
||||
ReadBatch([]ipv6.Message, int) (int, error)
|
||||
|
|
@ -269,9 +238,9 @@ func (s *StdNetBind) receiveIP(
|
|||
}
|
||||
defer s.putMessages(msgs)
|
||||
var numMsgs int
|
||||
if runtime.GOOS == "linux" {
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||
if rxOffload {
|
||||
readAt := len(*msgs) - 2
|
||||
readAt := len(*msgs) - (IdealBatchSize / udpSegmentMaxDatagrams)
|
||||
numMsgs, err = br.ReadBatch((*msgs)[readAt:], 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
@ -300,10 +269,8 @@ func (s *StdNetBind) receiveIP(
|
|||
if sizes[i] == 0 {
|
||||
continue
|
||||
}
|
||||
if msg.N > 3 {
|
||||
common.ClearArray(bufs[i][1:4])
|
||||
}
|
||||
ep := &StdNetEndpoint{AddrPort: M.AddrPortFromNet(msg.Addr)} // TODO: remove allocation
|
||||
addrPort := msg.Addr.(*net.UDPAddr).AddrPort()
|
||||
ep := &StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation
|
||||
getSrcFromControl(msg.OOB[:msg.NN], ep)
|
||||
eps[i] = ep
|
||||
}
|
||||
|
|
@ -325,7 +292,7 @@ func (s *StdNetBind) makeReceiveIPv6(pc *ipv6.PacketConn, conn *net.UDPConn, rxO
|
|||
// TODO: When all Binds handle IdealBatchSize, remove this dynamic function and
|
||||
// rename the IdealBatchSize constant to BatchSize.
|
||||
func (s *StdNetBind) BatchSize() int {
|
||||
if runtime.GOOS == "linux" {
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||
return IdealBatchSize
|
||||
}
|
||||
return 1
|
||||
|
|
@ -364,14 +331,14 @@ type ErrUDPGSODisabled struct {
|
|||
}
|
||||
|
||||
func (e ErrUDPGSODisabled) Error() string {
|
||||
return fmt.Sprintf("disabled UDP GSO on %s, NIC(s) may not support checksum offload", e.onLaddr)
|
||||
return fmt.Sprintf("disabled UDP GSO on %s, NIC(s) may not support checksum offload or peer MTU with protocol headers is greater than path MTU", e.onLaddr)
|
||||
}
|
||||
|
||||
func (e ErrUDPGSODisabled) Unwrap() error {
|
||||
return e.RetryErr
|
||||
}
|
||||
|
||||
func (s *StdNetBind) Send(bufs [][]byte, endpoint Endpoint, offset int) error {
|
||||
func (s *StdNetBind) Send(bufs [][]byte, endpoint Endpoint) error {
|
||||
s.mu.Lock()
|
||||
blackhole := s.blackhole4
|
||||
conn := s.ipv4
|
||||
|
|
@ -412,17 +379,9 @@ func (s *StdNetBind) Send(bufs [][]byte, endpoint Endpoint, offset int) error {
|
|||
retried bool
|
||||
err error
|
||||
)
|
||||
for _, buf := range bufs {
|
||||
if len(buf) > offset+3 {
|
||||
reserved, loaded := s.reservedForEndpoint[endpoint.(*StdNetEndpoint).AddrPort]
|
||||
if loaded {
|
||||
copy(buf[offset+1:offset+4], reserved[:])
|
||||
}
|
||||
}
|
||||
}
|
||||
retry:
|
||||
if offload {
|
||||
n := coalesceMessages(ua, endpoint.(*StdNetEndpoint), bufs, offset, *msgs, setGSOSize)
|
||||
n := coalesceMessages(ua, endpoint.(*StdNetEndpoint), bufs, *msgs, setGSOSize)
|
||||
err = s.send(conn, br, (*msgs)[:n])
|
||||
if err != nil && offload && errShouldDisableUDPGSO(err) {
|
||||
offload = false
|
||||
|
|
@ -439,7 +398,7 @@ retry:
|
|||
} else {
|
||||
for i := range bufs {
|
||||
(*msgs)[i].Addr = ua
|
||||
(*msgs)[i].Buffers[0] = bufs[i][offset:]
|
||||
(*msgs)[i].Buffers[0] = bufs[i]
|
||||
setSrcControl(&(*msgs)[i].OOB, endpoint.(*StdNetEndpoint))
|
||||
}
|
||||
err = s.send(conn, br, (*msgs)[:len(bufs)])
|
||||
|
|
@ -450,17 +409,13 @@ retry:
|
|||
return err
|
||||
}
|
||||
|
||||
func (s *StdNetBind) SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte) {
|
||||
s.reservedForEndpoint[destination] = reserved
|
||||
}
|
||||
|
||||
func (s *StdNetBind) send(conn *net.UDPConn, pc batchWriter, msgs []ipv6.Message) error {
|
||||
var (
|
||||
n int
|
||||
err error
|
||||
start int
|
||||
)
|
||||
if runtime.GOOS == "linux" {
|
||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
||||
for {
|
||||
n, err = pc.WriteBatch(msgs[start:], 0)
|
||||
if err != nil || n == len(msgs[start:]) {
|
||||
|
|
@ -492,7 +447,7 @@ const (
|
|||
|
||||
type setGSOFunc func(control *[]byte, gsoSize uint16)
|
||||
|
||||
func coalesceMessages(addr *net.UDPAddr, ep *StdNetEndpoint, bufs [][]byte, offset int, msgs []ipv6.Message, setGSO setGSOFunc) int {
|
||||
func coalesceMessages(addr *net.UDPAddr, ep *StdNetEndpoint, bufs [][]byte, msgs []ipv6.Message, setGSO setGSOFunc) int {
|
||||
var (
|
||||
base = -1 // index of msg we are currently coalescing into
|
||||
gsoSize int // segmentation size of msgs[base]
|
||||
|
|
@ -504,7 +459,6 @@ func coalesceMessages(addr *net.UDPAddr, ep *StdNetEndpoint, bufs [][]byte, offs
|
|||
maxPayloadLen = maxIPv6PayloadLen
|
||||
}
|
||||
for i, buf := range bufs {
|
||||
buf = buf[offset:]
|
||||
if i > 0 {
|
||||
msgLen := len(buf)
|
||||
baseLenBefore := len(msgs[base].Buffers[0])
|
||||
|
|
|
|||
250
conn/bind_std_test.go
Normal file
250
conn/bind_std_test.go
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
package conn
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
func TestStdNetBindReceiveFuncAfterClose(t *testing.T) {
|
||||
bind := NewStdNetBind().(*StdNetBind)
|
||||
fns, _, err := bind.Open(0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bind.Close()
|
||||
bufs := make([][]byte, 1)
|
||||
bufs[0] = make([]byte, 1)
|
||||
sizes := make([]int, 1)
|
||||
eps := make([]Endpoint, 1)
|
||||
for _, fn := range fns {
|
||||
// The ReceiveFuncs must not access conn-related fields on StdNetBind
|
||||
// unguarded. Close() nils the conn-related fields resulting in a panic
|
||||
// if they violate the mutex.
|
||||
fn(bufs, sizes, eps)
|
||||
}
|
||||
}
|
||||
|
||||
func mockSetGSOSize(control *[]byte, gsoSize uint16) {
|
||||
*control = (*control)[:cap(*control)]
|
||||
binary.LittleEndian.PutUint16(*control, gsoSize)
|
||||
}
|
||||
|
||||
func Test_coalesceMessages(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
buffs [][]byte
|
||||
wantLens []int
|
||||
wantGSO []int
|
||||
}{
|
||||
{
|
||||
name: "one message no coalesce",
|
||||
buffs: [][]byte{
|
||||
make([]byte, 1, 1),
|
||||
},
|
||||
wantLens: []int{1},
|
||||
wantGSO: []int{0},
|
||||
},
|
||||
{
|
||||
name: "two messages equal len coalesce",
|
||||
buffs: [][]byte{
|
||||
make([]byte, 1, 2),
|
||||
make([]byte, 1, 1),
|
||||
},
|
||||
wantLens: []int{2},
|
||||
wantGSO: []int{1},
|
||||
},
|
||||
{
|
||||
name: "two messages unequal len coalesce",
|
||||
buffs: [][]byte{
|
||||
make([]byte, 2, 3),
|
||||
make([]byte, 1, 1),
|
||||
},
|
||||
wantLens: []int{3},
|
||||
wantGSO: []int{2},
|
||||
},
|
||||
{
|
||||
name: "three messages second unequal len coalesce",
|
||||
buffs: [][]byte{
|
||||
make([]byte, 2, 3),
|
||||
make([]byte, 1, 1),
|
||||
make([]byte, 2, 2),
|
||||
},
|
||||
wantLens: []int{3, 2},
|
||||
wantGSO: []int{2, 0},
|
||||
},
|
||||
{
|
||||
name: "three messages limited cap coalesce",
|
||||
buffs: [][]byte{
|
||||
make([]byte, 2, 4),
|
||||
make([]byte, 2, 2),
|
||||
make([]byte, 2, 2),
|
||||
},
|
||||
wantLens: []int{4, 2},
|
||||
wantGSO: []int{2, 0},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
addr := &net.UDPAddr{
|
||||
IP: net.ParseIP("127.0.0.1").To4(),
|
||||
Port: 1,
|
||||
}
|
||||
msgs := make([]ipv6.Message, len(tt.buffs))
|
||||
for i := range msgs {
|
||||
msgs[i].Buffers = make([][]byte, 1)
|
||||
msgs[i].OOB = make([]byte, 0, 2)
|
||||
}
|
||||
got := coalesceMessages(addr, &StdNetEndpoint{AddrPort: addr.AddrPort()}, tt.buffs, msgs, mockSetGSOSize)
|
||||
if got != len(tt.wantLens) {
|
||||
t.Fatalf("got len %d want: %d", got, len(tt.wantLens))
|
||||
}
|
||||
for i := 0; i < got; i++ {
|
||||
if msgs[i].Addr != addr {
|
||||
t.Errorf("msgs[%d].Addr != passed addr", i)
|
||||
}
|
||||
gotLen := len(msgs[i].Buffers[0])
|
||||
if gotLen != tt.wantLens[i] {
|
||||
t.Errorf("len(msgs[%d].Buffers[0]) %d != %d", i, gotLen, tt.wantLens[i])
|
||||
}
|
||||
gotGSO, err := mockGetGSOSize(msgs[i].OOB)
|
||||
if err != nil {
|
||||
t.Fatalf("msgs[%d] getGSOSize err: %v", i, err)
|
||||
}
|
||||
if gotGSO != tt.wantGSO[i] {
|
||||
t.Errorf("msgs[%d] gsoSize %d != %d", i, gotGSO, tt.wantGSO[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mockGetGSOSize(control []byte) (int, error) {
|
||||
if len(control) < 2 {
|
||||
return 0, nil
|
||||
}
|
||||
return int(binary.LittleEndian.Uint16(control)), nil
|
||||
}
|
||||
|
||||
func Test_splitCoalescedMessages(t *testing.T) {
|
||||
newMsg := func(n, gso int) ipv6.Message {
|
||||
msg := ipv6.Message{
|
||||
Buffers: [][]byte{make([]byte, 1<<16-1)},
|
||||
N: n,
|
||||
OOB: make([]byte, 2),
|
||||
}
|
||||
binary.LittleEndian.PutUint16(msg.OOB, uint16(gso))
|
||||
if gso > 0 {
|
||||
msg.NN = 2
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
msgs []ipv6.Message
|
||||
firstMsgAt int
|
||||
wantNumEval int
|
||||
wantMsgLens []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "second last split last empty",
|
||||
msgs: []ipv6.Message{
|
||||
newMsg(0, 0),
|
||||
newMsg(0, 0),
|
||||
newMsg(3, 1),
|
||||
newMsg(0, 0),
|
||||
},
|
||||
firstMsgAt: 2,
|
||||
wantNumEval: 3,
|
||||
wantMsgLens: []int{1, 1, 1, 0},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "second last no split last empty",
|
||||
msgs: []ipv6.Message{
|
||||
newMsg(0, 0),
|
||||
newMsg(0, 0),
|
||||
newMsg(1, 0),
|
||||
newMsg(0, 0),
|
||||
},
|
||||
firstMsgAt: 2,
|
||||
wantNumEval: 1,
|
||||
wantMsgLens: []int{1, 0, 0, 0},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "second last no split last no split",
|
||||
msgs: []ipv6.Message{
|
||||
newMsg(0, 0),
|
||||
newMsg(0, 0),
|
||||
newMsg(1, 0),
|
||||
newMsg(1, 0),
|
||||
},
|
||||
firstMsgAt: 2,
|
||||
wantNumEval: 2,
|
||||
wantMsgLens: []int{1, 1, 0, 0},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "second last no split last split",
|
||||
msgs: []ipv6.Message{
|
||||
newMsg(0, 0),
|
||||
newMsg(0, 0),
|
||||
newMsg(1, 0),
|
||||
newMsg(3, 1),
|
||||
},
|
||||
firstMsgAt: 2,
|
||||
wantNumEval: 4,
|
||||
wantMsgLens: []int{1, 1, 1, 1},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "second last split last split",
|
||||
msgs: []ipv6.Message{
|
||||
newMsg(0, 0),
|
||||
newMsg(0, 0),
|
||||
newMsg(2, 1),
|
||||
newMsg(2, 1),
|
||||
},
|
||||
firstMsgAt: 2,
|
||||
wantNumEval: 4,
|
||||
wantMsgLens: []int{1, 1, 1, 1},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "second last no split last split overflow",
|
||||
msgs: []ipv6.Message{
|
||||
newMsg(0, 0),
|
||||
newMsg(0, 0),
|
||||
newMsg(1, 0),
|
||||
newMsg(4, 1),
|
||||
},
|
||||
firstMsgAt: 2,
|
||||
wantNumEval: 4,
|
||||
wantMsgLens: []int{1, 1, 1, 1},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := splitCoalescedMessages(tt.msgs, 2, mockGetGSOSize)
|
||||
if err != nil && !tt.wantErr {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if got != tt.wantNumEval {
|
||||
t.Fatalf("got to eval: %d want: %d", got, tt.wantNumEval)
|
||||
}
|
||||
for i, msg := range tt.msgs {
|
||||
if msg.N != tt.wantMsgLens[i] {
|
||||
t.Fatalf("msg[%d].N: %d want: %d", i, msg.N, tt.wantMsgLens[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
|
@ -15,12 +15,9 @@ import (
|
|||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/wireguard-go/conn/winrio"
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn/winrio"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -75,26 +72,18 @@ type afWinRingBind struct {
|
|||
|
||||
// WinRingBind uses Windows registered I/O for fast ring buffered networking.
|
||||
type WinRingBind struct {
|
||||
externalControl control.Func
|
||||
reservedForEndpoint map[WinRingEndpoint][3]uint8
|
||||
|
||||
v4, v6 afWinRingBind
|
||||
mu sync.RWMutex
|
||||
isOpen atomic.Uint32 // 0, 1, or 2
|
||||
}
|
||||
|
||||
func NewDefaultBind(externalControl control.Func) Bind {
|
||||
return NewWinRingBind(externalControl)
|
||||
}
|
||||
func NewDefaultBind() Bind { return NewWinRingBind() }
|
||||
|
||||
func NewWinRingBind(externalControl control.Func) Bind {
|
||||
func NewWinRingBind() Bind {
|
||||
if !winrio.Initialize() {
|
||||
return NewStdNetBind(externalControl)
|
||||
}
|
||||
return &WinRingBind{
|
||||
externalControl: externalControl,
|
||||
reservedForEndpoint: make(map[WinRingEndpoint][3]uint8),
|
||||
return NewStdNetBind()
|
||||
}
|
||||
return new(WinRingBind)
|
||||
}
|
||||
|
||||
type WinRingEndpoint struct {
|
||||
|
|
@ -250,7 +239,7 @@ func (ring *ringBuffer) Open() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr, externalControl control.Func) (windows.Sockaddr, error) {
|
||||
func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sockaddr, error) {
|
||||
var err error
|
||||
bind.sock, err = winrio.Socket(family, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
||||
if err != nil {
|
||||
|
|
@ -268,19 +257,6 @@ func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr, externalContr
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var network string
|
||||
switch family {
|
||||
case windows.AF_INET:
|
||||
network = "udp4"
|
||||
case windows.AF_INET6:
|
||||
network = "udp6"
|
||||
}
|
||||
if externalControl != nil {
|
||||
err = externalControl(network, M.AddrPortFromSockaddr(sa).String(), &fakeRawConn{bind.sock})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = windows.Bind(bind.sock, sa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -292,23 +268,6 @@ func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr, externalContr
|
|||
return sa, nil
|
||||
}
|
||||
|
||||
type fakeRawConn struct {
|
||||
socket windows.Handle
|
||||
}
|
||||
|
||||
func (c *fakeRawConn) Control(f func(fd uintptr)) error {
|
||||
f(uintptr(c.socket))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeRawConn) Read(f func(fd uintptr) (done bool)) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c *fakeRawConn) Write(f func(fd uintptr) (done bool)) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (bind *WinRingBind) Open(port uint16) (recvFns []ReceiveFunc, selectedPort uint16, err error) {
|
||||
bind.mu.Lock()
|
||||
defer bind.mu.Unlock()
|
||||
|
|
@ -321,11 +280,11 @@ func (bind *WinRingBind) Open(port uint16) (recvFns []ReceiveFunc, selectedPort
|
|||
return nil, 0, ErrBindAlreadyOpen
|
||||
}
|
||||
var sa windows.Sockaddr
|
||||
sa, err = bind.v4.Open(windows.AF_INET, &windows.SockaddrInet4{Port: int(port)}, bind.externalControl)
|
||||
sa, err = bind.v4.Open(windows.AF_INET, &windows.SockaddrInet4{Port: int(port)})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
sa, err = bind.v6.Open(windows.AF_INET6, &windows.SockaddrInet6{Port: sa.(*windows.SockaddrInet4).Port}, bind.externalControl)
|
||||
sa, err = bind.v6.Open(windows.AF_INET6, &windows.SockaddrInet6{Port: sa.(*windows.SockaddrInet4).Port})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
|
@ -461,9 +420,6 @@ func (bind *WinRingBind) receiveIPv4(bufs [][]byte, sizes []int, eps []Endpoint)
|
|||
bind.mu.RLock()
|
||||
defer bind.mu.RUnlock()
|
||||
n, ep, err := bind.v4.Receive(bufs[0], &bind.isOpen)
|
||||
if n > 3 {
|
||||
common.ClearArray(bufs[0][1:4])
|
||||
}
|
||||
sizes[0] = n
|
||||
eps[0] = ep
|
||||
return 1, err
|
||||
|
|
@ -473,9 +429,6 @@ func (bind *WinRingBind) receiveIPv6(bufs [][]byte, sizes []int, eps []Endpoint)
|
|||
bind.mu.RLock()
|
||||
defer bind.mu.RUnlock()
|
||||
n, ep, err := bind.v6.Receive(bufs[0], &bind.isOpen)
|
||||
if n > 3 {
|
||||
common.ClearArray(bufs[0][1:4])
|
||||
}
|
||||
sizes[0] = n
|
||||
eps[0] = ep
|
||||
return 1, err
|
||||
|
|
@ -533,7 +486,7 @@ func (bind *afWinRingBind) Send(buf []byte, nend *WinRingEndpoint, isOpen *atomi
|
|||
return winrio.SendEx(bind.rq, dataBuffer, 1, nil, addressBuffer, nil, nil, 0, 0)
|
||||
}
|
||||
|
||||
func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint, offset int) error {
|
||||
func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint) error {
|
||||
nend, ok := endpoint.(*WinRingEndpoint)
|
||||
if !ok {
|
||||
return ErrWrongEndpointType
|
||||
|
|
@ -541,13 +494,6 @@ func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint, offset int) erro
|
|||
bind.mu.RLock()
|
||||
defer bind.mu.RUnlock()
|
||||
for _, buf := range bufs {
|
||||
buf = buf[offset:]
|
||||
if len(buf) > 3 {
|
||||
reserved, loaded := bind.reservedForEndpoint[*endpoint.(*WinRingEndpoint)]
|
||||
if loaded {
|
||||
copy(buf[1:4], reserved[:])
|
||||
}
|
||||
}
|
||||
switch nend.family {
|
||||
case windows.AF_INET:
|
||||
if bind.v4.blackhole {
|
||||
|
|
@ -568,14 +514,6 @@ func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint, offset int) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func (bind *WinRingBind) SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte) {
|
||||
endpoint, err := bind.ParseEndpoint(destination.String())
|
||||
if err != nil {
|
||||
panic(E.Cause(err, "parse destination as WinRingEndpoint"))
|
||||
}
|
||||
bind.reservedForEndpoint[*endpoint.(*WinRingEndpoint)] = reserved
|
||||
}
|
||||
|
||||
func (s *StdNetBind) BindSocketToInterface4(interfaceIndex uint32, blackhole bool) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
|
|
|||
136
conn/bindtest/bindtest.go
Normal file
136
conn/bindtest/bindtest.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package bindtest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
)
|
||||
|
||||
type ChannelBind struct {
|
||||
rx4, tx4 *chan []byte
|
||||
rx6, tx6 *chan []byte
|
||||
closeSignal chan bool
|
||||
source4, source6 ChannelEndpoint
|
||||
target4, target6 ChannelEndpoint
|
||||
}
|
||||
|
||||
type ChannelEndpoint uint16
|
||||
|
||||
var (
|
||||
_ conn.Bind = (*ChannelBind)(nil)
|
||||
_ conn.Endpoint = (*ChannelEndpoint)(nil)
|
||||
)
|
||||
|
||||
func NewChannelBinds() [2]conn.Bind {
|
||||
arx4 := make(chan []byte, 8192)
|
||||
brx4 := make(chan []byte, 8192)
|
||||
arx6 := make(chan []byte, 8192)
|
||||
brx6 := make(chan []byte, 8192)
|
||||
var binds [2]ChannelBind
|
||||
binds[0].rx4 = &arx4
|
||||
binds[0].tx4 = &brx4
|
||||
binds[1].rx4 = &brx4
|
||||
binds[1].tx4 = &arx4
|
||||
binds[0].rx6 = &arx6
|
||||
binds[0].tx6 = &brx6
|
||||
binds[1].rx6 = &brx6
|
||||
binds[1].tx6 = &arx6
|
||||
binds[0].target4 = ChannelEndpoint(1)
|
||||
binds[1].target4 = ChannelEndpoint(2)
|
||||
binds[0].target6 = ChannelEndpoint(3)
|
||||
binds[1].target6 = ChannelEndpoint(4)
|
||||
binds[0].source4 = binds[1].target4
|
||||
binds[0].source6 = binds[1].target6
|
||||
binds[1].source4 = binds[0].target4
|
||||
binds[1].source6 = binds[0].target6
|
||||
return [2]conn.Bind{&binds[0], &binds[1]}
|
||||
}
|
||||
|
||||
func (c ChannelEndpoint) ClearSrc() {}
|
||||
|
||||
func (c ChannelEndpoint) SrcToString() string { return "" }
|
||||
|
||||
func (c ChannelEndpoint) DstToString() string { return fmt.Sprintf("127.0.0.1:%d", c) }
|
||||
|
||||
func (c ChannelEndpoint) DstToBytes() []byte { return []byte{byte(c)} }
|
||||
|
||||
func (c ChannelEndpoint) DstIP() netip.Addr { return netip.AddrFrom4([4]byte{127, 0, 0, 1}) }
|
||||
|
||||
func (c ChannelEndpoint) SrcIP() netip.Addr { return netip.Addr{} }
|
||||
|
||||
func (c *ChannelBind) Open(port uint16) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
||||
c.closeSignal = make(chan bool)
|
||||
fns = append(fns, c.makeReceiveFunc(*c.rx4))
|
||||
fns = append(fns, c.makeReceiveFunc(*c.rx6))
|
||||
if rand.Uint32()&1 == 0 {
|
||||
return fns, uint16(c.source4), nil
|
||||
} else {
|
||||
return fns, uint16(c.source6), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChannelBind) Close() error {
|
||||
if c.closeSignal != nil {
|
||||
select {
|
||||
case <-c.closeSignal:
|
||||
default:
|
||||
close(c.closeSignal)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ChannelBind) BatchSize() int { return 1 }
|
||||
|
||||
func (c *ChannelBind) SetMark(mark uint32) error { return nil }
|
||||
|
||||
func (c *ChannelBind) makeReceiveFunc(ch chan []byte) conn.ReceiveFunc {
|
||||
return func(bufs [][]byte, sizes []int, eps []conn.Endpoint) (n int, err error) {
|
||||
select {
|
||||
case <-c.closeSignal:
|
||||
return 0, net.ErrClosed
|
||||
case rx := <-ch:
|
||||
copied := copy(bufs[0], rx)
|
||||
sizes[0] = copied
|
||||
eps[0] = c.target6
|
||||
return 1, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChannelBind) Send(bufs [][]byte, ep conn.Endpoint) error {
|
||||
for _, b := range bufs {
|
||||
select {
|
||||
case <-c.closeSignal:
|
||||
return net.ErrClosed
|
||||
default:
|
||||
bc := make([]byte, len(b))
|
||||
copy(bc, b)
|
||||
if ep.(ChannelEndpoint) == c.target4 {
|
||||
*c.tx4 <- bc
|
||||
} else if ep.(ChannelEndpoint) == c.target6 {
|
||||
*c.tx6 <- bc
|
||||
} else {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ChannelBind) ParseEndpoint(s string) (conn.Endpoint, error) {
|
||||
addr, err := netip.ParseAddrPort(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ChannelEndpoint(addr.Port()), nil
|
||||
}
|
||||
44
conn/conn.go
44
conn/conn.go
|
|
@ -45,11 +45,9 @@ type Bind interface {
|
|||
// This mark is passed to the kernel as the socket option SO_MARK.
|
||||
SetMark(mark uint32) error
|
||||
|
||||
// Send writes one or more packets in bufs to address ep. A nonzero offset
|
||||
// can be used to instruct the Bind on where packet data begins in each
|
||||
// element of the bufs slice. Space preceding offset is free to use for
|
||||
// additional encapsulation. The length of bufs must not exceed BatchSize().
|
||||
Send(bufs [][]byte, ep Endpoint, offset int) error
|
||||
// Send writes one or more packets in bufs to address ep. The length of
|
||||
// bufs must not exceed BatchSize().
|
||||
Send(bufs [][]byte, ep Endpoint) error
|
||||
|
||||
// ParseEndpoint creates a new endpoint from a string.
|
||||
ParseEndpoint(s string) (Endpoint, error)
|
||||
|
|
@ -57,8 +55,6 @@ type Bind interface {
|
|||
// BatchSize is the number of buffers expected to be passed to
|
||||
// the ReceiveFuncs, and the maximum expected to be passed to SendBatch.
|
||||
BatchSize() int
|
||||
|
||||
SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte)
|
||||
}
|
||||
|
||||
// BindSocketToInterface is implemented by Bind objects that support being
|
||||
|
|
@ -88,40 +84,6 @@ type Endpoint interface {
|
|||
SrcIP() netip.Addr
|
||||
}
|
||||
|
||||
// InitiationAwareEndpoint is an optional [Endpoint] specialization for
|
||||
// integrations that want to know when a WireGuard handshake initiation
|
||||
// message has been received, enabling just-in-time peer configuration before
|
||||
// attempted decryption.
|
||||
//
|
||||
// It's most useful when used in combination with [PeerAwareEndpoint], enabling
|
||||
// JIT peer configuration and post-decryption peer verification from a single
|
||||
// implementer.
|
||||
type InitiationAwareEndpoint interface {
|
||||
// InitiationMessagePublicKey is called when a handshake initiation message
|
||||
// has been received, and the sender's public key has been identified, but
|
||||
// BEFORE an attempt has been made to verify it.
|
||||
InitiationMessagePublicKey(peerPublicKey [32]byte)
|
||||
}
|
||||
|
||||
// PeerAwareEndpoint is an optional Endpoint specialization for
|
||||
// integrations that want to know about the outcome of Cryptokey Routing
|
||||
// identification.
|
||||
//
|
||||
// If they receive a packet from a source they had not pre-identified,
|
||||
// to learn the identification WireGuard can derive from the session
|
||||
// or handshake.
|
||||
//
|
||||
// A [PeerAwareEndpoint] may be installed as the [conn.Endpoint] following
|
||||
// successful decryption unless endpoint roaming has been disabled for
|
||||
// the peer.
|
||||
type PeerAwareEndpoint interface {
|
||||
// FromPeer is called at least once per successfully Cryptokey Routing ID'd
|
||||
// [ReceiveFunc] packets batch for a given node key. wireguard-go will
|
||||
// always call it for the latest/tail packet in the batch, only ever
|
||||
// suppressing calls for older packets.
|
||||
FromPeer(peerPublicKey [32]byte)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrBindAlreadyOpen = errors.New("bind is already open")
|
||||
ErrWrongEndpointType = errors.New("endpoint type does not correspond with bind type")
|
||||
|
|
|
|||
24
conn/conn_test.go
Normal file
24
conn/conn_test.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPrettyName(t *testing.T) {
|
||||
var (
|
||||
recvFunc ReceiveFunc = func(bufs [][]byte, sizes []int, eps []Endpoint) (n int, err error) { return }
|
||||
)
|
||||
|
||||
const want = "TestPrettyName"
|
||||
|
||||
t.Run("ReceiveFunc.PrettyName", func(t *testing.T) {
|
||||
if got := recvFunc.PrettyName(); got != want {
|
||||
t.Errorf("PrettyName() = %v, want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -6,7 +6,8 @@
|
|||
package conn
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing/common/control"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it is
|
||||
|
|
@ -16,6 +17,27 @@ import (
|
|||
// around this limitation)
|
||||
const socketBufferSize = 7 << 20
|
||||
|
||||
// controlFn is the callback function signature from net.ListenConfig.Control.
|
||||
// It is used to apply platform specific configuration to the socket prior to
|
||||
// bind.
|
||||
type controlFn func(network, address string, c syscall.RawConn) error
|
||||
|
||||
// controlFns is a list of functions that are called from the listen config
|
||||
// that can apply socket options.
|
||||
var controlFns []control.Func
|
||||
var controlFns = []controlFn{}
|
||||
|
||||
// listenConfig returns a net.ListenConfig that applies the controlFns to the
|
||||
// socket prior to bind. This is used to apply socket buffer sizing and packet
|
||||
// information OOB configuration for sticky sockets.
|
||||
func listenConfig() *net.ListenConfig {
|
||||
return &net.ListenConfig{
|
||||
Control: func(network, address string, c syscall.RawConn) error {
|
||||
for _, fn := range controlFns {
|
||||
if err := fn(network, address, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
|
@ -13,6 +13,35 @@ import (
|
|||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Taken from go/src/internal/syscall/unix/kernel_version_linux.go
|
||||
func kernelVersion() (major, minor int) {
|
||||
var uname unix.Utsname
|
||||
if err := unix.Uname(&uname); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
values [2]int
|
||||
value, vi int
|
||||
)
|
||||
for _, c := range uname.Release {
|
||||
if '0' <= c && c <= '9' {
|
||||
value = (value * 10) + int(c-'0')
|
||||
} else {
|
||||
// Note that we're assuming N.N.N here.
|
||||
// If we see anything else, we are likely to mis-parse it.
|
||||
values[vi] = value
|
||||
vi++
|
||||
if vi >= len(values) {
|
||||
break
|
||||
}
|
||||
value = 0
|
||||
}
|
||||
}
|
||||
|
||||
return values[0], values[1]
|
||||
}
|
||||
|
||||
func init() {
|
||||
controlFns = append(controlFns,
|
||||
|
||||
|
|
@ -60,17 +89,19 @@ func init() {
|
|||
|
||||
// Attempt to enable UDP_GRO
|
||||
func(network, address string, c syscall.RawConn) error {
|
||||
// lx(010): skip UDP_GRO on android. The GRO receive path in bind_std.go
|
||||
// is gated on runtime.GOOS=="linux", which is false on android — so a
|
||||
// coalesced super-packet is never split and corrupts the WG stream
|
||||
// (download dies). Belt-and-suspenders with the rxOffload guard in
|
||||
// features_linux.go. TX/GSO untouched.
|
||||
// See SPECS/010-WG_ENDPOINT_GRO_SPLIT_BRAIN.
|
||||
if runtime.GOOS == "android" {
|
||||
// Kernels below 5.12 are missing 98184612aca0 ("net:
|
||||
// udp: Add support for getsockopt(..., ..., UDP_GRO,
|
||||
// ..., ...);"), which means we can't read this back
|
||||
// later. We could pipe the return value through to
|
||||
// the rest of the code, but UDP_GRO is kind of buggy
|
||||
// anyway, so just gate this here.
|
||||
major, minor := kernelVersion()
|
||||
if major < 5 || (major == 5 && minor < 12) {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.Control(func(fd uintptr) {
|
||||
_ = unix.SetsockoptInt(int(fd), unix.IPPROTO_UDP, socketOptionUDPGRO, 1)
|
||||
_ = unix.SetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_GRO, 1)
|
||||
})
|
||||
return nil
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//go:build !windows && !linux && !wasm && !plan9 && !tamago
|
||||
//go:build !windows && !linux && !wasm
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,8 +7,4 @@
|
|||
|
||||
package conn
|
||||
|
||||
import "github.com/sagernet/sing/common/control"
|
||||
|
||||
func NewDefaultBind(externalControl control.Func) Bind {
|
||||
return NewStdNetBind(externalControl)
|
||||
}
|
||||
func NewDefaultBind() Bind { return NewStdNetBind() }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@
|
|||
|
||||
package conn
|
||||
|
||||
func errShouldDisableUDPGSO(err error) bool {
|
||||
func errShouldDisableUDPGSO(_ error) bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
|
@ -20,7 +20,9 @@ func errShouldDisableUDPGSO(err error) bool {
|
|||
// See:
|
||||
// https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man7/udp.7?id=806eabd74910447f21005160e90957bde4db0183#n228
|
||||
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/udp.c?h=v6.2&id=c9c3395d5e3dcc6daee66c6908354d47bf98cb0c#n942
|
||||
return serr.Err == unix.EIO
|
||||
// If gso_size + udp + ip headers > fragment size EINVAL is returned.
|
||||
// It occurs when the peer mtu + wg headers is greater than path mtu.
|
||||
return serr.Err == unix.EIO || serr.Err == unix.EINVAL
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
||||
import "net"
|
||||
|
||||
func supportsUDPOffload(conn *net.UDPConn) (txOffload, rxOffload bool) {
|
||||
func supportsUDPOffload(_ *net.UDPConn) (txOffload, rxOffload bool) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,28 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
||||
import (
|
||||
"net"
|
||||
"runtime"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: upstream to x/sys/unix
|
||||
socketOptionLevelUDP = 17
|
||||
socketOptionUDPSegment = 103
|
||||
socketOptionUDPGRO = 104
|
||||
)
|
||||
|
||||
func supportsUDPOffload(conn *net.UDPConn) (txOffload, rxOffload bool) {
|
||||
rc, err := conn.SyscallConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = rc.Control(func(fd uintptr) {
|
||||
_, errSyscall := unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, socketOptionUDPSegment)
|
||||
if errSyscall != nil {
|
||||
return
|
||||
}
|
||||
txOffload = true
|
||||
// lx(010): never advertise RX offload on android. runtime.GOOS=="android"
|
||||
// (not "linux"), so the GRO receive dispatcher in bind_std.go (gated on
|
||||
// GOOS=="linux") is dead there — a coalesced GRO super-packet would be read
|
||||
// as one datagram and corrupt the WG transport stream, killing download.
|
||||
// Confirmed on device (CPH2411/Android-15: rxOffload=true, dispatch=single).
|
||||
// TX is left untouched. See SPECS/010-WG_ENDPOINT_GRO_SPLIT_BRAIN.
|
||||
if runtime.GOOS == "android" {
|
||||
return
|
||||
}
|
||||
opt, errSyscall := unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, socketOptionUDPGRO)
|
||||
if errSyscall != nil {
|
||||
return
|
||||
}
|
||||
rxOffload = opt == 1
|
||||
_, errSyscall := unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_SEGMENT)
|
||||
txOffload = errSyscall == nil
|
||||
// getsockopt(IPPROTO_UDP, UDP_GRO) is not supported in android
|
||||
// use setsockopt workaround
|
||||
errSyscall = unix.SetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_GRO, 1)
|
||||
rxOffload = errSyscall == nil
|
||||
})
|
||||
if err != nil {
|
||||
return false, false
|
||||
|
|
|
|||
21
conn/gso_default.go
Normal file
21
conn/gso_default.go
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
//go:build !linux
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
||||
// getGSOSize parses control for UDP_GRO and if found returns its GSO size data.
|
||||
func getGSOSize(control []byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// setGSOSize sets a UDP_SEGMENT in control based on gsoSize.
|
||||
func setGSOSize(control *[]byte, gsoSize uint16) {
|
||||
}
|
||||
|
||||
// gsoControlSize returns the recommended buffer size for pooling sticky and UDP
|
||||
// offloading control data.
|
||||
const gsoControlSize = 0
|
||||
65
conn/gso_linux.go
Normal file
65
conn/gso_linux.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
//go:build linux
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
sizeOfGSOData = 2
|
||||
)
|
||||
|
||||
// getGSOSize parses control for UDP_GRO and if found returns its GSO size data.
|
||||
func getGSOSize(control []byte) (int, error) {
|
||||
var (
|
||||
hdr unix.Cmsghdr
|
||||
data []byte
|
||||
rem = control
|
||||
err error
|
||||
)
|
||||
|
||||
for len(rem) > unix.SizeofCmsghdr {
|
||||
hdr, data, rem, err = unix.ParseOneSocketControlMessage(rem)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error parsing socket control message: %w", err)
|
||||
}
|
||||
if hdr.Level == unix.SOL_UDP && hdr.Type == unix.UDP_GRO && len(data) >= sizeOfGSOData {
|
||||
var gso uint16
|
||||
copy(unsafe.Slice((*byte)(unsafe.Pointer(&gso)), sizeOfGSOData), data[:sizeOfGSOData])
|
||||
return int(gso), nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// setGSOSize sets a UDP_SEGMENT in control based on gsoSize. It leaves existing
|
||||
// data in control untouched.
|
||||
func setGSOSize(control *[]byte, gsoSize uint16) {
|
||||
existingLen := len(*control)
|
||||
avail := cap(*control) - existingLen
|
||||
space := unix.CmsgSpace(sizeOfGSOData)
|
||||
if avail < space {
|
||||
return
|
||||
}
|
||||
*control = (*control)[:cap(*control)]
|
||||
gsoControl := (*control)[existingLen:]
|
||||
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&(gsoControl)[0]))
|
||||
hdr.Level = unix.SOL_UDP
|
||||
hdr.Type = unix.UDP_SEGMENT
|
||||
hdr.SetLen(unix.CmsgLen(sizeOfGSOData))
|
||||
copy((gsoControl)[unix.CmsgLen(0):], unsafe.Slice((*byte)(unsafe.Pointer(&gsoSize)), sizeOfGSOData))
|
||||
*control = (*control)[:existingLen+space]
|
||||
}
|
||||
|
||||
// gsoControlSize returns the recommended buffer size for pooling UDP
|
||||
// offloading control data.
|
||||
var gsoControlSize = unix.CmsgSpace(sizeOfGSOData)
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
//go:build !(linux && !android)
|
||||
//go:build !linux || android
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
|
@ -22,7 +22,7 @@ func (e *StdNetEndpoint) SrcToString() string {
|
|||
}
|
||||
|
||||
// TODO: macOS, FreeBSD and other BSDs likely do support the sticky sockets
|
||||
// ({get,set}srcControl feature set, but use alternatively named flags and need
|
||||
// {get,set}srcControl feature set, but use alternatively named flags and need
|
||||
// ports and require testing.
|
||||
|
||||
// getSrcFromControl parses the control for PKTINFO and if found updates ep with
|
||||
|
|
@ -35,17 +35,8 @@ func getSrcFromControl(control []byte, ep *StdNetEndpoint) {
|
|||
func setSrcControl(control *[]byte, ep *StdNetEndpoint) {
|
||||
}
|
||||
|
||||
// getGSOSize parses control for UDP_GRO and if found returns its GSO size data.
|
||||
func getGSOSize(control []byte) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// setGSOSize sets a UDP_SEGMENT in control based on gsoSize.
|
||||
func setGSOSize(control *[]byte, gsoSize uint16) {
|
||||
}
|
||||
|
||||
// controlSize returns the recommended buffer size for pooling sticky and UDP
|
||||
// stickyControlSize returns the recommended buffer size for pooling sticky
|
||||
// offloading control data.
|
||||
const controlSize = 0
|
||||
const stickyControlSize = 0
|
||||
|
||||
const StdNetSupportsStickySockets = false
|
||||
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"unsafe"
|
||||
|
||||
|
|
@ -106,54 +105,8 @@ func setSrcControl(control *[]byte, ep *StdNetEndpoint) {
|
|||
*control = append(*control, ep.src...)
|
||||
}
|
||||
|
||||
const (
|
||||
sizeOfGSOData = 2
|
||||
)
|
||||
|
||||
// getGSOSize parses control for UDP_GRO and if found returns its GSO size data.
|
||||
func getGSOSize(control []byte) (int, error) {
|
||||
var (
|
||||
hdr unix.Cmsghdr
|
||||
data []byte
|
||||
rem = control
|
||||
err error
|
||||
)
|
||||
|
||||
for len(rem) > unix.SizeofCmsghdr {
|
||||
hdr, data, rem, err = unix.ParseOneSocketControlMessage(rem)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error parsing socket control message: %w", err)
|
||||
}
|
||||
if hdr.Level == socketOptionLevelUDP && hdr.Type == socketOptionUDPGRO && len(data) >= sizeOfGSOData {
|
||||
var gso uint16
|
||||
copy(unsafe.Slice((*byte)(unsafe.Pointer(&gso)), sizeOfGSOData), data[:sizeOfGSOData])
|
||||
return int(gso), nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// setGSOSize sets a UDP_SEGMENT in control based on gsoSize. It leaves existing
|
||||
// data in control untouched.
|
||||
func setGSOSize(control *[]byte, gsoSize uint16) {
|
||||
existingLen := len(*control)
|
||||
avail := cap(*control) - existingLen
|
||||
space := unix.CmsgSpace(sizeOfGSOData)
|
||||
if avail < space {
|
||||
return
|
||||
}
|
||||
*control = (*control)[:cap(*control)]
|
||||
gsoControl := (*control)[existingLen:]
|
||||
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&(gsoControl)[0]))
|
||||
hdr.Level = socketOptionLevelUDP
|
||||
hdr.Type = socketOptionUDPSegment
|
||||
hdr.SetLen(unix.CmsgLen(sizeOfGSOData))
|
||||
copy((gsoControl)[unix.SizeofCmsghdr:], unsafe.Slice((*byte)(unsafe.Pointer(&gsoSize)), sizeOfGSOData))
|
||||
*control = (*control)[:existingLen+space]
|
||||
}
|
||||
|
||||
// controlSize returns the recommended buffer size for pooling sticky and UDP
|
||||
// stickyControlSize returns the recommended buffer size for pooling sticky
|
||||
// offloading control data.
|
||||
var controlSize = unix.CmsgSpace(unix.SizeofInet6Pktinfo) + unix.CmsgSpace(sizeOfGSOData)
|
||||
var stickyControlSize = unix.CmsgSpace(unix.SizeofInet6Pktinfo)
|
||||
|
||||
const StdNetSupportsStickySockets = true
|
||||
266
conn/sticky_linux_test.go
Normal file
266
conn/sticky_linux_test.go
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
//go:build linux && !android
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func setSrc(ep *StdNetEndpoint, addr netip.Addr, ifidx int32) {
|
||||
var buf []byte
|
||||
if addr.Is4() {
|
||||
buf = make([]byte, unix.CmsgSpace(unix.SizeofInet4Pktinfo))
|
||||
hdr := unix.Cmsghdr{
|
||||
Level: unix.IPPROTO_IP,
|
||||
Type: unix.IP_PKTINFO,
|
||||
}
|
||||
hdr.SetLen(unix.CmsgLen(unix.SizeofInet4Pktinfo))
|
||||
copy(buf, unsafe.Slice((*byte)(unsafe.Pointer(&hdr)), int(unsafe.Sizeof(hdr))))
|
||||
|
||||
info := unix.Inet4Pktinfo{
|
||||
Ifindex: ifidx,
|
||||
Spec_dst: addr.As4(),
|
||||
}
|
||||
copy(buf[unix.CmsgLen(0):], unsafe.Slice((*byte)(unsafe.Pointer(&info)), unix.SizeofInet4Pktinfo))
|
||||
} else {
|
||||
buf = make([]byte, unix.CmsgSpace(unix.SizeofInet6Pktinfo))
|
||||
hdr := unix.Cmsghdr{
|
||||
Level: unix.IPPROTO_IPV6,
|
||||
Type: unix.IPV6_PKTINFO,
|
||||
}
|
||||
hdr.SetLen(unix.CmsgLen(unix.SizeofInet6Pktinfo))
|
||||
copy(buf, unsafe.Slice((*byte)(unsafe.Pointer(&hdr)), int(unsafe.Sizeof(hdr))))
|
||||
|
||||
info := unix.Inet6Pktinfo{
|
||||
Ifindex: uint32(ifidx),
|
||||
Addr: addr.As16(),
|
||||
}
|
||||
copy(buf[unix.CmsgLen(0):], unsafe.Slice((*byte)(unsafe.Pointer(&info)), unix.SizeofInet6Pktinfo))
|
||||
}
|
||||
|
||||
ep.src = buf
|
||||
}
|
||||
|
||||
func Test_setSrcControl(t *testing.T) {
|
||||
t.Run("IPv4", func(t *testing.T) {
|
||||
ep := &StdNetEndpoint{
|
||||
AddrPort: netip.MustParseAddrPort("127.0.0.1:1234"),
|
||||
}
|
||||
setSrc(ep, netip.MustParseAddr("127.0.0.1"), 5)
|
||||
|
||||
control := make([]byte, stickyControlSize)
|
||||
|
||||
setSrcControl(&control, ep)
|
||||
|
||||
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||
if hdr.Level != unix.IPPROTO_IP {
|
||||
t.Errorf("unexpected level: %d", hdr.Level)
|
||||
}
|
||||
if hdr.Type != unix.IP_PKTINFO {
|
||||
t.Errorf("unexpected type: %d", hdr.Type)
|
||||
}
|
||||
if uint(hdr.Len) != uint(unix.CmsgLen(int(unsafe.Sizeof(unix.Inet4Pktinfo{})))) {
|
||||
t.Errorf("unexpected length: %d", hdr.Len)
|
||||
}
|
||||
info := (*unix.Inet4Pktinfo)(unsafe.Pointer(&control[unix.CmsgLen(0)]))
|
||||
if info.Spec_dst[0] != 127 || info.Spec_dst[1] != 0 || info.Spec_dst[2] != 0 || info.Spec_dst[3] != 1 {
|
||||
t.Errorf("unexpected address: %v", info.Spec_dst)
|
||||
}
|
||||
if info.Ifindex != 5 {
|
||||
t.Errorf("unexpected ifindex: %d", info.Ifindex)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("IPv6", func(t *testing.T) {
|
||||
ep := &StdNetEndpoint{
|
||||
AddrPort: netip.MustParseAddrPort("[::1]:1234"),
|
||||
}
|
||||
setSrc(ep, netip.MustParseAddr("::1"), 5)
|
||||
|
||||
control := make([]byte, stickyControlSize)
|
||||
|
||||
setSrcControl(&control, ep)
|
||||
|
||||
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||
if hdr.Level != unix.IPPROTO_IPV6 {
|
||||
t.Errorf("unexpected level: %d", hdr.Level)
|
||||
}
|
||||
if hdr.Type != unix.IPV6_PKTINFO {
|
||||
t.Errorf("unexpected type: %d", hdr.Type)
|
||||
}
|
||||
if uint(hdr.Len) != uint(unix.CmsgLen(int(unsafe.Sizeof(unix.Inet6Pktinfo{})))) {
|
||||
t.Errorf("unexpected length: %d", hdr.Len)
|
||||
}
|
||||
info := (*unix.Inet6Pktinfo)(unsafe.Pointer(&control[unix.CmsgLen(0)]))
|
||||
if info.Addr != ep.SrcIP().As16() {
|
||||
t.Errorf("unexpected address: %v", info.Addr)
|
||||
}
|
||||
if info.Ifindex != 5 {
|
||||
t.Errorf("unexpected ifindex: %d", info.Ifindex)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ClearOnNoSrc", func(t *testing.T) {
|
||||
control := make([]byte, stickyControlSize)
|
||||
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||
hdr.Level = 1
|
||||
hdr.Type = 2
|
||||
hdr.Len = 3
|
||||
|
||||
setSrcControl(&control, &StdNetEndpoint{})
|
||||
|
||||
if len(control) != 0 {
|
||||
t.Errorf("unexpected control: %v", control)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_getSrcFromControl(t *testing.T) {
|
||||
t.Run("IPv4", func(t *testing.T) {
|
||||
control := make([]byte, stickyControlSize)
|
||||
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||
hdr.Level = unix.IPPROTO_IP
|
||||
hdr.Type = unix.IP_PKTINFO
|
||||
hdr.SetLen(unix.CmsgLen(int(unsafe.Sizeof(unix.Inet4Pktinfo{}))))
|
||||
info := (*unix.Inet4Pktinfo)(unsafe.Pointer(&control[unix.CmsgLen(0)]))
|
||||
info.Spec_dst = [4]byte{127, 0, 0, 1}
|
||||
info.Ifindex = 5
|
||||
|
||||
ep := &StdNetEndpoint{}
|
||||
getSrcFromControl(control, ep)
|
||||
|
||||
if ep.SrcIP() != netip.MustParseAddr("127.0.0.1") {
|
||||
t.Errorf("unexpected address: %v", ep.SrcIP())
|
||||
}
|
||||
if ep.SrcIfidx() != 5 {
|
||||
t.Errorf("unexpected ifindex: %d", ep.SrcIfidx())
|
||||
}
|
||||
})
|
||||
t.Run("IPv6", func(t *testing.T) {
|
||||
control := make([]byte, stickyControlSize)
|
||||
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||
hdr.Level = unix.IPPROTO_IPV6
|
||||
hdr.Type = unix.IPV6_PKTINFO
|
||||
hdr.SetLen(unix.CmsgLen(int(unsafe.Sizeof(unix.Inet6Pktinfo{}))))
|
||||
info := (*unix.Inet6Pktinfo)(unsafe.Pointer(&control[unix.CmsgLen(0)]))
|
||||
info.Addr = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||||
info.Ifindex = 5
|
||||
|
||||
ep := &StdNetEndpoint{}
|
||||
getSrcFromControl(control, ep)
|
||||
|
||||
if ep.SrcIP() != netip.MustParseAddr("::1") {
|
||||
t.Errorf("unexpected address: %v", ep.SrcIP())
|
||||
}
|
||||
if ep.SrcIfidx() != 5 {
|
||||
t.Errorf("unexpected ifindex: %d", ep.SrcIfidx())
|
||||
}
|
||||
})
|
||||
t.Run("ClearOnEmpty", func(t *testing.T) {
|
||||
var control []byte
|
||||
ep := &StdNetEndpoint{}
|
||||
setSrc(ep, netip.MustParseAddr("::1"), 5)
|
||||
|
||||
getSrcFromControl(control, ep)
|
||||
if ep.SrcIP().IsValid() {
|
||||
t.Errorf("unexpected address: %v", ep.SrcIP())
|
||||
}
|
||||
if ep.SrcIfidx() != 0 {
|
||||
t.Errorf("unexpected ifindex: %d", ep.SrcIfidx())
|
||||
}
|
||||
})
|
||||
t.Run("Multiple", func(t *testing.T) {
|
||||
zeroControl := make([]byte, unix.CmsgSpace(0))
|
||||
zeroHdr := (*unix.Cmsghdr)(unsafe.Pointer(&zeroControl[0]))
|
||||
zeroHdr.SetLen(unix.CmsgLen(0))
|
||||
|
||||
control := make([]byte, unix.CmsgSpace(unix.SizeofInet4Pktinfo))
|
||||
hdr := (*unix.Cmsghdr)(unsafe.Pointer(&control[0]))
|
||||
hdr.Level = unix.IPPROTO_IP
|
||||
hdr.Type = unix.IP_PKTINFO
|
||||
hdr.SetLen(unix.CmsgLen(int(unsafe.Sizeof(unix.Inet4Pktinfo{}))))
|
||||
info := (*unix.Inet4Pktinfo)(unsafe.Pointer(&control[unix.CmsgLen(0)]))
|
||||
info.Spec_dst = [4]byte{127, 0, 0, 1}
|
||||
info.Ifindex = 5
|
||||
|
||||
combined := make([]byte, 0)
|
||||
combined = append(combined, zeroControl...)
|
||||
combined = append(combined, control...)
|
||||
|
||||
ep := &StdNetEndpoint{}
|
||||
getSrcFromControl(combined, ep)
|
||||
|
||||
if ep.SrcIP() != netip.MustParseAddr("127.0.0.1") {
|
||||
t.Errorf("unexpected address: %v", ep.SrcIP())
|
||||
}
|
||||
if ep.SrcIfidx() != 5 {
|
||||
t.Errorf("unexpected ifindex: %d", ep.SrcIfidx())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_listenConfig(t *testing.T) {
|
||||
t.Run("IPv4", func(t *testing.T) {
|
||||
conn, err := listenConfig().ListenPacket(context.Background(), "udp4", ":0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
sc, err := conn.(*net.UDPConn).SyscallConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
var i int
|
||||
sc.Control(func(fd uintptr) {
|
||||
i, err = unix.GetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_PKTINFO)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i != 1 {
|
||||
t.Error("IP_PKTINFO not set!")
|
||||
}
|
||||
} else {
|
||||
t.Logf("listenConfig() does not set IPV6_RECVPKTINFO on %s", runtime.GOOS)
|
||||
}
|
||||
})
|
||||
t.Run("IPv6", func(t *testing.T) {
|
||||
conn, err := listenConfig().ListenPacket(context.Background(), "udp6", ":0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sc, err := conn.(*net.UDPConn).SyscallConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
var i int
|
||||
sc.Control(func(fd uintptr) {
|
||||
i, err = unix.GetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i != 1 {
|
||||
t.Error("IPV6_PKTINFO not set!")
|
||||
}
|
||||
} else {
|
||||
t.Logf("listenConfig() does not set IPV6_RECVPKTINFO on %s", runtime.GOOS)
|
||||
}
|
||||
})
|
||||
}
|
||||
141
device/allowedips_rand_test.go
Normal file
141
device/allowedips_rand_test.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
NumberOfPeers = 100
|
||||
NumberOfPeerRemovals = 4
|
||||
NumberOfAddresses = 250
|
||||
NumberOfTests = 10000
|
||||
)
|
||||
|
||||
type SlowNode struct {
|
||||
peer *Peer
|
||||
cidr uint8
|
||||
bits []byte
|
||||
}
|
||||
|
||||
type SlowRouter []*SlowNode
|
||||
|
||||
func (r SlowRouter) Len() int {
|
||||
return len(r)
|
||||
}
|
||||
|
||||
func (r SlowRouter) Less(i, j int) bool {
|
||||
return r[i].cidr > r[j].cidr
|
||||
}
|
||||
|
||||
func (r SlowRouter) Swap(i, j int) {
|
||||
r[i], r[j] = r[j], r[i]
|
||||
}
|
||||
|
||||
func (r SlowRouter) Insert(addr []byte, cidr uint8, peer *Peer) SlowRouter {
|
||||
for _, t := range r {
|
||||
if t.cidr == cidr && commonBits(t.bits, addr) >= cidr {
|
||||
t.peer = peer
|
||||
t.bits = addr
|
||||
return r
|
||||
}
|
||||
}
|
||||
r = append(r, &SlowNode{
|
||||
cidr: cidr,
|
||||
bits: addr,
|
||||
peer: peer,
|
||||
})
|
||||
sort.Sort(r)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r SlowRouter) Lookup(addr []byte) *Peer {
|
||||
for _, t := range r {
|
||||
common := commonBits(t.bits, addr)
|
||||
if common >= t.cidr {
|
||||
return t.peer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r SlowRouter) RemoveByPeer(peer *Peer) SlowRouter {
|
||||
n := 0
|
||||
for _, x := range r {
|
||||
if x.peer != peer {
|
||||
r[n] = x
|
||||
n++
|
||||
}
|
||||
}
|
||||
return r[:n]
|
||||
}
|
||||
|
||||
func TestTrieRandom(t *testing.T) {
|
||||
var slow4, slow6 SlowRouter
|
||||
var peers []*Peer
|
||||
var allowedIPs AllowedIPs
|
||||
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
|
||||
for n := 0; n < NumberOfPeers; n++ {
|
||||
peers = append(peers, &Peer{})
|
||||
}
|
||||
|
||||
for n := 0; n < NumberOfAddresses; n++ {
|
||||
var addr4 [4]byte
|
||||
rng.Read(addr4[:])
|
||||
cidr := uint8(rand.Intn(32) + 1)
|
||||
index := rand.Intn(NumberOfPeers)
|
||||
allowedIPs.Insert(netip.PrefixFrom(netip.AddrFrom4(addr4), int(cidr)), peers[index])
|
||||
slow4 = slow4.Insert(addr4[:], cidr, peers[index])
|
||||
|
||||
var addr6 [16]byte
|
||||
rng.Read(addr6[:])
|
||||
cidr = uint8(rand.Intn(128) + 1)
|
||||
index = rand.Intn(NumberOfPeers)
|
||||
allowedIPs.Insert(netip.PrefixFrom(netip.AddrFrom16(addr6), int(cidr)), peers[index])
|
||||
slow6 = slow6.Insert(addr6[:], cidr, peers[index])
|
||||
}
|
||||
|
||||
var p int
|
||||
for p = 0; ; p++ {
|
||||
for n := 0; n < NumberOfTests; n++ {
|
||||
var addr4 [4]byte
|
||||
rng.Read(addr4[:])
|
||||
peer1 := slow4.Lookup(addr4[:])
|
||||
peer2 := allowedIPs.Lookup(addr4[:])
|
||||
if peer1 != peer2 {
|
||||
t.Errorf("Trie did not match naive implementation, for %v: want %p, got %p", net.IP(addr4[:]), peer1, peer2)
|
||||
}
|
||||
|
||||
var addr6 [16]byte
|
||||
rng.Read(addr6[:])
|
||||
peer1 = slow6.Lookup(addr6[:])
|
||||
peer2 = allowedIPs.Lookup(addr6[:])
|
||||
if peer1 != peer2 {
|
||||
t.Errorf("Trie did not match naive implementation, for %v: want %p, got %p", net.IP(addr6[:]), peer1, peer2)
|
||||
}
|
||||
}
|
||||
if p >= len(peers) || p >= NumberOfPeerRemovals {
|
||||
break
|
||||
}
|
||||
allowedIPs.RemoveByPeer(peers[p])
|
||||
slow4 = slow4.RemoveByPeer(peers[p])
|
||||
slow6 = slow6.RemoveByPeer(peers[p])
|
||||
}
|
||||
for ; p < len(peers); p++ {
|
||||
allowedIPs.RemoveByPeer(peers[p])
|
||||
}
|
||||
|
||||
if allowedIPs.IPv4 != nil || allowedIPs.IPv6 != nil {
|
||||
t.Error("Failed to remove all nodes from trie by peer")
|
||||
}
|
||||
}
|
||||
304
device/allowedips_test.go
Normal file
304
device/allowedips_test.go
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testPairCommonBits struct {
|
||||
s1 []byte
|
||||
s2 []byte
|
||||
match uint8
|
||||
}
|
||||
|
||||
func TestCommonBits(t *testing.T) {
|
||||
tests := []testPairCommonBits{
|
||||
{s1: []byte{1, 4, 53, 128}, s2: []byte{0, 0, 0, 0}, match: 7},
|
||||
{s1: []byte{0, 4, 53, 128}, s2: []byte{0, 0, 0, 0}, match: 13},
|
||||
{s1: []byte{0, 4, 53, 253}, s2: []byte{0, 4, 53, 252}, match: 31},
|
||||
{s1: []byte{192, 168, 1, 1}, s2: []byte{192, 169, 1, 1}, match: 15},
|
||||
{s1: []byte{65, 168, 1, 1}, s2: []byte{192, 169, 1, 1}, match: 0},
|
||||
}
|
||||
|
||||
for _, p := range tests {
|
||||
v := commonBits(p.s1, p.s2)
|
||||
if v != p.match {
|
||||
t.Error(
|
||||
"For slice", p.s1, p.s2,
|
||||
"expected match", p.match,
|
||||
",but got", v,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkTrie(peerNumber, addressNumber, _ int, b *testing.B) {
|
||||
var trie *trieEntry
|
||||
var peers []*Peer
|
||||
root := parentIndirection{&trie, 2}
|
||||
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
|
||||
const AddressLength = 4
|
||||
|
||||
for n := 0; n < peerNumber; n++ {
|
||||
peers = append(peers, &Peer{})
|
||||
}
|
||||
|
||||
for n := 0; n < addressNumber; n++ {
|
||||
var addr [AddressLength]byte
|
||||
rng.Read(addr[:])
|
||||
cidr := uint8(rng.Uint32() % (AddressLength * 8))
|
||||
index := rng.Int() % peerNumber
|
||||
root.insert(addr[:], cidr, peers[index])
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
var addr [AddressLength]byte
|
||||
rng.Read(addr[:])
|
||||
trie.lookup(addr[:])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrieIPv4Peers100Addresses1000(b *testing.B) {
|
||||
benchmarkTrie(100, 1000, net.IPv4len, b)
|
||||
}
|
||||
|
||||
func BenchmarkTrieIPv4Peers10Addresses10(b *testing.B) {
|
||||
benchmarkTrie(10, 10, net.IPv4len, b)
|
||||
}
|
||||
|
||||
func BenchmarkTrieIPv6Peers100Addresses1000(b *testing.B) {
|
||||
benchmarkTrie(100, 1000, net.IPv6len, b)
|
||||
}
|
||||
|
||||
func BenchmarkTrieIPv6Peers10Addresses10(b *testing.B) {
|
||||
benchmarkTrie(10, 10, net.IPv6len, b)
|
||||
}
|
||||
|
||||
/* Test ported from kernel implementation:
|
||||
* selftest/allowedips.h
|
||||
*/
|
||||
func TestTrieIPv4(t *testing.T) {
|
||||
a := &Peer{}
|
||||
b := &Peer{}
|
||||
c := &Peer{}
|
||||
d := &Peer{}
|
||||
e := &Peer{}
|
||||
g := &Peer{}
|
||||
h := &Peer{}
|
||||
|
||||
var allowedIPs AllowedIPs
|
||||
|
||||
insert := func(peer *Peer, a, b, c, d byte, cidr uint8) {
|
||||
allowedIPs.Insert(netip.PrefixFrom(netip.AddrFrom4([4]byte{a, b, c, d}), int(cidr)), peer)
|
||||
}
|
||||
|
||||
remove := func(peer *Peer, a, b, c, d byte, cidr uint8) {
|
||||
allowedIPs.Remove(netip.PrefixFrom(netip.AddrFrom4([4]byte{a, b, c, d}), int(cidr)), peer)
|
||||
}
|
||||
|
||||
assertEQ := func(peer *Peer, a, b, c, d byte) {
|
||||
p := allowedIPs.Lookup([]byte{a, b, c, d})
|
||||
if p != peer {
|
||||
t.Error("Assert EQ failed")
|
||||
}
|
||||
}
|
||||
|
||||
assertNEQ := func(peer *Peer, a, b, c, d byte) {
|
||||
p := allowedIPs.Lookup([]byte{a, b, c, d})
|
||||
if p == peer {
|
||||
t.Error("Assert NEQ failed")
|
||||
}
|
||||
}
|
||||
|
||||
insert(a, 192, 168, 4, 0, 24)
|
||||
insert(b, 192, 168, 4, 4, 32)
|
||||
insert(c, 192, 168, 0, 0, 16)
|
||||
insert(d, 192, 95, 5, 64, 27)
|
||||
insert(c, 192, 95, 5, 65, 27)
|
||||
insert(e, 0, 0, 0, 0, 0)
|
||||
insert(g, 64, 15, 112, 0, 20)
|
||||
insert(h, 64, 15, 123, 211, 25)
|
||||
insert(a, 10, 0, 0, 0, 25)
|
||||
insert(b, 10, 0, 0, 128, 25)
|
||||
insert(a, 10, 1, 0, 0, 30)
|
||||
insert(b, 10, 1, 0, 4, 30)
|
||||
insert(c, 10, 1, 0, 8, 29)
|
||||
insert(d, 10, 1, 0, 16, 29)
|
||||
|
||||
assertEQ(a, 192, 168, 4, 20)
|
||||
assertEQ(a, 192, 168, 4, 0)
|
||||
assertEQ(b, 192, 168, 4, 4)
|
||||
assertEQ(c, 192, 168, 200, 182)
|
||||
assertEQ(c, 192, 95, 5, 68)
|
||||
assertEQ(e, 192, 95, 5, 96)
|
||||
assertEQ(g, 64, 15, 116, 26)
|
||||
assertEQ(g, 64, 15, 127, 3)
|
||||
|
||||
insert(a, 1, 0, 0, 0, 32)
|
||||
insert(a, 64, 0, 0, 0, 32)
|
||||
insert(a, 128, 0, 0, 0, 32)
|
||||
insert(a, 192, 0, 0, 0, 32)
|
||||
insert(a, 255, 0, 0, 0, 32)
|
||||
|
||||
assertEQ(a, 1, 0, 0, 0)
|
||||
assertEQ(a, 64, 0, 0, 0)
|
||||
assertEQ(a, 128, 0, 0, 0)
|
||||
assertEQ(a, 192, 0, 0, 0)
|
||||
assertEQ(a, 255, 0, 0, 0)
|
||||
|
||||
allowedIPs.RemoveByPeer(a)
|
||||
|
||||
assertNEQ(a, 1, 0, 0, 0)
|
||||
assertNEQ(a, 64, 0, 0, 0)
|
||||
assertNEQ(a, 128, 0, 0, 0)
|
||||
assertNEQ(a, 192, 0, 0, 0)
|
||||
assertNEQ(a, 255, 0, 0, 0)
|
||||
|
||||
allowedIPs.RemoveByPeer(a)
|
||||
allowedIPs.RemoveByPeer(b)
|
||||
allowedIPs.RemoveByPeer(c)
|
||||
allowedIPs.RemoveByPeer(d)
|
||||
allowedIPs.RemoveByPeer(e)
|
||||
allowedIPs.RemoveByPeer(g)
|
||||
allowedIPs.RemoveByPeer(h)
|
||||
if allowedIPs.IPv4 != nil || allowedIPs.IPv6 != nil {
|
||||
t.Error("Expected removing all the peers to empty trie, but it did not")
|
||||
}
|
||||
|
||||
insert(a, 192, 168, 0, 0, 16)
|
||||
insert(a, 192, 168, 0, 0, 24)
|
||||
|
||||
allowedIPs.RemoveByPeer(a)
|
||||
|
||||
assertNEQ(a, 192, 168, 0, 1)
|
||||
|
||||
insert(a, 1, 0, 0, 0, 32)
|
||||
insert(a, 192, 0, 0, 0, 24)
|
||||
assertEQ(a, 1, 0, 0, 0)
|
||||
assertEQ(a, 192, 0, 0, 1)
|
||||
remove(a, 192, 0, 0, 0, 32)
|
||||
assertEQ(a, 192, 0, 0, 1)
|
||||
remove(nil, 192, 0, 0, 0, 24)
|
||||
assertEQ(a, 192, 0, 0, 1)
|
||||
remove(b, 192, 0, 0, 0, 24)
|
||||
assertEQ(a, 192, 0, 0, 1)
|
||||
remove(a, 192, 0, 0, 0, 24)
|
||||
assertNEQ(a, 192, 0, 0, 1)
|
||||
remove(a, 1, 0, 0, 0, 32)
|
||||
assertNEQ(a, 1, 0, 0, 0)
|
||||
}
|
||||
|
||||
/* Test ported from kernel implementation:
|
||||
* selftest/allowedips.h
|
||||
*/
|
||||
func TestTrieIPv6(t *testing.T) {
|
||||
a := &Peer{}
|
||||
b := &Peer{}
|
||||
c := &Peer{}
|
||||
d := &Peer{}
|
||||
e := &Peer{}
|
||||
f := &Peer{}
|
||||
g := &Peer{}
|
||||
h := &Peer{}
|
||||
|
||||
var allowedIPs AllowedIPs
|
||||
|
||||
expand := func(a uint32) []byte {
|
||||
var out [4]byte
|
||||
out[0] = byte(a >> 24 & 0xff)
|
||||
out[1] = byte(a >> 16 & 0xff)
|
||||
out[2] = byte(a >> 8 & 0xff)
|
||||
out[3] = byte(a & 0xff)
|
||||
return out[:]
|
||||
}
|
||||
|
||||
insert := func(peer *Peer, a, b, c, d uint32, cidr uint8) {
|
||||
var addr []byte
|
||||
addr = append(addr, expand(a)...)
|
||||
addr = append(addr, expand(b)...)
|
||||
addr = append(addr, expand(c)...)
|
||||
addr = append(addr, expand(d)...)
|
||||
allowedIPs.Insert(netip.PrefixFrom(netip.AddrFrom16(*(*[16]byte)(addr)), int(cidr)), peer)
|
||||
}
|
||||
|
||||
remove := func(peer *Peer, a, b, c, d uint32, cidr uint8) {
|
||||
var addr []byte
|
||||
addr = append(addr, expand(a)...)
|
||||
addr = append(addr, expand(b)...)
|
||||
addr = append(addr, expand(c)...)
|
||||
addr = append(addr, expand(d)...)
|
||||
allowedIPs.Remove(netip.PrefixFrom(netip.AddrFrom16(*(*[16]byte)(addr)), int(cidr)), peer)
|
||||
}
|
||||
|
||||
assertEQ := func(peer *Peer, a, b, c, d uint32) {
|
||||
var addr []byte
|
||||
addr = append(addr, expand(a)...)
|
||||
addr = append(addr, expand(b)...)
|
||||
addr = append(addr, expand(c)...)
|
||||
addr = append(addr, expand(d)...)
|
||||
p := allowedIPs.Lookup(addr)
|
||||
if p != peer {
|
||||
t.Error("Assert EQ failed")
|
||||
}
|
||||
}
|
||||
|
||||
assertNEQ := func(peer *Peer, a, b, c, d uint32) {
|
||||
var addr []byte
|
||||
addr = append(addr, expand(a)...)
|
||||
addr = append(addr, expand(b)...)
|
||||
addr = append(addr, expand(c)...)
|
||||
addr = append(addr, expand(d)...)
|
||||
p := allowedIPs.Lookup(addr)
|
||||
if p == peer {
|
||||
t.Error("Assert NEQ failed")
|
||||
}
|
||||
}
|
||||
|
||||
insert(d, 0x26075300, 0x60006b00, 0, 0xc05f0543, 128)
|
||||
insert(c, 0x26075300, 0x60006b00, 0, 0, 64)
|
||||
insert(e, 0, 0, 0, 0, 0)
|
||||
insert(f, 0, 0, 0, 0, 0)
|
||||
insert(g, 0x24046800, 0, 0, 0, 32)
|
||||
insert(h, 0x24046800, 0x40040800, 0xdeadbeef, 0xdeadbeef, 64)
|
||||
insert(a, 0x24046800, 0x40040800, 0xdeadbeef, 0xdeadbeef, 128)
|
||||
insert(c, 0x24446800, 0x40e40800, 0xdeaebeef, 0xdefbeef, 128)
|
||||
insert(b, 0x24446800, 0xf0e40800, 0xeeaebeef, 0, 98)
|
||||
|
||||
assertEQ(d, 0x26075300, 0x60006b00, 0, 0xc05f0543)
|
||||
assertEQ(c, 0x26075300, 0x60006b00, 0, 0xc02e01ee)
|
||||
assertEQ(f, 0x26075300, 0x60006b01, 0, 0)
|
||||
assertEQ(g, 0x24046800, 0x40040806, 0, 0x1006)
|
||||
assertEQ(g, 0x24046800, 0x40040806, 0x1234, 0x5678)
|
||||
assertEQ(f, 0x240467ff, 0x40040806, 0x1234, 0x5678)
|
||||
assertEQ(f, 0x24046801, 0x40040806, 0x1234, 0x5678)
|
||||
assertEQ(h, 0x24046800, 0x40040800, 0x1234, 0x5678)
|
||||
assertEQ(h, 0x24046800, 0x40040800, 0, 0)
|
||||
assertEQ(h, 0x24046800, 0x40040800, 0x10101010, 0x10101010)
|
||||
assertEQ(a, 0x24046800, 0x40040800, 0xdeadbeef, 0xdeadbeef)
|
||||
|
||||
insert(a, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef, 128)
|
||||
insert(a, 0x24446800, 0xf0e40800, 0xeeaebeef, 0, 98)
|
||||
assertEQ(a, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef)
|
||||
assertEQ(a, 0x24446800, 0xf0e40800, 0xeeaebeef, 0x10101010)
|
||||
remove(a, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef, 96)
|
||||
assertEQ(a, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef)
|
||||
remove(nil, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef, 128)
|
||||
assertEQ(a, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef)
|
||||
remove(b, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef, 128)
|
||||
assertEQ(a, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef)
|
||||
remove(a, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef, 128)
|
||||
assertNEQ(a, 0x24446801, 0x40e40800, 0xdeaebeef, 0xdefbeef)
|
||||
remove(b, 0x24446800, 0xf0e40800, 0xeeaebeef, 0, 98)
|
||||
assertEQ(a, 0x24446800, 0xf0e40800, 0xeeaebeef, 0x10101010)
|
||||
remove(a, 0x24446800, 0xf0e40800, 0xeeaebeef, 0, 98)
|
||||
assertNEQ(a, 0x24446800, 0xf0e40800, 0xeeaebeef, 0x10101010)
|
||||
}
|
||||
56
device/bind_test.go
Normal file
56
device/bind_test.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
)
|
||||
|
||||
type DummyDatagram struct {
|
||||
msg []byte
|
||||
endpoint conn.Endpoint
|
||||
}
|
||||
|
||||
type DummyBind struct {
|
||||
in6 chan DummyDatagram
|
||||
in4 chan DummyDatagram
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (b *DummyBind) SetMark(v uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *DummyBind) ReceiveIPv6(buf []byte) (int, conn.Endpoint, error) {
|
||||
datagram, ok := <-b.in6
|
||||
if !ok {
|
||||
return 0, nil, errors.New("closed")
|
||||
}
|
||||
copy(buf, datagram.msg)
|
||||
return len(datagram.msg), datagram.endpoint, nil
|
||||
}
|
||||
|
||||
func (b *DummyBind) ReceiveIPv4(buf []byte) (int, conn.Endpoint, error) {
|
||||
datagram, ok := <-b.in4
|
||||
if !ok {
|
||||
return 0, nil, errors.New("closed")
|
||||
}
|
||||
copy(buf, datagram.msg)
|
||||
return len(datagram.msg), datagram.endpoint, nil
|
||||
}
|
||||
|
||||
func (b *DummyBind) Close() error {
|
||||
close(b.in6)
|
||||
close(b.in4)
|
||||
b.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *DummyBind) Send(buf []byte, end conn.Endpoint) error {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ const (
|
|||
const (
|
||||
MinMessageSize = MessageKeepaliveSize // minimum size of transport message (keepalive)
|
||||
MaxMessageSize = MaxSegmentSize // maximum size of transport message
|
||||
MaxContentSize = MaxSegmentSize - MessageTransportSize - MessageEncapsulatingTransportSize // maximum size of transport message content
|
||||
MaxContentSize = MaxSegmentSize - MessageTransportSize // maximum size of transport message content
|
||||
)
|
||||
|
||||
/* Implementation constants */
|
||||
|
|
|
|||
190
device/cookie_test.go
Normal file
190
device/cookie_test.go
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCookieMAC1(t *testing.T) {
|
||||
// setup generator / checker
|
||||
|
||||
var (
|
||||
generator CookieGenerator
|
||||
checker CookieChecker
|
||||
)
|
||||
|
||||
sk, err := newPrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pk := sk.publicKey()
|
||||
|
||||
generator.Init(pk)
|
||||
checker.Init(pk)
|
||||
|
||||
// check mac1
|
||||
|
||||
src := []byte{192, 168, 13, 37, 10, 10, 10}
|
||||
|
||||
checkMAC1 := func(msg []byte) {
|
||||
generator.AddMacs(msg)
|
||||
if !checker.CheckMAC1(msg) {
|
||||
t.Fatal("MAC1 generation/verification failed")
|
||||
}
|
||||
if checker.CheckMAC2(msg, src) {
|
||||
t.Fatal("MAC2 generation/verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
checkMAC1([]byte{
|
||||
0x99, 0xbb, 0xa5, 0xfc, 0x99, 0xaa, 0x83, 0xbd,
|
||||
0x7b, 0x00, 0xc5, 0x9a, 0x4c, 0xb9, 0xcf, 0x62,
|
||||
0x40, 0x23, 0xf3, 0x8e, 0xd8, 0xd0, 0x62, 0x64,
|
||||
0x5d, 0xb2, 0x80, 0x13, 0xda, 0xce, 0xc6, 0x91,
|
||||
0x61, 0xd6, 0x30, 0xf1, 0x32, 0xb3, 0xa2, 0xf4,
|
||||
0x7b, 0x43, 0xb5, 0xa7, 0xe2, 0xb1, 0xf5, 0x6c,
|
||||
0x74, 0x6b, 0xb0, 0xcd, 0x1f, 0x94, 0x86, 0x7b,
|
||||
0xc8, 0xfb, 0x92, 0xed, 0x54, 0x9b, 0x44, 0xf5,
|
||||
0xc8, 0x7d, 0xb7, 0x8e, 0xff, 0x49, 0xc4, 0xe8,
|
||||
0x39, 0x7c, 0x19, 0xe0, 0x60, 0x19, 0x51, 0xf8,
|
||||
0xe4, 0x8e, 0x02, 0xf1, 0x7f, 0x1d, 0xcc, 0x8e,
|
||||
0xb0, 0x07, 0xff, 0xf8, 0xaf, 0x7f, 0x66, 0x82,
|
||||
0x83, 0xcc, 0x7c, 0xfa, 0x80, 0xdb, 0x81, 0x53,
|
||||
0xad, 0xf7, 0xd8, 0x0c, 0x10, 0xe0, 0x20, 0xfd,
|
||||
0xe8, 0x0b, 0x3f, 0x90, 0x15, 0xcd, 0x93, 0xad,
|
||||
0x0b, 0xd5, 0x0c, 0xcc, 0x88, 0x56, 0xe4, 0x3f,
|
||||
})
|
||||
|
||||
checkMAC1([]byte{
|
||||
0x33, 0xe7, 0x2a, 0x84, 0x9f, 0xff, 0x57, 0x6c,
|
||||
0x2d, 0xc3, 0x2d, 0xe1, 0xf5, 0x5c, 0x97, 0x56,
|
||||
0xb8, 0x93, 0xc2, 0x7d, 0xd4, 0x41, 0xdd, 0x7a,
|
||||
0x4a, 0x59, 0x3b, 0x50, 0xdd, 0x7a, 0x7a, 0x8c,
|
||||
0x9b, 0x96, 0xaf, 0x55, 0x3c, 0xeb, 0x6d, 0x0b,
|
||||
0x13, 0x0b, 0x97, 0x98, 0xb3, 0x40, 0xc3, 0xcc,
|
||||
0xb8, 0x57, 0x33, 0x45, 0x6e, 0x8b, 0x09, 0x2b,
|
||||
0x81, 0x2e, 0xd2, 0xb9, 0x66, 0x0b, 0x93, 0x05,
|
||||
})
|
||||
|
||||
checkMAC1([]byte{
|
||||
0x9b, 0x96, 0xaf, 0x55, 0x3c, 0xeb, 0x6d, 0x0b,
|
||||
0x13, 0x0b, 0x97, 0x98, 0xb3, 0x40, 0xc3, 0xcc,
|
||||
0xb8, 0x57, 0x33, 0x45, 0x6e, 0x8b, 0x09, 0x2b,
|
||||
0x81, 0x2e, 0xd2, 0xb9, 0x66, 0x0b, 0x93, 0x05,
|
||||
})
|
||||
|
||||
// exchange cookie reply
|
||||
|
||||
func() {
|
||||
msg := []byte{
|
||||
0x6d, 0xd7, 0xc3, 0x2e, 0xb0, 0x76, 0xd8, 0xdf,
|
||||
0x30, 0x65, 0x7d, 0x62, 0x3e, 0xf8, 0x9a, 0xe8,
|
||||
0xe7, 0x3c, 0x64, 0xa3, 0x78, 0x48, 0xda, 0xf5,
|
||||
0x25, 0x61, 0x28, 0x53, 0x79, 0x32, 0x86, 0x9f,
|
||||
0xa0, 0x27, 0x95, 0x69, 0xb6, 0xba, 0xd0, 0xa2,
|
||||
0xf8, 0x68, 0xea, 0xa8, 0x62, 0xf2, 0xfd, 0x1b,
|
||||
0xe0, 0xb4, 0x80, 0xe5, 0x6b, 0x3a, 0x16, 0x9e,
|
||||
0x35, 0xf6, 0xa8, 0xf2, 0x4f, 0x9a, 0x7b, 0xe9,
|
||||
0x77, 0x0b, 0xc2, 0xb4, 0xed, 0xba, 0xf9, 0x22,
|
||||
0xc3, 0x03, 0x97, 0x42, 0x9f, 0x79, 0x74, 0x27,
|
||||
0xfe, 0xf9, 0x06, 0x6e, 0x97, 0x3a, 0xa6, 0x8f,
|
||||
0xc9, 0x57, 0x0a, 0x54, 0x4c, 0x64, 0x4a, 0xe2,
|
||||
0x4f, 0xa1, 0xce, 0x95, 0x9b, 0x23, 0xa9, 0x2b,
|
||||
0x85, 0x93, 0x42, 0xb0, 0xa5, 0x53, 0xed, 0xeb,
|
||||
0x63, 0x2a, 0xf1, 0x6d, 0x46, 0xcb, 0x2f, 0x61,
|
||||
0x8c, 0xe1, 0xe8, 0xfa, 0x67, 0x20, 0x80, 0x6d,
|
||||
}
|
||||
generator.AddMacs(msg)
|
||||
reply, err := checker.CreateReply(msg, 1377, src, MessageCookieReplyType)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create cookie reply:", err)
|
||||
}
|
||||
if !generator.ConsumeReply(reply) {
|
||||
t.Fatal("Failed to consume cookie reply")
|
||||
}
|
||||
}()
|
||||
|
||||
// check mac2
|
||||
|
||||
checkMAC2 := func(msg []byte) {
|
||||
generator.AddMacs(msg)
|
||||
|
||||
if !checker.CheckMAC1(msg) {
|
||||
t.Fatal("MAC1 generation/verification failed")
|
||||
}
|
||||
if !checker.CheckMAC2(msg, src) {
|
||||
t.Fatal("MAC2 generation/verification failed")
|
||||
}
|
||||
|
||||
msg[5] ^= 0x20
|
||||
|
||||
if checker.CheckMAC1(msg) {
|
||||
t.Fatal("MAC1 generation/verification failed")
|
||||
}
|
||||
if checker.CheckMAC2(msg, src) {
|
||||
t.Fatal("MAC2 generation/verification failed")
|
||||
}
|
||||
|
||||
msg[5] ^= 0x20
|
||||
|
||||
srcBad1 := []byte{192, 168, 13, 37, 40, 1}
|
||||
if checker.CheckMAC2(msg, srcBad1) {
|
||||
t.Fatal("MAC2 generation/verification failed")
|
||||
}
|
||||
|
||||
srcBad2 := []byte{192, 168, 13, 38, 40, 1}
|
||||
if checker.CheckMAC2(msg, srcBad2) {
|
||||
t.Fatal("MAC2 generation/verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
checkMAC2([]byte{
|
||||
0x03, 0x31, 0xb9, 0x9e, 0xb0, 0x2a, 0x54, 0xa3,
|
||||
0xc1, 0x3f, 0xb4, 0x96, 0x16, 0xb9, 0x25, 0x15,
|
||||
0x3d, 0x3a, 0x82, 0xf9, 0x58, 0x36, 0x86, 0x3f,
|
||||
0x13, 0x2f, 0xfe, 0xb2, 0x53, 0x20, 0x8c, 0x3f,
|
||||
0xba, 0xeb, 0xfb, 0x4b, 0x1b, 0x22, 0x02, 0x69,
|
||||
0x2c, 0x90, 0xbc, 0xdc, 0xcf, 0xcf, 0x85, 0xeb,
|
||||
0x62, 0x66, 0x6f, 0xe8, 0xe1, 0xa6, 0xa8, 0x4c,
|
||||
0xa0, 0x04, 0x23, 0x15, 0x42, 0xac, 0xfa, 0x38,
|
||||
})
|
||||
|
||||
checkMAC2([]byte{
|
||||
0x0e, 0x2f, 0x0e, 0xa9, 0x29, 0x03, 0xe1, 0xf3,
|
||||
0x24, 0x01, 0x75, 0xad, 0x16, 0xa5, 0x66, 0x85,
|
||||
0xca, 0x66, 0xe0, 0xbd, 0xc6, 0x34, 0xd8, 0x84,
|
||||
0x09, 0x9a, 0x58, 0x14, 0xfb, 0x05, 0xda, 0xf5,
|
||||
0x90, 0xf5, 0x0c, 0x4e, 0x22, 0x10, 0xc9, 0x85,
|
||||
0x0f, 0xe3, 0x77, 0x35, 0xe9, 0x6b, 0xc2, 0x55,
|
||||
0x32, 0x46, 0xae, 0x25, 0xe0, 0xe3, 0x37, 0x7a,
|
||||
0x4b, 0x71, 0xcc, 0xfc, 0x91, 0xdf, 0xd6, 0xca,
|
||||
0xfe, 0xee, 0xce, 0x3f, 0x77, 0xa2, 0xfd, 0x59,
|
||||
0x8e, 0x73, 0x0a, 0x8d, 0x5c, 0x24, 0x14, 0xca,
|
||||
0x38, 0x91, 0xb8, 0x2c, 0x8c, 0xa2, 0x65, 0x7b,
|
||||
0xbc, 0x49, 0xbc, 0xb5, 0x58, 0xfc, 0xe3, 0xd7,
|
||||
0x02, 0xcf, 0xf7, 0x4c, 0x60, 0x91, 0xed, 0x55,
|
||||
0xe9, 0xf9, 0xfe, 0xd1, 0x44, 0x2c, 0x75, 0xf2,
|
||||
0xb3, 0x5d, 0x7b, 0x27, 0x56, 0xc0, 0x48, 0x4f,
|
||||
0xb0, 0xba, 0xe4, 0x7d, 0xd0, 0xaa, 0xcd, 0x3d,
|
||||
0xe3, 0x50, 0xd2, 0xcf, 0xb9, 0xfa, 0x4b, 0x2d,
|
||||
0xc6, 0xdf, 0x3b, 0x32, 0x98, 0x45, 0xe6, 0x8f,
|
||||
0x1c, 0x5c, 0xa2, 0x20, 0x7d, 0x1c, 0x28, 0xc2,
|
||||
0xd4, 0xa1, 0xe0, 0x21, 0x52, 0x8f, 0x1c, 0xd0,
|
||||
0x62, 0x97, 0x48, 0xbb, 0xf4, 0xa9, 0xcb, 0x35,
|
||||
0xf2, 0x07, 0xd3, 0x50, 0xd8, 0xa9, 0xc5, 0x9a,
|
||||
0x0f, 0xbd, 0x37, 0xaf, 0xe1, 0x45, 0x19, 0xee,
|
||||
0x41, 0xf3, 0xf7, 0xe5, 0xe0, 0x30, 0x3f, 0xbe,
|
||||
0x3d, 0x39, 0x64, 0x00, 0x7a, 0x1a, 0x51, 0x5e,
|
||||
0xe1, 0x70, 0x0b, 0xb9, 0x77, 0x5a, 0xf0, 0xc4,
|
||||
0x8a, 0xa1, 0x3a, 0x77, 0x1a, 0xe0, 0xc2, 0x06,
|
||||
0x91, 0xd5, 0xe9, 0x1c, 0xd3, 0xfe, 0xab, 0x93,
|
||||
0x1a, 0x0a, 0x4c, 0xbb, 0xf0, 0xff, 0xdc, 0xaa,
|
||||
0x61, 0x73, 0xcb, 0x03, 0x4b, 0x71, 0x68, 0x64,
|
||||
0x3d, 0x82, 0x31, 0x41, 0xd7, 0x8b, 0x22, 0x7b,
|
||||
0x7d, 0xa1, 0xd5, 0x85, 0x6d, 0xf0, 0x1b, 0xaa,
|
||||
})
|
||||
}
|
||||
|
|
@ -6,18 +6,15 @@
|
|||
package device
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/service"
|
||||
"github.com/sagernet/sing/service/pause"
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
"github.com/sagernet/wireguard-go/ratelimiter"
|
||||
"github.com/sagernet/wireguard-go/rwcancel"
|
||||
"github.com/sagernet/wireguard-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ratelimiter"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
|
|
@ -92,9 +89,7 @@ type Device struct {
|
|||
ipcMutex sync.RWMutex
|
||||
closed chan struct{}
|
||||
log *Logger
|
||||
pauseManager pause.Manager
|
||||
|
||||
// lx: AmneziaWG obfuscation state (grafted from amneziawg-go).
|
||||
junk struct {
|
||||
min int
|
||||
max int
|
||||
|
|
@ -309,9 +304,8 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewDevice(ctx context.Context, tunDevice tun.Device, bind conn.Bind, logger *Logger, workers int) *Device {
|
||||
func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
|
||||
device := new(Device)
|
||||
device.pauseManager = service.FromContext[pause.Manager](ctx)
|
||||
device.state.state.Store(uint32(deviceStateDown))
|
||||
device.closed = make(chan struct{})
|
||||
device.log = logger
|
||||
|
|
@ -342,12 +336,10 @@ func NewDevice(ctx context.Context, tunDevice tun.Device, bind conn.Bind, logger
|
|||
|
||||
// start workers
|
||||
|
||||
if workers == 0 {
|
||||
workers = runtime.NumCPU()
|
||||
}
|
||||
cpus := runtime.NumCPU()
|
||||
device.state.stopping.Wait()
|
||||
device.queue.encryption.wg.Add(workers) // One for each RoutineHandshake
|
||||
for i := 0; i < workers; i++ {
|
||||
device.queue.encryption.wg.Add(cpus) // One for each RoutineHandshake
|
||||
for i := 0; i < cpus; i++ {
|
||||
go device.RoutineEncryption(i + 1)
|
||||
go device.RoutineDecryption(i + 1)
|
||||
go device.RoutineHandshake(i + 1)
|
||||
|
|
|
|||
579
device/device_test.go
Normal file
579
device/device_test.go
Normal file
|
|
@ -0,0 +1,579 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn/bindtest"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/tuntest"
|
||||
)
|
||||
|
||||
// uapiCfg returns a string that contains cfg formatted use with IpcSet.
|
||||
// cfg is a series of alternating key/value strings.
|
||||
// uapiCfg exists because editors and humans like to insert
|
||||
// whitespace into configs, which can cause failures, some of which are silent.
|
||||
// For example, a leading blank newline causes the remainder
|
||||
// of the config to be silently ignored.
|
||||
func uapiCfg(cfg ...string) string {
|
||||
if len(cfg)%2 != 0 {
|
||||
panic("odd number of args to uapiReader")
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
for i, s := range cfg {
|
||||
buf.WriteString(s)
|
||||
sep := byte('\n')
|
||||
if i%2 == 0 {
|
||||
sep = '='
|
||||
}
|
||||
buf.WriteByte(sep)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// genConfigs generates a pair of configs that connect to each other.
|
||||
// The configs use distinct, probably-usable ports.
|
||||
func genConfigs(tb testing.TB, cfg ...string) (cfgs, endpointCfgs [2]string) {
|
||||
var key1, key2 NoisePrivateKey
|
||||
_, err := rand.Read(key1[:])
|
||||
if err != nil {
|
||||
tb.Errorf("unable to generate private key random bytes: %v", err)
|
||||
}
|
||||
_, err = rand.Read(key2[:])
|
||||
if err != nil {
|
||||
tb.Errorf("unable to generate private key random bytes: %v", err)
|
||||
}
|
||||
pub1, pub2 := key1.publicKey(), key2.publicKey()
|
||||
|
||||
args0 := append([]string(nil), cfg...)
|
||||
args0 = append(args0, []string{
|
||||
"private_key", hex.EncodeToString(key1[:]),
|
||||
"listen_port", "0",
|
||||
"replace_peers", "true",
|
||||
"public_key", hex.EncodeToString(pub2[:]),
|
||||
"protocol_version", "1",
|
||||
"replace_allowed_ips", "true",
|
||||
"allowed_ip", "1.0.0.2/32",
|
||||
}...)
|
||||
cfgs[0] = uapiCfg(args0...)
|
||||
|
||||
endpointCfgs[0] = uapiCfg(
|
||||
"public_key", hex.EncodeToString(pub2[:]),
|
||||
"endpoint", "127.0.0.1:%d",
|
||||
)
|
||||
|
||||
args1 := append([]string(nil), cfg...)
|
||||
args1 = append(args1, []string{
|
||||
"private_key", hex.EncodeToString(key2[:]),
|
||||
"listen_port", "0",
|
||||
"replace_peers", "true",
|
||||
"public_key", hex.EncodeToString(pub1[:]),
|
||||
"protocol_version", "1",
|
||||
"replace_allowed_ips", "true",
|
||||
"allowed_ip", "1.0.0.1/32",
|
||||
}...)
|
||||
|
||||
cfgs[1] = uapiCfg(args1...)
|
||||
endpointCfgs[1] = uapiCfg(
|
||||
"public_key", hex.EncodeToString(pub1[:]),
|
||||
"endpoint", "127.0.0.1:%d",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// A testPair is a pair of testPeers.
|
||||
type testPair [2]testPeer
|
||||
|
||||
// A testPeer is a peer used for testing.
|
||||
type testPeer struct {
|
||||
tun *tuntest.ChannelTUN
|
||||
dev *Device
|
||||
ip netip.Addr
|
||||
}
|
||||
|
||||
type SendDirection bool
|
||||
|
||||
const (
|
||||
Ping SendDirection = true
|
||||
Pong SendDirection = false
|
||||
)
|
||||
|
||||
func (d SendDirection) String() string {
|
||||
if d == Ping {
|
||||
return "ping"
|
||||
}
|
||||
return "pong"
|
||||
}
|
||||
|
||||
func (pair *testPair) Send(
|
||||
tb testing.TB,
|
||||
ping SendDirection,
|
||||
done chan struct{},
|
||||
) {
|
||||
tb.Helper()
|
||||
p0, p1 := pair[0], pair[1]
|
||||
if !ping {
|
||||
// pong is the new ping
|
||||
p0, p1 = p1, p0
|
||||
}
|
||||
|
||||
msg := tuntest.Ping(p0.ip, p1.ip)
|
||||
p1.tun.Outbound <- msg
|
||||
timer := time.NewTimer(6 * time.Second)
|
||||
defer timer.Stop()
|
||||
var err error
|
||||
select {
|
||||
case msgRecv := <-p0.tun.Inbound:
|
||||
if !bytes.Equal(msg, msgRecv) {
|
||||
err = fmt.Errorf("%s did not transit correctly", ping)
|
||||
}
|
||||
case <-timer.C:
|
||||
err = fmt.Errorf("%s did not transit", ping)
|
||||
case <-done:
|
||||
}
|
||||
if err != nil {
|
||||
// The error may have occurred because the test is done.
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
// Real error.
|
||||
tb.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// genTestPair creates a testPair.
|
||||
func genTestPair(
|
||||
tb testing.TB,
|
||||
realSocket bool,
|
||||
extraCfg ...string,
|
||||
) (pair testPair) {
|
||||
var cfg, endpointCfg [2]string
|
||||
cfg, endpointCfg = genConfigs(tb, extraCfg...)
|
||||
|
||||
var binds [2]conn.Bind
|
||||
if realSocket {
|
||||
binds[0], binds[1] = conn.NewDefaultBind(), conn.NewDefaultBind()
|
||||
} else {
|
||||
binds = bindtest.NewChannelBinds()
|
||||
}
|
||||
// Bring up a ChannelTun for each config.
|
||||
for i := range pair {
|
||||
p := &pair[i]
|
||||
p.tun = tuntest.NewChannelTUN()
|
||||
p.ip = netip.AddrFrom4([4]byte{1, 0, 0, byte(i + 1)})
|
||||
level := LogLevelVerbose
|
||||
if _, ok := tb.(*testing.B); ok && !testing.Verbose() {
|
||||
level = LogLevelError
|
||||
}
|
||||
p.dev = NewDevice(p.tun.TUN(), binds[i], NewLogger(level, fmt.Sprintf("dev%d: ", i)))
|
||||
if err := p.dev.IpcSet(cfg[i]); err != nil {
|
||||
tb.Errorf("failed to configure device %d: %v", i, err)
|
||||
p.dev.Close()
|
||||
continue
|
||||
}
|
||||
if err := p.dev.Up(); err != nil {
|
||||
tb.Errorf("failed to bring up device %d: %v", i, err)
|
||||
p.dev.Close()
|
||||
continue
|
||||
}
|
||||
endpointCfg[i^1] = fmt.Sprintf(endpointCfg[i^1], p.dev.net.port)
|
||||
}
|
||||
for i := range pair {
|
||||
p := &pair[i]
|
||||
if err := p.dev.IpcSet(endpointCfg[i]); err != nil {
|
||||
tb.Errorf("failed to configure device endpoint %d: %v", i, err)
|
||||
p.dev.Close()
|
||||
continue
|
||||
}
|
||||
// The device is ready. Close it when the test completes.
|
||||
tb.Cleanup(p.dev.Close)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestTwoDevicePing(t *testing.T) {
|
||||
goroutineLeakCheck(t)
|
||||
pair := genTestPair(t, true)
|
||||
t.Run("ping 1.0.0.1", func(t *testing.T) {
|
||||
pair.Send(t, Ping, nil)
|
||||
})
|
||||
t.Run("ping 1.0.0.2", func(t *testing.T) {
|
||||
pair.Send(t, Pong, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Run test with -race=false to avoid the race for setting the default msgTypes 2 times
|
||||
func TestAWGDevicePing(t *testing.T) {
|
||||
goroutineLeakCheck(t)
|
||||
|
||||
pair := genTestPair(t, true,
|
||||
"jc", "5",
|
||||
"jmin", "500",
|
||||
"jmax", "1000",
|
||||
"s1", "15",
|
||||
"s2", "18",
|
||||
"s3", "20",
|
||||
"s4", "25",
|
||||
"h1", "123456-123500",
|
||||
"h2", "67543-67550",
|
||||
"h3", "123123-123200",
|
||||
"h4", "32345-32350",
|
||||
)
|
||||
t.Run("ping 1.0.0.1", func(t *testing.T) {
|
||||
pair.Send(t, Ping, nil)
|
||||
})
|
||||
t.Run("ping 1.0.0.2", func(t *testing.T) {
|
||||
pair.Send(t, Pong, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Needs to be stopped with Ctrl-C
|
||||
func TestAWGHandshakeDevicePing(t *testing.T) {
|
||||
t.Skip("This test is intended to be run manually, not as part of the test suite.")
|
||||
|
||||
signalContext, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer cancel()
|
||||
isRunning := atomic.NewBool(true)
|
||||
go func() {
|
||||
<-signalContext.Done()
|
||||
fmt.Println("Waiting to finish")
|
||||
isRunning.Store(false)
|
||||
}()
|
||||
|
||||
goroutineLeakCheck(t)
|
||||
pair := genTestPair(t, true,
|
||||
"i1", "<b 0xf6ab3267fa><c><b 0xf6ab><t><r 10>",
|
||||
"i2", "<b 0xf6ab3267fa><c><b 0xf6ab><t><rc 10>",
|
||||
"i3", "<b 0xf6ab3267fa><c><b 0xf6ab><t><rd 10>",
|
||||
"i4", "<b 0xf6ab3267fa><r 100>",
|
||||
// "jc", "1",
|
||||
// "jmin", "500",
|
||||
// "jmax", "1000",
|
||||
// "s1", "30",
|
||||
// "s2", "40",
|
||||
// "h1", "123456",
|
||||
// "h2", "67543",
|
||||
// "h4", "32345",
|
||||
// "h3", "123123",
|
||||
)
|
||||
t.Run("ping 1.0.0.1", func(t *testing.T) {
|
||||
for isRunning.Load() {
|
||||
pair.Send(t, Ping, nil)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
})
|
||||
t.Run("ping 1.0.0.2", func(t *testing.T) {
|
||||
for isRunning.Load() {
|
||||
pair.Send(t, Pong, nil)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpDown(t *testing.T) {
|
||||
goroutineLeakCheck(t)
|
||||
const itrials = 50
|
||||
const otrials = 10
|
||||
|
||||
for n := 0; n < otrials; n++ {
|
||||
pair := genTestPair(t, false)
|
||||
for i := range pair {
|
||||
for k := range pair[i].dev.peers.keyMap {
|
||||
pair[i].dev.IpcSet(fmt.Sprintf("public_key=%s\npersistent_keepalive_interval=1\n", hex.EncodeToString(k[:])))
|
||||
}
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(pair))
|
||||
for i := range pair {
|
||||
go func(d *Device) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < itrials; i++ {
|
||||
if err := d.Up(); err != nil {
|
||||
t.Errorf("failed up bring up device: %v", err)
|
||||
}
|
||||
time.Sleep(time.Duration(rand.Intn(int(time.Nanosecond * (0x10000 - 1)))))
|
||||
if err := d.Down(); err != nil {
|
||||
t.Errorf("failed to bring down device: %v", err)
|
||||
}
|
||||
time.Sleep(time.Duration(rand.Intn(int(time.Nanosecond * (0x10000 - 1)))))
|
||||
}
|
||||
}(pair[i].dev)
|
||||
}
|
||||
wg.Wait()
|
||||
for i := range pair {
|
||||
pair[i].dev.Up()
|
||||
pair[i].dev.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrencySafety does other things concurrently with tunnel use.
|
||||
// It is intended to be used with the race detector to catch data races.
|
||||
func TestConcurrencySafety(t *testing.T) {
|
||||
pair := genTestPair(t, true)
|
||||
done := make(chan struct{})
|
||||
|
||||
const warmupIters = 10
|
||||
var warmup sync.WaitGroup
|
||||
warmup.Add(warmupIters)
|
||||
go func() {
|
||||
// Send data continuously back and forth until we're done.
|
||||
// Note that we may continue to attempt to send data
|
||||
// even after done is closed.
|
||||
i := warmupIters
|
||||
for ping := Ping; ; ping = !ping {
|
||||
pair.Send(t, ping, done)
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
if i > 0 {
|
||||
warmup.Done()
|
||||
i--
|
||||
}
|
||||
}
|
||||
}()
|
||||
warmup.Wait()
|
||||
|
||||
applyCfg := func(cfg string) {
|
||||
err := pair[0].dev.IpcSet(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Change persistent_keepalive_interval concurrently with tunnel use.
|
||||
t.Run("persistentKeepaliveInterval", func(t *testing.T) {
|
||||
var pub NoisePublicKey
|
||||
for key := range pair[0].dev.peers.keyMap {
|
||||
pub = key
|
||||
break
|
||||
}
|
||||
cfg := uapiCfg(
|
||||
"public_key", hex.EncodeToString(pub[:]),
|
||||
"persistent_keepalive_interval", "1",
|
||||
)
|
||||
for i := 0; i < 1000; i++ {
|
||||
applyCfg(cfg)
|
||||
}
|
||||
})
|
||||
|
||||
// Change private keys concurrently with tunnel use.
|
||||
t.Run("privateKey", func(t *testing.T) {
|
||||
bad := uapiCfg("private_key", "7777777777777777777777777777777777777777777777777777777777777777")
|
||||
good := uapiCfg("private_key", hex.EncodeToString(pair[0].dev.staticIdentity.privateKey[:]))
|
||||
// Set iters to a large number like 1000 to flush out data races quickly.
|
||||
// Don't leave it large. That can cause logical races
|
||||
// in which the handshake is interleaved with key changes
|
||||
// such that the private key appears to be unchanging but
|
||||
// other state gets reset, which can cause handshake failures like
|
||||
// "Received packet with invalid mac1".
|
||||
const iters = 1
|
||||
for i := 0; i < iters; i++ {
|
||||
applyCfg(bad)
|
||||
applyCfg(good)
|
||||
}
|
||||
})
|
||||
|
||||
// Perform bind updates and keepalive sends concurrently with tunnel use.
|
||||
t.Run("bindUpdate and keepalive", func(t *testing.T) {
|
||||
const iters = 10
|
||||
for i := 0; i < iters; i++ {
|
||||
for _, peer := range pair {
|
||||
peer.dev.BindUpdate()
|
||||
peer.dev.SendKeepalivesToPeersWithCurrentKeypair()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
close(done)
|
||||
}
|
||||
|
||||
func BenchmarkLatency(b *testing.B) {
|
||||
pair := genTestPair(b, true)
|
||||
|
||||
// Establish a connection.
|
||||
pair.Send(b, Ping, nil)
|
||||
pair.Send(b, Pong, nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pair.Send(b, Ping, nil)
|
||||
pair.Send(b, Pong, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkThroughput(b *testing.B) {
|
||||
pair := genTestPair(b, true)
|
||||
|
||||
// Establish a connection.
|
||||
pair.Send(b, Ping, nil)
|
||||
pair.Send(b, Pong, nil)
|
||||
|
||||
// Measure how long it takes to receive b.N packets,
|
||||
// starting when we receive the first packet.
|
||||
var recv atomic.Uint64
|
||||
var elapsed time.Duration
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
var start time.Time
|
||||
for {
|
||||
<-pair[0].tun.Inbound
|
||||
new := recv.Add(1)
|
||||
if new == 1 {
|
||||
start = time.Now()
|
||||
}
|
||||
// Careful! Don't change this to else if; b.N can be equal to 1.
|
||||
if new == uint64(b.N) {
|
||||
elapsed = time.Since(start)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Send packets as fast as we can until we've received enough.
|
||||
ping := tuntest.Ping(pair[0].ip, pair[1].ip)
|
||||
pingc := pair[1].tun.Outbound
|
||||
var sent uint64
|
||||
for recv.Load() != uint64(b.N) {
|
||||
sent++
|
||||
pingc <- ping
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
b.ReportMetric(float64(elapsed)/float64(b.N), "ns/op")
|
||||
b.ReportMetric(1-float64(b.N)/float64(sent), "packet-loss")
|
||||
}
|
||||
|
||||
func BenchmarkUAPIGet(b *testing.B) {
|
||||
pair := genTestPair(b, true)
|
||||
pair.Send(b, Ping, nil)
|
||||
pair.Send(b, Pong, nil)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pair[0].dev.IpcGetOperation(io.Discard)
|
||||
}
|
||||
}
|
||||
|
||||
func goroutineLeakCheck(t *testing.T) {
|
||||
goroutines := func() (int, []byte) {
|
||||
p := pprof.Lookup("goroutine")
|
||||
b := new(bytes.Buffer)
|
||||
p.WriteTo(b, 1)
|
||||
return p.Count(), b.Bytes()
|
||||
}
|
||||
|
||||
startGoroutines, startStacks := goroutines()
|
||||
t.Cleanup(func() {
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
// Give goroutines time to exit, if they need it.
|
||||
for i := 0; i < 10000; i++ {
|
||||
if runtime.NumGoroutine() <= startGoroutines {
|
||||
return
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
endGoroutines, endStacks := goroutines()
|
||||
t.Logf("starting stacks:\n%s\n", startStacks)
|
||||
t.Logf("ending stacks:\n%s\n", endStacks)
|
||||
t.Fatalf("expected %d goroutines, got %d, leak?", startGoroutines, endGoroutines)
|
||||
})
|
||||
}
|
||||
|
||||
type fakeBindSized struct {
|
||||
size int
|
||||
}
|
||||
|
||||
func (b *fakeBindSized) Open(
|
||||
port uint16,
|
||||
) (fns []conn.ReceiveFunc, actualPort uint16, err error) {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
func (b *fakeBindSized) Close() error { return nil }
|
||||
|
||||
func (b *fakeBindSized) SetMark(mark uint32) error { return nil }
|
||||
|
||||
func (b *fakeBindSized) Send(bufs [][]byte, ep conn.Endpoint) error { return nil }
|
||||
|
||||
func (b *fakeBindSized) ParseEndpoint(s string) (conn.Endpoint, error) { return nil, nil }
|
||||
|
||||
func (b *fakeBindSized) BatchSize() int { return b.size }
|
||||
|
||||
type fakeTUNDeviceSized struct {
|
||||
size int
|
||||
}
|
||||
|
||||
func (t *fakeTUNDeviceSized) File() *os.File { return nil }
|
||||
|
||||
func (t *fakeTUNDeviceSized) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (t *fakeTUNDeviceSized) Write(bufs [][]byte, offset int) (int, error) { return 0, nil }
|
||||
|
||||
func (t *fakeTUNDeviceSized) MTU() (int, error) { return 0, nil }
|
||||
|
||||
func (t *fakeTUNDeviceSized) Name() (string, error) { return "", nil }
|
||||
|
||||
func (t *fakeTUNDeviceSized) Events() <-chan tun.Event { return nil }
|
||||
|
||||
func (t *fakeTUNDeviceSized) Close() error { return nil }
|
||||
|
||||
func (t *fakeTUNDeviceSized) BatchSize() int { return t.size }
|
||||
|
||||
func TestBatchSize(t *testing.T) {
|
||||
d := Device{}
|
||||
|
||||
d.net.bind = &fakeBindSized{1}
|
||||
d.tun.device = &fakeTUNDeviceSized{1}
|
||||
if want, got := 1, d.BatchSize(); got != want {
|
||||
t.Errorf("expected batch size %d, got %d", want, got)
|
||||
}
|
||||
|
||||
d.net.bind = &fakeBindSized{1}
|
||||
d.tun.device = &fakeTUNDeviceSized{128}
|
||||
if want, got := 128, d.BatchSize(); got != want {
|
||||
t.Errorf("expected batch size %d, got %d", want, got)
|
||||
}
|
||||
|
||||
d.net.bind = &fakeBindSized{128}
|
||||
d.tun.device = &fakeTUNDeviceSized{1}
|
||||
if want, got := 128, d.BatchSize(); got != want {
|
||||
t.Errorf("expected batch size %d, got %d", want, got)
|
||||
}
|
||||
|
||||
d.net.bind = &fakeBindSized{128}
|
||||
d.tun.device = &fakeTUNDeviceSized{128}
|
||||
if want, got := 128, d.BatchSize(); got != want {
|
||||
t.Errorf("expected batch size %d, got %d", want, got)
|
||||
}
|
||||
}
|
||||
49
device/endpoint_test.go
Normal file
49
device/endpoint_test.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type DummyEndpoint struct {
|
||||
src, dst netip.Addr
|
||||
}
|
||||
|
||||
func CreateDummyEndpoint() (*DummyEndpoint, error) {
|
||||
var src, dst [16]byte
|
||||
if _, err := rand.Read(src[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err := rand.Read(dst[:])
|
||||
return &DummyEndpoint{netip.AddrFrom16(src), netip.AddrFrom16(dst)}, err
|
||||
}
|
||||
|
||||
func (e *DummyEndpoint) ClearSrc() {}
|
||||
|
||||
func (e *DummyEndpoint) SrcToString() string {
|
||||
return netip.AddrPortFrom(e.SrcIP(), 1000).String()
|
||||
}
|
||||
|
||||
func (e *DummyEndpoint) DstToString() string {
|
||||
return netip.AddrPortFrom(e.DstIP(), 1000).String()
|
||||
}
|
||||
|
||||
func (e *DummyEndpoint) DstToBytes() []byte {
|
||||
out := e.DstIP().AsSlice()
|
||||
out = append(out, byte(1000&0xff))
|
||||
out = append(out, byte((1000>>8)&0xff))
|
||||
return out
|
||||
}
|
||||
|
||||
func (e *DummyEndpoint) DstIP() netip.Addr {
|
||||
return e.dst
|
||||
}
|
||||
|
||||
func (e *DummyEndpoint) SrcIP() netip.Addr {
|
||||
return e.src
|
||||
}
|
||||
85
device/kdf_test.go
Normal file
85
device/kdf_test.go
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/blake2s"
|
||||
)
|
||||
|
||||
type KDFTest struct {
|
||||
key string
|
||||
input string
|
||||
t0 string
|
||||
t1 string
|
||||
t2 string
|
||||
}
|
||||
|
||||
func assertEquals(t *testing.T, a, b string) {
|
||||
if a != b {
|
||||
t.Fatal("expected", a, "=", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKDF(t *testing.T) {
|
||||
tests := []KDFTest{
|
||||
{
|
||||
key: "746573742d6b6579",
|
||||
input: "746573742d696e707574",
|
||||
t0: "6f0e5ad38daba1bea8a0d213688736f19763239305e0f58aba697f9ffc41c633",
|
||||
t1: "df1194df20802a4fe594cde27e92991c8cae66c366e8106aaa937a55fa371e8a",
|
||||
t2: "fac6e2745a325f5dc5d11a5b165aad08b0ada28e7b4e666b7c077934a4d76c24",
|
||||
},
|
||||
{
|
||||
key: "776972656775617264",
|
||||
input: "776972656775617264",
|
||||
t0: "491d43bbfdaa8750aaf535e334ecbfe5129967cd64635101c566d4caefda96e8",
|
||||
t1: "1e71a379baefd8a79aa4662212fcafe19a23e2b609a3db7d6bcba8f560e3d25f",
|
||||
t2: "31e1ae48bddfbe5de38f295e5452b1909a1b4e38e183926af3780b0c1e1f0160",
|
||||
},
|
||||
{
|
||||
key: "",
|
||||
input: "",
|
||||
t0: "8387b46bf43eccfcf349552a095d8315c4055beb90208fb1be23b894bc2ed5d0",
|
||||
t1: "58a0e5f6faefccf4807bff1f05fa8a9217945762040bcec2f4b4a62bdfe0e86e",
|
||||
t2: "0ce6ea98ec548f8e281e93e32db65621c45eb18dc6f0a7ad94178610a2f7338e",
|
||||
},
|
||||
}
|
||||
|
||||
var t0, t1, t2 [blake2s.Size]byte
|
||||
|
||||
for _, test := range tests {
|
||||
key, _ := hex.DecodeString(test.key)
|
||||
input, _ := hex.DecodeString(test.input)
|
||||
KDF3(&t0, &t1, &t2, key, input)
|
||||
t0s := hex.EncodeToString(t0[:])
|
||||
t1s := hex.EncodeToString(t1[:])
|
||||
t2s := hex.EncodeToString(t2[:])
|
||||
assertEquals(t, t0s, test.t0)
|
||||
assertEquals(t, t1s, test.t1)
|
||||
assertEquals(t, t2s, test.t2)
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
key, _ := hex.DecodeString(test.key)
|
||||
input, _ := hex.DecodeString(test.input)
|
||||
KDF2(&t0, &t1, key, input)
|
||||
t0s := hex.EncodeToString(t0[:])
|
||||
t1s := hex.EncodeToString(t1[:])
|
||||
assertEquals(t, t0s, test.t0)
|
||||
assertEquals(t, t1s, test.t1)
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
key, _ := hex.DecodeString(test.key)
|
||||
input, _ := hex.DecodeString(test.input)
|
||||
KDF1(&t0, key, input)
|
||||
t0s := hex.EncodeToString(t0[:])
|
||||
assertEquals(t, t0s, test.t0)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/wireguard-go/replay"
|
||||
"github.com/amnezia-vpn/amneziawg-go/replay"
|
||||
)
|
||||
|
||||
/* Due to limitations in Go and /x/crypto there is currently
|
||||
|
|
|
|||
|
|
@ -6,17 +6,16 @@
|
|||
package device
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
"github.com/sagernet/wireguard-go/tai64n"
|
||||
"golang.org/x/crypto/blake2s"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/poly1305"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/tai64n"
|
||||
)
|
||||
|
||||
type handshakeState int
|
||||
|
|
@ -66,7 +65,6 @@ const (
|
|||
MessageResponseSize = 92 // size of response message
|
||||
MessageCookieReplySize = 64 // size of cookie reply message
|
||||
MessageTransportHeaderSize = 16 // size of data preceding content in transport message
|
||||
MessageEncapsulatingTransportSize = 0 // lx: zeroed so AmneziaWG obfuscation composes without sagernet headroom (AWG path doesn't use the Bind.Send prepend)
|
||||
MessageTransportSize = MessageTransportHeaderSize + poly1305.TagSize // size of empty transport
|
||||
MessageKeepaliveSize = MessageTransportSize // size of keepalive
|
||||
MessageHandshakeSize = MessageInitiationSize // size of largest handshake related message
|
||||
|
|
@ -118,98 +116,6 @@ type MessageCookieReply struct {
|
|||
Cookie [blake2s.Size128 + poly1305.TagSize]byte
|
||||
}
|
||||
|
||||
var errMessageLengthMismatch = errors.New("message length mismatch")
|
||||
|
||||
func (msg *MessageInitiation) unmarshal(b []byte) error {
|
||||
if len(b) != MessageInitiationSize {
|
||||
return errMessageLengthMismatch
|
||||
}
|
||||
|
||||
msg.Type = binary.LittleEndian.Uint32(b)
|
||||
msg.Sender = binary.LittleEndian.Uint32(b[4:])
|
||||
copy(msg.Ephemeral[:], b[8:])
|
||||
copy(msg.Static[:], b[8+len(msg.Ephemeral):])
|
||||
copy(msg.Timestamp[:], b[8+len(msg.Ephemeral)+len(msg.Static):])
|
||||
copy(msg.MAC1[:], b[8+len(msg.Ephemeral)+len(msg.Static)+len(msg.Timestamp):])
|
||||
copy(msg.MAC2[:], b[8+len(msg.Ephemeral)+len(msg.Static)+len(msg.Timestamp)+len(msg.MAC1):])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg *MessageInitiation) marshal(b []byte) error {
|
||||
if len(b) != MessageInitiationSize {
|
||||
return errMessageLengthMismatch
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(b, msg.Type)
|
||||
binary.LittleEndian.PutUint32(b[4:], msg.Sender)
|
||||
copy(b[8:], msg.Ephemeral[:])
|
||||
copy(b[8+len(msg.Ephemeral):], msg.Static[:])
|
||||
copy(b[8+len(msg.Ephemeral)+len(msg.Static):], msg.Timestamp[:])
|
||||
copy(b[8+len(msg.Ephemeral)+len(msg.Static)+len(msg.Timestamp):], msg.MAC1[:])
|
||||
copy(b[8+len(msg.Ephemeral)+len(msg.Static)+len(msg.Timestamp)+len(msg.MAC1):], msg.MAC2[:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg *MessageResponse) unmarshal(b []byte) error {
|
||||
if len(b) != MessageResponseSize {
|
||||
return errMessageLengthMismatch
|
||||
}
|
||||
|
||||
msg.Type = binary.LittleEndian.Uint32(b)
|
||||
msg.Sender = binary.LittleEndian.Uint32(b[4:])
|
||||
msg.Receiver = binary.LittleEndian.Uint32(b[8:])
|
||||
copy(msg.Ephemeral[:], b[12:])
|
||||
copy(msg.Empty[:], b[12+len(msg.Ephemeral):])
|
||||
copy(msg.MAC1[:], b[12+len(msg.Ephemeral)+len(msg.Empty):])
|
||||
copy(msg.MAC2[:], b[12+len(msg.Ephemeral)+len(msg.Empty)+len(msg.MAC1):])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg *MessageResponse) marshal(b []byte) error {
|
||||
if len(b) != MessageResponseSize {
|
||||
return errMessageLengthMismatch
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(b, msg.Type)
|
||||
binary.LittleEndian.PutUint32(b[4:], msg.Sender)
|
||||
binary.LittleEndian.PutUint32(b[8:], msg.Receiver)
|
||||
copy(b[12:], msg.Ephemeral[:])
|
||||
copy(b[12+len(msg.Ephemeral):], msg.Empty[:])
|
||||
copy(b[12+len(msg.Ephemeral)+len(msg.Empty):], msg.MAC1[:])
|
||||
copy(b[12+len(msg.Ephemeral)+len(msg.Empty)+len(msg.MAC1):], msg.MAC2[:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg *MessageCookieReply) unmarshal(b []byte) error {
|
||||
if len(b) != MessageCookieReplySize {
|
||||
return errMessageLengthMismatch
|
||||
}
|
||||
|
||||
msg.Type = binary.LittleEndian.Uint32(b)
|
||||
msg.Receiver = binary.LittleEndian.Uint32(b[4:])
|
||||
copy(msg.Nonce[:], b[8:])
|
||||
copy(msg.Cookie[:], b[8+len(msg.Nonce):])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg *MessageCookieReply) marshal(b []byte) error {
|
||||
if len(b) != MessageCookieReplySize {
|
||||
return errMessageLengthMismatch
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(b, msg.Type)
|
||||
binary.LittleEndian.PutUint32(b[4:], msg.Receiver)
|
||||
copy(b[8:], msg.Nonce[:])
|
||||
copy(b[8+len(msg.Nonce):], msg.Cookie[:])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Handshake struct {
|
||||
state handshakeState
|
||||
mutex sync.RWMutex
|
||||
|
|
@ -219,7 +125,7 @@ type Handshake struct {
|
|||
localEphemeral NoisePrivateKey // ephemeral secret key
|
||||
localIndex uint32 // used to clear hash-table
|
||||
remoteIndex uint32 // index for sending
|
||||
remoteStatic NoisePublicKey // long term key, never changes, can be accessed without mutex
|
||||
remoteStatic NoisePublicKey // long term key
|
||||
remoteEphemeral NoisePublicKey // ephemeral public key
|
||||
precomputedStaticStatic [NoisePublicKeySize]byte // precomputed shared secret
|
||||
lastTimestamp tai64n.Timestamp
|
||||
|
|
@ -341,7 +247,7 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
|||
return &msg, nil
|
||||
}
|
||||
|
||||
func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation, endpoint conn.Endpoint) *Peer {
|
||||
func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
||||
var (
|
||||
hash [blake2s.Size]byte
|
||||
chainKey [blake2s.Size]byte
|
||||
|
|
@ -375,11 +281,6 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation, endpoint
|
|||
|
||||
// lookup peer
|
||||
|
||||
initEP, ok := endpoint.(conn.InitiationAwareEndpoint)
|
||||
if ok {
|
||||
initEP.InitiationMessagePublicKey(peerPK)
|
||||
}
|
||||
|
||||
peer := device.LookupPeer(peerPK)
|
||||
if peer == nil || !peer.isRunning.Load() {
|
||||
return nil
|
||||
|
|
|
|||
179
device/noise_test.go
Normal file
179
device/noise_test.go
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/tuntest"
|
||||
)
|
||||
|
||||
func TestCurveWrappers(t *testing.T) {
|
||||
sk1, err := newPrivateKey()
|
||||
assertNil(t, err)
|
||||
|
||||
sk2, err := newPrivateKey()
|
||||
assertNil(t, err)
|
||||
|
||||
pk1 := sk1.publicKey()
|
||||
pk2 := sk2.publicKey()
|
||||
|
||||
ss1, err1 := sk1.sharedSecret(pk2)
|
||||
ss2, err2 := sk2.sharedSecret(pk1)
|
||||
|
||||
if ss1 != ss2 || err1 != nil || err2 != nil {
|
||||
t.Fatal("Failed to compute shared secet")
|
||||
}
|
||||
}
|
||||
|
||||
func randDevice(t *testing.T) *Device {
|
||||
sk, err := newPrivateKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tun := tuntest.NewChannelTUN()
|
||||
logger := NewLogger(LogLevelError, "")
|
||||
device := NewDevice(tun.TUN(), conn.NewDefaultBind(), logger)
|
||||
device.SetPrivateKey(sk)
|
||||
return device
|
||||
}
|
||||
|
||||
func assertNil(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, a, b []byte) {
|
||||
if !bytes.Equal(a, b) {
|
||||
t.Fatal(a, "!=", b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoiseHandshake(t *testing.T) {
|
||||
dev1 := randDevice(t)
|
||||
dev2 := randDevice(t)
|
||||
|
||||
defer dev1.Close()
|
||||
defer dev2.Close()
|
||||
|
||||
peer1, err := dev2.NewPeer(dev1.staticIdentity.privateKey.publicKey())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer2, err := dev1.NewPeer(dev2.staticIdentity.privateKey.publicKey())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer1.Start()
|
||||
peer2.Start()
|
||||
|
||||
assertEqual(
|
||||
t,
|
||||
peer1.handshake.precomputedStaticStatic[:],
|
||||
peer2.handshake.precomputedStaticStatic[:],
|
||||
)
|
||||
|
||||
/* simulate handshake */
|
||||
|
||||
// initiation message
|
||||
|
||||
t.Log("exchange initiation message")
|
||||
|
||||
msg1, err := dev1.CreateMessageInitiation(peer2)
|
||||
assertNil(t, err)
|
||||
|
||||
packet := make([]byte, 0, 256)
|
||||
writer := bytes.NewBuffer(packet)
|
||||
err = binary.Write(writer, binary.LittleEndian, msg1)
|
||||
assertNil(t, err)
|
||||
peer := dev2.ConsumeMessageInitiation(msg1)
|
||||
if peer == nil {
|
||||
t.Fatal("handshake failed at initiation message")
|
||||
}
|
||||
|
||||
assertEqual(
|
||||
t,
|
||||
peer1.handshake.chainKey[:],
|
||||
peer2.handshake.chainKey[:],
|
||||
)
|
||||
|
||||
assertEqual(
|
||||
t,
|
||||
peer1.handshake.hash[:],
|
||||
peer2.handshake.hash[:],
|
||||
)
|
||||
|
||||
// response message
|
||||
|
||||
t.Log("exchange response message")
|
||||
|
||||
msg2, err := dev2.CreateMessageResponse(peer1)
|
||||
assertNil(t, err)
|
||||
|
||||
peer = dev1.ConsumeMessageResponse(msg2)
|
||||
if peer == nil {
|
||||
t.Fatal("handshake failed at response message")
|
||||
}
|
||||
|
||||
assertEqual(
|
||||
t,
|
||||
peer1.handshake.chainKey[:],
|
||||
peer2.handshake.chainKey[:],
|
||||
)
|
||||
|
||||
assertEqual(
|
||||
t,
|
||||
peer1.handshake.hash[:],
|
||||
peer2.handshake.hash[:],
|
||||
)
|
||||
|
||||
// key pairs
|
||||
|
||||
t.Log("deriving keys")
|
||||
|
||||
err = peer1.BeginSymmetricSession()
|
||||
if err != nil {
|
||||
t.Fatal("failed to derive keypair for peer 1", err)
|
||||
}
|
||||
|
||||
err = peer2.BeginSymmetricSession()
|
||||
if err != nil {
|
||||
t.Fatal("failed to derive keypair for peer 2", err)
|
||||
}
|
||||
|
||||
key1 := peer1.keypairs.next.Load()
|
||||
key2 := peer2.keypairs.current
|
||||
|
||||
// encrypting / decryption test
|
||||
|
||||
t.Log("test key pairs")
|
||||
|
||||
func() {
|
||||
testMsg := []byte("wireguard test message 1")
|
||||
var err error
|
||||
var out []byte
|
||||
var nonce [12]byte
|
||||
out = key1.send.Seal(out, nonce[:], testMsg, nil)
|
||||
out, err = key2.receive.Open(out[:0], nonce[:], out, nil)
|
||||
assertNil(t, err)
|
||||
assertEqual(t, out, testMsg)
|
||||
}()
|
||||
|
||||
func() {
|
||||
testMsg := []byte("wireguard test message 2")
|
||||
var err error
|
||||
var out []byte
|
||||
var nonce [12]byte
|
||||
out = key2.send.Seal(out, nonce[:], testMsg, nil)
|
||||
out, err = key1.receive.Open(out[:0], nonce[:], out, nil)
|
||||
assertNil(t, err)
|
||||
assertEqual(t, out, testMsg)
|
||||
}()
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
)
|
||||
|
||||
type Peer struct {
|
||||
|
|
@ -113,9 +113,6 @@ func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
|
|||
return peer, nil
|
||||
}
|
||||
|
||||
// SendBuffers sends buffers to peer. WireGuard packet data in each element of
|
||||
// buffers must be preceded by MessageEncapsulatingTransportSize number of
|
||||
// bytes.
|
||||
func (peer *Peer) SendBuffers(buffers [][]byte) error {
|
||||
peer.device.net.RLock()
|
||||
defer peer.device.net.RUnlock()
|
||||
|
|
@ -136,7 +133,7 @@ func (peer *Peer) SendBuffers(buffers [][]byte) error {
|
|||
}
|
||||
peer.endpoint.Unlock()
|
||||
|
||||
err := peer.device.net.bind.Send(buffers, endpoint, MessageEncapsulatingTransportSize)
|
||||
err := peer.device.net.bind.Send(buffers, endpoint)
|
||||
if err == nil {
|
||||
var totalLen uint64
|
||||
for _, b := range buffers {
|
||||
|
|
|
|||
141
device/pools_test.go
Normal file
141
device/pools_test.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWaitPool(t *testing.T) {
|
||||
t.Skip("Currently disabled")
|
||||
var wg sync.WaitGroup
|
||||
var trials atomic.Int32
|
||||
startTrials := int32(100000)
|
||||
if raceEnabled {
|
||||
// This test can be very slow with -race.
|
||||
startTrials /= 10
|
||||
}
|
||||
trials.Store(startTrials)
|
||||
workers := runtime.NumCPU() + 2
|
||||
if workers-4 <= 0 {
|
||||
t.Skip("Not enough cores")
|
||||
}
|
||||
p := NewWaitPool(uint32(workers-4), func() any { return make([]byte, 16) })
|
||||
wg.Add(workers)
|
||||
var max atomic.Uint32
|
||||
updateMax := func() {
|
||||
p.lock.Lock()
|
||||
count := p.count
|
||||
p.lock.Unlock()
|
||||
if count > p.max {
|
||||
t.Errorf("count (%d) > max (%d)", count, p.max)
|
||||
}
|
||||
for {
|
||||
old := max.Load()
|
||||
if count <= old {
|
||||
break
|
||||
}
|
||||
if max.CompareAndSwap(old, count) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for trials.Add(-1) > 0 {
|
||||
updateMax()
|
||||
x := p.Get()
|
||||
updateMax()
|
||||
time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
|
||||
updateMax()
|
||||
p.Put(x)
|
||||
updateMax()
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
if max.Load() != p.max {
|
||||
t.Errorf("Actual maximum count (%d) != ideal maximum count (%d)", max, p.max)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWaitPool(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
var trials atomic.Int32
|
||||
trials.Store(int32(b.N))
|
||||
workers := runtime.NumCPU() + 2
|
||||
if workers-4 <= 0 {
|
||||
b.Skip("Not enough cores")
|
||||
}
|
||||
p := NewWaitPool(uint32(workers-4), func() any { return make([]byte, 16) })
|
||||
wg.Add(workers)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for trials.Add(-1) > 0 {
|
||||
x := p.Get()
|
||||
time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
|
||||
p.Put(x)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func BenchmarkWaitPoolEmpty(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
var trials atomic.Int32
|
||||
trials.Store(int32(b.N))
|
||||
workers := runtime.NumCPU() + 2
|
||||
if workers-4 <= 0 {
|
||||
b.Skip("Not enough cores")
|
||||
}
|
||||
p := NewWaitPool(0, func() any { return make([]byte, 16) })
|
||||
wg.Add(workers)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for trials.Add(-1) > 0 {
|
||||
x := p.Get()
|
||||
time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
|
||||
p.Put(x)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func BenchmarkSyncPool(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
var trials atomic.Int32
|
||||
trials.Store(int32(b.N))
|
||||
workers := runtime.NumCPU() + 2
|
||||
if workers-4 <= 0 {
|
||||
b.Skip("Not enough cores")
|
||||
}
|
||||
p := sync.Pool{New: func() any { return make([]byte, 16) }}
|
||||
wg.Add(workers)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < workers; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for trials.Add(-1) > 0 {
|
||||
x := p.Get()
|
||||
time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
|
||||
p.Put(x)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import "github.com/sagernet/wireguard-go/conn"
|
||||
import "github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
|
||||
/* Reduce memory consumption for Android */
|
||||
|
||||
|
|
@ -14,6 +14,6 @@ const (
|
|||
QueueOutboundSize = 1024
|
||||
QueueInboundSize = 1024
|
||||
QueueHandshakeSize = 1024
|
||||
MaxSegmentSize = 2200
|
||||
MaxSegmentSize = (1 << 16) - 1 // largest possible UDP datagram
|
||||
PreallocatedBuffersPerPool = 4096
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import "github.com/sagernet/wireguard-go/conn"
|
||||
import "github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
|
||||
const (
|
||||
QueueStagedSize = conn.IdealBatchSize
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
//go:build !plan9
|
||||
//go:build !race
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
package device
|
||||
|
||||
import "syscall"
|
||||
|
||||
func init() {
|
||||
errEADDRINUSE = syscall.EADDRINUSE
|
||||
}
|
||||
const raceEnabled = false
|
||||
10
device/race_enabled_test.go
Normal file
10
device/race_enabled_test.go
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//go:build race
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
const raceEnabled = true
|
||||
|
|
@ -6,13 +6,14 @@
|
|||
package device
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
|
|
@ -293,7 +294,8 @@ func (device *Device) RoutineHandshake(id int) {
|
|||
// unmarshal packet
|
||||
|
||||
var reply MessageCookieReply
|
||||
err := reply.unmarshal(elem.packet)
|
||||
reader := bytes.NewReader(elem.packet)
|
||||
err := binary.Read(reader, binary.LittleEndian, &reply)
|
||||
if err != nil {
|
||||
device.log.Verbosef("Failed to decode cookie reply")
|
||||
goto skip
|
||||
|
|
@ -361,7 +363,8 @@ func (device *Device) RoutineHandshake(id int) {
|
|||
case MessageInitiationType:
|
||||
// unmarshal
|
||||
var msg MessageInitiation
|
||||
err := msg.unmarshal(elem.packet)
|
||||
reader := bytes.NewReader(elem.packet)
|
||||
err := binary.Read(reader, binary.LittleEndian, &msg)
|
||||
if err != nil {
|
||||
device.log.Errorf("Failed to decode initiation message")
|
||||
goto skip
|
||||
|
|
@ -370,7 +373,8 @@ func (device *Device) RoutineHandshake(id int) {
|
|||
// have to reassign msgType for ranged msgType to work
|
||||
msg.Type = elem.msgType
|
||||
|
||||
peer := device.ConsumeMessageInitiation(&msg, elem.endpoint)
|
||||
// consume initiation
|
||||
peer := device.ConsumeMessageInitiation(&msg)
|
||||
if peer == nil {
|
||||
device.log.Verbosef("Received invalid initiation message from %s", elem.endpoint.DstToString())
|
||||
goto skip
|
||||
|
|
@ -394,7 +398,8 @@ func (device *Device) RoutineHandshake(id int) {
|
|||
// unmarshal
|
||||
|
||||
var msg MessageResponse
|
||||
err := msg.unmarshal(elem.packet)
|
||||
reader := bytes.NewReader(elem.packet)
|
||||
err := binary.Read(reader, binary.LittleEndian, &msg)
|
||||
if err != nil {
|
||||
device.log.Errorf("Failed to decode response message")
|
||||
goto skip
|
||||
|
|
@ -425,6 +430,7 @@ func (device *Device) RoutineHandshake(id int) {
|
|||
// derive keypair
|
||||
|
||||
err = peer.BeginSymmetricSession()
|
||||
|
||||
if err != nil {
|
||||
device.log.Errorf("%v - Failed to derive keypair: %v", peer, err)
|
||||
goto skip
|
||||
|
|
@ -473,9 +479,6 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
|||
peer.timersHandshakeComplete()
|
||||
peer.SendStagedPackets()
|
||||
}
|
||||
if ep, ok := elem.endpoint.(conn.PeerAwareEndpoint); ok {
|
||||
ep.FromPeer(peer.handshake.remoteStatic)
|
||||
}
|
||||
rxBytesLen += uint64(len(elem.packet) + MinMessageSize)
|
||||
|
||||
if len(elem.packet) == 0 {
|
||||
|
|
@ -532,17 +535,6 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
|||
)
|
||||
}
|
||||
|
||||
peer.rxBytes.Add(rxBytesLen)
|
||||
if validTailPacket >= 0 {
|
||||
peer.SetEndpointFromPacket(elemsContainer.elems[validTailPacket].endpoint)
|
||||
peer.keepKeyFreshReceiving()
|
||||
peer.timersAnyAuthenticatedPacketTraversal()
|
||||
peer.timersAnyAuthenticatedPacketReceived()
|
||||
}
|
||||
if dataPacketReceived {
|
||||
peer.timersDataReceived()
|
||||
}
|
||||
|
||||
peer.rxBytes.Add(rxBytesLen)
|
||||
if validTailPacket >= 0 {
|
||||
peer.SetEndpointFromPacket(elemsContainer.elems[validTailPacket].endpoint)
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
"github.com/sagernet/wireguard-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
|
|
@ -49,11 +49,7 @@ import (
|
|||
|
||||
type QueueOutboundElement struct {
|
||||
buffer *[MaxMessageSize]byte // slice holding the packet data
|
||||
// packet is always a slice of "buffer". The starting offset in buffer
|
||||
// is either:
|
||||
// a) MessageEncapsulatingTransportSize+MessageTransportHeaderSize (plaintext)
|
||||
// b) 0 (post-encryption)
|
||||
packet []byte
|
||||
packet []byte // slice of "buffer" (always!)
|
||||
nonce uint64 // nonce for encryption
|
||||
keypair *Keypair // keypair for encryption
|
||||
peer *Peer // related peer
|
||||
|
|
@ -255,7 +251,7 @@ func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement)
|
|||
}
|
||||
|
||||
// TODO: allocation could be avoided
|
||||
device.net.bind.Send([][]byte{packet}, initiatingElem.endpoint, 0)
|
||||
device.net.bind.Send([][]byte{packet}, initiatingElem.endpoint)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -287,7 +283,7 @@ func (device *Device) RoutineReadFromTUN() {
|
|||
elemsByPeer = make(map[*Peer]*QueueOutboundElementsContainer, batchSize)
|
||||
count = 0
|
||||
sizes = make([]int, batchSize)
|
||||
offset = MessageEncapsulatingTransportSize + MessageTransportHeaderSize
|
||||
offset = MessageTransportHeaderSize
|
||||
)
|
||||
|
||||
for i := range elems {
|
||||
|
|
@ -382,30 +378,6 @@ func (device *Device) RoutineReadFromTUN() {
|
|||
}
|
||||
}
|
||||
|
||||
func (device *Device) InputPacket(destination []byte, packetSlices [][]byte) {
|
||||
peer := device.allowedips.Lookup(destination)
|
||||
if peer == nil {
|
||||
return
|
||||
}
|
||||
elem := device.NewOutboundElement()
|
||||
packet := elem.buffer[MessageEncapsulatingTransportSize+MessageTransportHeaderSize:]
|
||||
var n int
|
||||
for _, packetSlice := range packetSlices {
|
||||
n += copy(packet[n:], packetSlice)
|
||||
}
|
||||
elem.packet = packet[:n]
|
||||
elemsForPeer := device.GetOutboundElementsContainer()
|
||||
if peer.isRunning.Load() {
|
||||
elemsForPeer.elems = append(elemsForPeer.elems, elem)
|
||||
peer.StagePackets(elemsForPeer)
|
||||
peer.SendStagedPackets()
|
||||
} else {
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
device.PutOutboundElement(elem)
|
||||
device.PutOutboundElementsContainer(elemsForPeer)
|
||||
}
|
||||
}
|
||||
|
||||
func (peer *Peer) StagePackets(elems *QueueOutboundElementsContainer) {
|
||||
for {
|
||||
select {
|
||||
|
|
@ -602,7 +574,7 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
|
|||
for _, elem := range elemsContainer.elems {
|
||||
if len(elem.packet) != MessageKeepaliveSize {
|
||||
dataSent = true
|
||||
}
|
||||
|
||||
if padding := device.paddings.transport; padding > 0 {
|
||||
// elem.packet is stored at the start of elem.buffer
|
||||
// with zero padding
|
||||
|
|
@ -612,6 +584,7 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
|
|||
rand.Read(elem.buffer[:padding])
|
||||
elem.packet = elem.buffer[:padding+len(elem.packet)]
|
||||
}
|
||||
}
|
||||
bufs = append(bufs, elem.packet)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
package device
|
||||
|
||||
import (
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
"github.com/sagernet/wireguard-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
)
|
||||
|
||||
func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
|
||||
func (device *Device) startRouteListener(_ conn.Bind) (*rwcancel.RWCancel, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*
|
||||
* This implements userspace semantics of "sticky sockets", modeled after
|
||||
* WireGuard's kernelspace implementation. This is more or less a straight port
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
*
|
||||
* Currently there is no way to achieve this within the net package:
|
||||
* See e.g. https://github.com/golang/go/issues/17930
|
||||
* So this code is remains platform dependent.
|
||||
* So this code remains platform dependent.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
|
@ -18,9 +18,10 @@ import (
|
|||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
"github.com/sagernet/wireguard-go/rwcancel"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
)
|
||||
|
||||
func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
|
||||
|
|
@ -46,7 +47,7 @@ func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, er
|
|||
return netlinkCancel, nil
|
||||
}
|
||||
|
||||
func (device *Device) routineRouteListener(bind conn.Bind, netlinkSock int, netlinkCancel *rwcancel.RWCancel) {
|
||||
func (device *Device) routineRouteListener(_ conn.Bind, netlinkSock int, netlinkCancel *rwcancel.RWCancel) {
|
||||
type peerEndpointPtr struct {
|
||||
peer *Peer
|
||||
endpoint *conn.Endpoint
|
||||
|
|
|
|||
|
|
@ -39,9 +39,6 @@ func (peer *Peer) NewTimer(expirationFunction func(*Peer)) *Timer {
|
|||
timer.isPending = false
|
||||
timer.modifyingLock.Unlock()
|
||||
|
||||
if pauseManager := peer.device.pauseManager; pauseManager != nil {
|
||||
pauseManager.WaitActive()
|
||||
}
|
||||
expirationFunction(peer)
|
||||
})
|
||||
timer.Stop()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
|
@ -8,7 +8,7 @@ package device
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/sagernet/wireguard-go/tun"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
)
|
||||
|
||||
const DefaultMTU = 1420
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/wireguard-go/ipc"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc"
|
||||
)
|
||||
|
||||
type IPCError struct {
|
||||
|
|
|
|||
51
format_test.go
Normal file
51
format_test.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/format"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatting(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
t.Errorf("unable to walk %s: %v", path, err)
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() || filepath.Ext(path) != ".go" {
|
||||
return nil
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(path string) {
|
||||
defer wg.Done()
|
||||
src, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Errorf("unable to read %s: %v", path, err)
|
||||
return
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
src = bytes.ReplaceAll(src, []byte{'\r', '\n'}, []byte{'\n'})
|
||||
}
|
||||
formatted, err := format.Source(src)
|
||||
if err != nil {
|
||||
t.Errorf("unable to format %s: %v", path, err)
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(src, formatted) {
|
||||
t.Errorf("unformatted code: %s", path)
|
||||
}
|
||||
}(path)
|
||||
return nil
|
||||
})
|
||||
wg.Wait()
|
||||
}
|
||||
33
go.mod
33
go.mod
|
|
@ -1,11 +1,32 @@
|
|||
module github.com/sagernet/wireguard-go
|
||||
module github.com/amnezia-vpn/amneziawg-go
|
||||
|
||||
go 1.24
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/sagernet/sing v0.7.10
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/net v0.15.0
|
||||
golang.org/x/sys v0.12.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tevino/abool v1.2.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/crypto v0.42.0
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||
golang.org/x/net v0.44.0
|
||||
golang.org/x/sys v0.36.0
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Jigsaw-Code/outline-sdk v0.0.20 // indirect
|
||||
github.com/Jigsaw-Code/outline-sdk/x v0.0.8 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/goccy/go-yaml v1.17.1 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect
|
||||
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
golang.org/x/tools v0.37.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
70
go.sum
70
go.sum
|
|
@ -1,10 +1,64 @@
|
|||
github.com/sagernet/sing v0.7.10 h1:2yPhZFx+EkyHPH8hXNezgyRSHyGY12CboId7CtwLROw=
|
||||
github.com/sagernet/sing v0.7.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/Jigsaw-Code/outline-sdk v0.0.20 h1:4ep7MK9lFmcyPIRIbn4xrP1VKdJNsqR6+iJEOHDKnNg=
|
||||
github.com/Jigsaw-Code/outline-sdk v0.0.20/go.mod h1:CFDKyGZA4zatKE4vMLe8TyQpZCyINOeRFbMAmYHxodw=
|
||||
github.com/Jigsaw-Code/outline-sdk/x v0.0.8 h1:fFHFXW7CKhRiegyNSdP25S/WIiVrRnMKysHDoO/N2Xg=
|
||||
github.com/Jigsaw-Code/outline-sdk/x v0.0.8/go.mod h1:zqSH7yEYIQ0pYOhrr4QnodATVb5X/eZXV4AjUp9zhvs=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
|
||||
github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
|
||||
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b h1:WX7nnnLfCEXg+FmdYZPai2XuP3VqCP1HZVMST0n9DF0=
|
||||
golang.org/x/mobile v0.0.0-20240520174638-fa72addaaa1b/go.mod h1:EiXZlVfUTaAyySFVJb9rsODuiO+WXu8HrUuySb7nYFw=
|
||||
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
|
||||
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
|
||||
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
|
|
|
|||
674
ipc/namedpipe/namedpipe_test.go
Normal file
674
ipc/namedpipe/namedpipe_test.go
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
// Copyright 2021 The Go Authors. All rights reserved.
|
||||
// Copyright 2015 Microsoft
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package namedpipe_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc/namedpipe"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func randomPipePath() string {
|
||||
guid, err := windows.GenerateGUID()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return `\\.\PIPE\go-namedpipe-test-` + guid.String()
|
||||
}
|
||||
|
||||
func TestPingPong(t *testing.T) {
|
||||
const (
|
||||
ping = 42
|
||||
pong = 24
|
||||
)
|
||||
pipePath := randomPipePath()
|
||||
listener, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to listen on pipe: %v", err)
|
||||
}
|
||||
defer listener.Close()
|
||||
go func() {
|
||||
incoming, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to accept pipe connection: %v", err)
|
||||
}
|
||||
defer incoming.Close()
|
||||
var data [1]byte
|
||||
_, err = incoming.Read(data[:])
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read ping from pipe: %v", err)
|
||||
}
|
||||
if data[0] != ping {
|
||||
t.Fatalf("expected ping, got %d", data[0])
|
||||
}
|
||||
data[0] = pong
|
||||
_, err = incoming.Write(data[:])
|
||||
if err != nil {
|
||||
t.Fatalf("unable to write pong to pipe: %v", err)
|
||||
}
|
||||
}()
|
||||
client, err := namedpipe.DialTimeout(pipePath, time.Duration(0))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to dial pipe: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
client.SetDeadline(time.Now().Add(time.Second * 5))
|
||||
var data [1]byte
|
||||
data[0] = ping
|
||||
_, err = client.Write(data[:])
|
||||
if err != nil {
|
||||
t.Fatalf("unable to write ping to pipe: %v", err)
|
||||
}
|
||||
_, err = client.Read(data[:])
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read pong from pipe: %v", err)
|
||||
}
|
||||
if data[0] != pong {
|
||||
t.Fatalf("expected pong, got %d", data[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialUnknownFailsImmediately(t *testing.T) {
|
||||
_, err := namedpipe.DialTimeout(randomPipePath(), time.Duration(0))
|
||||
if !errors.Is(err, syscall.ENOENT) {
|
||||
t.Fatalf("expected ENOENT got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialListenerTimesOut(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
pipe, err := namedpipe.DialTimeout(pipePath, 10*time.Millisecond)
|
||||
if err == nil {
|
||||
pipe.Close()
|
||||
}
|
||||
if err != os.ErrDeadlineExceeded {
|
||||
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialContextListenerTimesOut(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
d := 10 * time.Millisecond
|
||||
ctx, _ := context.WithTimeout(context.Background(), d)
|
||||
pipe, err := namedpipe.DialContext(ctx, pipePath)
|
||||
if err == nil {
|
||||
pipe.Close()
|
||||
}
|
||||
if err != context.DeadlineExceeded {
|
||||
t.Fatalf("expected context.DeadlineExceeded, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialListenerGetsCancelled(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
ch := make(chan error)
|
||||
go func(ctx context.Context, ch chan error) {
|
||||
_, err := namedpipe.DialContext(ctx, pipePath)
|
||||
ch <- err
|
||||
}(ctx, ch)
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
cancel()
|
||||
err = <-ch
|
||||
if err != context.Canceled {
|
||||
t.Fatalf("expected context.Canceled, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialAccessDeniedWithRestrictedSD(t *testing.T) {
|
||||
if windows.NewLazySystemDLL("ntdll.dll").NewProc("wine_get_version").Find() == nil {
|
||||
t.Skip("dacls on named pipes are broken on wine")
|
||||
}
|
||||
pipePath := randomPipePath()
|
||||
sd, _ := windows.SecurityDescriptorFromString("D:")
|
||||
l, err := (&namedpipe.ListenConfig{
|
||||
SecurityDescriptor: sd,
|
||||
}).Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
pipe, err := namedpipe.DialTimeout(pipePath, time.Duration(0))
|
||||
if err == nil {
|
||||
pipe.Close()
|
||||
}
|
||||
if !errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||
t.Fatalf("expected ERROR_ACCESS_DENIED, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getConnection(cfg *namedpipe.ListenConfig) (client, server net.Conn, err error) {
|
||||
pipePath := randomPipePath()
|
||||
if cfg == nil {
|
||||
cfg = &namedpipe.ListenConfig{}
|
||||
}
|
||||
l, err := cfg.Listen(pipePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
type response struct {
|
||||
c net.Conn
|
||||
err error
|
||||
}
|
||||
ch := make(chan response)
|
||||
go func() {
|
||||
c, err := l.Accept()
|
||||
ch <- response{c, err}
|
||||
}()
|
||||
|
||||
c, err := namedpipe.DialTimeout(pipePath, time.Duration(0))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r := <-ch
|
||||
if err = r.err; err != nil {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
client = c
|
||||
server = r.c
|
||||
return
|
||||
}
|
||||
|
||||
func TestReadTimeout(t *testing.T) {
|
||||
c, s, err := getConnection(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
defer s.Close()
|
||||
|
||||
c.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
|
||||
|
||||
buf := make([]byte, 10)
|
||||
_, err = c.Read(buf)
|
||||
if err != os.ErrDeadlineExceeded {
|
||||
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func server(l net.Listener, ch chan int) {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c))
|
||||
s, err := rw.ReadString('\n')
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = rw.WriteString("got " + s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = rw.Flush()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.Close()
|
||||
ch <- 1
|
||||
}
|
||||
|
||||
func TestFullListenDialReadWrite(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
ch := make(chan int)
|
||||
go server(l, ch)
|
||||
|
||||
c, err := namedpipe.DialTimeout(pipePath, time.Duration(0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c))
|
||||
_, err = rw.WriteString("hello world\n")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = rw.Flush()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s, err := rw.ReadString('\n')
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ms := "got hello world\n"
|
||||
if s != ms {
|
||||
t.Errorf("expected '%s', got '%s'", ms, s)
|
||||
}
|
||||
|
||||
<-ch
|
||||
}
|
||||
|
||||
func TestCloseAbortsListen(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ch := make(chan error)
|
||||
go func() {
|
||||
_, err := l.Accept()
|
||||
ch <- err
|
||||
}()
|
||||
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
l.Close()
|
||||
|
||||
err = <-ch
|
||||
if err != net.ErrClosed {
|
||||
t.Fatalf("expected net.ErrClosed, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func ensureEOFOnClose(t *testing.T, r io.Reader, w io.Closer) {
|
||||
b := make([]byte, 10)
|
||||
w.Close()
|
||||
n, err := r.Read(b)
|
||||
if n > 0 {
|
||||
t.Errorf("unexpected byte count %d", n)
|
||||
}
|
||||
if err != io.EOF {
|
||||
t.Errorf("expected EOF: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseClientEOFServer(t *testing.T) {
|
||||
c, s, err := getConnection(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
defer s.Close()
|
||||
ensureEOFOnClose(t, c, s)
|
||||
}
|
||||
|
||||
func TestCloseServerEOFClient(t *testing.T) {
|
||||
c, s, err := getConnection(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
defer s.Close()
|
||||
ensureEOFOnClose(t, s, c)
|
||||
}
|
||||
|
||||
func TestCloseWriteEOF(t *testing.T) {
|
||||
cfg := &namedpipe.ListenConfig{
|
||||
MessageMode: true,
|
||||
}
|
||||
c, s, err := getConnection(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
defer s.Close()
|
||||
|
||||
type closeWriter interface {
|
||||
CloseWrite() error
|
||||
}
|
||||
|
||||
err = c.(closeWriter).CloseWrite()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b := make([]byte, 10)
|
||||
_, err = s.Read(b)
|
||||
if err != io.EOF {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcceptAfterCloseFails(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l.Close()
|
||||
_, err = l.Accept()
|
||||
if err != net.ErrClosed {
|
||||
t.Fatalf("expected net.ErrClosed, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDialTimesOutByDefault(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
pipe, err := namedpipe.DialTimeout(pipePath, time.Duration(0)) // Should timeout after 2 seconds.
|
||||
if err == nil {
|
||||
pipe.Close()
|
||||
}
|
||||
if err != os.ErrDeadlineExceeded {
|
||||
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutPendingRead(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
s, err := l.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
s.Close()
|
||||
close(serverDone)
|
||||
}()
|
||||
|
||||
client, err := namedpipe.DialTimeout(pipePath, time.Duration(0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
clientErr := make(chan error)
|
||||
go func() {
|
||||
buf := make([]byte, 10)
|
||||
_, err = client.Read(buf)
|
||||
clientErr <- err
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // make *sure* the pipe is reading before we set the deadline
|
||||
client.SetReadDeadline(time.Unix(1, 0))
|
||||
|
||||
select {
|
||||
case err = <-clientErr:
|
||||
if err != os.ErrDeadlineExceeded {
|
||||
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatalf("timed out while waiting for read to cancel")
|
||||
<-clientErr
|
||||
}
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
func TestTimeoutPendingWrite(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
s, err := l.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
s.Close()
|
||||
close(serverDone)
|
||||
}()
|
||||
|
||||
client, err := namedpipe.DialTimeout(pipePath, time.Duration(0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
clientErr := make(chan error)
|
||||
go func() {
|
||||
_, err = client.Write([]byte("this should timeout"))
|
||||
clientErr <- err
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond) // make *sure* the pipe is writing before we set the deadline
|
||||
client.SetWriteDeadline(time.Unix(1, 0))
|
||||
|
||||
select {
|
||||
case err = <-clientErr:
|
||||
if err != os.ErrDeadlineExceeded {
|
||||
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
|
||||
}
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatalf("timed out while waiting for write to cancel")
|
||||
<-clientErr
|
||||
}
|
||||
<-serverDone
|
||||
}
|
||||
|
||||
type CloseWriter interface {
|
||||
CloseWrite() error
|
||||
}
|
||||
|
||||
func TestEchoWithMessaging(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := (&namedpipe.ListenConfig{
|
||||
MessageMode: true, // Use message mode so that CloseWrite() is supported
|
||||
InputBufferSize: 65536, // Use 64KB buffers to improve performance
|
||||
OutputBufferSize: 65536,
|
||||
}).Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
listenerDone := make(chan bool)
|
||||
clientDone := make(chan bool)
|
||||
go func() {
|
||||
// server echo
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
time.Sleep(500 * time.Millisecond) // make *sure* we don't begin to read before eof signal is sent
|
||||
_, err = io.Copy(conn, conn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn.(CloseWriter).CloseWrite()
|
||||
close(listenerDone)
|
||||
}()
|
||||
client, err := namedpipe.DialTimeout(pipePath, time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
go func() {
|
||||
// client read back
|
||||
bytes := make([]byte, 2)
|
||||
n, e := client.Read(bytes)
|
||||
if e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
if n != 2 || bytes[0] != 0 || bytes[1] != 1 {
|
||||
t.Fatalf("expected 2 bytes, got %v", n)
|
||||
}
|
||||
close(clientDone)
|
||||
}()
|
||||
|
||||
payload := make([]byte, 2)
|
||||
payload[0] = 0
|
||||
payload[1] = 1
|
||||
|
||||
n, err := client.Write(payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 2 {
|
||||
t.Fatalf("expected 2 bytes, got %v", n)
|
||||
}
|
||||
client.(CloseWriter).CloseWrite()
|
||||
<-listenerDone
|
||||
<-clientDone
|
||||
}
|
||||
|
||||
func TestConnectRace(t *testing.T) {
|
||||
pipePath := randomPipePath()
|
||||
l, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
go func() {
|
||||
for {
|
||||
s, err := l.Accept()
|
||||
if err == net.ErrClosed {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
c, err := namedpipe.DialTimeout(pipePath, time.Duration(0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageReadMode(t *testing.T) {
|
||||
if maj, _, _ := windows.RtlGetNtVersionNumbers(); maj <= 8 {
|
||||
t.Skipf("Skipping on Windows %d", maj)
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
pipePath := randomPipePath()
|
||||
l, err := (&namedpipe.ListenConfig{MessageMode: true}).Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
msg := ([]byte)("hello world")
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
s, err := l.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = s.Write(msg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s.Close()
|
||||
}()
|
||||
|
||||
c, err := namedpipe.DialTimeout(pipePath, time.Duration(0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
mode := uint32(windows.PIPE_READMODE_MESSAGE)
|
||||
err = windows.SetNamedPipeHandleState(c.(interface{ Handle() windows.Handle }).Handle(), &mode, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ch := make([]byte, 1)
|
||||
var vmsg []byte
|
||||
for {
|
||||
n, err := c.Read(ch)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Fatalf("expected 1, got %d", n)
|
||||
}
|
||||
vmsg = append(vmsg, ch[0])
|
||||
}
|
||||
if !bytes.Equal(msg, vmsg) {
|
||||
t.Fatalf("expected %s, got %s", msg, vmsg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenConnectRace(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping long race test")
|
||||
}
|
||||
pipePath := randomPipePath()
|
||||
for i := 0; i < 50 && !t.Failed(); i++ {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
c, err := namedpipe.DialTimeout(pipePath, time.Duration(0))
|
||||
if err == nil {
|
||||
c.Close()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
s, err := namedpipe.Listen(pipePath)
|
||||
if err != nil {
|
||||
t.Error(i, err)
|
||||
} else {
|
||||
s.Close()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
//go:build wasm || plan9 || aix || solaris || illumos
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ipc
|
||||
|
||||
// Made up sentinel error codes for {js,wasip1}/wasm, and plan9.
|
||||
const (
|
||||
IpcErrorIO = 1
|
||||
IpcErrorInvalid = 2
|
||||
IpcErrorPortInUse = 3
|
||||
IpcErrorUnknown = 4
|
||||
IpcErrorProtocol = 5
|
||||
)
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ipc
|
||||
|
|
@ -9,7 +9,7 @@ import (
|
|||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/wireguard-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
|
|
@ -84,6 +84,7 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
|||
unix.IN_DELETE|
|
||||
unix.IN_DELETE_SELF,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
//go:build tamago
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
|
|
@ -7,7 +5,7 @@
|
|||
|
||||
package ipc
|
||||
|
||||
// Made up sentinel error codes for tamago platform.
|
||||
// Made up sentinel error codes for {js,wasip1}/wasm.
|
||||
const (
|
||||
IpcErrorIO = 1
|
||||
IpcErrorInvalid = 2
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ipc
|
||||
|
|
@ -8,7 +8,7 @@ package ipc
|
|||
import (
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/wireguard-go/ipc/namedpipe"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc/namedpipe"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ func init() {
|
|||
func UAPIListen(name string) (net.Listener, error) {
|
||||
listener, err := (&namedpipe.ListenConfig{
|
||||
SecurityDescriptor: UAPISecurityDescriptor,
|
||||
}).Listen(`\\.\pipe\ProtectedPrefix\Administrators\WireGuard\` + name)
|
||||
}).Listen(`\\.\pipe\ProtectedPrefix\Administrators\AmneziaWG\` + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
268
main.go
Normal file
268
main.go
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
//go:build !windows
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
ExitSetupSuccess = 0
|
||||
ExitSetupFailed = 1
|
||||
)
|
||||
|
||||
const (
|
||||
ENV_WG_TUN_FD = "WG_TUN_FD"
|
||||
ENV_WG_UAPI_FD = "WG_UAPI_FD"
|
||||
ENV_WG_PROCESS_FOREGROUND = "WG_PROCESS_FOREGROUND"
|
||||
)
|
||||
|
||||
func printUsage() {
|
||||
fmt.Printf("Usage: %s [-f/--foreground] INTERFACE-NAME\n", os.Args[0])
|
||||
}
|
||||
|
||||
func warning() {
|
||||
switch runtime.GOOS {
|
||||
case "linux", "freebsd", "openbsd":
|
||||
if os.Getenv(ENV_WG_PROCESS_FOREGROUND) == "1" {
|
||||
return
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "┌──────────────────────────────────────────────────────────────┐")
|
||||
fmt.Fprintln(os.Stderr, "│ │")
|
||||
fmt.Fprintln(os.Stderr, "│ Running amneziawg-go is not required because this │")
|
||||
fmt.Fprintln(os.Stderr, "│ kernel has first class support for AmneziaWG. For │")
|
||||
fmt.Fprintln(os.Stderr, "│ information on installing the kernel module, │")
|
||||
fmt.Fprintln(os.Stderr, "│ please visit: │")
|
||||
fmt.Fprintln(os.Stderr, "| https://github.com/amnezia-vpn/amneziawg-linux-kernel-module │")
|
||||
fmt.Fprintln(os.Stderr, "│ │")
|
||||
fmt.Fprintln(os.Stderr, "└──────────────────────────────────────────────────────────────┘")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 2 && os.Args[1] == "--version" {
|
||||
fmt.Printf("amneziawg-go %s\n\nUserspace AmneziaWG daemon for %s-%s.\nInformation available at https://amnezia.org\n", Version, runtime.GOOS, runtime.GOARCH)
|
||||
return
|
||||
}
|
||||
|
||||
warning()
|
||||
|
||||
var foreground bool
|
||||
var interfaceName string
|
||||
if len(os.Args) < 2 || len(os.Args) > 3 {
|
||||
printUsage()
|
||||
return
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
|
||||
case "-f", "--foreground":
|
||||
foreground = true
|
||||
if len(os.Args) != 3 {
|
||||
printUsage()
|
||||
return
|
||||
}
|
||||
interfaceName = os.Args[2]
|
||||
|
||||
default:
|
||||
foreground = false
|
||||
if len(os.Args) != 2 {
|
||||
printUsage()
|
||||
return
|
||||
}
|
||||
interfaceName = os.Args[1]
|
||||
}
|
||||
|
||||
if !foreground {
|
||||
foreground = os.Getenv(ENV_WG_PROCESS_FOREGROUND) == "1"
|
||||
}
|
||||
|
||||
// get log level (default: info)
|
||||
|
||||
logLevel := func() int {
|
||||
switch os.Getenv("LOG_LEVEL") {
|
||||
case "verbose", "debug":
|
||||
return device.LogLevelVerbose
|
||||
case "error":
|
||||
return device.LogLevelError
|
||||
case "silent":
|
||||
return device.LogLevelSilent
|
||||
}
|
||||
return device.LogLevelError
|
||||
}()
|
||||
|
||||
// open TUN device (or use supplied fd)
|
||||
|
||||
tdev, err := func() (tun.Device, error) {
|
||||
tunFdStr := os.Getenv(ENV_WG_TUN_FD)
|
||||
if tunFdStr == "" {
|
||||
return tun.CreateTUN(interfaceName, device.DefaultMTU)
|
||||
}
|
||||
|
||||
// construct tun device from supplied fd
|
||||
|
||||
fd, err := strconv.ParseUint(tunFdStr, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = unix.SetNonblock(int(fd), true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file := os.NewFile(uintptr(fd), "")
|
||||
return tun.CreateTUNFromFile(file, device.DefaultMTU)
|
||||
}()
|
||||
|
||||
if err == nil {
|
||||
realInterfaceName, err2 := tdev.Name()
|
||||
if err2 == nil {
|
||||
interfaceName = realInterfaceName
|
||||
}
|
||||
}
|
||||
|
||||
logger := device.NewLogger(
|
||||
logLevel,
|
||||
fmt.Sprintf("(%s) ", interfaceName),
|
||||
)
|
||||
|
||||
logger.Verbosef("Starting amneziawg-go version %s", Version)
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to create TUN device: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
|
||||
// open UAPI file (or use supplied fd)
|
||||
|
||||
fileUAPI, err := func() (*os.File, error) {
|
||||
uapiFdStr := os.Getenv(ENV_WG_UAPI_FD)
|
||||
if uapiFdStr == "" {
|
||||
return ipc.UAPIOpen(interfaceName)
|
||||
}
|
||||
|
||||
// use supplied fd
|
||||
|
||||
fd, err := strconv.ParseUint(uapiFdStr, 10, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return os.NewFile(uintptr(fd), ""), nil
|
||||
}()
|
||||
if err != nil {
|
||||
logger.Errorf("UAPI listen error: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
return
|
||||
}
|
||||
// daemonize the process
|
||||
|
||||
if !foreground {
|
||||
env := os.Environ()
|
||||
env = append(env, fmt.Sprintf("%s=3", ENV_WG_TUN_FD))
|
||||
env = append(env, fmt.Sprintf("%s=4", ENV_WG_UAPI_FD))
|
||||
env = append(env, fmt.Sprintf("%s=1", ENV_WG_PROCESS_FOREGROUND))
|
||||
files := [3]*os.File{}
|
||||
if os.Getenv("LOG_LEVEL") != "" && logLevel != device.LogLevelSilent {
|
||||
files[0], _ = os.Open(os.DevNull)
|
||||
files[1] = os.Stdout
|
||||
files[2] = os.Stderr
|
||||
} else {
|
||||
files[0], _ = os.Open(os.DevNull)
|
||||
files[1], _ = os.Open(os.DevNull)
|
||||
files[2], _ = os.Open(os.DevNull)
|
||||
}
|
||||
attr := &os.ProcAttr{
|
||||
Files: []*os.File{
|
||||
files[0], // stdin
|
||||
files[1], // stdout
|
||||
files[2], // stderr
|
||||
tdev.File(),
|
||||
fileUAPI,
|
||||
},
|
||||
Dir: ".",
|
||||
Env: env,
|
||||
}
|
||||
|
||||
path, err := os.Executable()
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to determine executable: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
|
||||
process, err := os.StartProcess(
|
||||
path,
|
||||
os.Args,
|
||||
attr,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to daemonize: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
process.Release()
|
||||
return
|
||||
}
|
||||
|
||||
device := device.NewDevice(tdev, conn.NewDefaultBind(), logger)
|
||||
|
||||
logger.Verbosef("Device started")
|
||||
|
||||
errs := make(chan error)
|
||||
term := make(chan os.Signal, 1)
|
||||
|
||||
uapi, err := ipc.UAPIListen(interfaceName, fileUAPI)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to listen on uapi socket: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := uapi.Accept()
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
go device.IpcHandle(conn)
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Verbosef("UAPI listener started")
|
||||
|
||||
// wait for program to terminate
|
||||
|
||||
signal.Notify(term, unix.SIGTERM)
|
||||
signal.Notify(term, os.Interrupt)
|
||||
|
||||
select {
|
||||
case <-term:
|
||||
case <-errs:
|
||||
case <-device.Wait():
|
||||
}
|
||||
|
||||
// clean up
|
||||
|
||||
uapi.Close()
|
||||
device.Close()
|
||||
|
||||
logger.Verbosef("Shutting down")
|
||||
}
|
||||
99
main_windows.go
Normal file
99
main_windows.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/ipc"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
)
|
||||
|
||||
const (
|
||||
ExitSetupSuccess = 0
|
||||
ExitSetupFailed = 1
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) != 2 {
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
interfaceName := os.Args[1]
|
||||
|
||||
fmt.Fprintln(os.Stderr, "Warning: this is a test program for Windows, mainly used for debugging this Go package. For a real AmneziaWG for Windows client, please visit: https://amnezia.org")
|
||||
|
||||
logger := device.NewLogger(
|
||||
device.LogLevelVerbose,
|
||||
fmt.Sprintf("(%s) ", interfaceName),
|
||||
)
|
||||
logger.Verbosef("Starting amneziawg-go version %s", Version)
|
||||
|
||||
tun, err := tun.CreateTUN(interfaceName, 0)
|
||||
if err == nil {
|
||||
realInterfaceName, err2 := tun.Name()
|
||||
if err2 == nil {
|
||||
interfaceName = realInterfaceName
|
||||
}
|
||||
} else {
|
||||
logger.Errorf("Failed to create TUN device: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
|
||||
device := device.NewDevice(tun, conn.NewDefaultBind(), logger)
|
||||
err = device.Up()
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to bring up device: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
logger.Verbosef("Device started")
|
||||
|
||||
uapi, err := ipc.UAPIListen(interfaceName)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to listen on uapi socket: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
|
||||
errs := make(chan error)
|
||||
term := make(chan os.Signal, 1)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := uapi.Accept()
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
go device.IpcHandle(conn)
|
||||
}
|
||||
}()
|
||||
logger.Verbosef("UAPI listener started")
|
||||
|
||||
// wait for program to terminate
|
||||
|
||||
signal.Notify(term, os.Interrupt)
|
||||
signal.Notify(term, os.Kill)
|
||||
signal.Notify(term, windows.SIGTERM)
|
||||
|
||||
select {
|
||||
case <-term:
|
||||
case <-errs:
|
||||
case <-device.Wait():
|
||||
}
|
||||
|
||||
// clean up
|
||||
|
||||
uapi.Close()
|
||||
device.Close()
|
||||
|
||||
logger.Verbosef("Shutting down")
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import fileinput
|
||||
|
||||
|
||||
PKG_ORIGINAL = "github.com/tailscale/wireguard-go"
|
||||
PKG_NEW = "github.com/sagernet/wireguard-go"
|
||||
|
||||
EXTENSIONS = [".go", ".md", ".mod", ".sh"]
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-r", "--reverse", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def replace_line(line):
|
||||
if args.reverse:
|
||||
return line.replace(PKG_NEW, PKG_ORIGINAL)
|
||||
return line.replace(PKG_ORIGINAL, PKG_NEW)
|
||||
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk("."):
|
||||
# Skip hidden directories like .git
|
||||
dirnames[:] = [d for d in dirnames if not d[0] == "."]
|
||||
filenames = [f for f in filenames if os.path.splitext(f)[1] in EXTENSIONS]
|
||||
for filename in filenames:
|
||||
file_path = os.path.join(dirpath, filename)
|
||||
with fileinput.FileInput(file_path, inplace=True) as file:
|
||||
for line in file:
|
||||
print(replace_line(line), end="")
|
||||
77
outline/dialer.go
Normal file
77
outline/dialer.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package outline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/Jigsaw-Code/outline-sdk/transport"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
)
|
||||
|
||||
type DialerOptions struct {
|
||||
Ipc string
|
||||
Prefixes []netip.Prefix
|
||||
Mtu int
|
||||
Dns []netip.Addr
|
||||
}
|
||||
|
||||
func NewStreamDialer(opts DialerOptions) (*StreamDialer, error) {
|
||||
var localAddresses []netip.Addr
|
||||
for _, prefix := range opts.Prefixes {
|
||||
localAddresses = append(localAddresses, prefix.Addr())
|
||||
}
|
||||
|
||||
tun, tnet, err := netstack.CreateNetTUN(localAddresses, opts.Dns, opts.Mtu)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create network tun: %v", err)
|
||||
}
|
||||
|
||||
awgLogger := device.Logger{
|
||||
Verbosef: func(format string, args ...any) {
|
||||
},
|
||||
Errorf: func(format string, args ...any) {
|
||||
},
|
||||
}
|
||||
|
||||
dev := device.NewDevice(tun, conn.NewDefaultBind(), &awgLogger)
|
||||
if err := dev.IpcSet(opts.Ipc); err != nil {
|
||||
return nil, fmt.Errorf("failed to configure device: %v", err)
|
||||
}
|
||||
|
||||
if err := dev.Up(); err != nil {
|
||||
return nil, fmt.Errorf("failed to start awg device: %v", err)
|
||||
}
|
||||
|
||||
return &StreamDialer{
|
||||
tnet: tnet,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ transport.StreamDialer = (*StreamDialer)(nil)
|
||||
|
||||
type StreamDialer struct {
|
||||
tnet *netstack.Net
|
||||
}
|
||||
|
||||
func (d *StreamDialer) DialStream(ctx context.Context, raddr string) (transport.StreamConn, error) {
|
||||
host, port, err := net.SplitHostPort(raddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse raddr: %v", err)
|
||||
}
|
||||
if l := len(host); l > 0 && host[l-1] == '.' {
|
||||
host = host[:l-1]
|
||||
raddr = net.JoinHostPort(host, port)
|
||||
}
|
||||
|
||||
conn, err := d.tnet.DialContext(ctx, "tcp", raddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn.(*gonet.TCPConn), nil
|
||||
}
|
||||
224
outline/fallback.go
Normal file
224
outline/fallback.go
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
package outline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Jigsaw-Code/outline-sdk/transport"
|
||||
"github.com/Jigsaw-Code/outline-sdk/x/mobileproxy"
|
||||
"github.com/Jigsaw-Code/outline-sdk/x/smart"
|
||||
"github.com/goccy/go-yaml"
|
||||
)
|
||||
|
||||
type DeviceConfig struct {
|
||||
PrivateKey string `yaml:"private_key"`
|
||||
Address []string `yaml:"address"`
|
||||
Dns []string `yaml:"dns"`
|
||||
Mtu int `yaml:"mtu,omitempty"`
|
||||
Jc int `yaml:"jc,omitempty"`
|
||||
Jmin int `yaml:"jmin,omitempty"`
|
||||
Jmax int `yaml:"jmax,omitempty"`
|
||||
S1 int `yaml:"s1,omitempty"`
|
||||
S2 int `yaml:"s2,omitempty"`
|
||||
S3 int `yaml:"s3,omitempty"`
|
||||
S4 int `yaml:"s4,omitempty"`
|
||||
H1 string `yaml:"h1,omitempty"`
|
||||
H2 string `yaml:"h2,omitempty"`
|
||||
H3 string `yaml:"h3,omitempty"`
|
||||
H4 string `yaml:"h4,omitempty"`
|
||||
I1 string `yaml:"i1,omitempty"`
|
||||
I2 string `yaml:"i2,omitempty"`
|
||||
I3 string `yaml:"i3,omitempty"`
|
||||
I4 string `yaml:"i4,omitempty"`
|
||||
I5 string `yaml:"i5,omitempty"`
|
||||
Peers []PeerConfig `yaml:"peers,omitempty"`
|
||||
}
|
||||
|
||||
type PeerConfig struct {
|
||||
PublicKey string `yaml:"public_key"`
|
||||
PresharedKey string `yaml:"preshared_key,omitempty"`
|
||||
Endpoint string `yaml:"endpoint"`
|
||||
AllowedIPs []string `yaml:"allowed_ips"`
|
||||
PersistentKeepaliveInterval uint16 `yaml:"persistent_keepalive_interval,omitempty"`
|
||||
}
|
||||
|
||||
func mapYamlToConfig(y smart.YAMLNode) (*DeviceConfig, error) {
|
||||
bytes, err := yaml.Marshal(y)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal yaml: %v", err)
|
||||
}
|
||||
|
||||
var cfg DeviceConfig
|
||||
if err = yaml.Unmarshal(bytes, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal yaml: %v", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func genIpcString(cfg *DeviceConfig) (string, error) {
|
||||
privateKeyBytes, err := base64.StdEncoding.DecodeString(cfg.PrivateKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode private key: %v", err)
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString("private_key=")
|
||||
b.WriteString(hex.EncodeToString(privateKeyBytes))
|
||||
|
||||
if cfg.Jc != 0 {
|
||||
b.WriteString("\njc=")
|
||||
b.WriteString(strconv.Itoa(cfg.Jc))
|
||||
}
|
||||
if cfg.Jmin != 0 {
|
||||
b.WriteString("\njmin=")
|
||||
b.WriteString(strconv.Itoa(cfg.Jmin))
|
||||
}
|
||||
if cfg.Jmax != 0 {
|
||||
b.WriteString("\njmax=")
|
||||
b.WriteString(strconv.Itoa(cfg.Jmax))
|
||||
}
|
||||
if cfg.S1 != 0 {
|
||||
b.WriteString("\ns1=")
|
||||
b.WriteString(strconv.Itoa(cfg.S1))
|
||||
}
|
||||
if cfg.S2 != 0 {
|
||||
b.WriteString("\ns2=")
|
||||
b.WriteString(strconv.Itoa(cfg.S2))
|
||||
}
|
||||
if cfg.S3 != 0 {
|
||||
b.WriteString("\ns3=")
|
||||
b.WriteString(strconv.Itoa(cfg.S3))
|
||||
}
|
||||
if cfg.S4 != 0 {
|
||||
b.WriteString("\ns4=")
|
||||
b.WriteString(strconv.Itoa(cfg.S4))
|
||||
}
|
||||
if cfg.H1 != "" {
|
||||
b.WriteString("\nh1=")
|
||||
b.WriteString(cfg.H1)
|
||||
}
|
||||
if cfg.H2 != "" {
|
||||
b.WriteString("\nh2=")
|
||||
b.WriteString(cfg.H2)
|
||||
}
|
||||
if cfg.H3 != "" {
|
||||
b.WriteString("\nh3=")
|
||||
b.WriteString(cfg.H3)
|
||||
}
|
||||
if cfg.H4 != "" {
|
||||
b.WriteString("\nh4=")
|
||||
b.WriteString(cfg.H4)
|
||||
}
|
||||
if cfg.I1 != "" {
|
||||
b.WriteString("\ni1=")
|
||||
b.WriteString(cfg.I1)
|
||||
}
|
||||
if cfg.I2 != "" {
|
||||
b.WriteString("\ni2=")
|
||||
b.WriteString(cfg.I2)
|
||||
}
|
||||
if cfg.I3 != "" {
|
||||
b.WriteString("\ni3=")
|
||||
b.WriteString(cfg.I3)
|
||||
}
|
||||
if cfg.I4 != "" {
|
||||
b.WriteString("\ni4=")
|
||||
b.WriteString(cfg.I4)
|
||||
}
|
||||
if cfg.I5 != "" {
|
||||
b.WriteString("\ni5=")
|
||||
b.WriteString(cfg.I5)
|
||||
}
|
||||
|
||||
for _, peer := range cfg.Peers {
|
||||
publicKeyBytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode public key: %v", err)
|
||||
}
|
||||
|
||||
b.WriteString("\npublic_key=")
|
||||
b.WriteString(hex.EncodeToString(publicKeyBytes))
|
||||
|
||||
b.WriteString("\nendpoint=")
|
||||
b.WriteString(peer.Endpoint)
|
||||
|
||||
for _, allowedIp := range peer.AllowedIPs {
|
||||
b.WriteString("\nallowed_ip=")
|
||||
b.WriteString(allowedIp)
|
||||
}
|
||||
|
||||
if peer.PresharedKey != "" {
|
||||
presharedKeyBytes, err := base64.StdEncoding.DecodeString(peer.PresharedKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode preshared key: %v", err)
|
||||
}
|
||||
|
||||
b.WriteString("\npreshared_key=")
|
||||
b.WriteString(hex.EncodeToString(presharedKeyBytes))
|
||||
}
|
||||
|
||||
if peer.PersistentKeepaliveInterval != 0 {
|
||||
b.WriteString("\npersistent_keepalive_interval=")
|
||||
b.WriteString(strconv.Itoa(int(peer.PersistentKeepaliveInterval)))
|
||||
}
|
||||
}
|
||||
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func FallbackParser(ctx context.Context, y smart.YAMLNode) (transport.StreamDialer, string, error) {
|
||||
cfg, err := mapYamlToConfig(y)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to map yaml to config: %v", err)
|
||||
}
|
||||
|
||||
ipc, err := genIpcString(cfg)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("faield to generate ipc config: %v", err)
|
||||
}
|
||||
|
||||
var prefixes []netip.Prefix
|
||||
for _, address := range cfg.Address {
|
||||
prefix, err := netip.ParsePrefix(address)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to parse address: %v", err)
|
||||
}
|
||||
prefixes = append(prefixes, prefix)
|
||||
}
|
||||
|
||||
var dns []netip.Addr
|
||||
for _, saddr := range cfg.Dns {
|
||||
addr, err := netip.ParseAddr(saddr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to parse dns: %v", err)
|
||||
}
|
||||
dns = append(dns, addr)
|
||||
}
|
||||
|
||||
if cfg.Mtu == 0 {
|
||||
cfg.Mtu = 1408
|
||||
}
|
||||
|
||||
dialer, err := NewStreamDialer(DialerOptions{
|
||||
Ipc: ipc,
|
||||
Prefixes: prefixes,
|
||||
Mtu: cfg.Mtu,
|
||||
Dns: dns,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to create dialer: %v", err)
|
||||
}
|
||||
|
||||
return dialer, ipc, nil
|
||||
}
|
||||
|
||||
func RegisterFallbackParser(opt *mobileproxy.SmartDialerOptions, name string) {
|
||||
opt.RegisterFallbackParser(name, FallbackParser)
|
||||
}
|
||||
52
outline/fallback_test.go
Normal file
52
outline/fallback_test.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package outline_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Jigsaw-Code/outline-sdk/x/mobileproxy"
|
||||
awg "github.com/amnezia-vpn/amneziawg-go/outline"
|
||||
)
|
||||
|
||||
const cfg = `
|
||||
dns:
|
||||
- {system: {}}
|
||||
tls:
|
||||
- ""
|
||||
fallback:
|
||||
- awg:
|
||||
address: [10.0.0.0/32]
|
||||
dns: [8.8.8.8, 8.8.4.4]
|
||||
private_key: +CdqlYvjqZ3OUr4mLWvGJo1h67CWpQwMIxA5OpyiJUM=
|
||||
jc: 4
|
||||
jmin: 50
|
||||
jmax: 100
|
||||
s1: 87
|
||||
s2: 65
|
||||
s3: 43
|
||||
s4: 21
|
||||
h1: 1000000000-1000000001
|
||||
h2: 2000000000-2000000002
|
||||
h3: 3000000000-3000000003
|
||||
h4: 4000000000-4000000004
|
||||
peers:
|
||||
- public_key: EGxNYihRLKQ9nvdOE5j5aZ7rtw3ttzJS1xxaJpgYYHI=
|
||||
preshared_key: 2OiSh6rP3t/g39jgJNGK70B+nize821yIFNtUqi8/XU=
|
||||
endpoint: 123.123.123.123:51820
|
||||
allowed_ips: [0.0.0.0/0, ::/0]
|
||||
persistent_keepalive_interval: 25
|
||||
`
|
||||
|
||||
var testDomains = mobileproxy.NewListFromLines("example.com")
|
||||
|
||||
func Test_outlineIntegration(t *testing.T) {
|
||||
opts := mobileproxy.NewSmartDialerOptions(testDomains, cfg)
|
||||
opts.SetLogWriter(mobileproxy.NewStderrLogWriter())
|
||||
awg.RegisterFallbackParser(opts, "awg")
|
||||
dialer, err := opts.NewStreamDialer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = mobileproxy.RunProxy("", dialer); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
119
ratelimiter/ratelimiter_test.go
Normal file
119
ratelimiter/ratelimiter_test.go
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ratelimiter
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type result struct {
|
||||
allowed bool
|
||||
text string
|
||||
wait time.Duration
|
||||
}
|
||||
|
||||
func TestRatelimiter(t *testing.T) {
|
||||
var rate Ratelimiter
|
||||
var expectedResults []result
|
||||
|
||||
nano := func(nano int64) time.Duration {
|
||||
return time.Nanosecond * time.Duration(nano)
|
||||
}
|
||||
|
||||
add := func(res result) {
|
||||
expectedResults = append(
|
||||
expectedResults,
|
||||
res,
|
||||
)
|
||||
}
|
||||
|
||||
for i := 0; i < packetsBurstable; i++ {
|
||||
add(result{
|
||||
allowed: true,
|
||||
text: "initial burst",
|
||||
})
|
||||
}
|
||||
|
||||
add(result{
|
||||
allowed: false,
|
||||
text: "after burst",
|
||||
})
|
||||
|
||||
add(result{
|
||||
allowed: true,
|
||||
wait: nano(time.Second.Nanoseconds() / packetsPerSecond),
|
||||
text: "filling tokens for single packet",
|
||||
})
|
||||
|
||||
add(result{
|
||||
allowed: false,
|
||||
text: "not having refilled enough",
|
||||
})
|
||||
|
||||
add(result{
|
||||
allowed: true,
|
||||
wait: 2 * (nano(time.Second.Nanoseconds() / packetsPerSecond)),
|
||||
text: "filling tokens for two packet burst",
|
||||
})
|
||||
|
||||
add(result{
|
||||
allowed: true,
|
||||
text: "second packet in 2 packet burst",
|
||||
})
|
||||
|
||||
add(result{
|
||||
allowed: false,
|
||||
text: "packet following 2 packet burst",
|
||||
})
|
||||
|
||||
ips := []netip.Addr{
|
||||
netip.MustParseAddr("127.0.0.1"),
|
||||
netip.MustParseAddr("192.168.1.1"),
|
||||
netip.MustParseAddr("172.167.2.3"),
|
||||
netip.MustParseAddr("97.231.252.215"),
|
||||
netip.MustParseAddr("248.97.91.167"),
|
||||
netip.MustParseAddr("188.208.233.47"),
|
||||
netip.MustParseAddr("104.2.183.179"),
|
||||
netip.MustParseAddr("72.129.46.120"),
|
||||
netip.MustParseAddr("2001:0db8:0a0b:12f0:0000:0000:0000:0001"),
|
||||
netip.MustParseAddr("f5c2:818f:c052:655a:9860:b136:6894:25f0"),
|
||||
netip.MustParseAddr("b2d7:15ab:48a7:b07c:a541:f144:a9fe:54fc"),
|
||||
netip.MustParseAddr("a47b:786e:1671:a22b:d6f9:4ab0:abc7:c918"),
|
||||
netip.MustParseAddr("ea1e:d155:7f7a:98fb:2bf5:9483:80f6:5445"),
|
||||
netip.MustParseAddr("3f0e:54a2:f5b4:cd19:a21d:58e1:3746:84c4"),
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
rate.timeNow = func() time.Time {
|
||||
return now
|
||||
}
|
||||
defer func() {
|
||||
// Lock to avoid data race with cleanup goroutine from Init.
|
||||
rate.mu.Lock()
|
||||
defer rate.mu.Unlock()
|
||||
|
||||
rate.timeNow = time.Now
|
||||
}()
|
||||
timeSleep := func(d time.Duration) {
|
||||
now = now.Add(d + 1)
|
||||
rate.cleanup()
|
||||
}
|
||||
|
||||
rate.Init()
|
||||
defer rate.Close()
|
||||
|
||||
for i, res := range expectedResults {
|
||||
timeSleep(res.wait)
|
||||
for _, ip := range ips {
|
||||
allowed := rate.Allow(ip)
|
||||
if allowed != res.allowed {
|
||||
t.Fatalf("%d: %s: rate.Allow(%q)=%v, want %v", i, res.text, ip, allowed, res.allowed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
function remove_unused() {
|
||||
git rm -rf --ignore-unmatch \
|
||||
.github \
|
||||
tests \
|
||||
*_test.go \
|
||||
**/*_test.go \
|
||||
conn/bindtest \
|
||||
tun/netstack \
|
||||
tun/tuntest \
|
||||
tun/testdata \
|
||||
main*.go \
|
||||
*.md
|
||||
}
|
||||
|
||||
remove_unused
|
||||
remove_unused
|
||||
|
||||
go mod tidy
|
||||
git commit -a -m "Remove unused"
|
||||
119
replay/replay_test.go
Normal file
119
replay/replay_test.go
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package replay
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
/* Ported from the linux kernel implementation
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const RejectAfterMessages = 1<<64 - 1<<13 - 1
|
||||
|
||||
func TestReplay(t *testing.T) {
|
||||
var filter Filter
|
||||
|
||||
const T_LIM = windowSize + 1
|
||||
|
||||
testNumber := 0
|
||||
T := func(n uint64, expected bool) {
|
||||
testNumber++
|
||||
if filter.ValidateCounter(n, RejectAfterMessages) != expected {
|
||||
t.Fatal("Test", testNumber, "failed", n, expected)
|
||||
}
|
||||
}
|
||||
|
||||
filter.Reset()
|
||||
|
||||
T(0, true) /* 1 */
|
||||
T(1, true) /* 2 */
|
||||
T(1, false) /* 3 */
|
||||
T(9, true) /* 4 */
|
||||
T(8, true) /* 5 */
|
||||
T(7, true) /* 6 */
|
||||
T(7, false) /* 7 */
|
||||
T(T_LIM, true) /* 8 */
|
||||
T(T_LIM-1, true) /* 9 */
|
||||
T(T_LIM-1, false) /* 10 */
|
||||
T(T_LIM-2, true) /* 11 */
|
||||
T(2, true) /* 12 */
|
||||
T(2, false) /* 13 */
|
||||
T(T_LIM+16, true) /* 14 */
|
||||
T(3, false) /* 15 */
|
||||
T(T_LIM+16, false) /* 16 */
|
||||
T(T_LIM*4, true) /* 17 */
|
||||
T(T_LIM*4-(T_LIM-1), true) /* 18 */
|
||||
T(10, false) /* 19 */
|
||||
T(T_LIM*4-T_LIM, false) /* 20 */
|
||||
T(T_LIM*4-(T_LIM+1), false) /* 21 */
|
||||
T(T_LIM*4-(T_LIM-2), true) /* 22 */
|
||||
T(T_LIM*4+1-T_LIM, false) /* 23 */
|
||||
T(0, false) /* 24 */
|
||||
T(RejectAfterMessages, false) /* 25 */
|
||||
T(RejectAfterMessages-1, true) /* 26 */
|
||||
T(RejectAfterMessages, false) /* 27 */
|
||||
T(RejectAfterMessages-1, false) /* 28 */
|
||||
T(RejectAfterMessages-2, true) /* 29 */
|
||||
T(RejectAfterMessages+1, false) /* 30 */
|
||||
T(RejectAfterMessages+2, false) /* 31 */
|
||||
T(RejectAfterMessages-2, false) /* 32 */
|
||||
T(RejectAfterMessages-3, true) /* 33 */
|
||||
T(0, false) /* 34 */
|
||||
|
||||
t.Log("Bulk test 1")
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := uint64(1); i <= windowSize; i++ {
|
||||
T(i, true)
|
||||
}
|
||||
T(0, true)
|
||||
T(0, false)
|
||||
|
||||
t.Log("Bulk test 2")
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := uint64(2); i <= windowSize+1; i++ {
|
||||
T(i, true)
|
||||
}
|
||||
T(1, true)
|
||||
T(0, false)
|
||||
|
||||
t.Log("Bulk test 3")
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := uint64(windowSize + 1); i > 0; i-- {
|
||||
T(i, true)
|
||||
}
|
||||
|
||||
t.Log("Bulk test 4")
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := uint64(windowSize + 2); i > 1; i-- {
|
||||
T(i, true)
|
||||
}
|
||||
T(0, false)
|
||||
|
||||
t.Log("Bulk test 5")
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := uint64(windowSize); i > 0; i-- {
|
||||
T(i, true)
|
||||
}
|
||||
T(windowSize+1, true)
|
||||
T(0, false)
|
||||
|
||||
t.Log("Bulk test 6")
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := uint64(windowSize); i > 0; i-- {
|
||||
T(i, true)
|
||||
}
|
||||
T(0, true)
|
||||
T(windowSize+1, true)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
//go:build !windows && !wasm && !plan9 && !tamago
|
||||
//go:build !windows && !wasm
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
//go:build windows || wasm || plan9 || tamago
|
||||
//go:build windows || wasm
|
||||
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
|
|
|
|||
40
tai64n/tai64n_test.go
Normal file
40
tai64n/tai64n_test.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tai64n
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Test that timestamps are monotonic as required by Wireguard and that
|
||||
// nanosecond-level information is whitened to prevent side channel attacks.
|
||||
func TestMonotonic(t *testing.T) {
|
||||
startTime := time.Unix(0, 123456789) // a nontrivial bit pattern
|
||||
// Whitening should reduce timestamp granularity
|
||||
// to more than 10 but fewer than 20 milliseconds.
|
||||
tests := []struct {
|
||||
name string
|
||||
t1, t2 time.Time
|
||||
wantAfter bool
|
||||
}{
|
||||
{"after_10_ns", startTime, startTime.Add(10 * time.Nanosecond), false},
|
||||
{"after_10_us", startTime, startTime.Add(10 * time.Microsecond), false},
|
||||
{"after_1_ms", startTime, startTime.Add(time.Millisecond), false},
|
||||
{"after_10_ms", startTime, startTime.Add(10 * time.Millisecond), false},
|
||||
{"after_20_ms", startTime, startTime.Add(20 * time.Millisecond), true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts1, ts2 := stamp(tt.t1), stamp(tt.t2)
|
||||
got := ts2.After(ts1)
|
||||
if got != tt.wantAfter {
|
||||
t.Errorf("after = %v; want %v", got, tt.wantAfter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
425
tests/netns.sh
Executable file
425
tests/netns.sh
Executable file
|
|
@ -0,0 +1,425 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Copyright (C) 2015-2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
|
||||
# This script tests the below topology:
|
||||
#
|
||||
# ┌─────────────────────┐ ┌──────────────────────────────────┐ ┌─────────────────────┐
|
||||
# │ $ns1 namespace │ │ $ns0 namespace │ │ $ns2 namespace │
|
||||
# │ │ │ │ │ │
|
||||
# │┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐│
|
||||
# ││ wg1 │───────────┼───┼────────────│ lo │────────────┼───┼───────────│ wg2 ││
|
||||
# │├────────┴──────────┐│ │ ┌───────┴────────┴────────┐ │ │┌──────────┴────────┤│
|
||||
# ││192.168.241.1/24 ││ │ │(ns1) (ns2) │ │ ││192.168.241.2/24 ││
|
||||
# ││fd00::1/24 ││ │ │127.0.0.1:1 127.0.0.1:2│ │ ││fd00::2/24 ││
|
||||
# │└───────────────────┘│ │ │[::]:1 [::]:2 │ │ │└───────────────────┘│
|
||||
# └─────────────────────┘ │ └─────────────────────────┘ │ └─────────────────────┘
|
||||
# └──────────────────────────────────┘
|
||||
#
|
||||
# After the topology is prepared we run a series of TCP/UDP iperf3 tests between the
|
||||
# wireguard peers in $ns1 and $ns2. Note that $ns0 is the endpoint for the wg1
|
||||
# interfaces in $ns1 and $ns2. See https://www.wireguard.com/netns/ for further
|
||||
# details on how this is accomplished.
|
||||
|
||||
# This code is ported to the WireGuard-Go directly from the kernel project.
|
||||
#
|
||||
# Please ensure that you have installed the newest version of the WireGuard
|
||||
# tools from the WireGuard project and before running these tests as:
|
||||
#
|
||||
# ./netns.sh <path to wireguard-go>
|
||||
|
||||
set -e
|
||||
|
||||
exec 3>&1
|
||||
export WG_HIDE_KEYS=never
|
||||
netns0="wg-test-$$-0"
|
||||
netns1="wg-test-$$-1"
|
||||
netns2="wg-test-$$-2"
|
||||
program=$1
|
||||
export LOG_LEVEL="verbose"
|
||||
|
||||
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
|
||||
pp() { pretty "" "$*"; "$@"; }
|
||||
maybe_exec() { if [[ $BASHPID -eq $$ ]]; then "$@"; else exec "$@"; fi; }
|
||||
n0() { pretty 0 "$*"; maybe_exec ip netns exec $netns0 "$@"; }
|
||||
n1() { pretty 1 "$*"; maybe_exec ip netns exec $netns1 "$@"; }
|
||||
n2() { pretty 2 "$*"; maybe_exec ip netns exec $netns2 "$@"; }
|
||||
ip0() { pretty 0 "ip $*"; ip -n $netns0 "$@"; }
|
||||
ip1() { pretty 1 "ip $*"; ip -n $netns1 "$@"; }
|
||||
ip2() { pretty 2 "ip $*"; ip -n $netns2 "$@"; }
|
||||
sleep() { read -t "$1" -N 0 || true; }
|
||||
waitiperf() { pretty "${1//*-}" "wait for iperf:5201"; while [[ $(ss -N "$1" -tlp 'sport = 5201') != *iperf3* ]]; do sleep 0.1; done; }
|
||||
waitncatudp() { pretty "${1//*-}" "wait for udp:1111"; while [[ $(ss -N "$1" -ulp 'sport = 1111') != *ncat* ]]; do sleep 0.1; done; }
|
||||
waitiface() { pretty "${1//*-}" "wait for $2 to come up"; ip netns exec "$1" bash -c "while [[ \$(< \"/sys/class/net/$2/operstate\") != up ]]; do read -t .1 -N 0 || true; done;"; }
|
||||
|
||||
cleanup() {
|
||||
set +e
|
||||
exec 2>/dev/null
|
||||
printf "$orig_message_cost" > /proc/sys/net/core/message_cost
|
||||
ip0 link del dev wg1
|
||||
ip1 link del dev wg1
|
||||
ip2 link del dev wg1
|
||||
local to_kill="$(ip netns pids $netns0) $(ip netns pids $netns1) $(ip netns pids $netns2)"
|
||||
[[ -n $to_kill ]] && kill $to_kill
|
||||
pp ip netns del $netns1
|
||||
pp ip netns del $netns2
|
||||
pp ip netns del $netns0
|
||||
exit
|
||||
}
|
||||
|
||||
orig_message_cost="$(< /proc/sys/net/core/message_cost)"
|
||||
trap cleanup EXIT
|
||||
printf 0 > /proc/sys/net/core/message_cost
|
||||
|
||||
ip netns del $netns0 2>/dev/null || true
|
||||
ip netns del $netns1 2>/dev/null || true
|
||||
ip netns del $netns2 2>/dev/null || true
|
||||
pp ip netns add $netns0
|
||||
pp ip netns add $netns1
|
||||
pp ip netns add $netns2
|
||||
ip0 link set up dev lo
|
||||
|
||||
# ip0 link add dev wg1 type wireguard
|
||||
n0 $program wg1
|
||||
ip0 link set wg1 netns $netns1
|
||||
|
||||
# ip0 link add dev wg1 type wireguard
|
||||
n0 $program wg2
|
||||
ip0 link set wg2 netns $netns2
|
||||
|
||||
key1="$(pp wg genkey)"
|
||||
key2="$(pp wg genkey)"
|
||||
pub1="$(pp wg pubkey <<<"$key1")"
|
||||
pub2="$(pp wg pubkey <<<"$key2")"
|
||||
psk="$(pp wg genpsk)"
|
||||
[[ -n $key1 && -n $key2 && -n $psk ]]
|
||||
|
||||
configure_peers() {
|
||||
|
||||
ip1 addr add 192.168.241.1/24 dev wg1
|
||||
ip1 addr add fd00::1/24 dev wg1
|
||||
|
||||
ip2 addr add 192.168.241.2/24 dev wg2
|
||||
ip2 addr add fd00::2/24 dev wg2
|
||||
|
||||
n0 wg set wg1 \
|
||||
private-key <(echo "$key1") \
|
||||
listen-port 10000 \
|
||||
peer "$pub2" \
|
||||
preshared-key <(echo "$psk") \
|
||||
allowed-ips 192.168.241.2/32,fd00::2/128
|
||||
n0 wg set wg2 \
|
||||
private-key <(echo "$key2") \
|
||||
listen-port 20000 \
|
||||
peer "$pub1" \
|
||||
preshared-key <(echo "$psk") \
|
||||
allowed-ips 192.168.241.1/32,fd00::1/128
|
||||
|
||||
n0 wg showconf wg1
|
||||
n0 wg showconf wg2
|
||||
|
||||
ip1 link set up dev wg1
|
||||
ip2 link set up dev wg2
|
||||
sleep 1
|
||||
}
|
||||
configure_peers
|
||||
|
||||
tests() {
|
||||
# Ping over IPv4
|
||||
n2 ping -c 10 -f -W 1 192.168.241.1
|
||||
n1 ping -c 10 -f -W 1 192.168.241.2
|
||||
|
||||
# Ping over IPv6
|
||||
n2 ping6 -c 10 -f -W 1 fd00::1
|
||||
n1 ping6 -c 10 -f -W 1 fd00::2
|
||||
|
||||
# TCP over IPv4
|
||||
n2 iperf3 -s -1 -B 192.168.241.2 &
|
||||
waitiperf $netns2
|
||||
n1 iperf3 -Z -n 1G -c 192.168.241.2
|
||||
|
||||
# TCP over IPv6
|
||||
n1 iperf3 -s -1 -B fd00::1 &
|
||||
waitiperf $netns1
|
||||
n2 iperf3 -Z -n 1G -c fd00::1
|
||||
|
||||
# UDP over IPv4
|
||||
n1 iperf3 -s -1 -B 192.168.241.1 &
|
||||
waitiperf $netns1
|
||||
n2 iperf3 -Z -n 1G -b 0 -u -c 192.168.241.1
|
||||
|
||||
# UDP over IPv6
|
||||
n2 iperf3 -s -1 -B fd00::2 &
|
||||
waitiperf $netns2
|
||||
n1 iperf3 -Z -n 1G -b 0 -u -c fd00::2
|
||||
}
|
||||
|
||||
[[ $(ip1 link show dev wg1) =~ mtu\ ([0-9]+) ]] && orig_mtu="${BASH_REMATCH[1]}"
|
||||
big_mtu=$(( 34816 - 1500 + $orig_mtu ))
|
||||
|
||||
# Test using IPv4 as outer transport
|
||||
n0 wg set wg1 peer "$pub2" endpoint 127.0.0.1:20000
|
||||
n0 wg set wg2 peer "$pub1" endpoint 127.0.0.1:10000
|
||||
|
||||
# Before calling tests, we first make sure that the stats counters are working
|
||||
n2 ping -c 10 -f -W 1 192.168.241.1
|
||||
{ read _; read _; read _; read rx_bytes _; read _; read tx_bytes _; } < <(ip2 -stats link show dev wg2)
|
||||
ip2 -stats link show dev wg2
|
||||
n0 wg show
|
||||
[[ $rx_bytes -ge 840 && $tx_bytes -ge 880 && $rx_bytes -lt 2500 && $rx_bytes -lt 2500 ]]
|
||||
echo "counters working"
|
||||
tests
|
||||
ip1 link set wg1 mtu $big_mtu
|
||||
ip2 link set wg2 mtu $big_mtu
|
||||
tests
|
||||
|
||||
ip1 link set wg1 mtu $orig_mtu
|
||||
ip2 link set wg2 mtu $orig_mtu
|
||||
|
||||
# Test using IPv6 as outer transport
|
||||
n0 wg set wg1 peer "$pub2" endpoint [::1]:20000
|
||||
n0 wg set wg2 peer "$pub1" endpoint [::1]:10000
|
||||
tests
|
||||
ip1 link set wg1 mtu $big_mtu
|
||||
ip2 link set wg2 mtu $big_mtu
|
||||
tests
|
||||
|
||||
ip1 link set wg1 mtu $orig_mtu
|
||||
ip2 link set wg2 mtu $orig_mtu
|
||||
|
||||
# Test using IPv4 that roaming works
|
||||
ip0 -4 addr del 127.0.0.1/8 dev lo
|
||||
ip0 -4 addr add 127.212.121.99/8 dev lo
|
||||
n0 wg set wg1 listen-port 9999
|
||||
n0 wg set wg1 peer "$pub2" endpoint 127.0.0.1:20000
|
||||
n1 ping6 -W 1 -c 1 fd00::2
|
||||
[[ $(n2 wg show wg2 endpoints) == "$pub1 127.212.121.99:9999" ]]
|
||||
|
||||
# Test using IPv6 that roaming works
|
||||
n1 wg set wg1 listen-port 9998
|
||||
n1 wg set wg1 peer "$pub2" endpoint [::1]:20000
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
[[ $(n2 wg show wg2 endpoints) == "$pub1 [::1]:9998" ]]
|
||||
|
||||
# Test that crypto-RP filter works
|
||||
n1 wg set wg1 peer "$pub2" allowed-ips 192.168.241.0/24
|
||||
exec 4< <(n1 ncat -l -u -p 1111)
|
||||
nmap_pid=$!
|
||||
waitncatudp $netns1
|
||||
n2 ncat -u 192.168.241.1 1111 <<<"X"
|
||||
read -r -N 1 -t 1 out <&4 && [[ $out == "X" ]]
|
||||
kill $nmap_pid
|
||||
more_specific_key="$(pp wg genkey | pp wg pubkey)"
|
||||
n0 wg set wg1 peer "$more_specific_key" allowed-ips 192.168.241.2/32
|
||||
n0 wg set wg2 listen-port 9997
|
||||
exec 4< <(n1 ncat -l -u -p 1111)
|
||||
nmap_pid=$!
|
||||
waitncatudp $netns1
|
||||
n2 ncat -u 192.168.241.1 1111 <<<"X"
|
||||
! read -r -N 1 -t 1 out <&4
|
||||
kill $nmap_pid
|
||||
n0 wg set wg1 peer "$more_specific_key" remove
|
||||
[[ $(n1 wg show wg1 endpoints) == "$pub2 [::1]:9997" ]]
|
||||
|
||||
ip1 link del wg1
|
||||
ip2 link del wg2
|
||||
|
||||
# Test using NAT. We now change the topology to this:
|
||||
# ┌────────────────────────────────────────┐ ┌────────────────────────────────────────────────┐ ┌────────────────────────────────────────┐
|
||||
# │ $ns1 namespace │ │ $ns0 namespace │ │ $ns2 namespace │
|
||||
# │ │ │ │ │ │
|
||||
# │ ┌─────┐ ┌─────┐ │ │ ┌──────┐ ┌──────┐ │ │ ┌─────┐ ┌─────┐ │
|
||||
# │ │ wg1 │─────────────│vethc│───────────┼────┼────│vethrc│ │vethrs│──────────────┼─────┼──│veths│────────────│ wg2 │ │
|
||||
# │ ├─────┴──────────┐ ├─────┴──────────┐│ │ ├──────┴─────────┐ ├──────┴────────────┐ │ │ ├─────┴──────────┐ ├─────┴──────────┐ │
|
||||
# │ │192.168.241.1/24│ │192.168.1.100/24││ │ │192.168.1.100/24│ │10.0.0.1/24 │ │ │ │10.0.0.100/24 │ │192.168.241.2/24│ │
|
||||
# │ │fd00::1/24 │ │ ││ │ │ │ │SNAT:192.168.1.0/24│ │ │ │ │ │fd00::2/24 │ │
|
||||
# │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └───────────────────┘ │ │ └────────────────┘ └────────────────┘ │
|
||||
# └────────────────────────────────────────┘ └────────────────────────────────────────────────┘ └────────────────────────────────────────┘
|
||||
|
||||
# ip1 link add dev wg1 type wireguard
|
||||
# ip2 link add dev wg1 type wireguard
|
||||
|
||||
n1 $program wg1
|
||||
n2 $program wg2
|
||||
|
||||
configure_peers
|
||||
|
||||
ip0 link add vethrc type veth peer name vethc
|
||||
ip0 link add vethrs type veth peer name veths
|
||||
ip0 link set vethc netns $netns1
|
||||
ip0 link set veths netns $netns2
|
||||
ip0 link set vethrc up
|
||||
ip0 link set vethrs up
|
||||
ip0 addr add 192.168.1.1/24 dev vethrc
|
||||
ip0 addr add 10.0.0.1/24 dev vethrs
|
||||
ip1 addr add 192.168.1.100/24 dev vethc
|
||||
ip1 link set vethc up
|
||||
ip1 route add default via 192.168.1.1
|
||||
ip2 addr add 10.0.0.100/24 dev veths
|
||||
ip2 link set veths up
|
||||
waitiface $netns0 vethrc
|
||||
waitiface $netns0 vethrs
|
||||
waitiface $netns1 vethc
|
||||
waitiface $netns2 veths
|
||||
|
||||
n0 bash -c 'printf 1 > /proc/sys/net/ipv4/ip_forward'
|
||||
n0 bash -c 'printf 2 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout'
|
||||
n0 bash -c 'printf 2 > /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream'
|
||||
n0 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -d 10.0.0.0/24 -j SNAT --to 10.0.0.1
|
||||
|
||||
n0 wg set wg1 peer "$pub2" endpoint 10.0.0.100:20000 persistent-keepalive 1
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n2 wg show wg2 endpoints) == "$pub1 10.0.0.1:10000" ]]
|
||||
# Demonstrate n2 can still send packets to n1, since persistent-keepalive will prevent connection tracking entry from expiring (to see entries: `n0 conntrack -L`).
|
||||
pp sleep 3
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
|
||||
n0 iptables -t nat -F
|
||||
ip0 link del vethrc
|
||||
ip0 link del vethrs
|
||||
ip1 link del wg1
|
||||
ip2 link del wg2
|
||||
|
||||
# Test that saddr routing is sticky but not too sticky, changing to this topology:
|
||||
# ┌────────────────────────────────────────┐ ┌────────────────────────────────────────┐
|
||||
# │ $ns1 namespace │ │ $ns2 namespace │
|
||||
# │ │ │ │
|
||||
# │ ┌─────┐ ┌─────┐ │ │ ┌─────┐ ┌─────┐ │
|
||||
# │ │ wg1 │─────────────│veth1│───────────┼────┼──│veth2│────────────│ wg2 │ │
|
||||
# │ ├─────┴──────────┐ ├─────┴──────────┐│ │ ├─────┴──────────┐ ├─────┴──────────┐ │
|
||||
# │ │192.168.241.1/24│ │10.0.0.1/24 ││ │ │10.0.0.2/24 │ │192.168.241.2/24│ │
|
||||
# │ │fd00::1/24 │ │fd00:aa::1/96 ││ │ │fd00:aa::2/96 │ │fd00::2/24 │ │
|
||||
# │ └────────────────┘ └────────────────┘│ │ └────────────────┘ └────────────────┘ │
|
||||
# └────────────────────────────────────────┘ └────────────────────────────────────────┘
|
||||
|
||||
# ip1 link add dev wg1 type wireguard
|
||||
# ip2 link add dev wg1 type wireguard
|
||||
n1 $program wg1
|
||||
n2 $program wg2
|
||||
|
||||
configure_peers
|
||||
|
||||
ip1 link add veth1 type veth peer name veth2
|
||||
ip1 link set veth2 netns $netns2
|
||||
n1 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/veth1/accept_dad'
|
||||
n2 bash -c 'printf 0 > /proc/sys/net/ipv6/conf/veth2/accept_dad'
|
||||
n1 bash -c 'printf 1 > /proc/sys/net/ipv4/conf/veth1/promote_secondaries'
|
||||
|
||||
# First we check that we aren't overly sticky and can fall over to new IPs when old ones are removed
|
||||
ip1 addr add 10.0.0.1/24 dev veth1
|
||||
ip1 addr add fd00:aa::1/96 dev veth1
|
||||
ip2 addr add 10.0.0.2/24 dev veth2
|
||||
ip2 addr add fd00:aa::2/96 dev veth2
|
||||
ip1 link set veth1 up
|
||||
ip2 link set veth2 up
|
||||
waitiface $netns1 veth1
|
||||
waitiface $netns2 veth2
|
||||
n0 wg set wg1 peer "$pub2" endpoint 10.0.0.2:20000
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
ip1 addr add 10.0.0.10/24 dev veth1
|
||||
ip1 addr del 10.0.0.1/24 dev veth1
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
n0 wg set wg1 peer "$pub2" endpoint [fd00:aa::2]:20000
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
ip1 addr add fd00:aa::10/96 dev veth1
|
||||
ip1 addr del fd00:aa::1/96 dev veth1
|
||||
n1 ping -W 1 -c 1 192.168.241.2
|
||||
|
||||
# Now we show that we can successfully do reply to sender routing
|
||||
ip1 link set veth1 down
|
||||
ip2 link set veth2 down
|
||||
ip1 addr flush dev veth1
|
||||
ip2 addr flush dev veth2
|
||||
ip1 addr add 10.0.0.1/24 dev veth1
|
||||
ip1 addr add 10.0.0.2/24 dev veth1
|
||||
ip1 addr add fd00:aa::1/96 dev veth1
|
||||
ip1 addr add fd00:aa::2/96 dev veth1
|
||||
ip2 addr add 10.0.0.3/24 dev veth2
|
||||
ip2 addr add fd00:aa::3/96 dev veth2
|
||||
ip1 link set veth1 up
|
||||
ip2 link set veth2 up
|
||||
waitiface $netns1 veth1
|
||||
waitiface $netns2 veth2
|
||||
n0 wg set wg2 peer "$pub1" endpoint 10.0.0.1:10000
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n0 wg show wg2 endpoints) == "$pub1 10.0.0.1:10000" ]]
|
||||
n0 wg set wg2 peer "$pub1" endpoint [fd00:aa::1]:10000
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n0 wg show wg2 endpoints) == "$pub1 [fd00:aa::1]:10000" ]]
|
||||
n0 wg set wg2 peer "$pub1" endpoint 10.0.0.2:10000
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n0 wg show wg2 endpoints) == "$pub1 10.0.0.2:10000" ]]
|
||||
n0 wg set wg2 peer "$pub1" endpoint [fd00:aa::2]:10000
|
||||
n2 ping -W 1 -c 1 192.168.241.1
|
||||
[[ $(n0 wg show wg2 endpoints) == "$pub1 [fd00:aa::2]:10000" ]]
|
||||
|
||||
ip1 link del veth1
|
||||
ip1 link del wg1
|
||||
ip2 link del wg2
|
||||
|
||||
# Test that Netlink/IPC is working properly by doing things that usually cause split responses
|
||||
|
||||
n0 $program wg0
|
||||
sleep 5
|
||||
config=( "[Interface]" "PrivateKey=$(wg genkey)" "[Peer]" "PublicKey=$(wg genkey)" )
|
||||
for a in {1..255}; do
|
||||
for b in {0..255}; do
|
||||
config+=( "AllowedIPs=$a.$b.0.0/16,$a::$b/128" )
|
||||
done
|
||||
done
|
||||
n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
|
||||
i=0
|
||||
for ip in $(n0 wg show wg0 allowed-ips); do
|
||||
((++i))
|
||||
done
|
||||
((i == 255*256*2+1))
|
||||
ip0 link del wg0
|
||||
|
||||
n0 $program wg0
|
||||
config=( "[Interface]" "PrivateKey=$(wg genkey)" )
|
||||
for a in {1..40}; do
|
||||
config+=( "[Peer]" "PublicKey=$(wg genkey)" )
|
||||
for b in {1..52}; do
|
||||
config+=( "AllowedIPs=$a.$b.0.0/16" )
|
||||
done
|
||||
done
|
||||
n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
|
||||
i=0
|
||||
while read -r line; do
|
||||
j=0
|
||||
for ip in $line; do
|
||||
((++j))
|
||||
done
|
||||
((j == 53))
|
||||
((++i))
|
||||
done < <(n0 wg show wg0 allowed-ips)
|
||||
((i == 40))
|
||||
ip0 link del wg0
|
||||
|
||||
n0 $program wg0
|
||||
config=( )
|
||||
for i in {1..29}; do
|
||||
config+=( "[Peer]" "PublicKey=$(wg genkey)" )
|
||||
done
|
||||
config+=( "[Peer]" "PublicKey=$(wg genkey)" "AllowedIPs=255.2.3.4/32,abcd::255/128" )
|
||||
n0 wg setconf wg0 <(printf '%s\n' "${config[@]}")
|
||||
n0 wg showconf wg0 > /dev/null
|
||||
ip0 link del wg0
|
||||
|
||||
! n0 wg show doesnotexist || false
|
||||
|
||||
declare -A objects
|
||||
while read -t 0.1 -r line 2>/dev/null || [[ $? -ne 142 ]]; do
|
||||
[[ $line =~ .*(wg[0-9]+:\ [A-Z][a-z]+\ [0-9]+)\ .*(created|destroyed).* ]] || continue
|
||||
objects["${BASH_REMATCH[1]}"]+="${BASH_REMATCH[2]}"
|
||||
done < /dev/kmsg
|
||||
alldeleted=1
|
||||
for object in "${!objects[@]}"; do
|
||||
if [[ ${objects["$object"]} != *createddestroyed ]]; then
|
||||
echo "Error: $object: merely ${objects["$object"]}" >&3
|
||||
alldeleted=0
|
||||
fi
|
||||
done
|
||||
[[ $alldeleted -eq 1 ]]
|
||||
pretty "" "Objects that were created were also destroyed."
|
||||
67
tun/alignment_windows_test.go
Normal file
67
tun/alignment_windows_test.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func checkAlignment(t *testing.T, name string, offset uintptr) {
|
||||
t.Helper()
|
||||
if offset%8 != 0 {
|
||||
t.Errorf("offset of %q within struct is %d bytes, which does not align to 64-bit word boundaries (missing %d bytes). Atomic operations will crash on 32-bit systems.", name, offset, 8-(offset%8))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRateJugglerAlignment checks that atomically-accessed fields are
|
||||
// aligned to 64-bit boundaries, as required by the atomic package.
|
||||
//
|
||||
// Unfortunately, violating this rule on 32-bit platforms results in a
|
||||
// hard segfault at runtime.
|
||||
func TestRateJugglerAlignment(t *testing.T) {
|
||||
var r rateJuggler
|
||||
|
||||
typ := reflect.TypeOf(&r).Elem()
|
||||
t.Logf("Peer type size: %d, with fields:", typ.Size())
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
t.Logf("\t%30s\toffset=%3v\t(type size=%3d, align=%d)",
|
||||
field.Name,
|
||||
field.Offset,
|
||||
field.Type.Size(),
|
||||
field.Type.Align(),
|
||||
)
|
||||
}
|
||||
|
||||
checkAlignment(t, "rateJuggler.current", unsafe.Offsetof(r.current))
|
||||
checkAlignment(t, "rateJuggler.nextByteCount", unsafe.Offsetof(r.nextByteCount))
|
||||
checkAlignment(t, "rateJuggler.nextStartTime", unsafe.Offsetof(r.nextStartTime))
|
||||
}
|
||||
|
||||
// TestNativeTunAlignment checks that atomically-accessed fields are
|
||||
// aligned to 64-bit boundaries, as required by the atomic package.
|
||||
//
|
||||
// Unfortunately, violating this rule on 32-bit platforms results in a
|
||||
// hard segfault at runtime.
|
||||
func TestNativeTunAlignment(t *testing.T) {
|
||||
var tun NativeTun
|
||||
|
||||
typ := reflect.TypeOf(&tun).Elem()
|
||||
t.Logf("Peer type size: %d, with fields:", typ.Size())
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
t.Logf("\t%30s\toffset=%3v\t(type size=%3d, align=%d)",
|
||||
field.Name,
|
||||
field.Offset,
|
||||
field.Type.Size(),
|
||||
field.Type.Align(),
|
||||
)
|
||||
}
|
||||
|
||||
checkAlignment(t, "NativeTun.rate", unsafe.Offsetof(tun.rate))
|
||||
}
|
||||
740
tun/checksum.go
740
tun/checksum.go
|
|
@ -3,710 +3,100 @@ package tun
|
|||
import (
|
||||
"encoding/binary"
|
||||
"math/bits"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
// checksumGeneric64 is a reference implementation of checksum using 64 bit
|
||||
// arithmetic for use in testing or when an architecture-specific implementation
|
||||
// is not available.
|
||||
func checksumGeneric64(b []byte, initial uint16) uint16 {
|
||||
var ac uint64
|
||||
// TODO: Explore SIMD and/or other assembly optimizations.
|
||||
func checksumNoFold(b []byte, initial uint64) uint64 {
|
||||
tmp := make([]byte, 8)
|
||||
binary.NativeEndian.PutUint64(tmp, initial)
|
||||
ac := binary.BigEndian.Uint64(tmp)
|
||||
var carry uint64
|
||||
|
||||
if cpu.IsBigEndian {
|
||||
ac = uint64(initial)
|
||||
} else {
|
||||
ac = uint64(bits.ReverseBytes16(initial))
|
||||
}
|
||||
|
||||
for len(b) >= 128 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[:8]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[8:16]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[16:24]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[24:32]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[32:40]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[40:48]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[48:56]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[56:64]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[64:72]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[72:80]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[80:88]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[88:96]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[96:104]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[104:112]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[112:120]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[120:128]), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[:8]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[8:16]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[16:24]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[24:32]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[32:40]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[40:48]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[48:56]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[56:64]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[64:72]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[72:80]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[80:88]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[88:96]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[96:104]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[104:112]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[112:120]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[120:128]), carry)
|
||||
}
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[:8]), 0)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[8:16]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[16:24]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[24:32]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[32:40]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[40:48]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[48:56]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[56:64]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[64:72]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[72:80]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[80:88]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[88:96]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[96:104]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[104:112]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[112:120]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[120:128]), carry)
|
||||
ac += carry
|
||||
b = b[128:]
|
||||
}
|
||||
if len(b) >= 64 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[:8]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[8:16]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[16:24]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[24:32]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[32:40]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[40:48]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[48:56]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[56:64]), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[:8]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[8:16]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[16:24]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[24:32]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[32:40]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[40:48]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[48:56]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[56:64]), carry)
|
||||
}
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[:8]), 0)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[8:16]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[16:24]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[24:32]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[32:40]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[40:48]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[48:56]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[56:64]), carry)
|
||||
ac += carry
|
||||
b = b[64:]
|
||||
}
|
||||
if len(b) >= 32 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[:8]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[8:16]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[16:24]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[24:32]), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[:8]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[8:16]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[16:24]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[24:32]), carry)
|
||||
}
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[:8]), 0)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[8:16]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[16:24]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[24:32]), carry)
|
||||
ac += carry
|
||||
b = b[32:]
|
||||
}
|
||||
if len(b) >= 16 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[:8]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b[8:16]), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[:8]), carry)
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b[8:16]), carry)
|
||||
}
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[:8]), 0)
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[8:16]), carry)
|
||||
ac += carry
|
||||
b = b[16:]
|
||||
}
|
||||
if len(b) >= 8 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add64(ac, binary.BigEndian.Uint64(b), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add64(ac, binary.LittleEndian.Uint64(b), carry)
|
||||
}
|
||||
ac, carry = bits.Add64(ac, binary.NativeEndian.Uint64(b[:8]), 0)
|
||||
ac += carry
|
||||
b = b[8:]
|
||||
}
|
||||
if len(b) >= 4 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add64(ac, uint64(binary.BigEndian.Uint32(b)), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add64(ac, uint64(binary.LittleEndian.Uint32(b)), carry)
|
||||
}
|
||||
ac, carry = bits.Add64(ac, uint64(binary.NativeEndian.Uint32(b[:4])), 0)
|
||||
ac += carry
|
||||
b = b[4:]
|
||||
}
|
||||
if len(b) >= 2 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add64(ac, uint64(binary.BigEndian.Uint16(b)), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add64(ac, uint64(binary.LittleEndian.Uint16(b)), carry)
|
||||
}
|
||||
ac, carry = bits.Add64(ac, uint64(binary.NativeEndian.Uint16(b[:2])), 0)
|
||||
ac += carry
|
||||
b = b[2:]
|
||||
}
|
||||
if len(b) >= 1 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add64(ac, uint64(b[0])<<8, carry)
|
||||
} else {
|
||||
ac, carry = bits.Add64(ac, uint64(b[0]), carry)
|
||||
}
|
||||
if len(b) == 1 {
|
||||
tmp := binary.NativeEndian.Uint16([]byte{b[0], 0})
|
||||
ac, carry = bits.Add64(ac, uint64(tmp), 0)
|
||||
ac += carry
|
||||
}
|
||||
|
||||
folded := ipChecksumFold64(ac, carry)
|
||||
if !cpu.IsBigEndian {
|
||||
folded = bits.ReverseBytes16(folded)
|
||||
}
|
||||
return folded
|
||||
binary.NativeEndian.PutUint64(tmp, ac)
|
||||
return binary.BigEndian.Uint64(tmp)
|
||||
}
|
||||
|
||||
// checksumGeneric32 is a reference implementation of checksum using 32 bit
|
||||
// arithmetic for use in testing or when an architecture-specific implementation
|
||||
// is not available.
|
||||
func checksumGeneric32(b []byte, initial uint16) uint16 {
|
||||
var ac uint32
|
||||
var carry uint32
|
||||
|
||||
if cpu.IsBigEndian {
|
||||
ac = uint32(initial)
|
||||
} else {
|
||||
ac = uint32(bits.ReverseBytes16(initial))
|
||||
}
|
||||
|
||||
for len(b) >= 64 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[:8]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[4:8]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[8:12]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[12:16]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[16:20]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[20:24]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[24:28]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[28:32]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[32:36]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[36:40]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[40:44]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[44:48]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[48:52]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[52:56]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[56:60]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[60:64]), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[:8]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[4:8]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[8:12]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[12:16]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[16:20]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[20:24]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[24:28]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[28:32]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[32:36]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[36:40]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[40:44]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[44:48]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[48:52]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[52:56]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[56:60]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[60:64]), carry)
|
||||
}
|
||||
b = b[64:]
|
||||
}
|
||||
if len(b) >= 32 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[:4]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[4:8]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[8:12]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[12:16]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[16:20]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[20:24]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[24:28]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[28:32]), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[:4]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[4:8]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[8:12]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[12:16]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[16:20]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[20:24]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[24:28]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[28:32]), carry)
|
||||
}
|
||||
b = b[32:]
|
||||
}
|
||||
if len(b) >= 16 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[:4]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[4:8]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[8:12]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[12:16]), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[:4]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[4:8]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[8:12]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[12:16]), carry)
|
||||
}
|
||||
b = b[16:]
|
||||
}
|
||||
if len(b) >= 8 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[:4]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b[4:8]), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[:4]), carry)
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b[4:8]), carry)
|
||||
}
|
||||
b = b[8:]
|
||||
}
|
||||
if len(b) >= 4 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add32(ac, binary.BigEndian.Uint32(b), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add32(ac, binary.LittleEndian.Uint32(b), carry)
|
||||
}
|
||||
b = b[4:]
|
||||
}
|
||||
if len(b) >= 2 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add32(ac, uint32(binary.BigEndian.Uint16(b)), carry)
|
||||
} else {
|
||||
ac, carry = bits.Add32(ac, uint32(binary.LittleEndian.Uint16(b)), carry)
|
||||
}
|
||||
b = b[2:]
|
||||
}
|
||||
if len(b) >= 1 {
|
||||
if cpu.IsBigEndian {
|
||||
ac, carry = bits.Add32(ac, uint32(b[0])<<8, carry)
|
||||
} else {
|
||||
ac, carry = bits.Add32(ac, uint32(b[0]), carry)
|
||||
}
|
||||
}
|
||||
|
||||
folded := ipChecksumFold32(ac, carry)
|
||||
if !cpu.IsBigEndian {
|
||||
folded = bits.ReverseBytes16(folded)
|
||||
}
|
||||
return folded
|
||||
func checksum(b []byte, initial uint64) uint16 {
|
||||
ac := checksumNoFold(b, initial)
|
||||
ac = (ac >> 16) + (ac & 0xffff)
|
||||
ac = (ac >> 16) + (ac & 0xffff)
|
||||
ac = (ac >> 16) + (ac & 0xffff)
|
||||
ac = (ac >> 16) + (ac & 0xffff)
|
||||
return uint16(ac)
|
||||
}
|
||||
|
||||
// checksumGeneric32Alternate is an alternate reference implementation of
|
||||
// checksum using 32 bit arithmetic for use in testing or when an
|
||||
// architecture-specific implementation is not available.
|
||||
func checksumGeneric32Alternate(b []byte, initial uint16) uint16 {
|
||||
var ac uint32
|
||||
|
||||
if cpu.IsBigEndian {
|
||||
ac = uint32(initial)
|
||||
} else {
|
||||
ac = uint32(bits.ReverseBytes16(initial))
|
||||
}
|
||||
|
||||
for len(b) >= 64 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint32(binary.BigEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[2:4]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[4:6]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[6:8]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[8:10]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[10:12]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[12:14]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[14:16]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[16:18]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[18:20]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[20:22]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[22:24]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[24:26]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[26:28]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[28:30]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[30:32]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[32:34]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[34:36]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[36:38]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[38:40]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[40:42]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[42:44]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[44:46]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[46:48]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[48:50]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[50:52]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[52:54]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[54:56]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[56:58]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[58:60]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[60:62]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[62:64]))
|
||||
} else {
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[2:4]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[4:6]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[6:8]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[8:10]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[10:12]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[12:14]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[14:16]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[16:18]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[18:20]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[20:22]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[22:24]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[24:26]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[26:28]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[28:30]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[30:32]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[32:34]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[34:36]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[36:38]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[38:40]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[40:42]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[42:44]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[44:46]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[46:48]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[48:50]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[50:52]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[52:54]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[54:56]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[56:58]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[58:60]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[60:62]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[62:64]))
|
||||
}
|
||||
b = b[64:]
|
||||
}
|
||||
if len(b) >= 32 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint32(binary.BigEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[2:4]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[4:6]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[6:8]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[8:10]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[10:12]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[12:14]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[14:16]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[16:18]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[18:20]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[20:22]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[22:24]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[24:26]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[26:28]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[28:30]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[30:32]))
|
||||
} else {
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[2:4]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[4:6]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[6:8]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[8:10]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[10:12]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[12:14]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[14:16]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[16:18]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[18:20]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[20:22]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[22:24]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[24:26]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[26:28]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[28:30]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[30:32]))
|
||||
}
|
||||
b = b[32:]
|
||||
}
|
||||
if len(b) >= 16 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint32(binary.BigEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[2:4]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[4:6]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[6:8]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[8:10]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[10:12]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[12:14]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[14:16]))
|
||||
} else {
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[2:4]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[4:6]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[6:8]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[8:10]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[10:12]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[12:14]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[14:16]))
|
||||
}
|
||||
b = b[16:]
|
||||
}
|
||||
if len(b) >= 8 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint32(binary.BigEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[2:4]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[4:6]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[6:8]))
|
||||
} else {
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[2:4]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[4:6]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[6:8]))
|
||||
}
|
||||
b = b[8:]
|
||||
}
|
||||
if len(b) >= 4 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint32(binary.BigEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.BigEndian.Uint16(b[2:4]))
|
||||
} else {
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[:2]))
|
||||
ac += uint32(binary.LittleEndian.Uint16(b[2:4]))
|
||||
}
|
||||
b = b[4:]
|
||||
}
|
||||
if len(b) >= 2 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint32(binary.BigEndian.Uint16(b))
|
||||
} else {
|
||||
ac += uint32(binary.LittleEndian.Uint16(b))
|
||||
}
|
||||
b = b[2:]
|
||||
}
|
||||
if len(b) >= 1 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint32(b[0]) << 8
|
||||
} else {
|
||||
ac += uint32(b[0])
|
||||
}
|
||||
}
|
||||
|
||||
folded := ipChecksumFold32(ac, 0)
|
||||
if !cpu.IsBigEndian {
|
||||
folded = bits.ReverseBytes16(folded)
|
||||
}
|
||||
return folded
|
||||
}
|
||||
|
||||
// checksumGeneric64Alternate is an alternate reference implementation of
|
||||
// checksum using 64 bit arithmetic for use in testing or when an
|
||||
// architecture-specific implementation is not available.
|
||||
func checksumGeneric64Alternate(b []byte, initial uint16) uint16 {
|
||||
var ac uint64
|
||||
|
||||
if cpu.IsBigEndian {
|
||||
ac = uint64(initial)
|
||||
} else {
|
||||
ac = uint64(bits.ReverseBytes16(initial))
|
||||
}
|
||||
|
||||
for len(b) >= 64 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint64(binary.BigEndian.Uint32(b[:4]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[4:8]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[8:12]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[12:16]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[16:20]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[20:24]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[24:28]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[28:32]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[32:36]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[36:40]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[40:44]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[44:48]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[48:52]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[52:56]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[56:60]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[60:64]))
|
||||
} else {
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[:4]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[4:8]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[8:12]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[12:16]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[16:20]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[20:24]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[24:28]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[28:32]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[32:36]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[36:40]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[40:44]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[44:48]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[48:52]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[52:56]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[56:60]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[60:64]))
|
||||
}
|
||||
b = b[64:]
|
||||
}
|
||||
if len(b) >= 32 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint64(binary.BigEndian.Uint32(b[:4]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[4:8]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[8:12]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[12:16]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[16:20]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[20:24]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[24:28]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[28:32]))
|
||||
} else {
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[:4]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[4:8]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[8:12]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[12:16]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[16:20]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[20:24]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[24:28]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[28:32]))
|
||||
}
|
||||
b = b[32:]
|
||||
}
|
||||
if len(b) >= 16 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint64(binary.BigEndian.Uint32(b[:4]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[4:8]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[8:12]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[12:16]))
|
||||
} else {
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[:4]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[4:8]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[8:12]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[12:16]))
|
||||
}
|
||||
b = b[16:]
|
||||
}
|
||||
if len(b) >= 8 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint64(binary.BigEndian.Uint32(b[:4]))
|
||||
ac += uint64(binary.BigEndian.Uint32(b[4:8]))
|
||||
} else {
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[:4]))
|
||||
ac += uint64(binary.LittleEndian.Uint32(b[4:8]))
|
||||
}
|
||||
b = b[8:]
|
||||
}
|
||||
if len(b) >= 4 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint64(binary.BigEndian.Uint32(b))
|
||||
} else {
|
||||
ac += uint64(binary.LittleEndian.Uint32(b))
|
||||
}
|
||||
b = b[4:]
|
||||
}
|
||||
if len(b) >= 2 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint64(binary.BigEndian.Uint16(b))
|
||||
} else {
|
||||
ac += uint64(binary.LittleEndian.Uint16(b))
|
||||
}
|
||||
b = b[2:]
|
||||
}
|
||||
if len(b) >= 1 {
|
||||
if cpu.IsBigEndian {
|
||||
ac += uint64(b[0]) << 8
|
||||
} else {
|
||||
ac += uint64(b[0])
|
||||
}
|
||||
}
|
||||
|
||||
folded := ipChecksumFold64(ac, 0)
|
||||
if !cpu.IsBigEndian {
|
||||
folded = bits.ReverseBytes16(folded)
|
||||
}
|
||||
return folded
|
||||
}
|
||||
|
||||
func ipChecksumFold64(unfolded uint64, initialCarry uint64) uint16 {
|
||||
sum, carry := bits.Add32(uint32(unfolded>>32), uint32(unfolded&0xffff_ffff), uint32(initialCarry))
|
||||
// if carry != 0, sum <= 0xffff_fffe, otherwise sum <= 0xffff_ffff
|
||||
// therefore (sum >> 16) + (sum & 0xffff) + carry <= 0x1_fffe; so there is
|
||||
// no need to save the carry flag
|
||||
sum = (sum >> 16) + (sum & 0xffff) + carry
|
||||
// sum <= 0x1_fffe therefore this is the last fold needed:
|
||||
// if (sum >> 16) > 0 then
|
||||
// (sum >> 16) == 1 && (sum & 0xffff) <= 0xfffe and therefore
|
||||
// the addition will not overflow
|
||||
// otherwise (sum >> 16) == 0 and sum will be unchanged
|
||||
sum = (sum >> 16) + (sum & 0xffff)
|
||||
return uint16(sum)
|
||||
}
|
||||
|
||||
func ipChecksumFold32(unfolded uint32, initialCarry uint32) uint16 {
|
||||
sum := (unfolded >> 16) + (unfolded & 0xffff) + initialCarry
|
||||
// sum <= 0x1_ffff:
|
||||
// 0xffff + 0xffff = 0x1_fffe
|
||||
// initialCarry is 0 or 1, for a combined maximum of 0x1_ffff
|
||||
sum = (sum >> 16) + (sum & 0xffff)
|
||||
// sum <= 0x1_0000 therefore this is the last fold needed:
|
||||
// if (sum >> 16) > 0 then
|
||||
// (sum >> 16) == 1 && (sum & 0xffff) == 0 and therefore
|
||||
// the addition will not overflow
|
||||
// otherwise (sum >> 16) == 0 and sum will be unchanged
|
||||
sum = (sum >> 16) + (sum & 0xffff)
|
||||
return uint16(sum)
|
||||
}
|
||||
|
||||
func addrPartialChecksum64(addr []byte, initial, carryIn uint64) (sum, carry uint64) {
|
||||
sum, carry = initial, carryIn
|
||||
switch len(addr) {
|
||||
case 4: // IPv4
|
||||
if cpu.IsBigEndian {
|
||||
sum, carry = bits.Add64(sum, uint64(binary.BigEndian.Uint32(addr)), carry)
|
||||
} else {
|
||||
sum, carry = bits.Add64(sum, uint64(binary.LittleEndian.Uint32(addr)), carry)
|
||||
}
|
||||
case 16: // IPv6
|
||||
if cpu.IsBigEndian {
|
||||
sum, carry = bits.Add64(sum, binary.BigEndian.Uint64(addr), carry)
|
||||
sum, carry = bits.Add64(sum, binary.BigEndian.Uint64(addr[8:]), carry)
|
||||
} else {
|
||||
sum, carry = bits.Add64(sum, binary.LittleEndian.Uint64(addr), carry)
|
||||
sum, carry = bits.Add64(sum, binary.LittleEndian.Uint64(addr[8:]), carry)
|
||||
}
|
||||
default:
|
||||
panic("bad addr length")
|
||||
}
|
||||
return sum, carry
|
||||
}
|
||||
|
||||
func addrPartialChecksum32(addr []byte, initial, carryIn uint32) (sum, carry uint32) {
|
||||
sum, carry = initial, carryIn
|
||||
switch len(addr) {
|
||||
case 4: // IPv4
|
||||
if cpu.IsBigEndian {
|
||||
sum, carry = bits.Add32(sum, binary.BigEndian.Uint32(addr), carry)
|
||||
} else {
|
||||
sum, carry = bits.Add32(sum, binary.LittleEndian.Uint32(addr), carry)
|
||||
}
|
||||
case 16: // IPv6
|
||||
if cpu.IsBigEndian {
|
||||
sum, carry = bits.Add32(sum, binary.BigEndian.Uint32(addr), carry)
|
||||
sum, carry = bits.Add32(sum, binary.BigEndian.Uint32(addr[4:8]), carry)
|
||||
sum, carry = bits.Add32(sum, binary.BigEndian.Uint32(addr[8:12]), carry)
|
||||
sum, carry = bits.Add32(sum, binary.BigEndian.Uint32(addr[12:16]), carry)
|
||||
} else {
|
||||
sum, carry = bits.Add32(sum, binary.LittleEndian.Uint32(addr), carry)
|
||||
sum, carry = bits.Add32(sum, binary.LittleEndian.Uint32(addr[4:8]), carry)
|
||||
sum, carry = bits.Add32(sum, binary.LittleEndian.Uint32(addr[8:12]), carry)
|
||||
sum, carry = bits.Add32(sum, binary.LittleEndian.Uint32(addr[12:16]), carry)
|
||||
}
|
||||
default:
|
||||
panic("bad addr length")
|
||||
}
|
||||
return sum, carry
|
||||
}
|
||||
|
||||
func pseudoHeaderChecksum64(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 {
|
||||
var sum uint64
|
||||
if cpu.IsBigEndian {
|
||||
sum = uint64(totalLen) + uint64(protocol)
|
||||
} else {
|
||||
sum = uint64(bits.ReverseBytes16(totalLen)) + uint64(protocol)<<8
|
||||
}
|
||||
sum, carry := addrPartialChecksum64(srcAddr, sum, 0)
|
||||
sum, carry = addrPartialChecksum64(dstAddr, sum, carry)
|
||||
|
||||
foldedSum := ipChecksumFold64(sum, carry)
|
||||
if !cpu.IsBigEndian {
|
||||
foldedSum = bits.ReverseBytes16(foldedSum)
|
||||
}
|
||||
return foldedSum
|
||||
}
|
||||
|
||||
func pseudoHeaderChecksum32(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 {
|
||||
var sum uint32
|
||||
if cpu.IsBigEndian {
|
||||
sum = uint32(totalLen) + uint32(protocol)
|
||||
} else {
|
||||
sum = uint32(bits.ReverseBytes16(totalLen)) + uint32(protocol)<<8
|
||||
}
|
||||
sum, carry := addrPartialChecksum32(srcAddr, sum, 0)
|
||||
sum, carry = addrPartialChecksum32(dstAddr, sum, carry)
|
||||
|
||||
foldedSum := ipChecksumFold32(sum, carry)
|
||||
if !cpu.IsBigEndian {
|
||||
foldedSum = bits.ReverseBytes16(foldedSum)
|
||||
}
|
||||
return foldedSum
|
||||
}
|
||||
|
||||
// PseudoHeaderChecksum computes an IP pseudo-header checksum. srcAddr and
|
||||
// dstAddr must be 4 or 16 bytes in length.
|
||||
func PseudoHeaderChecksum(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 {
|
||||
if strconv.IntSize < 64 {
|
||||
return pseudoHeaderChecksum32(protocol, srcAddr, dstAddr, totalLen)
|
||||
}
|
||||
return pseudoHeaderChecksum64(protocol, srcAddr, dstAddr, totalLen)
|
||||
func pseudoHeaderChecksumNoFold(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint64 {
|
||||
sum := checksumNoFold(srcAddr, 0)
|
||||
sum = checksumNoFold(dstAddr, sum)
|
||||
sum = checksumNoFold([]byte{0, protocol}, sum)
|
||||
tmp := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(tmp, totalLen)
|
||||
return checksumNoFold(tmp, sum)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
package tun
|
||||
|
||||
import "golang.org/x/sys/cpu"
|
||||
|
||||
var checksum = checksumAMD64
|
||||
|
||||
// Checksum computes an IP checksum starting with the provided initial value.
|
||||
// The length of data should be at least 128 bytes for best performance. Smaller
|
||||
// buffers will still compute a correct result.
|
||||
func Checksum(data []byte, initial uint16) uint16 {
|
||||
return checksum(data, initial)
|
||||
}
|
||||
|
||||
func init() {
|
||||
if cpu.X86.HasAVX && cpu.X86.HasAVX2 && cpu.X86.HasBMI2 {
|
||||
checksum = checksumAVX2
|
||||
return
|
||||
}
|
||||
if cpu.X86.HasSSE2 {
|
||||
checksum = checksumSSE2
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
// Code generated by command: go run generate_amd64.go -out checksum_generated_amd64.s -stubs checksum_generated_amd64.go. DO NOT EDIT.
|
||||
|
||||
package tun
|
||||
|
||||
// checksumAVX2 computes an IP checksum using amd64 v3 instructions (AVX2, BMI2)
|
||||
//
|
||||
//go:noescape
|
||||
func checksumAVX2(b []byte, initial uint16) uint16
|
||||
|
||||
// checksumSSE2 computes an IP checksum using amd64 baseline instructions (SSE2)
|
||||
//
|
||||
//go:noescape
|
||||
func checksumSSE2(b []byte, initial uint16) uint16
|
||||
|
||||
// checksumAMD64 computes an IP checksum using amd64 baseline instructions
|
||||
//
|
||||
//go:noescape
|
||||
func checksumAMD64(b []byte, initial uint16) uint16
|
||||
|
|
@ -1,851 +0,0 @@
|
|||
// Code generated by command: go run generate_amd64.go -out checksum_generated_amd64.s -stubs checksum_generated_amd64.go. DO NOT EDIT.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
DATA xmmLoadMasks<>+0(SB)/16, $"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff"
|
||||
DATA xmmLoadMasks<>+16(SB)/16, $"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff"
|
||||
DATA xmmLoadMasks<>+32(SB)/16, $"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff"
|
||||
DATA xmmLoadMasks<>+48(SB)/16, $"\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
DATA xmmLoadMasks<>+64(SB)/16, $"\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
DATA xmmLoadMasks<>+80(SB)/16, $"\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
DATA xmmLoadMasks<>+96(SB)/16, $"\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
|
||||
GLOBL xmmLoadMasks<>(SB), RODATA|NOPTR, $112
|
||||
|
||||
// func checksumAVX2(b []byte, initial uint16) uint16
|
||||
// Requires: AVX, AVX2, BMI2
|
||||
TEXT ·checksumAVX2(SB), NOSPLIT|NOFRAME, $0-34
|
||||
MOVWQZX initial+24(FP), AX
|
||||
XCHGB AH, AL
|
||||
MOVQ b_base+0(FP), DX
|
||||
MOVQ b_len+8(FP), BX
|
||||
|
||||
// handle odd length buffers; they are difficult to handle in general
|
||||
TESTQ $0x00000001, BX
|
||||
JZ lengthIsEven
|
||||
MOVBQZX -1(DX)(BX*1), CX
|
||||
DECQ BX
|
||||
ADDQ CX, AX
|
||||
|
||||
lengthIsEven:
|
||||
// handle tiny buffers (<=31 bytes) specially
|
||||
CMPQ BX, $0x1f
|
||||
JGT bufferIsNotTiny
|
||||
XORQ CX, CX
|
||||
XORQ SI, SI
|
||||
XORQ DI, DI
|
||||
|
||||
// shift twice to start because length is guaranteed to be even
|
||||
// n = n >> 2; CF = originalN & 2
|
||||
SHRQ $0x02, BX
|
||||
JNC handleTiny4
|
||||
|
||||
// tmp2 = binary.LittleEndian.Uint16(buf[:2]); buf = buf[2:]
|
||||
MOVWQZX (DX), CX
|
||||
ADDQ $0x02, DX
|
||||
|
||||
handleTiny4:
|
||||
// n = n >> 1; CF = originalN & 4
|
||||
SHRQ $0x01, BX
|
||||
JNC handleTiny8
|
||||
|
||||
// tmp4 = binary.LittleEndian.Uint32(buf[:4]); buf = buf[4:]
|
||||
MOVLQZX (DX), SI
|
||||
ADDQ $0x04, DX
|
||||
|
||||
handleTiny8:
|
||||
// n = n >> 1; CF = originalN & 8
|
||||
SHRQ $0x01, BX
|
||||
JNC handleTiny16
|
||||
|
||||
// tmp8 = binary.LittleEndian.Uint64(buf[:8]); buf = buf[8:]
|
||||
MOVQ (DX), DI
|
||||
ADDQ $0x08, DX
|
||||
|
||||
handleTiny16:
|
||||
// n = n >> 1; CF = originalN & 16
|
||||
// n == 0 now, otherwise we would have branched after comparing with tinyBufferSize
|
||||
SHRQ $0x01, BX
|
||||
JNC handleTinyFinish
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
|
||||
handleTinyFinish:
|
||||
// CF should be included from the previous add, so we use ADCQ.
|
||||
// If we arrived via the JNC above, then CF=0 due to the branch condition,
|
||||
// so ADCQ will still produce the correct result.
|
||||
ADCQ CX, AX
|
||||
ADCQ SI, AX
|
||||
ADCQ DI, AX
|
||||
JMP foldAndReturn
|
||||
|
||||
bufferIsNotTiny:
|
||||
// skip all SIMD for small buffers
|
||||
CMPQ BX, $0x00000100
|
||||
JGE startSIMD
|
||||
|
||||
// Accumulate carries in this register. It is never expected to overflow.
|
||||
XORQ SI, SI
|
||||
|
||||
// We will perform an overlapped read for buffers with length not a multiple of 8.
|
||||
// Overlapped in this context means some memory will be read twice, but a shift will
|
||||
// eliminate the duplicated data. This extra read is performed at the end of the buffer to
|
||||
// preserve any alignment that may exist for the start of the buffer.
|
||||
MOVQ BX, CX
|
||||
SHRQ $0x03, BX
|
||||
ANDQ $0x07, CX
|
||||
JZ handleRemaining8
|
||||
LEAQ (DX)(BX*8), DI
|
||||
MOVQ -8(DI)(CX*1), DI
|
||||
|
||||
// Shift out the duplicated data: overlapRead = overlapRead >> (64 - leftoverBytes*8)
|
||||
SHLQ $0x03, CX
|
||||
NEGQ CX
|
||||
ADDQ $0x40, CX
|
||||
SHRQ CL, DI
|
||||
ADDQ DI, AX
|
||||
ADCQ $0x00, SI
|
||||
|
||||
handleRemaining8:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining16
|
||||
ADDQ (DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x08, DX
|
||||
|
||||
handleRemaining16:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining32
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x10, DX
|
||||
|
||||
handleRemaining32:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining64
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x20, DX
|
||||
|
||||
handleRemaining64:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining128
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ 32(DX), AX
|
||||
ADCQ 40(DX), AX
|
||||
ADCQ 48(DX), AX
|
||||
ADCQ 56(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x40, DX
|
||||
|
||||
handleRemaining128:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemainingComplete
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ 32(DX), AX
|
||||
ADCQ 40(DX), AX
|
||||
ADCQ 48(DX), AX
|
||||
ADCQ 56(DX), AX
|
||||
ADCQ 64(DX), AX
|
||||
ADCQ 72(DX), AX
|
||||
ADCQ 80(DX), AX
|
||||
ADCQ 88(DX), AX
|
||||
ADCQ 96(DX), AX
|
||||
ADCQ 104(DX), AX
|
||||
ADCQ 112(DX), AX
|
||||
ADCQ 120(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x80, DX
|
||||
|
||||
handleRemainingComplete:
|
||||
ADDQ SI, AX
|
||||
JMP foldAndReturn
|
||||
|
||||
startSIMD:
|
||||
VPXOR Y0, Y0, Y0
|
||||
VPXOR Y1, Y1, Y1
|
||||
VPXOR Y2, Y2, Y2
|
||||
VPXOR Y3, Y3, Y3
|
||||
MOVQ BX, CX
|
||||
|
||||
// Update number of bytes remaining after the loop completes
|
||||
ANDQ $0xff, BX
|
||||
|
||||
// Number of 256 byte iterations
|
||||
SHRQ $0x08, CX
|
||||
JZ smallLoop
|
||||
|
||||
bigLoop:
|
||||
VPMOVZXWD (DX), Y4
|
||||
VPADDD Y4, Y0, Y0
|
||||
VPMOVZXWD 16(DX), Y4
|
||||
VPADDD Y4, Y1, Y1
|
||||
VPMOVZXWD 32(DX), Y4
|
||||
VPADDD Y4, Y2, Y2
|
||||
VPMOVZXWD 48(DX), Y4
|
||||
VPADDD Y4, Y3, Y3
|
||||
VPMOVZXWD 64(DX), Y4
|
||||
VPADDD Y4, Y0, Y0
|
||||
VPMOVZXWD 80(DX), Y4
|
||||
VPADDD Y4, Y1, Y1
|
||||
VPMOVZXWD 96(DX), Y4
|
||||
VPADDD Y4, Y2, Y2
|
||||
VPMOVZXWD 112(DX), Y4
|
||||
VPADDD Y4, Y3, Y3
|
||||
VPMOVZXWD 128(DX), Y4
|
||||
VPADDD Y4, Y0, Y0
|
||||
VPMOVZXWD 144(DX), Y4
|
||||
VPADDD Y4, Y1, Y1
|
||||
VPMOVZXWD 160(DX), Y4
|
||||
VPADDD Y4, Y2, Y2
|
||||
VPMOVZXWD 176(DX), Y4
|
||||
VPADDD Y4, Y3, Y3
|
||||
VPMOVZXWD 192(DX), Y4
|
||||
VPADDD Y4, Y0, Y0
|
||||
VPMOVZXWD 208(DX), Y4
|
||||
VPADDD Y4, Y1, Y1
|
||||
VPMOVZXWD 224(DX), Y4
|
||||
VPADDD Y4, Y2, Y2
|
||||
VPMOVZXWD 240(DX), Y4
|
||||
VPADDD Y4, Y3, Y3
|
||||
ADDQ $0x00000100, DX
|
||||
DECQ CX
|
||||
JNZ bigLoop
|
||||
CMPQ BX, $0x10
|
||||
JLT doneSmallLoop
|
||||
|
||||
// now read a single 16 byte unit of data at a time
|
||||
smallLoop:
|
||||
VPMOVZXWD (DX), Y4
|
||||
VPADDD Y4, Y0, Y0
|
||||
ADDQ $0x10, DX
|
||||
SUBQ $0x10, BX
|
||||
CMPQ BX, $0x10
|
||||
JGE smallLoop
|
||||
|
||||
doneSmallLoop:
|
||||
CMPQ BX, $0x00
|
||||
JE doneSIMD
|
||||
|
||||
// There are between 1 and 15 bytes remaining. Perform an overlapped read.
|
||||
LEAQ xmmLoadMasks<>+0(SB), CX
|
||||
VMOVDQU -16(DX)(BX*1), X4
|
||||
VPAND -16(CX)(BX*8), X4, X4
|
||||
VPMOVZXWD X4, Y4
|
||||
VPADDD Y4, Y0, Y0
|
||||
|
||||
doneSIMD:
|
||||
// Multi-chain loop is done, combine the accumulators
|
||||
VPADDD Y1, Y0, Y0
|
||||
VPADDD Y2, Y0, Y0
|
||||
VPADDD Y3, Y0, Y0
|
||||
|
||||
// extract the YMM into a pair of XMM and sum them
|
||||
VEXTRACTI128 $0x01, Y0, X1
|
||||
VPADDD X0, X1, X0
|
||||
|
||||
// extract the XMM into GP64
|
||||
VPEXTRQ $0x00, X0, CX
|
||||
VPEXTRQ $0x01, X0, DX
|
||||
|
||||
// no more AVX code, clear upper registers to avoid SSE slowdowns
|
||||
VZEROUPPER
|
||||
ADDQ CX, AX
|
||||
ADCQ DX, AX
|
||||
|
||||
foldAndReturn:
|
||||
// add CF and fold
|
||||
RORXQ $0x20, AX, CX
|
||||
ADCL CX, AX
|
||||
RORXL $0x10, AX, CX
|
||||
ADCW CX, AX
|
||||
ADCW $0x00, AX
|
||||
XCHGB AH, AL
|
||||
MOVW AX, ret+32(FP)
|
||||
RET
|
||||
|
||||
// func checksumSSE2(b []byte, initial uint16) uint16
|
||||
// Requires: SSE2
|
||||
TEXT ·checksumSSE2(SB), NOSPLIT|NOFRAME, $0-34
|
||||
MOVWQZX initial+24(FP), AX
|
||||
XCHGB AH, AL
|
||||
MOVQ b_base+0(FP), DX
|
||||
MOVQ b_len+8(FP), BX
|
||||
|
||||
// handle odd length buffers; they are difficult to handle in general
|
||||
TESTQ $0x00000001, BX
|
||||
JZ lengthIsEven
|
||||
MOVBQZX -1(DX)(BX*1), CX
|
||||
DECQ BX
|
||||
ADDQ CX, AX
|
||||
|
||||
lengthIsEven:
|
||||
// handle tiny buffers (<=31 bytes) specially
|
||||
CMPQ BX, $0x1f
|
||||
JGT bufferIsNotTiny
|
||||
XORQ CX, CX
|
||||
XORQ SI, SI
|
||||
XORQ DI, DI
|
||||
|
||||
// shift twice to start because length is guaranteed to be even
|
||||
// n = n >> 2; CF = originalN & 2
|
||||
SHRQ $0x02, BX
|
||||
JNC handleTiny4
|
||||
|
||||
// tmp2 = binary.LittleEndian.Uint16(buf[:2]); buf = buf[2:]
|
||||
MOVWQZX (DX), CX
|
||||
ADDQ $0x02, DX
|
||||
|
||||
handleTiny4:
|
||||
// n = n >> 1; CF = originalN & 4
|
||||
SHRQ $0x01, BX
|
||||
JNC handleTiny8
|
||||
|
||||
// tmp4 = binary.LittleEndian.Uint32(buf[:4]); buf = buf[4:]
|
||||
MOVLQZX (DX), SI
|
||||
ADDQ $0x04, DX
|
||||
|
||||
handleTiny8:
|
||||
// n = n >> 1; CF = originalN & 8
|
||||
SHRQ $0x01, BX
|
||||
JNC handleTiny16
|
||||
|
||||
// tmp8 = binary.LittleEndian.Uint64(buf[:8]); buf = buf[8:]
|
||||
MOVQ (DX), DI
|
||||
ADDQ $0x08, DX
|
||||
|
||||
handleTiny16:
|
||||
// n = n >> 1; CF = originalN & 16
|
||||
// n == 0 now, otherwise we would have branched after comparing with tinyBufferSize
|
||||
SHRQ $0x01, BX
|
||||
JNC handleTinyFinish
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
|
||||
handleTinyFinish:
|
||||
// CF should be included from the previous add, so we use ADCQ.
|
||||
// If we arrived via the JNC above, then CF=0 due to the branch condition,
|
||||
// so ADCQ will still produce the correct result.
|
||||
ADCQ CX, AX
|
||||
ADCQ SI, AX
|
||||
ADCQ DI, AX
|
||||
JMP foldAndReturn
|
||||
|
||||
bufferIsNotTiny:
|
||||
// skip all SIMD for small buffers
|
||||
CMPQ BX, $0x00000100
|
||||
JGE startSIMD
|
||||
|
||||
// Accumulate carries in this register. It is never expected to overflow.
|
||||
XORQ SI, SI
|
||||
|
||||
// We will perform an overlapped read for buffers with length not a multiple of 8.
|
||||
// Overlapped in this context means some memory will be read twice, but a shift will
|
||||
// eliminate the duplicated data. This extra read is performed at the end of the buffer to
|
||||
// preserve any alignment that may exist for the start of the buffer.
|
||||
MOVQ BX, CX
|
||||
SHRQ $0x03, BX
|
||||
ANDQ $0x07, CX
|
||||
JZ handleRemaining8
|
||||
LEAQ (DX)(BX*8), DI
|
||||
MOVQ -8(DI)(CX*1), DI
|
||||
|
||||
// Shift out the duplicated data: overlapRead = overlapRead >> (64 - leftoverBytes*8)
|
||||
SHLQ $0x03, CX
|
||||
NEGQ CX
|
||||
ADDQ $0x40, CX
|
||||
SHRQ CL, DI
|
||||
ADDQ DI, AX
|
||||
ADCQ $0x00, SI
|
||||
|
||||
handleRemaining8:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining16
|
||||
ADDQ (DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x08, DX
|
||||
|
||||
handleRemaining16:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining32
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x10, DX
|
||||
|
||||
handleRemaining32:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining64
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x20, DX
|
||||
|
||||
handleRemaining64:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining128
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ 32(DX), AX
|
||||
ADCQ 40(DX), AX
|
||||
ADCQ 48(DX), AX
|
||||
ADCQ 56(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x40, DX
|
||||
|
||||
handleRemaining128:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemainingComplete
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ 32(DX), AX
|
||||
ADCQ 40(DX), AX
|
||||
ADCQ 48(DX), AX
|
||||
ADCQ 56(DX), AX
|
||||
ADCQ 64(DX), AX
|
||||
ADCQ 72(DX), AX
|
||||
ADCQ 80(DX), AX
|
||||
ADCQ 88(DX), AX
|
||||
ADCQ 96(DX), AX
|
||||
ADCQ 104(DX), AX
|
||||
ADCQ 112(DX), AX
|
||||
ADCQ 120(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x80, DX
|
||||
|
||||
handleRemainingComplete:
|
||||
ADDQ SI, AX
|
||||
JMP foldAndReturn
|
||||
|
||||
startSIMD:
|
||||
PXOR X0, X0
|
||||
PXOR X1, X1
|
||||
PXOR X2, X2
|
||||
PXOR X3, X3
|
||||
PXOR X4, X4
|
||||
MOVQ BX, CX
|
||||
|
||||
// Update number of bytes remaining after the loop completes
|
||||
ANDQ $0xff, BX
|
||||
|
||||
// Number of 256 byte iterations
|
||||
SHRQ $0x08, CX
|
||||
JZ smallLoop
|
||||
|
||||
bigLoop:
|
||||
MOVOU (DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X0
|
||||
PADDD X6, X2
|
||||
MOVOU 16(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X1
|
||||
PADDD X6, X3
|
||||
MOVOU 32(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X2
|
||||
PADDD X6, X0
|
||||
MOVOU 48(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X3
|
||||
PADDD X6, X1
|
||||
MOVOU 64(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X0
|
||||
PADDD X6, X2
|
||||
MOVOU 80(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X1
|
||||
PADDD X6, X3
|
||||
MOVOU 96(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X2
|
||||
PADDD X6, X0
|
||||
MOVOU 112(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X3
|
||||
PADDD X6, X1
|
||||
MOVOU 128(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X0
|
||||
PADDD X6, X2
|
||||
MOVOU 144(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X1
|
||||
PADDD X6, X3
|
||||
MOVOU 160(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X2
|
||||
PADDD X6, X0
|
||||
MOVOU 176(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X3
|
||||
PADDD X6, X1
|
||||
MOVOU 192(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X0
|
||||
PADDD X6, X2
|
||||
MOVOU 208(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X1
|
||||
PADDD X6, X3
|
||||
MOVOU 224(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X2
|
||||
PADDD X6, X0
|
||||
MOVOU 240(DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X3
|
||||
PADDD X6, X1
|
||||
ADDQ $0x00000100, DX
|
||||
DECQ CX
|
||||
JNZ bigLoop
|
||||
CMPQ BX, $0x10
|
||||
JLT doneSmallLoop
|
||||
|
||||
// now read a single 16 byte unit of data at a time
|
||||
smallLoop:
|
||||
MOVOU (DX), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X0
|
||||
PADDD X6, X1
|
||||
ADDQ $0x10, DX
|
||||
SUBQ $0x10, BX
|
||||
CMPQ BX, $0x10
|
||||
JGE smallLoop
|
||||
|
||||
doneSmallLoop:
|
||||
CMPQ BX, $0x00
|
||||
JE doneSIMD
|
||||
|
||||
// There are between 1 and 15 bytes remaining. Perform an overlapped read.
|
||||
LEAQ xmmLoadMasks<>+0(SB), CX
|
||||
MOVOU -16(DX)(BX*1), X5
|
||||
PAND -16(CX)(BX*8), X5
|
||||
MOVOA X5, X6
|
||||
PUNPCKHWL X4, X5
|
||||
PUNPCKLWL X4, X6
|
||||
PADDD X5, X0
|
||||
PADDD X6, X1
|
||||
|
||||
doneSIMD:
|
||||
// Multi-chain loop is done, combine the accumulators
|
||||
PADDD X1, X0
|
||||
PADDD X2, X0
|
||||
PADDD X3, X0
|
||||
|
||||
// extract the XMM into GP64
|
||||
MOVQ X0, CX
|
||||
PSRLDQ $0x08, X0
|
||||
MOVQ X0, DX
|
||||
ADDQ CX, AX
|
||||
ADCQ DX, AX
|
||||
|
||||
foldAndReturn:
|
||||
// add CF and fold
|
||||
MOVL AX, CX
|
||||
ADCQ $0x00, CX
|
||||
SHRQ $0x20, AX
|
||||
ADDQ CX, AX
|
||||
MOVWQZX AX, CX
|
||||
SHRQ $0x10, AX
|
||||
ADDQ CX, AX
|
||||
MOVW AX, CX
|
||||
SHRQ $0x10, AX
|
||||
ADDW CX, AX
|
||||
ADCW $0x00, AX
|
||||
XCHGB AH, AL
|
||||
MOVW AX, ret+32(FP)
|
||||
RET
|
||||
|
||||
// func checksumAMD64(b []byte, initial uint16) uint16
|
||||
TEXT ·checksumAMD64(SB), NOSPLIT|NOFRAME, $0-34
|
||||
MOVWQZX initial+24(FP), AX
|
||||
XCHGB AH, AL
|
||||
MOVQ b_base+0(FP), DX
|
||||
MOVQ b_len+8(FP), BX
|
||||
|
||||
// handle odd length buffers; they are difficult to handle in general
|
||||
TESTQ $0x00000001, BX
|
||||
JZ lengthIsEven
|
||||
MOVBQZX -1(DX)(BX*1), CX
|
||||
DECQ BX
|
||||
ADDQ CX, AX
|
||||
|
||||
lengthIsEven:
|
||||
// handle tiny buffers (<=31 bytes) specially
|
||||
CMPQ BX, $0x1f
|
||||
JGT bufferIsNotTiny
|
||||
XORQ CX, CX
|
||||
XORQ SI, SI
|
||||
XORQ DI, DI
|
||||
|
||||
// shift twice to start because length is guaranteed to be even
|
||||
// n = n >> 2; CF = originalN & 2
|
||||
SHRQ $0x02, BX
|
||||
JNC handleTiny4
|
||||
|
||||
// tmp2 = binary.LittleEndian.Uint16(buf[:2]); buf = buf[2:]
|
||||
MOVWQZX (DX), CX
|
||||
ADDQ $0x02, DX
|
||||
|
||||
handleTiny4:
|
||||
// n = n >> 1; CF = originalN & 4
|
||||
SHRQ $0x01, BX
|
||||
JNC handleTiny8
|
||||
|
||||
// tmp4 = binary.LittleEndian.Uint32(buf[:4]); buf = buf[4:]
|
||||
MOVLQZX (DX), SI
|
||||
ADDQ $0x04, DX
|
||||
|
||||
handleTiny8:
|
||||
// n = n >> 1; CF = originalN & 8
|
||||
SHRQ $0x01, BX
|
||||
JNC handleTiny16
|
||||
|
||||
// tmp8 = binary.LittleEndian.Uint64(buf[:8]); buf = buf[8:]
|
||||
MOVQ (DX), DI
|
||||
ADDQ $0x08, DX
|
||||
|
||||
handleTiny16:
|
||||
// n = n >> 1; CF = originalN & 16
|
||||
// n == 0 now, otherwise we would have branched after comparing with tinyBufferSize
|
||||
SHRQ $0x01, BX
|
||||
JNC handleTinyFinish
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
|
||||
handleTinyFinish:
|
||||
// CF should be included from the previous add, so we use ADCQ.
|
||||
// If we arrived via the JNC above, then CF=0 due to the branch condition,
|
||||
// so ADCQ will still produce the correct result.
|
||||
ADCQ CX, AX
|
||||
ADCQ SI, AX
|
||||
ADCQ DI, AX
|
||||
JMP foldAndReturn
|
||||
|
||||
bufferIsNotTiny:
|
||||
// Number of 256 byte iterations into loop counter
|
||||
MOVQ BX, CX
|
||||
|
||||
// Update number of bytes remaining after the loop completes
|
||||
ANDQ $0xff, BX
|
||||
SHRQ $0x08, CX
|
||||
JZ startCleanup
|
||||
CLC
|
||||
XORQ SI, SI
|
||||
XORQ DI, DI
|
||||
XORQ R8, R8
|
||||
XORQ R9, R9
|
||||
XORQ R10, R10
|
||||
XORQ R11, R11
|
||||
XORQ R12, R12
|
||||
|
||||
bigLoop:
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ 32(DX), DI
|
||||
ADCQ 40(DX), DI
|
||||
ADCQ 48(DX), DI
|
||||
ADCQ 56(DX), DI
|
||||
ADCQ $0x00, R8
|
||||
ADDQ 64(DX), R9
|
||||
ADCQ 72(DX), R9
|
||||
ADCQ 80(DX), R9
|
||||
ADCQ 88(DX), R9
|
||||
ADCQ $0x00, R10
|
||||
ADDQ 96(DX), R11
|
||||
ADCQ 104(DX), R11
|
||||
ADCQ 112(DX), R11
|
||||
ADCQ 120(DX), R11
|
||||
ADCQ $0x00, R12
|
||||
ADDQ 128(DX), AX
|
||||
ADCQ 136(DX), AX
|
||||
ADCQ 144(DX), AX
|
||||
ADCQ 152(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ 160(DX), DI
|
||||
ADCQ 168(DX), DI
|
||||
ADCQ 176(DX), DI
|
||||
ADCQ 184(DX), DI
|
||||
ADCQ $0x00, R8
|
||||
ADDQ 192(DX), R9
|
||||
ADCQ 200(DX), R9
|
||||
ADCQ 208(DX), R9
|
||||
ADCQ 216(DX), R9
|
||||
ADCQ $0x00, R10
|
||||
ADDQ 224(DX), R11
|
||||
ADCQ 232(DX), R11
|
||||
ADCQ 240(DX), R11
|
||||
ADCQ 248(DX), R11
|
||||
ADCQ $0x00, R12
|
||||
ADDQ $0x00000100, DX
|
||||
SUBQ $0x01, CX
|
||||
JNZ bigLoop
|
||||
ADDQ SI, AX
|
||||
ADCQ DI, AX
|
||||
ADCQ R8, AX
|
||||
ADCQ R9, AX
|
||||
ADCQ R10, AX
|
||||
ADCQ R11, AX
|
||||
ADCQ R12, AX
|
||||
|
||||
// accumulate CF (twice, in case the first time overflows)
|
||||
ADCQ $0x00, AX
|
||||
ADCQ $0x00, AX
|
||||
|
||||
startCleanup:
|
||||
// Accumulate carries in this register. It is never expected to overflow.
|
||||
XORQ SI, SI
|
||||
|
||||
// We will perform an overlapped read for buffers with length not a multiple of 8.
|
||||
// Overlapped in this context means some memory will be read twice, but a shift will
|
||||
// eliminate the duplicated data. This extra read is performed at the end of the buffer to
|
||||
// preserve any alignment that may exist for the start of the buffer.
|
||||
MOVQ BX, CX
|
||||
SHRQ $0x03, BX
|
||||
ANDQ $0x07, CX
|
||||
JZ handleRemaining8
|
||||
LEAQ (DX)(BX*8), DI
|
||||
MOVQ -8(DI)(CX*1), DI
|
||||
|
||||
// Shift out the duplicated data: overlapRead = overlapRead >> (64 - leftoverBytes*8)
|
||||
SHLQ $0x03, CX
|
||||
NEGQ CX
|
||||
ADDQ $0x40, CX
|
||||
SHRQ CL, DI
|
||||
ADDQ DI, AX
|
||||
ADCQ $0x00, SI
|
||||
|
||||
handleRemaining8:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining16
|
||||
ADDQ (DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x08, DX
|
||||
|
||||
handleRemaining16:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining32
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x10, DX
|
||||
|
||||
handleRemaining32:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining64
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x20, DX
|
||||
|
||||
handleRemaining64:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemaining128
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ 32(DX), AX
|
||||
ADCQ 40(DX), AX
|
||||
ADCQ 48(DX), AX
|
||||
ADCQ 56(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x40, DX
|
||||
|
||||
handleRemaining128:
|
||||
SHRQ $0x01, BX
|
||||
JNC handleRemainingComplete
|
||||
ADDQ (DX), AX
|
||||
ADCQ 8(DX), AX
|
||||
ADCQ 16(DX), AX
|
||||
ADCQ 24(DX), AX
|
||||
ADCQ 32(DX), AX
|
||||
ADCQ 40(DX), AX
|
||||
ADCQ 48(DX), AX
|
||||
ADCQ 56(DX), AX
|
||||
ADCQ 64(DX), AX
|
||||
ADCQ 72(DX), AX
|
||||
ADCQ 80(DX), AX
|
||||
ADCQ 88(DX), AX
|
||||
ADCQ 96(DX), AX
|
||||
ADCQ 104(DX), AX
|
||||
ADCQ 112(DX), AX
|
||||
ADCQ 120(DX), AX
|
||||
ADCQ $0x00, SI
|
||||
ADDQ $0x80, DX
|
||||
|
||||
handleRemainingComplete:
|
||||
ADDQ SI, AX
|
||||
|
||||
foldAndReturn:
|
||||
// add CF and fold
|
||||
MOVL AX, CX
|
||||
ADCQ $0x00, CX
|
||||
SHRQ $0x20, AX
|
||||
ADDQ CX, AX
|
||||
MOVWQZX AX, CX
|
||||
SHRQ $0x10, AX
|
||||
ADDQ CX, AX
|
||||
MOVW AX, CX
|
||||
SHRQ $0x10, AX
|
||||
ADDW CX, AX
|
||||
ADCW $0x00, AX
|
||||
XCHGB AH, AL
|
||||
MOVW AX, ret+32(FP)
|
||||
RET
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
// This file contains IP checksum algorithms that are not specific to any
|
||||
// architecture and don't use hardware acceleration.
|
||||
|
||||
//go:build !amd64
|
||||
|
||||
package tun
|
||||
|
||||
import "strconv"
|
||||
|
||||
func Checksum(data []byte, initial uint16) uint16 {
|
||||
if strconv.IntSize < 64 {
|
||||
return checksumGeneric32(data, initial)
|
||||
}
|
||||
return checksumGeneric64(data, initial)
|
||||
}
|
||||
98
tun/checksum_test.go
Normal file
98
tun/checksum_test.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package tun
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func checksumRef(b []byte, initial uint16) uint16 {
|
||||
ac := uint64(initial)
|
||||
|
||||
for len(b) >= 2 {
|
||||
ac += uint64(binary.BigEndian.Uint16(b))
|
||||
b = b[2:]
|
||||
}
|
||||
if len(b) == 1 {
|
||||
ac += uint64(b[0]) << 8
|
||||
}
|
||||
|
||||
for (ac >> 16) > 0 {
|
||||
ac = (ac >> 16) + (ac & 0xffff)
|
||||
}
|
||||
return uint16(ac)
|
||||
}
|
||||
|
||||
func pseudoHeaderChecksumRefNoFold(protocol uint8, srcAddr, dstAddr []byte, totalLen uint16) uint16 {
|
||||
sum := checksumRef(srcAddr, 0)
|
||||
sum = checksumRef(dstAddr, sum)
|
||||
sum = checksumRef([]byte{0, protocol}, sum)
|
||||
tmp := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(tmp, totalLen)
|
||||
return checksumRef(tmp, sum)
|
||||
}
|
||||
|
||||
func TestChecksum(t *testing.T) {
|
||||
for length := 0; length <= 9001; length++ {
|
||||
buf := make([]byte, length)
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
rng.Read(buf)
|
||||
csum := checksum(buf, 0x1234)
|
||||
csumRef := checksumRef(buf, 0x1234)
|
||||
if csum != csumRef {
|
||||
t.Error("Expected checksum", csumRef, "got", csum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPseudoHeaderChecksum(t *testing.T) {
|
||||
for _, addrLen := range []int{4, 16} {
|
||||
for length := 0; length <= 9001; length++ {
|
||||
srcAddr := make([]byte, addrLen)
|
||||
dstAddr := make([]byte, addrLen)
|
||||
buf := make([]byte, length)
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
rng.Read(srcAddr)
|
||||
rng.Read(dstAddr)
|
||||
rng.Read(buf)
|
||||
phSum := pseudoHeaderChecksumNoFold(unix.IPPROTO_TCP, srcAddr, dstAddr, uint16(length))
|
||||
csum := checksum(buf, phSum)
|
||||
phSumRef := pseudoHeaderChecksumRefNoFold(unix.IPPROTO_TCP, srcAddr, dstAddr, uint16(length))
|
||||
csumRef := checksumRef(buf, phSumRef)
|
||||
if csum != csumRef {
|
||||
t.Error("Expected checksumRef", csumRef, "got", csum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChecksum(b *testing.B) {
|
||||
lengths := []int{
|
||||
64,
|
||||
128,
|
||||
256,
|
||||
512,
|
||||
1024,
|
||||
1500,
|
||||
2048,
|
||||
4096,
|
||||
8192,
|
||||
9000,
|
||||
9001,
|
||||
}
|
||||
|
||||
for _, length := range lengths {
|
||||
b.Run(fmt.Sprintf("%d", length), func(b *testing.B) {
|
||||
buf := make([]byte, length)
|
||||
rng := rand.New(rand.NewSource(1))
|
||||
rng.Read(buf)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
checksum(buf, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,9 @@ import (
|
|||
"errors"
|
||||
)
|
||||
|
||||
// ErrTooManySegments is returned by Device.Read() when segmentation
|
||||
// overflows the length of supplied buffers. This error should not cause
|
||||
// reads to cease.
|
||||
var ErrTooManySegments = errors.New("too many segments")
|
||||
var (
|
||||
// ErrTooManySegments is returned by Device.Read() when segmentation
|
||||
// overflows the length of supplied buffers. This error should not cause
|
||||
// reads to cease.
|
||||
ErrTooManySegments = errors.New("too many segments")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,579 +0,0 @@
|
|||
//go:build ignore
|
||||
|
||||
//go:generate go run generate_amd64.go -out checksum_generated_amd64.s -stubs checksum_generated_amd64.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/bits"
|
||||
|
||||
. "github.com/mmcloughlin/avo/build"
|
||||
"github.com/mmcloughlin/avo/operand"
|
||||
"github.com/mmcloughlin/avo/reg"
|
||||
)
|
||||
|
||||
const checksumSignature = "func(b []byte, initial uint16) uint16"
|
||||
|
||||
func loadParams() (accum, buf, n reg.GPVirtual) {
|
||||
accum, buf, n = GP64(), GP64(), GP64()
|
||||
Load(Param("initial"), accum)
|
||||
XCHGB(accum.As8H(), accum.As8L())
|
||||
Load(Param("b").Base(), buf)
|
||||
Load(Param("b").Len(), n)
|
||||
return
|
||||
}
|
||||
|
||||
type simdStrategy int
|
||||
|
||||
const (
|
||||
sse2 = iota
|
||||
avx2
|
||||
)
|
||||
|
||||
const tinyBufferSize = 31 // A buffer is tiny if it has at most 31 bytes.
|
||||
|
||||
func generateSIMDChecksum(name, doc string, minSIMDSize, chains int, strategy simdStrategy) {
|
||||
TEXT(name, NOSPLIT|NOFRAME, checksumSignature)
|
||||
Pragma("noescape")
|
||||
Doc(doc)
|
||||
|
||||
accum64, buf, n := loadParams()
|
||||
|
||||
handleOddLength(n, buf, accum64)
|
||||
// no chance of overflow because accum64 was initialized by a uint16 and
|
||||
// handleOddLength adds at most a uint8
|
||||
handleTinyBuffers(n, buf, accum64, operand.LabelRef("foldAndReturn"), operand.LabelRef("bufferIsNotTiny"))
|
||||
Label("bufferIsNotTiny")
|
||||
|
||||
const simdReadSize = 16
|
||||
|
||||
if minSIMDSize > tinyBufferSize {
|
||||
Comment("skip all SIMD for small buffers")
|
||||
if minSIMDSize <= math.MaxUint8 {
|
||||
CMPQ(n, operand.U8(minSIMDSize))
|
||||
} else {
|
||||
CMPQ(n, operand.U32(minSIMDSize))
|
||||
}
|
||||
JGE(operand.LabelRef("startSIMD"))
|
||||
|
||||
handleRemaining(n, buf, accum64, minSIMDSize-1)
|
||||
JMP(operand.LabelRef("foldAndReturn"))
|
||||
}
|
||||
|
||||
Label("startSIMD")
|
||||
|
||||
// chains is the number of accumulators to use. This improves speed via
|
||||
// reduced data dependency. We combine the accumulators once when the big
|
||||
// loop is complete.
|
||||
simdAccumulate := make([]reg.VecVirtual, chains)
|
||||
for i := range simdAccumulate {
|
||||
switch strategy {
|
||||
case sse2:
|
||||
simdAccumulate[i] = XMM()
|
||||
PXOR(simdAccumulate[i], simdAccumulate[i])
|
||||
case avx2:
|
||||
simdAccumulate[i] = YMM()
|
||||
VPXOR(simdAccumulate[i], simdAccumulate[i], simdAccumulate[i])
|
||||
}
|
||||
}
|
||||
var zero reg.VecVirtual
|
||||
if strategy == sse2 {
|
||||
zero = XMM()
|
||||
PXOR(zero, zero)
|
||||
}
|
||||
|
||||
// Number of loads per big loop
|
||||
const unroll = 16
|
||||
// Number of bytes
|
||||
loopSize := uint64(simdReadSize * unroll)
|
||||
if bits.Len64(loopSize) != bits.Len64(loopSize-1)+1 {
|
||||
panic("loopSize is not a power of 2")
|
||||
}
|
||||
loopCount := GP64()
|
||||
|
||||
MOVQ(n, loopCount)
|
||||
Comment("Update number of bytes remaining after the loop completes")
|
||||
ANDQ(operand.Imm(loopSize-1), n)
|
||||
Comment(fmt.Sprintf("Number of %d byte iterations", loopSize))
|
||||
SHRQ(operand.Imm(uint64(bits.Len64(loopSize-1))), loopCount)
|
||||
JZ(operand.LabelRef("smallLoop"))
|
||||
Label("bigLoop")
|
||||
for i := 0; i < unroll; i++ {
|
||||
chain := i % chains
|
||||
switch strategy {
|
||||
case sse2:
|
||||
sse2AccumulateStep(i*simdReadSize, buf, zero, simdAccumulate[chain], simdAccumulate[(chain+chains/2)%chains])
|
||||
case avx2:
|
||||
avx2AccumulateStep(i*simdReadSize, buf, simdAccumulate[chain])
|
||||
}
|
||||
}
|
||||
ADDQ(operand.U32(loopSize), buf)
|
||||
DECQ(loopCount)
|
||||
JNZ(operand.LabelRef("bigLoop"))
|
||||
|
||||
Label("bigCleanup")
|
||||
|
||||
CMPQ(n, operand.Imm(uint64(simdReadSize)))
|
||||
JLT(operand.LabelRef("doneSmallLoop"))
|
||||
|
||||
Commentf("now read a single %d byte unit of data at a time", simdReadSize)
|
||||
Label("smallLoop")
|
||||
|
||||
switch strategy {
|
||||
case sse2:
|
||||
sse2AccumulateStep(0, buf, zero, simdAccumulate[0], simdAccumulate[1])
|
||||
case avx2:
|
||||
avx2AccumulateStep(0, buf, simdAccumulate[0])
|
||||
}
|
||||
ADDQ(operand.Imm(uint64(simdReadSize)), buf)
|
||||
SUBQ(operand.Imm(uint64(simdReadSize)), n)
|
||||
CMPQ(n, operand.Imm(uint64(simdReadSize)))
|
||||
JGE(operand.LabelRef("smallLoop"))
|
||||
|
||||
Label("doneSmallLoop")
|
||||
CMPQ(n, operand.Imm(0))
|
||||
JE(operand.LabelRef("doneSIMD"))
|
||||
|
||||
Commentf("There are between 1 and %d bytes remaining. Perform an overlapped read.", simdReadSize-1)
|
||||
|
||||
maskDataPtr := GP64()
|
||||
LEAQ(operand.NewDataAddr(operand.NewStaticSymbol("xmmLoadMasks"), 0), maskDataPtr)
|
||||
dataAddr := operand.Mem{Index: n, Scale: 1, Base: buf, Disp: -simdReadSize}
|
||||
// scale 8 is only correct here because n is guaranteed to be even and we
|
||||
// do not generate masks for odd lengths
|
||||
maskAddr := operand.Mem{Base: maskDataPtr, Index: n, Scale: 8, Disp: -16}
|
||||
remainder := XMM()
|
||||
|
||||
switch strategy {
|
||||
case sse2:
|
||||
MOVOU(dataAddr, remainder)
|
||||
PAND(maskAddr, remainder)
|
||||
low := XMM()
|
||||
MOVOA(remainder, low)
|
||||
PUNPCKHWL(zero, remainder)
|
||||
PUNPCKLWL(zero, low)
|
||||
PADDD(remainder, simdAccumulate[0])
|
||||
PADDD(low, simdAccumulate[1])
|
||||
case avx2:
|
||||
// Note: this is very similar to the sse2 path but MOVOU has a massive
|
||||
// performance hit if used here, presumably due to switching between SSE
|
||||
// and AVX2 modes.
|
||||
VMOVDQU(dataAddr, remainder)
|
||||
VPAND(maskAddr, remainder, remainder)
|
||||
|
||||
temp := YMM()
|
||||
VPMOVZXWD(remainder, temp)
|
||||
VPADDD(temp, simdAccumulate[0], simdAccumulate[0])
|
||||
}
|
||||
|
||||
Label("doneSIMD")
|
||||
|
||||
Comment("Multi-chain loop is done, combine the accumulators")
|
||||
for i := range simdAccumulate {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
switch strategy {
|
||||
case sse2:
|
||||
PADDD(simdAccumulate[i], simdAccumulate[0])
|
||||
case avx2:
|
||||
VPADDD(simdAccumulate[i], simdAccumulate[0], simdAccumulate[0])
|
||||
}
|
||||
}
|
||||
|
||||
if strategy == avx2 {
|
||||
Comment("extract the YMM into a pair of XMM and sum them")
|
||||
tmp := YMM()
|
||||
VEXTRACTI128(operand.Imm(1), simdAccumulate[0], tmp.AsX())
|
||||
|
||||
xAccumulate := XMM()
|
||||
VPADDD(simdAccumulate[0].AsX(), tmp.AsX(), xAccumulate)
|
||||
simdAccumulate = []reg.VecVirtual{xAccumulate}
|
||||
}
|
||||
|
||||
Comment("extract the XMM into GP64")
|
||||
low, high := GP64(), GP64()
|
||||
switch strategy {
|
||||
case sse2:
|
||||
MOVQ(simdAccumulate[0], low)
|
||||
PSRLDQ(operand.Imm(8), simdAccumulate[0])
|
||||
MOVQ(simdAccumulate[0], high)
|
||||
case avx2:
|
||||
VPEXTRQ(operand.Imm(0), simdAccumulate[0], low)
|
||||
VPEXTRQ(operand.Imm(1), simdAccumulate[0], high)
|
||||
|
||||
Comment("no more AVX code, clear upper registers to avoid SSE slowdowns")
|
||||
VZEROUPPER()
|
||||
}
|
||||
ADDQ(low, accum64)
|
||||
ADCQ(high, accum64)
|
||||
Label("foldAndReturn")
|
||||
foldWithCF(accum64, strategy == avx2)
|
||||
XCHGB(accum64.As8H(), accum64.As8L())
|
||||
Store(accum64.As16(), ReturnIndex(0))
|
||||
RET()
|
||||
}
|
||||
|
||||
// handleOddLength generates instructions to incorporate the last byte into
|
||||
// accum64 if the length is odd. CF may be set if accum64 overflows; be sure to
|
||||
// handle that if overflow is possible.
|
||||
func handleOddLength(n, buf, accum64 reg.GPVirtual) {
|
||||
Comment("handle odd length buffers; they are difficult to handle in general")
|
||||
TESTQ(operand.U32(1), n)
|
||||
JZ(operand.LabelRef("lengthIsEven"))
|
||||
|
||||
tmp := GP64()
|
||||
MOVBQZX(operand.Mem{Base: buf, Index: n, Scale: 1, Disp: -1}, tmp)
|
||||
DECQ(n)
|
||||
ADDQ(tmp, accum64)
|
||||
|
||||
Label("lengthIsEven")
|
||||
}
|
||||
|
||||
func sse2AccumulateStep(offset int, buf reg.GPVirtual, zero, accumulate1, accumulate2 reg.VecVirtual) {
|
||||
high, low := XMM(), XMM()
|
||||
MOVOU(operand.Mem{Disp: offset, Base: buf}, high)
|
||||
MOVOA(high, low)
|
||||
PUNPCKHWL(zero, high)
|
||||
PUNPCKLWL(zero, low)
|
||||
PADDD(high, accumulate1)
|
||||
PADDD(low, accumulate2)
|
||||
}
|
||||
|
||||
func avx2AccumulateStep(offset int, buf reg.GPVirtual, accumulate reg.VecVirtual) {
|
||||
tmp := YMM()
|
||||
VPMOVZXWD(operand.Mem{Disp: offset, Base: buf}, tmp)
|
||||
VPADDD(tmp, accumulate, accumulate)
|
||||
}
|
||||
|
||||
func generateAMD64Checksum(name, doc string) {
|
||||
TEXT(name, NOSPLIT|NOFRAME, checksumSignature)
|
||||
Pragma("noescape")
|
||||
Doc(doc)
|
||||
|
||||
accum64, buf, n := loadParams()
|
||||
|
||||
handleOddLength(n, buf, accum64)
|
||||
// no chance of overflow because accum64 was initialized by a uint16 and
|
||||
// handleOddLength adds at most a uint8
|
||||
handleTinyBuffers(n, buf, accum64, operand.LabelRef("foldAndReturn"), operand.LabelRef("bufferIsNotTiny"))
|
||||
Label("bufferIsNotTiny")
|
||||
|
||||
const (
|
||||
// numChains is the number of accumulators and carry counters to use.
|
||||
// This improves speed via reduced data dependency. We combine the
|
||||
// accumulators and carry counters once when the loop is complete.
|
||||
numChains = 4
|
||||
unroll = 32 // The number of 64-bit reads to perform per iteration of the loop.
|
||||
loopSize = 8 * unroll // The number of bytes read per iteration of the loop.
|
||||
)
|
||||
if bits.Len(loopSize) != bits.Len(loopSize-1)+1 {
|
||||
panic("loopSize is not a power of 2")
|
||||
}
|
||||
loopCount := GP64()
|
||||
|
||||
Comment(fmt.Sprintf("Number of %d byte iterations into loop counter", loopSize))
|
||||
MOVQ(n, loopCount)
|
||||
Comment("Update number of bytes remaining after the loop completes")
|
||||
ANDQ(operand.Imm(loopSize-1), n)
|
||||
SHRQ(operand.Imm(uint64(bits.Len(loopSize-1))), loopCount)
|
||||
JZ(operand.LabelRef("startCleanup"))
|
||||
CLC()
|
||||
|
||||
chains := make([]struct {
|
||||
accum reg.GPVirtual
|
||||
carries reg.GPVirtual
|
||||
}, numChains)
|
||||
for i := range chains {
|
||||
if i == 0 {
|
||||
chains[i].accum = accum64
|
||||
} else {
|
||||
chains[i].accum = GP64()
|
||||
XORQ(chains[i].accum, chains[i].accum)
|
||||
}
|
||||
chains[i].carries = GP64()
|
||||
XORQ(chains[i].carries, chains[i].carries)
|
||||
}
|
||||
|
||||
Label("bigLoop")
|
||||
|
||||
var curChain int
|
||||
for i := 0; i < unroll; i++ {
|
||||
// It is significantly faster to use a ADCX/ADOX pair instead of plain
|
||||
// ADC, which results in two dependency chains, however those require
|
||||
// ADX support, which was added after AVX2. If AVX2 is available, that's
|
||||
// even better than ADCX/ADOX.
|
||||
//
|
||||
// However, multiple dependency chains using multiple accumulators and
|
||||
// occasionally storing CF into temporary counters seems to work almost
|
||||
// as well.
|
||||
addr := operand.Mem{Disp: i * 8, Base: buf}
|
||||
|
||||
if i%4 == 0 {
|
||||
if i > 0 {
|
||||
ADCQ(operand.Imm(0), chains[curChain].carries)
|
||||
curChain = (curChain + 1) % len(chains)
|
||||
}
|
||||
ADDQ(addr, chains[curChain].accum)
|
||||
} else {
|
||||
ADCQ(addr, chains[curChain].accum)
|
||||
}
|
||||
}
|
||||
ADCQ(operand.Imm(0), chains[curChain].carries)
|
||||
ADDQ(operand.U32(loopSize), buf)
|
||||
SUBQ(operand.Imm(1), loopCount)
|
||||
JNZ(operand.LabelRef("bigLoop"))
|
||||
for i := range chains {
|
||||
if i == 0 {
|
||||
ADDQ(chains[i].carries, accum64)
|
||||
continue
|
||||
}
|
||||
ADCQ(chains[i].accum, accum64)
|
||||
ADCQ(chains[i].carries, accum64)
|
||||
}
|
||||
|
||||
accumulateCF(accum64)
|
||||
|
||||
Label("startCleanup")
|
||||
handleRemaining(n, buf, accum64, loopSize-1)
|
||||
Label("foldAndReturn")
|
||||
foldWithCF(accum64, false)
|
||||
|
||||
XCHGB(accum64.As8H(), accum64.As8L())
|
||||
Store(accum64.As16(), ReturnIndex(0))
|
||||
RET()
|
||||
}
|
||||
|
||||
// handleTinyBuffers computes checksums if the buffer length (the n parameter)
|
||||
// is less than 32. After computing the checksum, a jump to returnLabel will
|
||||
// be executed. Otherwise, if the buffer length is at least 32, nothing will be
|
||||
// modified; a jump to continueLabel will be executed instead.
|
||||
//
|
||||
// When jumping to returnLabel, CF may be set and must be accommodated e.g.
|
||||
// using foldWithCF or accumulateCF.
|
||||
//
|
||||
// Anecdotally, this appears to be faster than attempting to coordinate an
|
||||
// overlapped read (which would also require special handling for buffers
|
||||
// smaller than 8).
|
||||
func handleTinyBuffers(n, buf, accum reg.GPVirtual, returnLabel, continueLabel operand.LabelRef) {
|
||||
Comment("handle tiny buffers (<=31 bytes) specially")
|
||||
CMPQ(n, operand.Imm(tinyBufferSize))
|
||||
JGT(continueLabel)
|
||||
|
||||
tmp2, tmp4, tmp8 := GP64(), GP64(), GP64()
|
||||
XORQ(tmp2, tmp2)
|
||||
XORQ(tmp4, tmp4)
|
||||
XORQ(tmp8, tmp8)
|
||||
|
||||
Comment("shift twice to start because length is guaranteed to be even",
|
||||
"n = n >> 2; CF = originalN & 2")
|
||||
SHRQ(operand.Imm(2), n)
|
||||
JNC(operand.LabelRef("handleTiny4"))
|
||||
Comment("tmp2 = binary.LittleEndian.Uint16(buf[:2]); buf = buf[2:]")
|
||||
MOVWQZX(operand.Mem{Base: buf}, tmp2)
|
||||
ADDQ(operand.Imm(2), buf)
|
||||
|
||||
Label("handleTiny4")
|
||||
Comment("n = n >> 1; CF = originalN & 4")
|
||||
SHRQ(operand.Imm(1), n)
|
||||
JNC(operand.LabelRef("handleTiny8"))
|
||||
Comment("tmp4 = binary.LittleEndian.Uint32(buf[:4]); buf = buf[4:]")
|
||||
MOVLQZX(operand.Mem{Base: buf}, tmp4)
|
||||
ADDQ(operand.Imm(4), buf)
|
||||
|
||||
Label("handleTiny8")
|
||||
Comment("n = n >> 1; CF = originalN & 8")
|
||||
SHRQ(operand.Imm(1), n)
|
||||
JNC(operand.LabelRef("handleTiny16"))
|
||||
Comment("tmp8 = binary.LittleEndian.Uint64(buf[:8]); buf = buf[8:]")
|
||||
MOVQ(operand.Mem{Base: buf}, tmp8)
|
||||
ADDQ(operand.Imm(8), buf)
|
||||
|
||||
Label("handleTiny16")
|
||||
Comment("n = n >> 1; CF = originalN & 16",
|
||||
"n == 0 now, otherwise we would have branched after comparing with tinyBufferSize")
|
||||
SHRQ(operand.Imm(1), n)
|
||||
JNC(operand.LabelRef("handleTinyFinish"))
|
||||
ADDQ(operand.Mem{Base: buf}, accum)
|
||||
ADCQ(operand.Mem{Base: buf, Disp: 8}, accum)
|
||||
|
||||
Label("handleTinyFinish")
|
||||
Comment("CF should be included from the previous add, so we use ADCQ.",
|
||||
"If we arrived via the JNC above, then CF=0 due to the branch condition,",
|
||||
"so ADCQ will still produce the correct result.")
|
||||
ADCQ(tmp2, accum)
|
||||
ADCQ(tmp4, accum)
|
||||
ADCQ(tmp8, accum)
|
||||
|
||||
JMP(returnLabel)
|
||||
}
|
||||
|
||||
// handleRemaining generates a series of conditional unrolled additions,
|
||||
// starting with 8 bytes long and doubling each time until the length reaches
|
||||
// max. This is the reverse order of what may be intuitive, but makes the branch
|
||||
// conditions convenient to compute: perform one right shift each time and test
|
||||
// against CF.
|
||||
//
|
||||
// When done, CF may be set and must be accommodated e.g., using foldWithCF or
|
||||
// accumulateCF.
|
||||
//
|
||||
// If n is not a multiple of 8, an extra 64 bit read at the end of the buffer
|
||||
// will be performed, overlapping with data that will be read later. The
|
||||
// duplicate data will be shifted off.
|
||||
//
|
||||
// The original buffer length must have been at least 8 bytes long, even if
|
||||
// n < 8, otherwise this will access memory before the start of the buffer,
|
||||
// which may be unsafe.
|
||||
func handleRemaining(n, buf, accum64 reg.GPVirtual, max int) {
|
||||
Comment("Accumulate carries in this register. It is never expected to overflow.")
|
||||
carries := GP64()
|
||||
XORQ(carries, carries)
|
||||
|
||||
Comment("We will perform an overlapped read for buffers with length not a multiple of 8.",
|
||||
"Overlapped in this context means some memory will be read twice, but a shift will",
|
||||
"eliminate the duplicated data. This extra read is performed at the end of the buffer to",
|
||||
"preserve any alignment that may exist for the start of the buffer.")
|
||||
leftover := reg.RCX
|
||||
MOVQ(n, leftover)
|
||||
SHRQ(operand.Imm(3), n) // n is now the number of 64 bit reads remaining
|
||||
ANDQ(operand.Imm(0x7), leftover) // leftover is now the number of bytes to read from the end
|
||||
JZ(operand.LabelRef("handleRemaining8"))
|
||||
endBuf := GP64()
|
||||
// endBuf is the position near the end of the buffer that is just past the
|
||||
// last multiple of 8: (buf + len(buf)) & ^0x7
|
||||
LEAQ(operand.Mem{Base: buf, Index: n, Scale: 8}, endBuf)
|
||||
|
||||
overlapRead := GP64()
|
||||
// equivalent to overlapRead = binary.LittleEndian.Uint64(buf[len(buf)-8:len(buf)])
|
||||
MOVQ(operand.Mem{Base: endBuf, Index: leftover, Scale: 1, Disp: -8}, overlapRead)
|
||||
|
||||
Comment("Shift out the duplicated data: overlapRead = overlapRead >> (64 - leftoverBytes*8)")
|
||||
SHLQ(operand.Imm(3), leftover) // leftover = leftover * 8
|
||||
NEGQ(leftover) // leftover = -leftover; this completes the (-leftoverBytes*8) part of the expression
|
||||
ADDQ(operand.Imm(64), leftover) // now we have (64 - leftoverBytes*8)
|
||||
SHRQ(reg.CL, overlapRead) // shift right by (64 - leftoverBytes*8); CL is the low 8 bits of leftover (set to RCX above) and variable shift only accepts CL
|
||||
|
||||
ADDQ(overlapRead, accum64)
|
||||
ADCQ(operand.Imm(0), carries)
|
||||
|
||||
for curBytes := 8; curBytes <= max; curBytes *= 2 {
|
||||
Label(fmt.Sprintf("handleRemaining%d", curBytes))
|
||||
SHRQ(operand.Imm(1), n)
|
||||
if curBytes*2 <= max {
|
||||
JNC(operand.LabelRef(fmt.Sprintf("handleRemaining%d", curBytes*2)))
|
||||
} else {
|
||||
JNC(operand.LabelRef("handleRemainingComplete"))
|
||||
}
|
||||
|
||||
numLoads := curBytes / 8
|
||||
for i := 0; i < numLoads; i++ {
|
||||
addr := operand.Mem{Base: buf, Disp: i * 8}
|
||||
// It is possible to add the multiple dependency chains trick here
|
||||
// that generateAMD64Checksum uses but anecdotally it does not
|
||||
// appear to outweigh the cost.
|
||||
if i == 0 {
|
||||
ADDQ(addr, accum64)
|
||||
continue
|
||||
}
|
||||
ADCQ(addr, accum64)
|
||||
}
|
||||
ADCQ(operand.Imm(0), carries)
|
||||
|
||||
if curBytes > math.MaxUint8 {
|
||||
ADDQ(operand.U32(uint64(curBytes)), buf)
|
||||
} else {
|
||||
ADDQ(operand.U8(uint64(curBytes)), buf)
|
||||
}
|
||||
if curBytes*2 >= max {
|
||||
continue
|
||||
}
|
||||
JMP(operand.LabelRef(fmt.Sprintf("handleRemaining%d", curBytes*2)))
|
||||
}
|
||||
Label("handleRemainingComplete")
|
||||
ADDQ(carries, accum64)
|
||||
}
|
||||
|
||||
func accumulateCF(accum64 reg.GPVirtual) {
|
||||
Comment("accumulate CF (twice, in case the first time overflows)")
|
||||
// accum64 += CF
|
||||
ADCQ(operand.Imm(0), accum64)
|
||||
// accum64 += CF again if the previous add overflowed. The previous add was
|
||||
// 0 or 1. If it overflowed, then accum64 == 0, so adding another 1 can
|
||||
// never overflow.
|
||||
ADCQ(operand.Imm(0), accum64)
|
||||
}
|
||||
|
||||
// foldWithCF generates instructions to fold accum (a GP64) into a 16-bit value
|
||||
// according to ones-complement arithmetic. BMI2 instructions will be used if
|
||||
// allowBMI2 is true (requires fewer instructions).
|
||||
func foldWithCF(accum reg.GPVirtual, allowBMI2 bool) {
|
||||
Comment("add CF and fold")
|
||||
|
||||
// CF|accum max value starts as 0x1_ffff_ffff_ffff_ffff
|
||||
|
||||
tmp := GP64()
|
||||
if allowBMI2 {
|
||||
// effectively, tmp = accum >> 32 (technically, this is a rotate)
|
||||
RORXQ(operand.Imm(32), accum, tmp)
|
||||
// accum as uint32 = uint32(accum) + uint32(tmp64) + CF; max value 0xffff_ffff + CF set
|
||||
ADCL(tmp.As32(), accum.As32())
|
||||
// effectively, tmp64 as uint32 = uint32(accum) >> 16 (also a rotate)
|
||||
RORXL(operand.Imm(16), accum.As32(), tmp.As32())
|
||||
// accum as uint16 = uint16(accum) + uint16(tmp) + CF; max value 0xffff + CF unset or 0xfffe + CF set
|
||||
ADCW(tmp.As16(), accum.As16())
|
||||
} else {
|
||||
// tmp = uint32(accum); max value 0xffff_ffff
|
||||
// MOVL clears the upper 32 bits of a GP64 so this is equivalent to the
|
||||
// non-existent MOVLQZX.
|
||||
MOVL(accum.As32(), tmp.As32())
|
||||
// tmp += CF; max value 0x1_0000_0000, CF unset
|
||||
ADCQ(operand.Imm(0), tmp)
|
||||
// accum = accum >> 32; max value 0xffff_ffff
|
||||
SHRQ(operand.Imm(32), accum)
|
||||
// accum = accum + tmp; max value 0x1_ffff_ffff + CF unset
|
||||
ADDQ(tmp, accum)
|
||||
// tmp = uint16(accum); max value 0xffff
|
||||
MOVWQZX(accum.As16(), tmp)
|
||||
// accum = accum >> 16; max value 0x1_ffff
|
||||
SHRQ(operand.Imm(16), accum)
|
||||
// accum = accum + tmp; max value 0x2_fffe + CF unset
|
||||
ADDQ(tmp, accum)
|
||||
// tmp as uint16 = uint16(accum); max value 0xffff
|
||||
MOVW(accum.As16(), tmp.As16())
|
||||
// accum = accum >> 16; max value 0x2
|
||||
SHRQ(operand.Imm(16), accum)
|
||||
// accum as uint16 = uint16(accum) + uint16(tmp); max value 0xffff + CF unset or 0x2 + CF set
|
||||
ADDW(tmp.As16(), accum.As16())
|
||||
}
|
||||
// accum as uint16 += CF; will not overflow: either CF was 0 or accum <= 0xfffe
|
||||
ADCW(operand.Imm(0), accum.As16())
|
||||
}
|
||||
|
||||
func generateLoadMasks() {
|
||||
var offset int
|
||||
// xmmLoadMasks is a table of masks that can be used with PAND to zero all but the last N bytes in an XMM, N=2,4,6,8,10,12,14
|
||||
GLOBL("xmmLoadMasks", RODATA|NOPTR)
|
||||
|
||||
for n := 2; n < 16; n += 2 {
|
||||
var pattern [16]byte
|
||||
for i := 0; i < len(pattern); i++ {
|
||||
if i < len(pattern)-n {
|
||||
pattern[i] = 0
|
||||
continue
|
||||
}
|
||||
pattern[i] = 0xff
|
||||
}
|
||||
DATA(offset, operand.String(pattern[:]))
|
||||
offset += len(pattern)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
generateLoadMasks()
|
||||
generateSIMDChecksum("checksumAVX2", "checksumAVX2 computes an IP checksum using amd64 v3 instructions (AVX2, BMI2)", 256, 4, avx2)
|
||||
generateSIMDChecksum("checksumSSE2", "checksumSSE2 computes an IP checksum using amd64 baseline instructions (SSE2)", 256, 4, sse2)
|
||||
generateAMD64Checksum("checksumAMD64", "checksumAMD64 computes an IP checksum using amd64 baseline instructions")
|
||||
Generate()
|
||||
}
|
||||
54
tun/netstack/examples/http_client.go
Normal file
54
tun/netstack/examples/http_client.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
//go:build ignore
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tun, tnet, err := netstack.CreateNetTUN(
|
||||
[]netip.Addr{netip.MustParseAddr("192.168.4.28")},
|
||||
[]netip.Addr{netip.MustParseAddr("8.8.8.8")},
|
||||
1420)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))
|
||||
err = dev.IpcSet(`private_key=087ec6e14bbed210e7215cdc73468dfa23f080a1bfb8665b2fd809bd99d28379
|
||||
public_key=c4c8e984c5322c8184c72265b92b250fdb63688705f504ba003c88f03393cf28
|
||||
allowed_ip=0.0.0.0/0
|
||||
endpoint=127.0.0.1:58120
|
||||
`)
|
||||
err = dev.Up()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: tnet.DialContext,
|
||||
},
|
||||
}
|
||||
resp, err := client.Get("http://192.168.4.29/")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
log.Println(string(body))
|
||||
}
|
||||
51
tun/netstack/examples/http_server.go
Normal file
51
tun/netstack/examples/http_server.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//go:build ignore
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tun, tnet, err := netstack.CreateNetTUN(
|
||||
[]netip.Addr{netip.MustParseAddr("192.168.4.29")},
|
||||
[]netip.Addr{netip.MustParseAddr("8.8.8.8"), netip.MustParseAddr("8.8.4.4")},
|
||||
1420,
|
||||
)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))
|
||||
dev.IpcSet(`private_key=003ed5d73b55806c30de3f8a7bdab38af13539220533055e635690b8b87ad641
|
||||
listen_port=58120
|
||||
public_key=f928d4f6c1b86c12f2562c10b07c555c5c57fd00f59e90c8d8d88767271cbf7c
|
||||
allowed_ip=192.168.4.28/32
|
||||
persistent_keepalive_interval=25
|
||||
`)
|
||||
dev.Up()
|
||||
listener, err := tnet.ListenTCP(&net.TCPAddr{Port: 80})
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
log.Printf("> %s - %s - %s", request.RemoteAddr, request.URL.String(), request.UserAgent())
|
||||
io.WriteString(writer, "Hello from userspace TCP!")
|
||||
})
|
||||
err = http.Serve(listener, nil)
|
||||
if err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
}
|
||||
75
tun/netstack/examples/ping_client.go
Normal file
75
tun/netstack/examples/ping_client.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
//go:build ignore
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/device"
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tun, tnet, err := netstack.CreateNetTUN(
|
||||
[]netip.Addr{netip.MustParseAddr("192.168.4.29")},
|
||||
[]netip.Addr{netip.MustParseAddr("8.8.8.8")},
|
||||
1420)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))
|
||||
dev.IpcSet(`private_key=a8dac1d8a70a751f0f699fb14ba1cff7b79cf4fbd8f09f44c6e6a90d0369604f
|
||||
public_key=25123c5dcd3328ff645e4f2a3fce0d754400d3887a0cb7c56f0267e20fbf3c5b
|
||||
endpoint=163.172.161.0:12912
|
||||
allowed_ip=0.0.0.0/0
|
||||
`)
|
||||
err = dev.Up()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
socket, err := tnet.Dial("ping4", "zx2c4.com")
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
requestPing := icmp.Echo{
|
||||
Seq: rand.Intn(1 << 16),
|
||||
Data: []byte("gopher burrow"),
|
||||
}
|
||||
icmpBytes, _ := (&icmp.Message{Type: ipv4.ICMPTypeEcho, Code: 0, Body: &requestPing}).Marshal(nil)
|
||||
socket.SetReadDeadline(time.Now().Add(time.Second * 10))
|
||||
start := time.Now()
|
||||
_, err = socket.Write(icmpBytes)
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
n, err := socket.Read(icmpBytes[:])
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
replyPacket, err := icmp.ParseMessage(1, icmpBytes[:n])
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
replyPing, ok := replyPacket.Body.(*icmp.Echo)
|
||||
if !ok {
|
||||
log.Panicf("invalid reply type: %v", replyPacket)
|
||||
}
|
||||
if !bytes.Equal(replyPing.Data, requestPing.Data) || replyPing.Seq != requestPing.Seq {
|
||||
log.Panicf("invalid ping reply: %v", replyPing)
|
||||
}
|
||||
log.Printf("Ping latency: %v", time.Since(start))
|
||||
}
|
||||
1057
tun/netstack/tun.go
Normal file
1057
tun/netstack/tun.go
Normal file
File diff suppressed because it is too large
Load diff
220
tun/offload.go
220
tun/offload.go
|
|
@ -1,220 +0,0 @@
|
|||
package tun
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// GSOType represents the type of segmentation offload.
|
||||
type GSOType int
|
||||
|
||||
const (
|
||||
GSONone GSOType = iota
|
||||
GSOTCPv4
|
||||
GSOTCPv6
|
||||
GSOUDPL4
|
||||
)
|
||||
|
||||
func (g GSOType) String() string {
|
||||
switch g {
|
||||
case GSONone:
|
||||
return "GSONone"
|
||||
case GSOTCPv4:
|
||||
return "GSOTCPv4"
|
||||
case GSOTCPv6:
|
||||
return "GSOTCPv6"
|
||||
case GSOUDPL4:
|
||||
return "GSOUDPL4"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// GSOOptions is loosely modeled after struct virtio_net_hdr from the VIRTIO
|
||||
// specification. It is a common representation of GSO metadata that can be
|
||||
// applied to support packet GSO across tun.Device implementations.
|
||||
type GSOOptions struct {
|
||||
// GSOType represents the type of segmentation offload.
|
||||
GSOType GSOType
|
||||
// HdrLen is the sum of the layer 3 and 4 header lengths. This field may be
|
||||
// zero when GSOType == GSONone.
|
||||
HdrLen uint16
|
||||
// CsumStart is the head byte index of the packet data to be checksummed,
|
||||
// i.e. the start of the TCP or UDP header.
|
||||
CsumStart uint16
|
||||
// CsumOffset is the offset from CsumStart where the 2-byte checksum value
|
||||
// should be placed.
|
||||
CsumOffset uint16
|
||||
// GSOSize is the size of each segment exclusive of HdrLen. The tail segment
|
||||
// may be smaller than this value.
|
||||
GSOSize uint16
|
||||
// NeedsCsum may be set where GSOType == GSONone. When set, the checksum
|
||||
// at CsumStart + CsumOffset must be a partial checksum, i.e. the
|
||||
// pseudo-header sum.
|
||||
NeedsCsum bool
|
||||
}
|
||||
|
||||
const (
|
||||
ipv4SrcAddrOffset = 12
|
||||
ipv6SrcAddrOffset = 8
|
||||
)
|
||||
|
||||
const tcpFlagsOffset = 13
|
||||
|
||||
const (
|
||||
tcpFlagFIN uint8 = 0x01
|
||||
tcpFlagPSH uint8 = 0x08
|
||||
tcpFlagACK uint8 = 0x10
|
||||
)
|
||||
|
||||
const (
|
||||
// defined here in order to avoid importation of any platform-specific pkgs
|
||||
ipProtoTCP = 6
|
||||
ipProtoUDP = 17
|
||||
)
|
||||
|
||||
// GSOSplit splits packets from 'in' into outBufs[<index>][outOffset:], writing
|
||||
// the size of each element into sizes. It returns the number of buffers
|
||||
// populated, and/or an error. Callers may pass an 'in' slice that overlaps with
|
||||
// the first element of outBuffers, i.e. &in[0] may be equal to
|
||||
// &outBufs[0][outOffset]. GSONone is a valid options.GSOType regardless of the
|
||||
// value of options.NeedsCsum. Length of each outBufs element must be greater
|
||||
// than or equal to the length of 'in', otherwise output may be silently
|
||||
// truncated.
|
||||
func GSOSplit(in []byte, options GSOOptions, outBufs [][]byte, sizes []int, outOffset int) (int, error) {
|
||||
cSumAt := int(options.CsumStart) + int(options.CsumOffset)
|
||||
if cSumAt+1 >= len(in) {
|
||||
return 0, fmt.Errorf("end of checksum offset (%d) exceeds packet length (%d)", cSumAt+1, len(in))
|
||||
}
|
||||
|
||||
if len(in) < int(options.HdrLen) {
|
||||
return 0, fmt.Errorf("length of packet (%d) < GSO HdrLen (%d)", len(in), options.HdrLen)
|
||||
}
|
||||
|
||||
// Handle the conditions where we are copying a single element to outBuffs.
|
||||
payloadLen := len(in) - int(options.HdrLen)
|
||||
if options.GSOType == GSONone || payloadLen < int(options.GSOSize) {
|
||||
if len(in) > len(outBufs[0][outOffset:]) {
|
||||
return 0, fmt.Errorf("length of packet (%d) exceeds output element length (%d)", len(in), len(outBufs[0][outOffset:]))
|
||||
}
|
||||
if options.NeedsCsum {
|
||||
// The initial value at the checksum offset should be summed with
|
||||
// the checksum we compute. This is typically the pseudo-header sum.
|
||||
initial := binary.BigEndian.Uint16(in[cSumAt:])
|
||||
in[cSumAt], in[cSumAt+1] = 0, 0
|
||||
binary.BigEndian.PutUint16(in[cSumAt:], ^Checksum(in[options.CsumStart:], initial))
|
||||
}
|
||||
sizes[0] = copy(outBufs[0][outOffset:], in)
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
if options.HdrLen < options.CsumStart {
|
||||
return 0, fmt.Errorf("GSO HdrLen (%d) < GSO CsumStart (%d)", options.HdrLen, options.CsumStart)
|
||||
}
|
||||
|
||||
ipVersion := in[0] >> 4
|
||||
switch ipVersion {
|
||||
case 4:
|
||||
if options.GSOType != GSOTCPv4 && options.GSOType != GSOUDPL4 {
|
||||
return 0, fmt.Errorf("ip header version: %d, GSO type: %s", ipVersion, options.GSOType)
|
||||
}
|
||||
if len(in) < 20 {
|
||||
return 0, fmt.Errorf("length of packet (%d) < minimum ipv4 header size (%d)", len(in), 20)
|
||||
}
|
||||
case 6:
|
||||
if options.GSOType != GSOTCPv6 && options.GSOType != GSOUDPL4 {
|
||||
return 0, fmt.Errorf("ip header version: %d, GSO type: %s", ipVersion, options.GSOType)
|
||||
}
|
||||
if len(in) < 40 {
|
||||
return 0, fmt.Errorf("length of packet (%d) < minimum ipv6 header size (%d)", len(in), 40)
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid ip header version: %d", ipVersion)
|
||||
}
|
||||
|
||||
iphLen := int(options.CsumStart)
|
||||
srcAddrOffset := ipv6SrcAddrOffset
|
||||
addrLen := 16
|
||||
if ipVersion == 4 {
|
||||
srcAddrOffset = ipv4SrcAddrOffset
|
||||
addrLen = 4
|
||||
}
|
||||
transportCsumAt := int(options.CsumStart + options.CsumOffset)
|
||||
var firstTCPSeqNum uint32
|
||||
var protocol uint8
|
||||
if options.GSOType == GSOTCPv4 || options.GSOType == GSOTCPv6 {
|
||||
protocol = ipProtoTCP
|
||||
if len(in) < int(options.CsumStart)+20 {
|
||||
return 0, fmt.Errorf("length of packet (%d) < GSO CsumStart (%d) + minimum TCP header size (%d)",
|
||||
len(in), options.CsumStart, 20)
|
||||
}
|
||||
firstTCPSeqNum = binary.BigEndian.Uint32(in[options.CsumStart+4:])
|
||||
} else {
|
||||
protocol = ipProtoUDP
|
||||
}
|
||||
nextSegmentDataAt := int(options.HdrLen)
|
||||
i := 0
|
||||
for ; nextSegmentDataAt < len(in); i++ {
|
||||
if i == len(outBufs) {
|
||||
return i - 1, ErrTooManySegments
|
||||
}
|
||||
nextSegmentEnd := nextSegmentDataAt + int(options.GSOSize)
|
||||
if nextSegmentEnd > len(in) {
|
||||
nextSegmentEnd = len(in)
|
||||
}
|
||||
segmentDataLen := nextSegmentEnd - nextSegmentDataAt
|
||||
totalLen := int(options.HdrLen) + segmentDataLen
|
||||
sizes[i] = totalLen
|
||||
out := outBufs[i][outOffset:]
|
||||
|
||||
copy(out, in[:iphLen])
|
||||
if ipVersion == 4 {
|
||||
// For IPv4 we are responsible for incrementing the ID field,
|
||||
// updating the total len field, and recalculating the header
|
||||
// checksum.
|
||||
if i > 0 {
|
||||
id := binary.BigEndian.Uint16(out[4:])
|
||||
id += uint16(i)
|
||||
binary.BigEndian.PutUint16(out[4:], id)
|
||||
}
|
||||
out[10], out[11] = 0, 0 // clear ipv4 header checksum
|
||||
binary.BigEndian.PutUint16(out[2:], uint16(totalLen))
|
||||
ipv4CSum := ^Checksum(out[:iphLen], 0)
|
||||
binary.BigEndian.PutUint16(out[10:], ipv4CSum)
|
||||
} else {
|
||||
// For IPv6 we are responsible for updating the payload length field.
|
||||
binary.BigEndian.PutUint16(out[4:], uint16(totalLen-iphLen))
|
||||
}
|
||||
|
||||
// copy transport header
|
||||
copy(out[options.CsumStart:options.HdrLen], in[options.CsumStart:options.HdrLen])
|
||||
|
||||
if protocol == ipProtoTCP {
|
||||
// set TCP seq and adjust TCP flags
|
||||
tcpSeq := firstTCPSeqNum + uint32(options.GSOSize*uint16(i))
|
||||
binary.BigEndian.PutUint32(out[options.CsumStart+4:], tcpSeq)
|
||||
if nextSegmentEnd != len(in) {
|
||||
// FIN and PSH should only be set on last segment
|
||||
clearFlags := tcpFlagFIN | tcpFlagPSH
|
||||
out[options.CsumStart+tcpFlagsOffset] &^= clearFlags
|
||||
}
|
||||
} else {
|
||||
// set UDP header len
|
||||
binary.BigEndian.PutUint16(out[options.CsumStart+4:], uint16(segmentDataLen)+(options.HdrLen-options.CsumStart))
|
||||
}
|
||||
|
||||
// payload
|
||||
copy(out[options.HdrLen:], in[nextSegmentDataAt:nextSegmentEnd])
|
||||
|
||||
// transport checksum
|
||||
out[transportCsumAt], out[transportCsumAt+1] = 0, 0 // clear tcp/udp checksum
|
||||
transportHeaderLen := int(options.HdrLen - options.CsumStart)
|
||||
lenForPseudo := uint16(transportHeaderLen + segmentDataLen)
|
||||
transportCSum := PseudoHeaderChecksum(protocol, in[srcAddrOffset:srcAddrOffset+addrLen], in[srcAddrOffset+addrLen:srcAddrOffset+addrLen*2], lenForPseudo)
|
||||
transportCSum = ^Checksum(out[options.CsumStart:totalLen], transportCSum)
|
||||
binary.BigEndian.PutUint16(out[options.CsumStart+options.CsumOffset:], transportCSum)
|
||||
|
||||
nextSegmentDataAt += int(options.GSOSize)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
|
@ -9,14 +9,21 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const tcpFlagsOffset = 13
|
||||
|
||||
const (
|
||||
tcpFlagFIN uint8 = 0x01
|
||||
tcpFlagPSH uint8 = 0x08
|
||||
tcpFlagACK uint8 = 0x10
|
||||
)
|
||||
|
||||
// virtioNetHdr is defined in the kernel in include/uapi/linux/virtio_net.h. The
|
||||
// kernel symbol is virtio_net_hdr.
|
||||
type virtioNetHdr struct {
|
||||
|
|
@ -28,30 +35,6 @@ type virtioNetHdr struct {
|
|||
csumOffset uint16
|
||||
}
|
||||
|
||||
func (v *virtioNetHdr) toGSOOptions() (GSOOptions, error) {
|
||||
var gsoType GSOType
|
||||
switch v.gsoType {
|
||||
case unix.VIRTIO_NET_HDR_GSO_NONE:
|
||||
gsoType = GSONone
|
||||
case unix.VIRTIO_NET_HDR_GSO_TCPV4:
|
||||
gsoType = GSOTCPv4
|
||||
case unix.VIRTIO_NET_HDR_GSO_TCPV6:
|
||||
gsoType = GSOTCPv6
|
||||
case unix.VIRTIO_NET_HDR_GSO_UDP_L4:
|
||||
gsoType = GSOUDPL4
|
||||
default:
|
||||
return GSOOptions{}, fmt.Errorf("unsupported virtio gsoType: %d", v.gsoType)
|
||||
}
|
||||
return GSOOptions{
|
||||
GSOType: gsoType,
|
||||
HdrLen: v.hdrLen,
|
||||
CsumStart: v.csumStart,
|
||||
CsumOffset: v.csumOffset,
|
||||
GSOSize: v.gsoSize,
|
||||
NeedsCsum: v.flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *virtioNetHdr) decode(b []byte) error {
|
||||
if len(b) < virtioNetHdrLen {
|
||||
return io.ErrShortBuffer
|
||||
|
|
@ -410,8 +393,8 @@ func checksumValid(pkt []byte, iphLen, proto uint8, isV6 bool) bool {
|
|||
addrSize = 16
|
||||
}
|
||||
lenForPseudo := uint16(len(pkt) - int(iphLen))
|
||||
cSum := PseudoHeaderChecksum(proto, pkt[srcAddrAt:srcAddrAt+addrSize], pkt[srcAddrAt+addrSize:srcAddrAt+addrSize*2], lenForPseudo)
|
||||
return ^Checksum(pkt[iphLen:], cSum) == 0
|
||||
cSum := pseudoHeaderChecksumNoFold(proto, pkt[srcAddrAt:srcAddrAt+addrSize], pkt[srcAddrAt+addrSize:srcAddrAt+addrSize*2], lenForPseudo)
|
||||
return ^checksum(pkt[iphLen:], cSum) == 0
|
||||
}
|
||||
|
||||
// coalesceResult represents the result of attempting to coalesce two TCP
|
||||
|
|
@ -527,6 +510,8 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
ipv4SrcAddrOffset = 12
|
||||
ipv6SrcAddrOffset = 8
|
||||
maxUint16 = 1<<16 - 1
|
||||
)
|
||||
|
||||
|
|
@ -659,7 +644,7 @@ func applyTCPCoalesceAccounting(bufs [][]byte, offset int, table *tcpGROTable) e
|
|||
hdr.gsoType = unix.VIRTIO_NET_HDR_GSO_TCPV4
|
||||
pkt[10], pkt[11] = 0, 0
|
||||
binary.BigEndian.PutUint16(pkt[2:], uint16(len(pkt))) // set new total length
|
||||
iphCSum := ^Checksum(pkt[:item.iphLen], 0) // compute IPv4 header checksum
|
||||
iphCSum := ^checksum(pkt[:item.iphLen], 0) // compute IPv4 header checksum
|
||||
binary.BigEndian.PutUint16(pkt[10:], iphCSum) // set IPv4 header checksum field
|
||||
}
|
||||
err := hdr.encode(bufs[item.bufsIndex][offset-virtioNetHdrLen:])
|
||||
|
|
@ -679,8 +664,8 @@ func applyTCPCoalesceAccounting(bufs [][]byte, offset int, table *tcpGROTable) e
|
|||
srcAddrAt := offset + addrOffset
|
||||
srcAddr := bufs[item.bufsIndex][srcAddrAt : srcAddrAt+addrLen]
|
||||
dstAddr := bufs[item.bufsIndex][srcAddrAt+addrLen : srcAddrAt+addrLen*2]
|
||||
psum := PseudoHeaderChecksum(unix.IPPROTO_TCP, srcAddr, dstAddr, uint16(len(pkt)-int(item.iphLen)))
|
||||
binary.BigEndian.PutUint16(pkt[hdr.csumStart+hdr.csumOffset:], Checksum([]byte{}, psum))
|
||||
psum := pseudoHeaderChecksumNoFold(unix.IPPROTO_TCP, srcAddr, dstAddr, uint16(len(pkt)-int(item.iphLen)))
|
||||
binary.BigEndian.PutUint16(pkt[hdr.csumStart+hdr.csumOffset:], checksum([]byte{}, psum))
|
||||
} else {
|
||||
hdr := virtioNetHdr{}
|
||||
err := hdr.encode(bufs[item.bufsIndex][offset-virtioNetHdrLen:])
|
||||
|
|
@ -716,7 +701,7 @@ func applyUDPCoalesceAccounting(bufs [][]byte, offset int, table *udpGROTable) e
|
|||
} else {
|
||||
pkt[10], pkt[11] = 0, 0
|
||||
binary.BigEndian.PutUint16(pkt[2:], uint16(len(pkt))) // set new total length
|
||||
iphCSum := ^Checksum(pkt[:item.iphLen], 0) // compute IPv4 header checksum
|
||||
iphCSum := ^checksum(pkt[:item.iphLen], 0) // compute IPv4 header checksum
|
||||
binary.BigEndian.PutUint16(pkt[10:], iphCSum) // set IPv4 header checksum field
|
||||
}
|
||||
err := hdr.encode(bufs[item.bufsIndex][offset-virtioNetHdrLen:])
|
||||
|
|
@ -739,8 +724,8 @@ func applyUDPCoalesceAccounting(bufs [][]byte, offset int, table *udpGROTable) e
|
|||
srcAddrAt := offset + addrOffset
|
||||
srcAddr := bufs[item.bufsIndex][srcAddrAt : srcAddrAt+addrLen]
|
||||
dstAddr := bufs[item.bufsIndex][srcAddrAt+addrLen : srcAddrAt+addrLen*2]
|
||||
psum := PseudoHeaderChecksum(unix.IPPROTO_UDP, srcAddr, dstAddr, uint16(len(pkt)-int(item.iphLen)))
|
||||
binary.BigEndian.PutUint16(pkt[hdr.csumStart+hdr.csumOffset:], Checksum([]byte{}, psum))
|
||||
psum := pseudoHeaderChecksumNoFold(unix.IPPROTO_UDP, srcAddr, dstAddr, uint16(len(pkt)-int(item.iphLen)))
|
||||
binary.BigEndian.PutUint16(pkt[hdr.csumStart+hdr.csumOffset:], checksum([]byte{}, psum))
|
||||
} else {
|
||||
hdr := virtioNetHdr{}
|
||||
err := hdr.encode(bufs[item.bufsIndex][offset-virtioNetHdrLen:])
|
||||
|
|
@ -763,7 +748,7 @@ const (
|
|||
udp6GROCandidate
|
||||
)
|
||||
|
||||
func packetIsGROCandidate(b []byte, gro groDisablementFlags) groCandidateType {
|
||||
func packetIsGROCandidate(b []byte, canUDPGRO bool) groCandidateType {
|
||||
if len(b) < 28 {
|
||||
return notGROCandidate
|
||||
}
|
||||
|
|
@ -772,17 +757,17 @@ func packetIsGROCandidate(b []byte, gro groDisablementFlags) groCandidateType {
|
|||
// IPv4 packets w/IP options do not coalesce
|
||||
return notGROCandidate
|
||||
}
|
||||
if b[9] == unix.IPPROTO_TCP && len(b) >= 40 && gro.canTCPGRO() {
|
||||
if b[9] == unix.IPPROTO_TCP && len(b) >= 40 {
|
||||
return tcp4GROCandidate
|
||||
}
|
||||
if b[9] == unix.IPPROTO_UDP && gro.canUDPGRO() {
|
||||
if b[9] == unix.IPPROTO_UDP && canUDPGRO {
|
||||
return udp4GROCandidate
|
||||
}
|
||||
} else if b[0]>>4 == 6 {
|
||||
if b[6] == unix.IPPROTO_TCP && len(b) >= 60 && gro.canTCPGRO() {
|
||||
if b[6] == unix.IPPROTO_TCP && len(b) >= 60 {
|
||||
return tcp6GROCandidate
|
||||
}
|
||||
if b[6] == unix.IPPROTO_UDP && len(b) >= 48 && gro.canUDPGRO() {
|
||||
if b[6] == unix.IPPROTO_UDP && len(b) >= 48 && canUDPGRO {
|
||||
return udp6GROCandidate
|
||||
}
|
||||
}
|
||||
|
|
@ -875,15 +860,15 @@ func udpGRO(bufs [][]byte, offset int, pktI int, table *udpGROTable, isV6 bool)
|
|||
// handleGRO evaluates bufs for GRO, and writes the indices of the resulting
|
||||
// packets into toWrite. toWrite, tcpTable, and udpTable should initially be
|
||||
// empty (but non-nil), and are passed in to save allocs as the caller may reset
|
||||
// and recycle them across vectors of packets. gro indicates if TCP and UDP GRO
|
||||
// are supported/enabled.
|
||||
func handleGRO(bufs [][]byte, offset int, tcpTable *tcpGROTable, udpTable *udpGROTable, gro groDisablementFlags, toWrite *[]int) error {
|
||||
// and recycle them across vectors of packets. canUDPGRO indicates if UDP GRO is
|
||||
// supported.
|
||||
func handleGRO(bufs [][]byte, offset int, tcpTable *tcpGROTable, udpTable *udpGROTable, canUDPGRO bool, toWrite *[]int) error {
|
||||
for i := range bufs {
|
||||
if offset < virtioNetHdrLen || offset > len(bufs[i])-1 {
|
||||
return errors.New("invalid offset")
|
||||
}
|
||||
var result groResult
|
||||
switch packetIsGROCandidate(bufs[i][offset:], gro) {
|
||||
switch packetIsGROCandidate(bufs[i][offset:], canUDPGRO) {
|
||||
case tcp4GROCandidate:
|
||||
result = tcpGRO(bufs, offset, i, tcpTable, false)
|
||||
case tcp6GROCandidate:
|
||||
|
|
@ -909,3 +894,100 @@ func handleGRO(bufs [][]byte, offset int, tcpTable *tcpGROTable, udpTable *udpGR
|
|||
errUDP := applyUDPCoalesceAccounting(bufs, offset, udpTable)
|
||||
return errors.Join(errTCP, errUDP)
|
||||
}
|
||||
|
||||
// gsoSplit splits packets from in into outBuffs, writing the size of each
|
||||
// element into sizes. It returns the number of buffers populated, and/or an
|
||||
// error.
|
||||
func gsoSplit(in []byte, hdr virtioNetHdr, outBuffs [][]byte, sizes []int, outOffset int, isV6 bool) (int, error) {
|
||||
iphLen := int(hdr.csumStart)
|
||||
srcAddrOffset := ipv6SrcAddrOffset
|
||||
addrLen := 16
|
||||
if !isV6 {
|
||||
in[10], in[11] = 0, 0 // clear ipv4 header checksum
|
||||
srcAddrOffset = ipv4SrcAddrOffset
|
||||
addrLen = 4
|
||||
}
|
||||
transportCsumAt := int(hdr.csumStart + hdr.csumOffset)
|
||||
in[transportCsumAt], in[transportCsumAt+1] = 0, 0 // clear tcp/udp checksum
|
||||
var firstTCPSeqNum uint32
|
||||
var protocol uint8
|
||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_TCPV4 || hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_TCPV6 {
|
||||
protocol = unix.IPPROTO_TCP
|
||||
firstTCPSeqNum = binary.BigEndian.Uint32(in[hdr.csumStart+4:])
|
||||
} else {
|
||||
protocol = unix.IPPROTO_UDP
|
||||
}
|
||||
nextSegmentDataAt := int(hdr.hdrLen)
|
||||
i := 0
|
||||
for ; nextSegmentDataAt < len(in); i++ {
|
||||
if i == len(outBuffs) {
|
||||
return i - 1, ErrTooManySegments
|
||||
}
|
||||
nextSegmentEnd := nextSegmentDataAt + int(hdr.gsoSize)
|
||||
if nextSegmentEnd > len(in) {
|
||||
nextSegmentEnd = len(in)
|
||||
}
|
||||
segmentDataLen := nextSegmentEnd - nextSegmentDataAt
|
||||
totalLen := int(hdr.hdrLen) + segmentDataLen
|
||||
sizes[i] = totalLen
|
||||
out := outBuffs[i][outOffset:]
|
||||
|
||||
copy(out, in[:iphLen])
|
||||
if !isV6 {
|
||||
// For IPv4 we are responsible for incrementing the ID field,
|
||||
// updating the total len field, and recalculating the header
|
||||
// checksum.
|
||||
if i > 0 {
|
||||
id := binary.BigEndian.Uint16(out[4:])
|
||||
id += uint16(i)
|
||||
binary.BigEndian.PutUint16(out[4:], id)
|
||||
}
|
||||
binary.BigEndian.PutUint16(out[2:], uint16(totalLen))
|
||||
ipv4CSum := ^checksum(out[:iphLen], 0)
|
||||
binary.BigEndian.PutUint16(out[10:], ipv4CSum)
|
||||
} else {
|
||||
// For IPv6 we are responsible for updating the payload length field.
|
||||
binary.BigEndian.PutUint16(out[4:], uint16(totalLen-iphLen))
|
||||
}
|
||||
|
||||
// copy transport header
|
||||
copy(out[hdr.csumStart:hdr.hdrLen], in[hdr.csumStart:hdr.hdrLen])
|
||||
|
||||
if protocol == unix.IPPROTO_TCP {
|
||||
// set TCP seq and adjust TCP flags
|
||||
tcpSeq := firstTCPSeqNum + uint32(hdr.gsoSize*uint16(i))
|
||||
binary.BigEndian.PutUint32(out[hdr.csumStart+4:], tcpSeq)
|
||||
if nextSegmentEnd != len(in) {
|
||||
// FIN and PSH should only be set on last segment
|
||||
clearFlags := tcpFlagFIN | tcpFlagPSH
|
||||
out[hdr.csumStart+tcpFlagsOffset] &^= clearFlags
|
||||
}
|
||||
} else {
|
||||
// set UDP header len
|
||||
binary.BigEndian.PutUint16(out[hdr.csumStart+4:], uint16(segmentDataLen)+(hdr.hdrLen-hdr.csumStart))
|
||||
}
|
||||
|
||||
// payload
|
||||
copy(out[hdr.hdrLen:], in[nextSegmentDataAt:nextSegmentEnd])
|
||||
|
||||
// transport checksum
|
||||
transportHeaderLen := int(hdr.hdrLen - hdr.csumStart)
|
||||
lenForPseudo := uint16(transportHeaderLen + segmentDataLen)
|
||||
transportCSumNoFold := pseudoHeaderChecksumNoFold(protocol, in[srcAddrOffset:srcAddrOffset+addrLen], in[srcAddrOffset+addrLen:srcAddrOffset+addrLen*2], lenForPseudo)
|
||||
transportCSum := ^checksum(out[hdr.csumStart:totalLen], transportCSumNoFold)
|
||||
binary.BigEndian.PutUint16(out[hdr.csumStart+hdr.csumOffset:], transportCSum)
|
||||
|
||||
nextSegmentDataAt += int(hdr.gsoSize)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func gsoNoneChecksum(in []byte, cSumStart, cSumOffset uint16) error {
|
||||
cSumAt := cSumStart + cSumOffset
|
||||
// The initial value at the checksum offset should be summed with the
|
||||
// checksum we compute. This is typically the pseudo-header checksum.
|
||||
initial := binary.BigEndian.Uint16(in[cSumAt:])
|
||||
in[cSumAt], in[cSumAt+1] = 0, 0
|
||||
binary.BigEndian.PutUint16(in[cSumAt:], ^checksum(in[cSumStart:], uint64(initial)))
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
752
tun/offload_linux_test.go
Normal file
752
tun/offload_linux_test.go
Normal file
|
|
@ -0,0 +1,752 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
)
|
||||
|
||||
const (
|
||||
offset = virtioNetHdrLen
|
||||
)
|
||||
|
||||
var (
|
||||
ip4PortA = netip.MustParseAddrPort("192.0.2.1:1")
|
||||
ip4PortB = netip.MustParseAddrPort("192.0.2.2:1")
|
||||
ip4PortC = netip.MustParseAddrPort("192.0.2.3:1")
|
||||
ip6PortA = netip.MustParseAddrPort("[2001:db8::1]:1")
|
||||
ip6PortB = netip.MustParseAddrPort("[2001:db8::2]:1")
|
||||
ip6PortC = netip.MustParseAddrPort("[2001:db8::3]:1")
|
||||
)
|
||||
|
||||
func udp4PacketMutateIPFields(srcIPPort, dstIPPort netip.AddrPort, payloadLen int, ipFn func(*header.IPv4Fields)) []byte {
|
||||
totalLen := 28 + payloadLen
|
||||
b := make([]byte, offset+int(totalLen), 65535)
|
||||
ipv4H := header.IPv4(b[offset:])
|
||||
srcAs4 := srcIPPort.Addr().As4()
|
||||
dstAs4 := dstIPPort.Addr().As4()
|
||||
ipFields := &header.IPv4Fields{
|
||||
SrcAddr: tcpip.AddrFromSlice(srcAs4[:]),
|
||||
DstAddr: tcpip.AddrFromSlice(dstAs4[:]),
|
||||
Protocol: unix.IPPROTO_UDP,
|
||||
TTL: 64,
|
||||
TotalLength: uint16(totalLen),
|
||||
}
|
||||
if ipFn != nil {
|
||||
ipFn(ipFields)
|
||||
}
|
||||
ipv4H.Encode(ipFields)
|
||||
udpH := header.UDP(b[offset+20:])
|
||||
udpH.Encode(&header.UDPFields{
|
||||
SrcPort: srcIPPort.Port(),
|
||||
DstPort: dstIPPort.Port(),
|
||||
Length: uint16(payloadLen + udphLen),
|
||||
})
|
||||
ipv4H.SetChecksum(^ipv4H.CalculateChecksum())
|
||||
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_UDP, ipv4H.SourceAddress(), ipv4H.DestinationAddress(), uint16(udphLen+payloadLen))
|
||||
udpH.SetChecksum(^udpH.CalculateChecksum(pseudoCsum))
|
||||
return b
|
||||
}
|
||||
|
||||
func udp6Packet(srcIPPort, dstIPPort netip.AddrPort, payloadLen int) []byte {
|
||||
return udp6PacketMutateIPFields(srcIPPort, dstIPPort, payloadLen, nil)
|
||||
}
|
||||
|
||||
func udp6PacketMutateIPFields(srcIPPort, dstIPPort netip.AddrPort, payloadLen int, ipFn func(*header.IPv6Fields)) []byte {
|
||||
totalLen := 48 + payloadLen
|
||||
b := make([]byte, offset+int(totalLen), 65535)
|
||||
ipv6H := header.IPv6(b[offset:])
|
||||
srcAs16 := srcIPPort.Addr().As16()
|
||||
dstAs16 := dstIPPort.Addr().As16()
|
||||
ipFields := &header.IPv6Fields{
|
||||
SrcAddr: tcpip.AddrFromSlice(srcAs16[:]),
|
||||
DstAddr: tcpip.AddrFromSlice(dstAs16[:]),
|
||||
TransportProtocol: unix.IPPROTO_UDP,
|
||||
HopLimit: 64,
|
||||
PayloadLength: uint16(payloadLen + udphLen),
|
||||
}
|
||||
if ipFn != nil {
|
||||
ipFn(ipFields)
|
||||
}
|
||||
ipv6H.Encode(ipFields)
|
||||
udpH := header.UDP(b[offset+40:])
|
||||
udpH.Encode(&header.UDPFields{
|
||||
SrcPort: srcIPPort.Port(),
|
||||
DstPort: dstIPPort.Port(),
|
||||
Length: uint16(payloadLen + udphLen),
|
||||
})
|
||||
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_UDP, ipv6H.SourceAddress(), ipv6H.DestinationAddress(), uint16(udphLen+payloadLen))
|
||||
udpH.SetChecksum(^udpH.CalculateChecksum(pseudoCsum))
|
||||
return b
|
||||
}
|
||||
|
||||
func udp4Packet(srcIPPort, dstIPPort netip.AddrPort, payloadLen int) []byte {
|
||||
return udp4PacketMutateIPFields(srcIPPort, dstIPPort, payloadLen, nil)
|
||||
}
|
||||
|
||||
func tcp4PacketMutateIPFields(srcIPPort, dstIPPort netip.AddrPort, flags header.TCPFlags, segmentSize, seq uint32, ipFn func(*header.IPv4Fields)) []byte {
|
||||
totalLen := 40 + segmentSize
|
||||
b := make([]byte, offset+int(totalLen), 65535)
|
||||
ipv4H := header.IPv4(b[offset:])
|
||||
srcAs4 := srcIPPort.Addr().As4()
|
||||
dstAs4 := dstIPPort.Addr().As4()
|
||||
ipFields := &header.IPv4Fields{
|
||||
SrcAddr: tcpip.AddrFromSlice(srcAs4[:]),
|
||||
DstAddr: tcpip.AddrFromSlice(dstAs4[:]),
|
||||
Protocol: unix.IPPROTO_TCP,
|
||||
TTL: 64,
|
||||
TotalLength: uint16(totalLen),
|
||||
}
|
||||
if ipFn != nil {
|
||||
ipFn(ipFields)
|
||||
}
|
||||
ipv4H.Encode(ipFields)
|
||||
tcpH := header.TCP(b[offset+20:])
|
||||
tcpH.Encode(&header.TCPFields{
|
||||
SrcPort: srcIPPort.Port(),
|
||||
DstPort: dstIPPort.Port(),
|
||||
SeqNum: seq,
|
||||
AckNum: 1,
|
||||
DataOffset: 20,
|
||||
Flags: flags,
|
||||
WindowSize: 3000,
|
||||
})
|
||||
ipv4H.SetChecksum(^ipv4H.CalculateChecksum())
|
||||
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv4H.SourceAddress(), ipv4H.DestinationAddress(), uint16(20+segmentSize))
|
||||
tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum))
|
||||
return b
|
||||
}
|
||||
|
||||
func tcp4Packet(srcIPPort, dstIPPort netip.AddrPort, flags header.TCPFlags, segmentSize, seq uint32) []byte {
|
||||
return tcp4PacketMutateIPFields(srcIPPort, dstIPPort, flags, segmentSize, seq, nil)
|
||||
}
|
||||
|
||||
func tcp6PacketMutateIPFields(srcIPPort, dstIPPort netip.AddrPort, flags header.TCPFlags, segmentSize, seq uint32, ipFn func(*header.IPv6Fields)) []byte {
|
||||
totalLen := 60 + segmentSize
|
||||
b := make([]byte, offset+int(totalLen), 65535)
|
||||
ipv6H := header.IPv6(b[offset:])
|
||||
srcAs16 := srcIPPort.Addr().As16()
|
||||
dstAs16 := dstIPPort.Addr().As16()
|
||||
ipFields := &header.IPv6Fields{
|
||||
SrcAddr: tcpip.AddrFromSlice(srcAs16[:]),
|
||||
DstAddr: tcpip.AddrFromSlice(dstAs16[:]),
|
||||
TransportProtocol: unix.IPPROTO_TCP,
|
||||
HopLimit: 64,
|
||||
PayloadLength: uint16(segmentSize + 20),
|
||||
}
|
||||
if ipFn != nil {
|
||||
ipFn(ipFields)
|
||||
}
|
||||
ipv6H.Encode(ipFields)
|
||||
tcpH := header.TCP(b[offset+40:])
|
||||
tcpH.Encode(&header.TCPFields{
|
||||
SrcPort: srcIPPort.Port(),
|
||||
DstPort: dstIPPort.Port(),
|
||||
SeqNum: seq,
|
||||
AckNum: 1,
|
||||
DataOffset: 20,
|
||||
Flags: flags,
|
||||
WindowSize: 3000,
|
||||
})
|
||||
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv6H.SourceAddress(), ipv6H.DestinationAddress(), uint16(20+segmentSize))
|
||||
tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum))
|
||||
return b
|
||||
}
|
||||
|
||||
func tcp6Packet(srcIPPort, dstIPPort netip.AddrPort, flags header.TCPFlags, segmentSize, seq uint32) []byte {
|
||||
return tcp6PacketMutateIPFields(srcIPPort, dstIPPort, flags, segmentSize, seq, nil)
|
||||
}
|
||||
|
||||
func Test_handleVirtioRead(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hdr virtioNetHdr
|
||||
pktIn []byte
|
||||
wantLens []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"tcp4",
|
||||
virtioNetHdr{
|
||||
flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
gsoType: unix.VIRTIO_NET_HDR_GSO_TCPV4,
|
||||
gsoSize: 100,
|
||||
hdrLen: 40,
|
||||
csumStart: 20,
|
||||
csumOffset: 16,
|
||||
},
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck|header.TCPFlagPsh, 200, 1),
|
||||
[]int{140, 140},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tcp6",
|
||||
virtioNetHdr{
|
||||
flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
gsoType: unix.VIRTIO_NET_HDR_GSO_TCPV6,
|
||||
gsoSize: 100,
|
||||
hdrLen: 60,
|
||||
csumStart: 40,
|
||||
csumOffset: 16,
|
||||
},
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck|header.TCPFlagPsh, 200, 1),
|
||||
[]int{160, 160},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"udp4",
|
||||
virtioNetHdr{
|
||||
flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
gsoType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
|
||||
gsoSize: 100,
|
||||
hdrLen: 28,
|
||||
csumStart: 20,
|
||||
csumOffset: 6,
|
||||
},
|
||||
udp4Packet(ip4PortA, ip4PortB, 200),
|
||||
[]int{128, 128},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"udp6",
|
||||
virtioNetHdr{
|
||||
flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
gsoType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
|
||||
gsoSize: 100,
|
||||
hdrLen: 48,
|
||||
csumStart: 40,
|
||||
csumOffset: 6,
|
||||
},
|
||||
udp6Packet(ip6PortA, ip6PortB, 200),
|
||||
[]int{148, 148},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
out := make([][]byte, conn.IdealBatchSize)
|
||||
sizes := make([]int, conn.IdealBatchSize)
|
||||
for i := range out {
|
||||
out[i] = make([]byte, 65535)
|
||||
}
|
||||
tt.hdr.encode(tt.pktIn)
|
||||
n, err := handleVirtioRead(tt.pktIn, out, sizes, offset)
|
||||
if err != nil {
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
t.Fatalf("got err: %v", err)
|
||||
}
|
||||
if n != len(tt.wantLens) {
|
||||
t.Fatalf("got %d packets, wanted %d", n, len(tt.wantLens))
|
||||
}
|
||||
for i := range tt.wantLens {
|
||||
if tt.wantLens[i] != sizes[i] {
|
||||
t.Fatalf("wantLens[%d]: %d != outSizes: %d", i, tt.wantLens[i], sizes[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func flipTCP4Checksum(b []byte) []byte {
|
||||
at := virtioNetHdrLen + 20 + 16 // 20 byte ipv4 header; tcp csum offset is 16
|
||||
b[at] ^= 0xFF
|
||||
b[at+1] ^= 0xFF
|
||||
return b
|
||||
}
|
||||
|
||||
func flipUDP4Checksum(b []byte) []byte {
|
||||
at := virtioNetHdrLen + 20 + 6 // 20 byte ipv4 header; udp csum offset is 6
|
||||
b[at] ^= 0xFF
|
||||
b[at+1] ^= 0xFF
|
||||
return b
|
||||
}
|
||||
|
||||
func Fuzz_handleGRO(f *testing.F) {
|
||||
pkt0 := tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1)
|
||||
pkt1 := tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101)
|
||||
pkt2 := tcp4Packet(ip4PortA, ip4PortC, header.TCPFlagAck, 100, 201)
|
||||
pkt3 := tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1)
|
||||
pkt4 := tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101)
|
||||
pkt5 := tcp6Packet(ip6PortA, ip6PortC, header.TCPFlagAck, 100, 201)
|
||||
pkt6 := udp4Packet(ip4PortA, ip4PortB, 100)
|
||||
pkt7 := udp4Packet(ip4PortA, ip4PortB, 100)
|
||||
pkt8 := udp4Packet(ip4PortA, ip4PortC, 100)
|
||||
pkt9 := udp6Packet(ip6PortA, ip6PortB, 100)
|
||||
pkt10 := udp6Packet(ip6PortA, ip6PortB, 100)
|
||||
pkt11 := udp6Packet(ip6PortA, ip6PortC, 100)
|
||||
f.Add(pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11, true, offset)
|
||||
f.Fuzz(func(t *testing.T, pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11 []byte, canUDPGRO bool, offset int) {
|
||||
pkts := [][]byte{pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11}
|
||||
toWrite := make([]int, 0, len(pkts))
|
||||
handleGRO(pkts, offset, newTCPGROTable(), newUDPGROTable(), canUDPGRO, &toWrite)
|
||||
if len(toWrite) > len(pkts) {
|
||||
t.Errorf("len(toWrite): %d > len(pkts): %d", len(toWrite), len(pkts))
|
||||
}
|
||||
seenWriteI := make(map[int]bool)
|
||||
for _, writeI := range toWrite {
|
||||
if writeI < 0 || writeI > len(pkts)-1 {
|
||||
t.Errorf("toWrite value (%d) outside bounds of len(pkts): %d", writeI, len(pkts))
|
||||
}
|
||||
if seenWriteI[writeI] {
|
||||
t.Errorf("duplicate toWrite value: %d", writeI)
|
||||
}
|
||||
seenWriteI[writeI] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_handleGRO(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pktsIn [][]byte
|
||||
canUDPGRO bool
|
||||
wantToWrite []int
|
||||
wantLens []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"multiple protocols and flows",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // tcp4 flow 1
|
||||
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
|
||||
udp4Packet(ip4PortA, ip4PortC, 100), // udp4 flow 2
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // tcp4 flow 1
|
||||
tcp4Packet(ip4PortA, ip4PortC, header.TCPFlagAck, 100, 201), // tcp4 flow 2
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1), // tcp6 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101), // tcp6 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortC, header.TCPFlagAck, 100, 201), // tcp6 flow 2
|
||||
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
|
||||
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
|
||||
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
|
||||
},
|
||||
true,
|
||||
[]int{0, 1, 2, 4, 5, 7, 9},
|
||||
[]int{240, 228, 128, 140, 260, 160, 248},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"multiple protocols and flows no UDP GRO",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // tcp4 flow 1
|
||||
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
|
||||
udp4Packet(ip4PortA, ip4PortC, 100), // udp4 flow 2
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // tcp4 flow 1
|
||||
tcp4Packet(ip4PortA, ip4PortC, header.TCPFlagAck, 100, 201), // tcp4 flow 2
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1), // tcp6 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101), // tcp6 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortC, header.TCPFlagAck, 100, 201), // tcp6 flow 2
|
||||
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
|
||||
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
|
||||
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
|
||||
},
|
||||
false,
|
||||
[]int{0, 1, 2, 4, 5, 7, 8, 9, 10},
|
||||
[]int{240, 128, 128, 140, 260, 160, 128, 148, 148},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"PSH interleaved",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // v4 flow 1
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck|header.TCPFlagPsh, 100, 101), // v4 flow 1
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 201), // v4 flow 1
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 301), // v4 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1), // v6 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck|header.TCPFlagPsh, 100, 101), // v6 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 201), // v6 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 301), // v6 flow 1
|
||||
},
|
||||
true,
|
||||
[]int{0, 2, 4, 6},
|
||||
[]int{240, 240, 260, 260},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"coalesceItemInvalidCSum",
|
||||
[][]byte{
|
||||
flipTCP4Checksum(tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1)), // v4 flow 1 seq 1 len 100
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // v4 flow 1 seq 101 len 100
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 201), // v4 flow 1 seq 201 len 100
|
||||
flipUDP4Checksum(udp4Packet(ip4PortA, ip4PortB, 100)),
|
||||
udp4Packet(ip4PortA, ip4PortB, 100),
|
||||
udp4Packet(ip4PortA, ip4PortB, 100),
|
||||
},
|
||||
true,
|
||||
[]int{0, 1, 3, 4},
|
||||
[]int{140, 240, 128, 228},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"out of order",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // v4 flow 1 seq 101 len 100
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // v4 flow 1 seq 1 len 100
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 201), // v4 flow 1 seq 201 len 100
|
||||
},
|
||||
true,
|
||||
[]int{0},
|
||||
[]int{340},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"unequal TTL",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
||||
fields.TTL++
|
||||
}),
|
||||
udp4Packet(ip4PortA, ip4PortB, 100),
|
||||
udp4PacketMutateIPFields(ip4PortA, ip4PortB, 100, func(fields *header.IPv4Fields) {
|
||||
fields.TTL++
|
||||
}),
|
||||
},
|
||||
true,
|
||||
[]int{0, 1, 2, 3},
|
||||
[]int{140, 140, 128, 128},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"unequal ToS",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
||||
fields.TOS++
|
||||
}),
|
||||
udp4Packet(ip4PortA, ip4PortB, 100),
|
||||
udp4PacketMutateIPFields(ip4PortA, ip4PortB, 100, func(fields *header.IPv4Fields) {
|
||||
fields.TOS++
|
||||
}),
|
||||
},
|
||||
true,
|
||||
[]int{0, 1, 2, 3},
|
||||
[]int{140, 140, 128, 128},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"unequal flags more fragments set",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
||||
fields.Flags = 1
|
||||
}),
|
||||
udp4Packet(ip4PortA, ip4PortB, 100),
|
||||
udp4PacketMutateIPFields(ip4PortA, ip4PortB, 100, func(fields *header.IPv4Fields) {
|
||||
fields.Flags = 1
|
||||
}),
|
||||
},
|
||||
true,
|
||||
[]int{0, 1, 2, 3},
|
||||
[]int{140, 140, 128, 128},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"unequal flags DF set",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
||||
fields.Flags = 2
|
||||
}),
|
||||
udp4Packet(ip4PortA, ip4PortB, 100),
|
||||
udp4PacketMutateIPFields(ip4PortA, ip4PortB, 100, func(fields *header.IPv4Fields) {
|
||||
fields.Flags = 2
|
||||
}),
|
||||
},
|
||||
true,
|
||||
[]int{0, 1, 2, 3},
|
||||
[]int{140, 140, 128, 128},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"ipv6 unequal hop limit",
|
||||
[][]byte{
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp6PacketMutateIPFields(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv6Fields) {
|
||||
fields.HopLimit++
|
||||
}),
|
||||
udp6Packet(ip6PortA, ip6PortB, 100),
|
||||
udp6PacketMutateIPFields(ip6PortA, ip6PortB, 100, func(fields *header.IPv6Fields) {
|
||||
fields.HopLimit++
|
||||
}),
|
||||
},
|
||||
true,
|
||||
[]int{0, 1, 2, 3},
|
||||
[]int{160, 160, 148, 148},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"ipv6 unequal traffic class",
|
||||
[][]byte{
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp6PacketMutateIPFields(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv6Fields) {
|
||||
fields.TrafficClass++
|
||||
}),
|
||||
udp6Packet(ip6PortA, ip6PortB, 100),
|
||||
udp6PacketMutateIPFields(ip6PortA, ip6PortB, 100, func(fields *header.IPv6Fields) {
|
||||
fields.TrafficClass++
|
||||
}),
|
||||
},
|
||||
true,
|
||||
[]int{0, 1, 2, 3},
|
||||
[]int{160, 160, 148, 148},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
toWrite := make([]int, 0, len(tt.pktsIn))
|
||||
err := handleGRO(tt.pktsIn, offset, newTCPGROTable(), newUDPGROTable(), tt.canUDPGRO, &toWrite)
|
||||
if err != nil {
|
||||
if tt.wantErr {
|
||||
return
|
||||
}
|
||||
t.Fatalf("got err: %v", err)
|
||||
}
|
||||
if len(toWrite) != len(tt.wantToWrite) {
|
||||
t.Fatalf("got %d packets, wanted %d", len(toWrite), len(tt.wantToWrite))
|
||||
}
|
||||
for i, pktI := range tt.wantToWrite {
|
||||
if tt.wantToWrite[i] != toWrite[i] {
|
||||
t.Fatalf("wantToWrite[%d]: %d != toWrite: %d", i, tt.wantToWrite[i], toWrite[i])
|
||||
}
|
||||
if tt.wantLens[i] != len(tt.pktsIn[pktI][offset:]) {
|
||||
t.Errorf("wanted len %d packet at %d, got: %d", tt.wantLens[i], i, len(tt.pktsIn[pktI][offset:]))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_packetIsGROCandidate(t *testing.T) {
|
||||
tcp4 := tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1)[virtioNetHdrLen:]
|
||||
tcp4TooShort := tcp4[:39]
|
||||
ip4InvalidHeaderLen := make([]byte, len(tcp4))
|
||||
copy(ip4InvalidHeaderLen, tcp4)
|
||||
ip4InvalidHeaderLen[0] = 0x46
|
||||
ip4InvalidProtocol := make([]byte, len(tcp4))
|
||||
copy(ip4InvalidProtocol, tcp4)
|
||||
ip4InvalidProtocol[9] = unix.IPPROTO_GRE
|
||||
|
||||
tcp6 := tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1)[virtioNetHdrLen:]
|
||||
tcp6TooShort := tcp6[:59]
|
||||
ip6InvalidProtocol := make([]byte, len(tcp6))
|
||||
copy(ip6InvalidProtocol, tcp6)
|
||||
ip6InvalidProtocol[6] = unix.IPPROTO_GRE
|
||||
|
||||
udp4 := udp4Packet(ip4PortA, ip4PortB, 100)[virtioNetHdrLen:]
|
||||
udp4TooShort := udp4[:27]
|
||||
|
||||
udp6 := udp6Packet(ip6PortA, ip6PortB, 100)[virtioNetHdrLen:]
|
||||
udp6TooShort := udp6[:47]
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
b []byte
|
||||
canUDPGRO bool
|
||||
want groCandidateType
|
||||
}{
|
||||
{
|
||||
"tcp4",
|
||||
tcp4,
|
||||
true,
|
||||
tcp4GROCandidate,
|
||||
},
|
||||
{
|
||||
"tcp6",
|
||||
tcp6,
|
||||
true,
|
||||
tcp6GROCandidate,
|
||||
},
|
||||
{
|
||||
"udp4",
|
||||
udp4,
|
||||
true,
|
||||
udp4GROCandidate,
|
||||
},
|
||||
{
|
||||
"udp4 no support",
|
||||
udp4,
|
||||
false,
|
||||
notGROCandidate,
|
||||
},
|
||||
{
|
||||
"udp6",
|
||||
udp6,
|
||||
true,
|
||||
udp6GROCandidate,
|
||||
},
|
||||
{
|
||||
"udp6 no support",
|
||||
udp6,
|
||||
false,
|
||||
notGROCandidate,
|
||||
},
|
||||
{
|
||||
"udp4 too short",
|
||||
udp4TooShort,
|
||||
true,
|
||||
notGROCandidate,
|
||||
},
|
||||
{
|
||||
"udp6 too short",
|
||||
udp6TooShort,
|
||||
true,
|
||||
notGROCandidate,
|
||||
},
|
||||
{
|
||||
"tcp4 too short",
|
||||
tcp4TooShort,
|
||||
true,
|
||||
notGROCandidate,
|
||||
},
|
||||
{
|
||||
"tcp6 too short",
|
||||
tcp6TooShort,
|
||||
true,
|
||||
notGROCandidate,
|
||||
},
|
||||
{
|
||||
"invalid IP version",
|
||||
[]byte{0x00},
|
||||
true,
|
||||
notGROCandidate,
|
||||
},
|
||||
{
|
||||
"invalid IP header len",
|
||||
ip4InvalidHeaderLen,
|
||||
true,
|
||||
notGROCandidate,
|
||||
},
|
||||
{
|
||||
"ip4 invalid protocol",
|
||||
ip4InvalidProtocol,
|
||||
true,
|
||||
notGROCandidate,
|
||||
},
|
||||
{
|
||||
"ip6 invalid protocol",
|
||||
ip6InvalidProtocol,
|
||||
true,
|
||||
notGROCandidate,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := packetIsGROCandidate(tt.b, tt.canUDPGRO); got != tt.want {
|
||||
t.Errorf("packetIsGROCandidate() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_udpPacketsCanCoalesce(t *testing.T) {
|
||||
udp4a := udp4Packet(ip4PortA, ip4PortB, 100)
|
||||
udp4b := udp4Packet(ip4PortA, ip4PortB, 100)
|
||||
udp4c := udp4Packet(ip4PortA, ip4PortB, 110)
|
||||
|
||||
type args struct {
|
||||
pkt []byte
|
||||
iphLen uint8
|
||||
gsoSize uint16
|
||||
item udpGROItem
|
||||
bufs [][]byte
|
||||
bufsOffset int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want canCoalesce
|
||||
}{
|
||||
{
|
||||
"coalesceAppend equal gso",
|
||||
args{
|
||||
pkt: udp4a[offset:],
|
||||
iphLen: 20,
|
||||
gsoSize: 100,
|
||||
item: udpGROItem{
|
||||
gsoSize: 100,
|
||||
iphLen: 20,
|
||||
},
|
||||
bufs: [][]byte{
|
||||
udp4a,
|
||||
udp4b,
|
||||
},
|
||||
bufsOffset: offset,
|
||||
},
|
||||
coalesceAppend,
|
||||
},
|
||||
{
|
||||
"coalesceAppend smaller gso",
|
||||
args{
|
||||
pkt: udp4a[offset : len(udp4a)-90],
|
||||
iphLen: 20,
|
||||
gsoSize: 10,
|
||||
item: udpGROItem{
|
||||
gsoSize: 100,
|
||||
iphLen: 20,
|
||||
},
|
||||
bufs: [][]byte{
|
||||
udp4a,
|
||||
udp4b,
|
||||
},
|
||||
bufsOffset: offset,
|
||||
},
|
||||
coalesceAppend,
|
||||
},
|
||||
{
|
||||
"coalesceUnavailable smaller gso previously appended",
|
||||
args{
|
||||
pkt: udp4a[offset:],
|
||||
iphLen: 20,
|
||||
gsoSize: 100,
|
||||
item: udpGROItem{
|
||||
gsoSize: 100,
|
||||
iphLen: 20,
|
||||
},
|
||||
bufs: [][]byte{
|
||||
udp4c,
|
||||
udp4b,
|
||||
},
|
||||
bufsOffset: offset,
|
||||
},
|
||||
coalesceUnavailable,
|
||||
},
|
||||
{
|
||||
"coalesceUnavailable larger following smaller",
|
||||
args{
|
||||
pkt: udp4c[offset:],
|
||||
iphLen: 20,
|
||||
gsoSize: 110,
|
||||
item: udpGROItem{
|
||||
gsoSize: 100,
|
||||
iphLen: 20,
|
||||
},
|
||||
bufs: [][]byte{
|
||||
udp4a,
|
||||
udp4c,
|
||||
},
|
||||
bufsOffset: offset,
|
||||
},
|
||||
coalesceUnavailable,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := udpPacketsCanCoalesce(tt.args.pkt, tt.args.iphLen, tt.args.gsoSize, tt.args.item, tt.args.bufs, tt.args.bufsOffset); got != tt.want {
|
||||
t.Errorf("udpPacketsCanCoalesce() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
23
tun/tun.go
23
tun/tun.go
|
|
@ -51,26 +51,3 @@ type Device interface {
|
|||
// lifetime of a Device.
|
||||
BatchSize() int
|
||||
}
|
||||
|
||||
// GRODevice is a Device extended with methods for disabling GRO. Certain OS
|
||||
// versions may have offload bugs. Where these bugs negatively impact throughput
|
||||
// or break connectivity entirely we can use these methods to disable the
|
||||
// related offload.
|
||||
//
|
||||
// Linux has the following known, GRO bugs.
|
||||
//
|
||||
// torvalds/linux@e269d79c7d35aa3808b1f3c1737d63dab504ddc8 broke virtio_net
|
||||
// TCP & UDP GRO causing GRO writes to return EINVAL. The bug was then
|
||||
// resolved later in
|
||||
// torvalds/linux@89add40066f9ed9abe5f7f886fe5789ff7e0c50e. The offending
|
||||
// commit was pulled into various LTS releases.
|
||||
//
|
||||
// UDP GRO writes end up blackholing/dropping packets destined for a
|
||||
// vxlan/geneve interface on kernel versions prior to 6.8.5.
|
||||
type GRODevice interface {
|
||||
Device
|
||||
// DisableUDPGRO disables UDP GRO if it is enabled.
|
||||
DisableUDPGRO()
|
||||
// DisableTCPGRO disables TCP GRO if it is enabled.
|
||||
DisableTCPGRO()
|
||||
}
|
||||
|
|
|
|||
144
tun/tun_linux.go
144
tun/tun_linux.go
|
|
@ -1,6 +1,6 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
|
@ -17,8 +17,8 @@ import (
|
|||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/wireguard-go/conn"
|
||||
"github.com/sagernet/wireguard-go/rwcancel"
|
||||
"github.com/amnezia-vpn/amneziawg-go/conn"
|
||||
"github.com/amnezia-vpn/amneziawg-go/rwcancel"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
|
|
@ -38,6 +38,7 @@ type NativeTun struct {
|
|||
statusListenersShutdown chan struct{}
|
||||
batchSize int
|
||||
vnetHdr bool
|
||||
udpGSO bool
|
||||
|
||||
closeOnce sync.Once
|
||||
|
||||
|
|
@ -48,34 +49,10 @@ type NativeTun struct {
|
|||
readOpMu sync.Mutex // readOpMu guards readBuff
|
||||
readBuff [virtioNetHdrLen + 65535]byte // if vnetHdr every read() is prefixed by virtioNetHdr
|
||||
|
||||
writeOpMu sync.Mutex // writeOpMu guards the following fields
|
||||
writeOpMu sync.Mutex // writeOpMu guards toWrite, tcpGROTable
|
||||
toWrite []int
|
||||
tcpGROTable *tcpGROTable
|
||||
udpGROTable *udpGROTable
|
||||
gro groDisablementFlags
|
||||
}
|
||||
|
||||
type groDisablementFlags int
|
||||
|
||||
const (
|
||||
tcpGRODisabled groDisablementFlags = 1 << iota
|
||||
udpGRODisabled
|
||||
)
|
||||
|
||||
func (g *groDisablementFlags) disableTCPGRO() {
|
||||
*g |= tcpGRODisabled
|
||||
}
|
||||
|
||||
func (g *groDisablementFlags) canTCPGRO() bool {
|
||||
return (*g)&tcpGRODisabled == 0
|
||||
}
|
||||
|
||||
func (g *groDisablementFlags) disableUDPGRO() {
|
||||
*g |= udpGRODisabled
|
||||
}
|
||||
|
||||
func (g *groDisablementFlags) canUDPGRO() bool {
|
||||
return (*g)&udpGRODisabled == 0
|
||||
}
|
||||
|
||||
func (tun *NativeTun) File() *os.File {
|
||||
|
|
@ -269,15 +246,21 @@ func (tun *NativeTun) setMTU(n int) error {
|
|||
|
||||
defer unix.Close(fd)
|
||||
|
||||
req, err := unix.NewIfreq(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unix.NewIfreq(%q): %w", name, err)
|
||||
}
|
||||
req.SetUint32(uint32(n))
|
||||
err = unix.IoctlIfreq(fd, unix.SIOCSIFMTU, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set MTU of TUN device %q: %w", name, err)
|
||||
// do ioctl call
|
||||
var ifr [ifReqSize]byte
|
||||
copy(ifr[:], name)
|
||||
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n)
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
uintptr(unix.SIOCSIFMTU),
|
||||
uintptr(unsafe.Pointer(&ifr[0])),
|
||||
)
|
||||
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to set MTU of TUN device: %w", errno)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -362,7 +345,7 @@ func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) {
|
|||
)
|
||||
tun.toWrite = tun.toWrite[:0]
|
||||
if tun.vnetHdr {
|
||||
err := handleGRO(bufs, offset, tun.tcpGROTable, tun.udpGROTable, tun.gro, &tun.toWrite)
|
||||
err := handleGRO(bufs, offset, tun.tcpGROTable, tun.udpGROTable, tun.udpGSO, &tun.toWrite)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -396,32 +379,73 @@ func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, e
|
|||
return 0, err
|
||||
}
|
||||
in = in[virtioNetHdrLen:]
|
||||
|
||||
options, err := hdr.toGSOOptions()
|
||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_NONE {
|
||||
if hdr.flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
|
||||
// This means CHECKSUM_PARTIAL in skb context. We are responsible
|
||||
// for computing the checksum starting at hdr.csumStart and placing
|
||||
// at hdr.csumOffset.
|
||||
err = gsoNoneChecksum(in, hdr.csumStart, hdr.csumOffset)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if len(in) > len(bufs[0][offset:]) {
|
||||
return 0, fmt.Errorf("read len %d overflows bufs element len %d", len(in), len(bufs[0][offset:]))
|
||||
}
|
||||
n := copy(bufs[0][offset:], in)
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV4 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV6 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
return 0, fmt.Errorf("unsupported virtio GSO type: %d", hdr.gsoType)
|
||||
}
|
||||
|
||||
// Don't trust HdrLen from the kernel as it can be equal to the length
|
||||
ipVersion := in[0] >> 4
|
||||
switch ipVersion {
|
||||
case 4:
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV4 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
return 0, fmt.Errorf("ip header version: %d, GSO type: %d", ipVersion, hdr.gsoType)
|
||||
}
|
||||
case 6:
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV6 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
return 0, fmt.Errorf("ip header version: %d, GSO type: %d", ipVersion, hdr.gsoType)
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid ip header version: %d", ipVersion)
|
||||
}
|
||||
|
||||
// Don't trust hdr.hdrLen from the kernel as it can be equal to the length
|
||||
// of the entire first packet when the kernel is handling it as part of a
|
||||
// FORWARD path. Instead, parse the transport header length and add it onto
|
||||
// CsumStart, which is synonymous for IP header length.
|
||||
if options.GSOType == GSOUDPL4 {
|
||||
options.HdrLen = options.CsumStart + 8
|
||||
} else if options.GSOType != GSONone {
|
||||
if len(in) <= int(options.CsumStart+12) {
|
||||
// csumStart, which is synonymous for IP header length.
|
||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
hdr.hdrLen = hdr.csumStart + 8
|
||||
} else {
|
||||
if len(in) <= int(hdr.csumStart+12) {
|
||||
return 0, errors.New("packet is too short")
|
||||
}
|
||||
|
||||
tcpHLen := uint16(in[options.CsumStart+12] >> 4 * 4)
|
||||
tcpHLen := uint16(in[hdr.csumStart+12] >> 4 * 4)
|
||||
if tcpHLen < 20 || tcpHLen > 60 {
|
||||
// A TCP header must be between 20 and 60 bytes in length.
|
||||
return 0, fmt.Errorf("tcp header len is invalid: %d", tcpHLen)
|
||||
}
|
||||
options.HdrLen = options.CsumStart + tcpHLen
|
||||
hdr.hdrLen = hdr.csumStart + tcpHLen
|
||||
}
|
||||
|
||||
return GSOSplit(in, options, bufs, sizes, offset)
|
||||
if len(in) < int(hdr.hdrLen) {
|
||||
return 0, fmt.Errorf("length of packet (%d) < virtioNetHdr.hdrLen (%d)", len(in), hdr.hdrLen)
|
||||
}
|
||||
|
||||
if hdr.hdrLen < hdr.csumStart {
|
||||
return 0, fmt.Errorf("virtioNetHdr.hdrLen (%d) < virtioNetHdr.csumStart (%d)", hdr.hdrLen, hdr.csumStart)
|
||||
}
|
||||
cSumAt := int(hdr.csumStart + hdr.csumOffset)
|
||||
if cSumAt+1 >= len(in) {
|
||||
return 0, fmt.Errorf("end of checksum offset (%d) exceeds packet length (%d)", cSumAt+1, len(in))
|
||||
}
|
||||
|
||||
return gsoSplit(in, hdr, bufs, sizes, offset, ipVersion == 6)
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) {
|
||||
|
|
@ -478,22 +502,6 @@ func (tun *NativeTun) BatchSize() int {
|
|||
return tun.batchSize
|
||||
}
|
||||
|
||||
// DisableUDPGRO disables UDP GRO if it is enabled. See the GRODevice interface
|
||||
// for cases where it should be called.
|
||||
func (tun *NativeTun) DisableUDPGRO() {
|
||||
tun.writeOpMu.Lock()
|
||||
tun.gro.disableUDPGRO()
|
||||
tun.writeOpMu.Unlock()
|
||||
}
|
||||
|
||||
// DisableTCPGRO disables TCP GRO if it is enabled. See the GRODevice interface
|
||||
// for cases where it should be called.
|
||||
func (tun *NativeTun) DisableTCPGRO() {
|
||||
tun.writeOpMu.Lock()
|
||||
tun.gro.disableTCPGRO()
|
||||
tun.writeOpMu.Unlock()
|
||||
}
|
||||
|
||||
const (
|
||||
// TODO: support TSO with ECN bits
|
||||
tunTCPOffloads = unix.TUN_F_CSUM | unix.TUN_F_TSO4 | unix.TUN_F_TSO6
|
||||
|
|
@ -506,7 +514,9 @@ func (tun *NativeTun) initFromFlags(name string) error {
|
|||
return err
|
||||
}
|
||||
if e := sc.Control(func(fd uintptr) {
|
||||
var ifr *unix.Ifreq
|
||||
var (
|
||||
ifr *unix.Ifreq
|
||||
)
|
||||
ifr, err = unix.NewIfreq(name)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -527,9 +537,7 @@ func (tun *NativeTun) initFromFlags(name string) error {
|
|||
tun.batchSize = conn.IdealBatchSize
|
||||
// tunUDPOffloads were added in Linux v6.2. We do not return an
|
||||
// error if they are unsupported at runtime.
|
||||
if unix.IoctlSetInt(int(fd), unix.TUNSETOFFLOAD, tunTCPOffloads|tunUDPOffloads) != nil {
|
||||
tun.gro.disableUDPGRO()
|
||||
}
|
||||
tun.udpGSO = unix.IoctlSetInt(int(fd), unix.TUNSETOFFLOAD, tunTCPOffloads|tunUDPOffloads) == nil
|
||||
} else {
|
||||
tun.batchSize = 1
|
||||
}
|
||||
|
|
|
|||
147
tun/tun_plan9.go
147
tun/tun_plan9.go
|
|
@ -1,147 +0,0 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type NativeTun struct {
|
||||
name string // "/net/ipifc/2"
|
||||
ctlFile *os.File
|
||||
dataFile *os.File
|
||||
events chan Event
|
||||
errors chan error
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func CreateTUN(_ string, mtu int) (Device, error) {
|
||||
ctl, err := os.OpenFile("/net/ipifc/clone", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nbuf := make([]byte, 5)
|
||||
n, err := ctl.Read(nbuf)
|
||||
if err != nil {
|
||||
ctl.Close()
|
||||
return nil, fmt.Errorf("error reading from clone file: %w", err)
|
||||
}
|
||||
ifn, err := strconv.Atoi(strings.TrimSpace(string(nbuf[:n])))
|
||||
if err != nil {
|
||||
ctl.Close()
|
||||
return nil, fmt.Errorf("error converting clone result %q to int: %w", nbuf[:n], err)
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprintf(ctl, "bind pkt\n"); err != nil {
|
||||
ctl.Close()
|
||||
return nil, fmt.Errorf("error binding to pkt: %w", err)
|
||||
}
|
||||
if mtu > 0 {
|
||||
if _, err := fmt.Fprintf(ctl, "mtu %d\n", mtu); err != nil {
|
||||
ctl.Close()
|
||||
return nil, fmt.Errorf("error setting MTU: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
dataFile, err := os.OpenFile(fmt.Sprintf("/net/ipifc/%d/data", ifn), os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
ctl.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tun := &NativeTun{
|
||||
ctlFile: ctl,
|
||||
dataFile: dataFile,
|
||||
name: fmt.Sprintf("/net/ipifc/%d", ifn),
|
||||
events: make(chan Event, 10),
|
||||
errors: make(chan error, 5),
|
||||
}
|
||||
tun.events <- EventUp
|
||||
|
||||
return tun, nil
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Name() (string, error) {
|
||||
return tun.name, nil
|
||||
}
|
||||
|
||||
func (tun *NativeTun) File() *os.File {
|
||||
return tun.ctlFile
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Events() <-chan Event {
|
||||
return tun.events
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) {
|
||||
select {
|
||||
case err := <-tun.errors:
|
||||
return 0, err
|
||||
default:
|
||||
n, err := tun.dataFile.Read(bufs[0][offset:])
|
||||
if n == 1 && bufs[0][offset] == 0 {
|
||||
// EOF
|
||||
err = io.EOF
|
||||
n = 0
|
||||
}
|
||||
sizes[0] = n
|
||||
return 1, err
|
||||
}
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) {
|
||||
for i, buf := range bufs {
|
||||
if _, err := tun.dataFile.Write(buf[offset:]); err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
return len(bufs), nil
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Close() error {
|
||||
var err1, err2 error
|
||||
tun.closeOnce.Do(func() {
|
||||
_, err1 := fmt.Fprintf(tun.ctlFile, "unbind\n")
|
||||
if err := tun.ctlFile.Close(); err != nil && err1 == nil {
|
||||
err1 = err
|
||||
}
|
||||
err2 = tun.dataFile.Close()
|
||||
})
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
return err2
|
||||
}
|
||||
|
||||
func (tun *NativeTun) MTU() (int, error) {
|
||||
var buf [100]byte
|
||||
f, err := os.Open(tun.name + "/status")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
n, err := f.Read(buf[:])
|
||||
_, res, ok := strings.Cut(string(buf[:n]), " maxtu ")
|
||||
if ok {
|
||||
if mtus, _, ok := strings.Cut(res, " "); ok {
|
||||
mtu, err := strconv.Atoi(mtus)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error converting mtu %q to int: %w", mtus, err)
|
||||
}
|
||||
return mtu, nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("no 'maxtu' field found in %s/status", tun.name)
|
||||
}
|
||||
|
||||
func (tun *NativeTun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
155
tun/tuntest/tuntest.go
Normal file
155
tun/tuntest/tuntest.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tuntest
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"github.com/amnezia-vpn/amneziawg-go/tun"
|
||||
)
|
||||
|
||||
func Ping(dst, src netip.Addr) []byte {
|
||||
localPort := uint16(1337)
|
||||
seq := uint16(0)
|
||||
|
||||
payload := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(payload[0:], localPort)
|
||||
binary.BigEndian.PutUint16(payload[2:], seq)
|
||||
|
||||
return genICMPv4(payload, dst, src)
|
||||
}
|
||||
|
||||
// Checksum is the "internet checksum" from https://tools.ietf.org/html/rfc1071.
|
||||
func checksum(buf []byte, initial uint16) uint16 {
|
||||
v := uint32(initial)
|
||||
for i := 0; i < len(buf)-1; i += 2 {
|
||||
v += uint32(binary.BigEndian.Uint16(buf[i:]))
|
||||
}
|
||||
if len(buf)%2 == 1 {
|
||||
v += uint32(buf[len(buf)-1]) << 8
|
||||
}
|
||||
for v > 0xffff {
|
||||
v = (v >> 16) + (v & 0xffff)
|
||||
}
|
||||
return ^uint16(v)
|
||||
}
|
||||
|
||||
func genICMPv4(payload []byte, dst, src netip.Addr) []byte {
|
||||
const (
|
||||
icmpv4ProtocolNumber = 1
|
||||
icmpv4Echo = 8
|
||||
icmpv4ChecksumOffset = 2
|
||||
icmpv4Size = 8
|
||||
ipv4Size = 20
|
||||
ipv4TotalLenOffset = 2
|
||||
ipv4ChecksumOffset = 10
|
||||
ttl = 65
|
||||
headerSize = ipv4Size + icmpv4Size
|
||||
)
|
||||
|
||||
pkt := make([]byte, headerSize+len(payload))
|
||||
|
||||
ip := pkt[0:ipv4Size]
|
||||
icmpv4 := pkt[ipv4Size : ipv4Size+icmpv4Size]
|
||||
|
||||
// https://tools.ietf.org/html/rfc792
|
||||
icmpv4[0] = icmpv4Echo // type
|
||||
icmpv4[1] = 0 // code
|
||||
chksum := ^checksum(icmpv4, checksum(payload, 0))
|
||||
binary.BigEndian.PutUint16(icmpv4[icmpv4ChecksumOffset:], chksum)
|
||||
|
||||
// https://tools.ietf.org/html/rfc760 section 3.1
|
||||
length := uint16(len(pkt))
|
||||
ip[0] = (4 << 4) | (ipv4Size / 4)
|
||||
binary.BigEndian.PutUint16(ip[ipv4TotalLenOffset:], length)
|
||||
ip[8] = ttl
|
||||
ip[9] = icmpv4ProtocolNumber
|
||||
copy(ip[12:], src.AsSlice())
|
||||
copy(ip[16:], dst.AsSlice())
|
||||
chksum = ^checksum(ip[:], 0)
|
||||
binary.BigEndian.PutUint16(ip[ipv4ChecksumOffset:], chksum)
|
||||
|
||||
copy(pkt[headerSize:], payload)
|
||||
return pkt
|
||||
}
|
||||
|
||||
type ChannelTUN struct {
|
||||
Inbound chan []byte // incoming packets, closed on TUN close
|
||||
Outbound chan []byte // outbound packets, blocks forever on TUN close
|
||||
|
||||
closed chan struct{}
|
||||
events chan tun.Event
|
||||
tun chTun
|
||||
}
|
||||
|
||||
func NewChannelTUN() *ChannelTUN {
|
||||
c := &ChannelTUN{
|
||||
Inbound: make(chan []byte),
|
||||
Outbound: make(chan []byte),
|
||||
closed: make(chan struct{}),
|
||||
events: make(chan tun.Event, 1),
|
||||
}
|
||||
c.tun.c = c
|
||||
c.events <- tun.EventUp
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ChannelTUN) TUN() tun.Device {
|
||||
return &c.tun
|
||||
}
|
||||
|
||||
type chTun struct {
|
||||
c *ChannelTUN
|
||||
}
|
||||
|
||||
func (t *chTun) File() *os.File { return nil }
|
||||
|
||||
func (t *chTun) Read(packets [][]byte, sizes []int, offset int) (int, error) {
|
||||
select {
|
||||
case <-t.c.closed:
|
||||
return 0, os.ErrClosed
|
||||
case msg := <-t.c.Outbound:
|
||||
n := copy(packets[0][offset:], msg)
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Write is called by the wireguard device to deliver a packet for routing.
|
||||
func (t *chTun) Write(packets [][]byte, offset int) (int, error) {
|
||||
if offset == -1 {
|
||||
close(t.c.closed)
|
||||
close(t.c.events)
|
||||
return 0, io.EOF
|
||||
}
|
||||
for i, data := range packets {
|
||||
msg := make([]byte, len(data)-offset)
|
||||
copy(msg, data[offset:])
|
||||
select {
|
||||
case <-t.c.closed:
|
||||
return i, os.ErrClosed
|
||||
case t.c.Inbound <- msg:
|
||||
}
|
||||
}
|
||||
return len(packets), nil
|
||||
}
|
||||
|
||||
func (t *chTun) BatchSize() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
const DefaultMTU = 1420
|
||||
|
||||
func (t *chTun) MTU() (int, error) { return DefaultMTU, nil }
|
||||
func (t *chTun) Name() (string, error) { return "loopbackTun1", nil }
|
||||
func (t *chTun) Events() <-chan tun.Event { return t.c.events }
|
||||
func (t *chTun) Close() error {
|
||||
t.Write(nil, -1)
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue