Compare commits
131 commits
lx-awg2-v0
...
lx
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c0c10b5d3 | ||
|
|
6513629626 | ||
|
|
fb8d8d8fca | ||
|
|
7b15aacbfe | ||
|
|
a2307b51f7 | ||
|
|
7e50606f80 | ||
|
|
58f534ff10 | ||
|
|
27290b6de3 | ||
|
|
f4f4c99926 | ||
|
|
12a012205e | ||
|
|
e7ef4339e7 | ||
|
|
506b763185 | ||
|
|
449d7cffd4 | ||
|
|
e796d477d8 | ||
|
|
730d6c39d0 | ||
|
|
0361c54dca | ||
|
|
16510ac472 | ||
|
|
c63bc19bc9 | ||
|
|
b2a20cdd77 | ||
|
|
606102e010 | ||
|
|
2835be4411 | ||
|
|
d79d3c88fd | ||
|
|
558e1e0493 | ||
|
|
dfa4a8968f | ||
|
|
177dea9806 | ||
|
|
e4aedc6f6e | ||
|
|
f8eafb781b | ||
|
|
f6542209f4 | ||
|
|
1d0488a3d7 | ||
|
|
4064566eca | ||
|
|
1f398ae148 | ||
|
|
1abd24b5b9 | ||
|
|
3f19f1c657 | ||
|
|
c207898480 | ||
|
|
fe75b639fa | ||
|
|
24483d7a00 | ||
|
|
169ed49a46 | ||
|
|
eeb8aae13e | ||
|
|
99f2e6d66f | ||
|
|
d5359f52f0 | ||
|
|
6768090667 | ||
|
|
2cad62c40b | ||
|
|
8051f17147 | ||
|
|
ace3e11ef2 | ||
|
|
8a2b2bf4f4 | ||
|
|
75d6c67a67 | ||
|
|
ac8a885a03 | ||
|
|
6a7c878409 | ||
|
|
704d57c27a | ||
|
|
c0b6e6a200 | ||
|
|
c803ce1e5b | ||
|
|
deedce495a | ||
|
|
65cd6eed7d | ||
|
|
022546570f | ||
|
|
ae0636254c | ||
|
|
19f7e29805 | ||
|
|
f74ff38c79 | ||
|
|
2b555120c8 | ||
|
|
6413b491d4 | ||
|
|
27e661d68e | ||
|
|
71be0eb3a6 | ||
|
|
91a0587fb2 | ||
|
|
e3f1273f8a | ||
|
|
c97b5b7615 | ||
|
|
668ddfd455 | ||
|
|
b8da08c106 | ||
|
|
0b8b35511f | ||
|
|
4e883d38c8 | ||
|
|
799c1978fa | ||
|
|
71393c576b | ||
|
|
6c039a188c | ||
|
|
60eeedfd62 | ||
|
|
2f5d148bcf | ||
|
|
2e3f7d122c | ||
|
|
cfa45674af | ||
|
|
1e088837d1 | ||
|
|
2e7780471a | ||
|
|
87d8c00f86 | ||
|
|
c00bda9200 | ||
|
|
d2b0fc9789 | ||
|
|
77d39ff3b9 | ||
|
|
e433d13df6 | ||
|
|
3ddf952973 | ||
|
|
03c5a0ccf7 | ||
|
|
64040e6646 | ||
|
|
3f0a3bcfa0 | ||
|
|
4dddf62e57 | ||
|
|
827ec6e14b | ||
|
|
92e28a0d14 | ||
|
|
52fed4d362 | ||
|
|
9c6b3ff332 | ||
|
|
7de7a9a754 | ||
|
|
0c347529b8 | ||
|
|
6705978fc8 | ||
|
|
032e33f577 | ||
|
|
59101fd202 | ||
|
|
8bcfbac230 | ||
|
|
f0dfb5eacc | ||
|
|
9195025d8f | ||
|
|
cbd414dfec | ||
|
|
7155d20913 | ||
|
|
bfeb3954f6 | ||
|
|
e3c9ec8012 | ||
|
|
ce9d3866a3 | ||
|
|
e5f355e843 | ||
|
|
c05b2ee2a3 | ||
|
|
180c9284f3 | ||
|
|
015e11875d | ||
|
|
b43118018e | ||
|
|
cc193a0b32 | ||
|
|
8cc8b8b11b | ||
|
|
7af55a3e6f | ||
|
|
db7604d1aa | ||
|
|
24f8d7c9e7 | ||
|
|
c493b95f66 | ||
|
|
b81ca925db | ||
|
|
b34974c476 | ||
|
|
f30419e0d1 | ||
|
|
8f1a6a10b2 | ||
|
|
2f6748dc88 | ||
|
|
202a3401e7 | ||
|
|
6cd5922a04 | ||
|
|
88b11b4a0d | ||
|
|
ec6f23b33e | ||
|
|
cc7b29b8c6 | ||
|
|
ceb9a09d03 | ||
|
|
915962ded2 | ||
|
|
d831fef379 | ||
|
|
e06231b861 | ||
|
|
e26adb828d | ||
|
|
bd7e9d35d1 |
126 changed files with 4629 additions and 7039 deletions
41
.github/workflows/build-if-tag.yml
vendored
Normal file
41
.github/workflows/build-if-tag.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
name: build-if-tag
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
|
||||||
|
env:
|
||||||
|
APP: amneziawg-go
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: build
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Setup metadata
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
id: metadata
|
||||||
|
with:
|
||||||
|
images: amneziavpn/${{ env.APP }}
|
||||||
|
tags: type=semver,pattern={{version}}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.metadata.outputs.tags }}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1 +1,3 @@
|
||||||
wireguard-go
|
wireguard-go
|
||||||
|
/.idea/
|
||||||
|
.DS_Store
|
||||||
18
Dockerfile
Normal file
18
Dockerfile
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
FROM golang:1.24.4 as awg
|
||||||
|
COPY . /awg
|
||||||
|
WORKDIR /awg
|
||||||
|
RUN go mod download && \
|
||||||
|
go mod verify && \
|
||||||
|
go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin
|
||||||
|
|
||||||
|
FROM alpine:3.19
|
||||||
|
ARG AWGTOOLS_RELEASE="1.0.20250901"
|
||||||
|
|
||||||
|
RUN apk --no-cache add iproute2 iptables bash && \
|
||||||
|
cd /usr/bin/ && \
|
||||||
|
wget https://github.com/amnezia-vpn/amneziawg-tools/releases/download/v${AWGTOOLS_RELEASE}/alpine-3.19-amneziawg-tools.zip && \
|
||||||
|
unzip -j alpine-3.19-amneziawg-tools.zip && \
|
||||||
|
chmod +x /usr/bin/awg /usr/bin/awg-quick && \
|
||||||
|
ln -s /usr/bin/awg /usr/bin/wg && \
|
||||||
|
ln -s /usr/bin/awg-quick /usr/bin/wg-quick
|
||||||
|
COPY --from=awg /usr/bin/amneziawg-go /usr/bin/amneziawg-go
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,3 +1,5 @@
|
||||||
|
Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
the Software without restriction, including without limitation the rights to
|
the Software without restriction, including without limitation the rights to
|
||||||
|
|
|
||||||
12
Makefile
12
Makefile
|
|
@ -9,23 +9,23 @@ MAKEFLAGS += --no-print-directory
|
||||||
|
|
||||||
generate-version-and-build:
|
generate-version-and-build:
|
||||||
@export GIT_CEILING_DIRECTORIES="$(realpath $(CURDIR)/..)" && \
|
@export GIT_CEILING_DIRECTORIES="$(realpath $(CURDIR)/..)" && \
|
||||||
tag="$$(git describe --dirty 2>/dev/null)" && \
|
tag="$$(git describe --tags --dirty 2>/dev/null)" && \
|
||||||
ver="$$(printf 'package main\n\nconst Version = "%s"\n' "$$tag")" && \
|
ver="$$(printf 'package main\n\nconst Version = "%s"\n' "$$tag")" && \
|
||||||
[ "$$(cat version.go 2>/dev/null)" != "$$ver" ] && \
|
[ "$$(cat version.go 2>/dev/null)" != "$$ver" ] && \
|
||||||
echo "$$ver" > version.go && \
|
echo "$$ver" > version.go && \
|
||||||
git update-index --assume-unchanged version.go || true
|
git update-index --assume-unchanged version.go || true
|
||||||
@$(MAKE) wireguard-go
|
@$(MAKE) amneziawg-go
|
||||||
|
|
||||||
wireguard-go: $(wildcard *.go) $(wildcard */*.go)
|
amneziawg-go: $(wildcard *.go) $(wildcard */*.go)
|
||||||
go build -v -o "$@"
|
go build -v -o "$@"
|
||||||
|
|
||||||
install: wireguard-go
|
install: amneziawg-go
|
||||||
@install -v -d "$(DESTDIR)$(BINDIR)" && install -v -m 0755 "$<" "$(DESTDIR)$(BINDIR)/wireguard-go"
|
@install -v -d "$(DESTDIR)$(BINDIR)" && install -v -m 0755 "$<" "$(DESTDIR)$(BINDIR)/amneziawg-go"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f wireguard-go
|
rm -f amneziawg-go
|
||||||
|
|
||||||
.PHONY: all clean test install generate-version-and-build
|
.PHONY: all clean test install generate-version-and-build
|
||||||
|
|
|
||||||
134
README.md
134
README.md
|
|
@ -1,77 +1,93 @@
|
||||||
# Go Implementation of [WireGuard](https://www.wireguard.com/)
|
**English** · [Русский](README.ru.md)
|
||||||
|
|
||||||
This is an implementation of WireGuard in Go.
|
# wireguard-go (lx fork) — sagernet + AmneziaWG 2.0
|
||||||
|
|
||||||
## Usage
|
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.
|
||||||
|
|
||||||
Most Linux kernel WireGuard users are used to adding an interface with `ip link add wg0 type wireguard`. With wireguard-go, instead simply run:
|
This is **not** a general-purpose project. It exists for one reason — see below — and lives on the **`lx`** branch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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`:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ wireguard-go wg0
|
# 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
|
||||||
```
|
```
|
||||||
|
|
||||||
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/wireguard/wg0.sock`, which will result in wireguard-go shutting down.
|
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.
|
||||||
|
|
||||||
To run wireguard-go without forking to the background, pass `-f` or `--foreground`:
|
## Maintaining it (rebase onto a new sagernet tag)
|
||||||
|
|
||||||
```
|
When sing-box bumps `sagernet/wireguard-go`, redo the merge:
|
||||||
$ wireguard-go -f wg0
|
|
||||||
|
```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
|
||||||
```
|
```
|
||||||
|
|
||||||
When an interface is running, you may use [`wg(8)`](https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8) to configure it, as well as the usual `ip(8)` and `ifconfig(8)` commands.
|
Conflict resolution recipe:
|
||||||
|
|
||||||
To run with more logging you may set the environment variable `LOG_LEVEL=debug`.
|
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).
|
||||||
|
|
||||||
## Platforms
|
Then in sing-box-lx: bump the submodule, `make -f Makefile.lx lx-build`, and re-test against an AWG2 server.
|
||||||
|
|
||||||
### Linux
|
## Links
|
||||||
|
|
||||||
This will run on Linux; however you should instead use the kernel module, which is faster and better integrated into the OS. See the [installation page](https://www.wireguard.com/install/) for instructions.
|
| | |
|
||||||
|
|---|---|
|
||||||
### macOS
|
| Consumer | [Leadaxe/sing-box-lx](https://github.com/Leadaxe/sing-box-lx) |
|
||||||
|
| Base | [sagernet/wireguard-go](https://github.com/sagernet/wireguard-go) |
|
||||||
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.
|
| 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/) |
|
||||||
### Windows
|
|
||||||
|
|
||||||
This runs on Windows, but you should instead use it from the more [fully featured Windows app](https://git.zx2c4.com/wireguard-windows/about/), which uses this as a module.
|
|
||||||
|
|
||||||
### FreeBSD
|
|
||||||
|
|
||||||
This will run on FreeBSD. It does not yet support sticky sockets. Fwmark is mapped to `SO_USER_COOKIE`.
|
|
||||||
|
|
||||||
### OpenBSD
|
|
||||||
|
|
||||||
This will run on OpenBSD. It does not yet support sticky sockets. Fwmark is mapped to `SO_RTABLE`. Since the tun driver cannot have arbitrary interface names, you must either use `tun[0-9]+` for an explicit interface name or `tun` to have the program select one for you. If you choose `tun` 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.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
This requires an installation of the latest version of [Go](https://go.dev/).
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git clone https://git.zx2c4.com/wireguard-go
|
|
||||||
$ cd wireguard-go
|
|
||||||
$ make
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
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.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
||||||
of the Software, and to permit persons to whom the Software is furnished to do
|
|
||||||
so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
|
||||||
93
README.ru.md
Normal file
93
README.ru.md
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
[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.
|
||||||
100
conn/bind_std.go
100
conn/bind_std.go
|
|
@ -16,12 +16,16 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"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/ipv4"
|
||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ Bind = (*StdNetBind)(nil)
|
_ Bind = (*StdNetBind)(nil)
|
||||||
|
_ Endpoint = (*StdNetEndpoint)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// StdNetBind implements Bind for all platforms. While Windows has its own Bind
|
// StdNetBind implements Bind for all platforms. While Windows has its own Bind
|
||||||
|
|
@ -30,6 +34,9 @@ var (
|
||||||
// methods for sending and receiving multiple datagrams per-syscall. See the
|
// methods for sending and receiving multiple datagrams per-syscall. See the
|
||||||
// proposal in https://github.com/golang/go/issues/45886#issuecomment-1218301564.
|
// proposal in https://github.com/golang/go/issues/45886#issuecomment-1218301564.
|
||||||
type StdNetBind struct {
|
type StdNetBind struct {
|
||||||
|
externalControl control.Func
|
||||||
|
reservedForEndpoint map[netip.AddrPort][3]uint8
|
||||||
|
|
||||||
mu sync.Mutex // protects all fields except as specified
|
mu sync.Mutex // protects all fields except as specified
|
||||||
ipv4 *net.UDPConn
|
ipv4 *net.UDPConn
|
||||||
ipv6 *net.UDPConn
|
ipv6 *net.UDPConn
|
||||||
|
|
@ -48,8 +55,11 @@ type StdNetBind struct {
|
||||||
blackhole6 bool
|
blackhole6 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStdNetBind() Bind {
|
func NewStdNetBind(externalControl control.Func) Bind {
|
||||||
return &StdNetBind{
|
return &StdNetBind{
|
||||||
|
externalControl: externalControl,
|
||||||
|
reservedForEndpoint: make(map[netip.AddrPort][3]uint8),
|
||||||
|
|
||||||
udpAddrPool: sync.Pool{
|
udpAddrPool: sync.Pool{
|
||||||
New: func() any {
|
New: func() any {
|
||||||
return &net.UDPAddr{
|
return &net.UDPAddr{
|
||||||
|
|
@ -60,12 +70,10 @@ func NewStdNetBind() Bind {
|
||||||
|
|
||||||
msgsPool: sync.Pool{
|
msgsPool: sync.Pool{
|
||||||
New: func() any {
|
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)
|
msgs := make([]ipv6.Message, IdealBatchSize)
|
||||||
for i := range msgs {
|
for i := range msgs {
|
||||||
msgs[i].Buffers = make(net.Buffers, 1)
|
msgs[i].Buffers = make(net.Buffers, 1)
|
||||||
msgs[i].OOB = make([]byte, 0, stickyControlSize+gsoControlSize)
|
msgs[i].OOB = make([]byte, controlSize)
|
||||||
}
|
}
|
||||||
return &msgs
|
return &msgs
|
||||||
},
|
},
|
||||||
|
|
@ -108,7 +116,7 @@ func (e *StdNetEndpoint) DstIP() netip.Addr {
|
||||||
return e.AddrPort.Addr()
|
return e.AddrPort.Addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
// See control_default,linux, etc for implementations of SrcIP and SrcIfidx.
|
// See sticky_default,linux, etc for implementations of SrcIP and SrcIfidx.
|
||||||
|
|
||||||
func (e *StdNetEndpoint) DstToBytes() []byte {
|
func (e *StdNetEndpoint) DstToBytes() []byte {
|
||||||
b, _ := e.AddrPort.MarshalBinary()
|
b, _ := e.AddrPort.MarshalBinary()
|
||||||
|
|
@ -119,8 +127,29 @@ func (e *StdNetEndpoint) DstToString() string {
|
||||||
return e.AddrPort.String()
|
return e.AddrPort.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func listenNet(network string, port int) (*net.UDPConn, int, error) {
|
func listenNet(externalControl control.Func, network string, port int) (*net.UDPConn, int, error) {
|
||||||
conn, err := listenConfig().ListenPacket(context.Background(), network, ":"+strconv.Itoa(port))
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
@ -137,6 +166,11 @@ func listenNet(network string, port int) (*net.UDPConn, int, error) {
|
||||||
return conn.(*net.UDPConn), uaddr.Port, nil
|
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) {
|
func (s *StdNetBind) Open(uport uint16) ([]ReceiveFunc, uint16, error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
@ -156,14 +190,14 @@ again:
|
||||||
var v4pc *ipv4.PacketConn
|
var v4pc *ipv4.PacketConn
|
||||||
var v6pc *ipv6.PacketConn
|
var v6pc *ipv6.PacketConn
|
||||||
|
|
||||||
v4conn, port, err = listenNet("udp4", port)
|
v4conn, port, err = listenNet(s.externalControl, "udp4", port)
|
||||||
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen on the same port as we're using for ipv4.
|
// Listen on the same port as we're using for ipv4.
|
||||||
v6conn, port, err = listenNet("udp6", port)
|
v6conn, port, err = listenNet(s.externalControl, "udp6", port)
|
||||||
if uport == 0 && errors.Is(err, syscall.EADDRINUSE) && tries < 100 {
|
if uport == 0 && errors.Is(err, errEADDRINUSE) && tries < 100 {
|
||||||
v4conn.Close()
|
v4conn.Close()
|
||||||
tries++
|
tries++
|
||||||
goto again
|
goto again
|
||||||
|
|
@ -175,7 +209,7 @@ again:
|
||||||
var fns []ReceiveFunc
|
var fns []ReceiveFunc
|
||||||
if v4conn != nil {
|
if v4conn != nil {
|
||||||
s.ipv4TxOffload, s.ipv4RxOffload = supportsUDPOffload(v4conn)
|
s.ipv4TxOffload, s.ipv4RxOffload = supportsUDPOffload(v4conn)
|
||||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
if runtime.GOOS == "linux" {
|
||||||
v4pc = ipv4.NewPacketConn(v4conn)
|
v4pc = ipv4.NewPacketConn(v4conn)
|
||||||
s.ipv4PC = v4pc
|
s.ipv4PC = v4pc
|
||||||
}
|
}
|
||||||
|
|
@ -184,7 +218,7 @@ again:
|
||||||
}
|
}
|
||||||
if v6conn != nil {
|
if v6conn != nil {
|
||||||
s.ipv6TxOffload, s.ipv6RxOffload = supportsUDPOffload(v6conn)
|
s.ipv6TxOffload, s.ipv6RxOffload = supportsUDPOffload(v6conn)
|
||||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
if runtime.GOOS == "linux" {
|
||||||
v6pc = ipv6.NewPacketConn(v6conn)
|
v6pc = ipv6.NewPacketConn(v6conn)
|
||||||
s.ipv6PC = v6pc
|
s.ipv6PC = v6pc
|
||||||
}
|
}
|
||||||
|
|
@ -200,7 +234,6 @@ again:
|
||||||
|
|
||||||
func (s *StdNetBind) putMessages(msgs *[]ipv6.Message) {
|
func (s *StdNetBind) putMessages(msgs *[]ipv6.Message) {
|
||||||
for i := range *msgs {
|
for i := range *msgs {
|
||||||
(*msgs)[i].OOB = (*msgs)[i].OOB[:0]
|
|
||||||
(*msgs)[i] = ipv6.Message{Buffers: (*msgs)[i].Buffers, OOB: (*msgs)[i].OOB}
|
(*msgs)[i] = ipv6.Message{Buffers: (*msgs)[i].Buffers, OOB: (*msgs)[i].OOB}
|
||||||
}
|
}
|
||||||
s.msgsPool.Put(msgs)
|
s.msgsPool.Put(msgs)
|
||||||
|
|
@ -210,10 +243,8 @@ func (s *StdNetBind) getMessages() *[]ipv6.Message {
|
||||||
return s.msgsPool.Get().(*[]ipv6.Message)
|
return s.msgsPool.Get().(*[]ipv6.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
// If compilation fails here these are no longer the same underlying type.
|
||||||
// If compilation fails here these are no longer the same underlying type.
|
var _ ipv6.Message = ipv4.Message{}
|
||||||
_ ipv6.Message = ipv4.Message{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type batchReader interface {
|
type batchReader interface {
|
||||||
ReadBatch([]ipv6.Message, int) (int, error)
|
ReadBatch([]ipv6.Message, int) (int, error)
|
||||||
|
|
@ -238,9 +269,9 @@ func (s *StdNetBind) receiveIP(
|
||||||
}
|
}
|
||||||
defer s.putMessages(msgs)
|
defer s.putMessages(msgs)
|
||||||
var numMsgs int
|
var numMsgs int
|
||||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
if runtime.GOOS == "linux" {
|
||||||
if rxOffload {
|
if rxOffload {
|
||||||
readAt := len(*msgs) - (IdealBatchSize / udpSegmentMaxDatagrams)
|
readAt := len(*msgs) - 2
|
||||||
numMsgs, err = br.ReadBatch((*msgs)[readAt:], 0)
|
numMsgs, err = br.ReadBatch((*msgs)[readAt:], 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|
@ -269,8 +300,10 @@ func (s *StdNetBind) receiveIP(
|
||||||
if sizes[i] == 0 {
|
if sizes[i] == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
addrPort := msg.Addr.(*net.UDPAddr).AddrPort()
|
if msg.N > 3 {
|
||||||
ep := &StdNetEndpoint{AddrPort: addrPort} // TODO: remove allocation
|
common.ClearArray(bufs[i][1:4])
|
||||||
|
}
|
||||||
|
ep := &StdNetEndpoint{AddrPort: M.AddrPortFromNet(msg.Addr)} // TODO: remove allocation
|
||||||
getSrcFromControl(msg.OOB[:msg.NN], ep)
|
getSrcFromControl(msg.OOB[:msg.NN], ep)
|
||||||
eps[i] = ep
|
eps[i] = ep
|
||||||
}
|
}
|
||||||
|
|
@ -292,7 +325,7 @@ func (s *StdNetBind) makeReceiveIPv6(pc *ipv6.PacketConn, conn *net.UDPConn, rxO
|
||||||
// TODO: When all Binds handle IdealBatchSize, remove this dynamic function and
|
// TODO: When all Binds handle IdealBatchSize, remove this dynamic function and
|
||||||
// rename the IdealBatchSize constant to BatchSize.
|
// rename the IdealBatchSize constant to BatchSize.
|
||||||
func (s *StdNetBind) BatchSize() int {
|
func (s *StdNetBind) BatchSize() int {
|
||||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
if runtime.GOOS == "linux" {
|
||||||
return IdealBatchSize
|
return IdealBatchSize
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -338,7 +371,7 @@ func (e ErrUDPGSODisabled) Unwrap() error {
|
||||||
return e.RetryErr
|
return e.RetryErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StdNetBind) Send(bufs [][]byte, endpoint Endpoint) error {
|
func (s *StdNetBind) Send(bufs [][]byte, endpoint Endpoint, offset int) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
blackhole := s.blackhole4
|
blackhole := s.blackhole4
|
||||||
conn := s.ipv4
|
conn := s.ipv4
|
||||||
|
|
@ -379,9 +412,17 @@ func (s *StdNetBind) Send(bufs [][]byte, endpoint Endpoint) error {
|
||||||
retried bool
|
retried bool
|
||||||
err error
|
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:
|
retry:
|
||||||
if offload {
|
if offload {
|
||||||
n := coalesceMessages(ua, endpoint.(*StdNetEndpoint), bufs, *msgs, setGSOSize)
|
n := coalesceMessages(ua, endpoint.(*StdNetEndpoint), bufs, offset, *msgs, setGSOSize)
|
||||||
err = s.send(conn, br, (*msgs)[:n])
|
err = s.send(conn, br, (*msgs)[:n])
|
||||||
if err != nil && offload && errShouldDisableUDPGSO(err) {
|
if err != nil && offload && errShouldDisableUDPGSO(err) {
|
||||||
offload = false
|
offload = false
|
||||||
|
|
@ -398,7 +439,7 @@ retry:
|
||||||
} else {
|
} else {
|
||||||
for i := range bufs {
|
for i := range bufs {
|
||||||
(*msgs)[i].Addr = ua
|
(*msgs)[i].Addr = ua
|
||||||
(*msgs)[i].Buffers[0] = bufs[i]
|
(*msgs)[i].Buffers[0] = bufs[i][offset:]
|
||||||
setSrcControl(&(*msgs)[i].OOB, endpoint.(*StdNetEndpoint))
|
setSrcControl(&(*msgs)[i].OOB, endpoint.(*StdNetEndpoint))
|
||||||
}
|
}
|
||||||
err = s.send(conn, br, (*msgs)[:len(bufs)])
|
err = s.send(conn, br, (*msgs)[:len(bufs)])
|
||||||
|
|
@ -409,13 +450,17 @@ retry:
|
||||||
return err
|
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 {
|
func (s *StdNetBind) send(conn *net.UDPConn, pc batchWriter, msgs []ipv6.Message) error {
|
||||||
var (
|
var (
|
||||||
n int
|
n int
|
||||||
err error
|
err error
|
||||||
start int
|
start int
|
||||||
)
|
)
|
||||||
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
|
if runtime.GOOS == "linux" {
|
||||||
for {
|
for {
|
||||||
n, err = pc.WriteBatch(msgs[start:], 0)
|
n, err = pc.WriteBatch(msgs[start:], 0)
|
||||||
if err != nil || n == len(msgs[start:]) {
|
if err != nil || n == len(msgs[start:]) {
|
||||||
|
|
@ -447,7 +492,7 @@ const (
|
||||||
|
|
||||||
type setGSOFunc func(control *[]byte, gsoSize uint16)
|
type setGSOFunc func(control *[]byte, gsoSize uint16)
|
||||||
|
|
||||||
func coalesceMessages(addr *net.UDPAddr, ep *StdNetEndpoint, bufs [][]byte, msgs []ipv6.Message, setGSO setGSOFunc) int {
|
func coalesceMessages(addr *net.UDPAddr, ep *StdNetEndpoint, bufs [][]byte, offset int, msgs []ipv6.Message, setGSO setGSOFunc) int {
|
||||||
var (
|
var (
|
||||||
base = -1 // index of msg we are currently coalescing into
|
base = -1 // index of msg we are currently coalescing into
|
||||||
gsoSize int // segmentation size of msgs[base]
|
gsoSize int // segmentation size of msgs[base]
|
||||||
|
|
@ -459,6 +504,7 @@ func coalesceMessages(addr *net.UDPAddr, ep *StdNetEndpoint, bufs [][]byte, msgs
|
||||||
maxPayloadLen = maxIPv6PayloadLen
|
maxPayloadLen = maxIPv6PayloadLen
|
||||||
}
|
}
|
||||||
for i, buf := range bufs {
|
for i, buf := range bufs {
|
||||||
|
buf = buf[offset:]
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
msgLen := len(buf)
|
msgLen := len(buf)
|
||||||
baseLenBefore := len(msgs[base].Buffers[0])
|
baseLenBefore := len(msgs[base].Buffers[0])
|
||||||
|
|
|
||||||
|
|
@ -1,250 +0,0 @@
|
||||||
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])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -15,9 +15,12 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"unsafe"
|
"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"
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/conn/winrio"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -72,18 +75,26 @@ type afWinRingBind struct {
|
||||||
|
|
||||||
// WinRingBind uses Windows registered I/O for fast ring buffered networking.
|
// WinRingBind uses Windows registered I/O for fast ring buffered networking.
|
||||||
type WinRingBind struct {
|
type WinRingBind struct {
|
||||||
|
externalControl control.Func
|
||||||
|
reservedForEndpoint map[WinRingEndpoint][3]uint8
|
||||||
|
|
||||||
v4, v6 afWinRingBind
|
v4, v6 afWinRingBind
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
isOpen atomic.Uint32 // 0, 1, or 2
|
isOpen atomic.Uint32 // 0, 1, or 2
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultBind() Bind { return NewWinRingBind() }
|
func NewDefaultBind(externalControl control.Func) Bind {
|
||||||
|
return NewWinRingBind(externalControl)
|
||||||
|
}
|
||||||
|
|
||||||
func NewWinRingBind() Bind {
|
func NewWinRingBind(externalControl control.Func) Bind {
|
||||||
if !winrio.Initialize() {
|
if !winrio.Initialize() {
|
||||||
return NewStdNetBind()
|
return NewStdNetBind(externalControl)
|
||||||
|
}
|
||||||
|
return &WinRingBind{
|
||||||
|
externalControl: externalControl,
|
||||||
|
reservedForEndpoint: make(map[WinRingEndpoint][3]uint8),
|
||||||
}
|
}
|
||||||
return new(WinRingBind)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WinRingEndpoint struct {
|
type WinRingEndpoint struct {
|
||||||
|
|
@ -239,7 +250,7 @@ func (ring *ringBuffer) Open() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sockaddr, error) {
|
func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr, externalControl control.Func) (windows.Sockaddr, error) {
|
||||||
var err error
|
var err error
|
||||||
bind.sock, err = winrio.Socket(family, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
bind.sock, err = winrio.Socket(family, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -257,6 +268,19 @@ func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sock
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
err = windows.Bind(bind.sock, sa)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -268,6 +292,23 @@ func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sock
|
||||||
return sa, nil
|
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) {
|
func (bind *WinRingBind) Open(port uint16) (recvFns []ReceiveFunc, selectedPort uint16, err error) {
|
||||||
bind.mu.Lock()
|
bind.mu.Lock()
|
||||||
defer bind.mu.Unlock()
|
defer bind.mu.Unlock()
|
||||||
|
|
@ -280,11 +321,11 @@ func (bind *WinRingBind) Open(port uint16) (recvFns []ReceiveFunc, selectedPort
|
||||||
return nil, 0, ErrBindAlreadyOpen
|
return nil, 0, ErrBindAlreadyOpen
|
||||||
}
|
}
|
||||||
var sa windows.Sockaddr
|
var sa windows.Sockaddr
|
||||||
sa, err = bind.v4.Open(windows.AF_INET, &windows.SockaddrInet4{Port: int(port)})
|
sa, err = bind.v4.Open(windows.AF_INET, &windows.SockaddrInet4{Port: int(port)}, bind.externalControl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
sa, err = bind.v6.Open(windows.AF_INET6, &windows.SockaddrInet6{Port: sa.(*windows.SockaddrInet4).Port})
|
sa, err = bind.v6.Open(windows.AF_INET6, &windows.SockaddrInet6{Port: sa.(*windows.SockaddrInet4).Port}, bind.externalControl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
@ -420,6 +461,9 @@ func (bind *WinRingBind) receiveIPv4(bufs [][]byte, sizes []int, eps []Endpoint)
|
||||||
bind.mu.RLock()
|
bind.mu.RLock()
|
||||||
defer bind.mu.RUnlock()
|
defer bind.mu.RUnlock()
|
||||||
n, ep, err := bind.v4.Receive(bufs[0], &bind.isOpen)
|
n, ep, err := bind.v4.Receive(bufs[0], &bind.isOpen)
|
||||||
|
if n > 3 {
|
||||||
|
common.ClearArray(bufs[0][1:4])
|
||||||
|
}
|
||||||
sizes[0] = n
|
sizes[0] = n
|
||||||
eps[0] = ep
|
eps[0] = ep
|
||||||
return 1, err
|
return 1, err
|
||||||
|
|
@ -429,6 +473,9 @@ func (bind *WinRingBind) receiveIPv6(bufs [][]byte, sizes []int, eps []Endpoint)
|
||||||
bind.mu.RLock()
|
bind.mu.RLock()
|
||||||
defer bind.mu.RUnlock()
|
defer bind.mu.RUnlock()
|
||||||
n, ep, err := bind.v6.Receive(bufs[0], &bind.isOpen)
|
n, ep, err := bind.v6.Receive(bufs[0], &bind.isOpen)
|
||||||
|
if n > 3 {
|
||||||
|
common.ClearArray(bufs[0][1:4])
|
||||||
|
}
|
||||||
sizes[0] = n
|
sizes[0] = n
|
||||||
eps[0] = ep
|
eps[0] = ep
|
||||||
return 1, err
|
return 1, err
|
||||||
|
|
@ -486,7 +533,7 @@ func (bind *afWinRingBind) Send(buf []byte, nend *WinRingEndpoint, isOpen *atomi
|
||||||
return winrio.SendEx(bind.rq, dataBuffer, 1, nil, addressBuffer, nil, nil, 0, 0)
|
return winrio.SendEx(bind.rq, dataBuffer, 1, nil, addressBuffer, nil, nil, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint) error {
|
func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint, offset int) error {
|
||||||
nend, ok := endpoint.(*WinRingEndpoint)
|
nend, ok := endpoint.(*WinRingEndpoint)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrWrongEndpointType
|
return ErrWrongEndpointType
|
||||||
|
|
@ -494,6 +541,13 @@ func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint) error {
|
||||||
bind.mu.RLock()
|
bind.mu.RLock()
|
||||||
defer bind.mu.RUnlock()
|
defer bind.mu.RUnlock()
|
||||||
for _, buf := range bufs {
|
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 {
|
switch nend.family {
|
||||||
case windows.AF_INET:
|
case windows.AF_INET:
|
||||||
if bind.v4.blackhole {
|
if bind.v4.blackhole {
|
||||||
|
|
@ -514,6 +568,14 @@ func (bind *WinRingBind) Send(bufs [][]byte, endpoint Endpoint) error {
|
||||||
return nil
|
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 {
|
func (s *StdNetBind) BindSocketToInterface4(interfaceIndex uint32, blackhole bool) error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bindtest
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/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
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
46
conn/conn.go
46
conn/conn.go
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package conn implements WireGuard's network connections.
|
// Package conn implements WireGuard's network connections.
|
||||||
|
|
@ -45,9 +45,11 @@ type Bind interface {
|
||||||
// This mark is passed to the kernel as the socket option SO_MARK.
|
// This mark is passed to the kernel as the socket option SO_MARK.
|
||||||
SetMark(mark uint32) error
|
SetMark(mark uint32) error
|
||||||
|
|
||||||
// Send writes one or more packets in bufs to address ep. The length of
|
// Send writes one or more packets in bufs to address ep. A nonzero offset
|
||||||
// bufs must not exceed BatchSize().
|
// can be used to instruct the Bind on where packet data begins in each
|
||||||
Send(bufs [][]byte, ep Endpoint) error
|
// 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
|
||||||
|
|
||||||
// ParseEndpoint creates a new endpoint from a string.
|
// ParseEndpoint creates a new endpoint from a string.
|
||||||
ParseEndpoint(s string) (Endpoint, error)
|
ParseEndpoint(s string) (Endpoint, error)
|
||||||
|
|
@ -55,6 +57,8 @@ type Bind interface {
|
||||||
// BatchSize is the number of buffers expected to be passed to
|
// BatchSize is the number of buffers expected to be passed to
|
||||||
// the ReceiveFuncs, and the maximum expected to be passed to SendBatch.
|
// the ReceiveFuncs, and the maximum expected to be passed to SendBatch.
|
||||||
BatchSize() int
|
BatchSize() int
|
||||||
|
|
||||||
|
SetReservedForEndpoint(destination netip.AddrPort, reserved [3]byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindSocketToInterface is implemented by Bind objects that support being
|
// BindSocketToInterface is implemented by Bind objects that support being
|
||||||
|
|
@ -84,6 +88,40 @@ type Endpoint interface {
|
||||||
SrcIP() netip.Addr
|
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 (
|
var (
|
||||||
ErrBindAlreadyOpen = errors.New("bind is already open")
|
ErrBindAlreadyOpen = errors.New("bind is already open")
|
||||||
ErrWrongEndpointType = errors.New("endpoint type does not correspond with bind type")
|
ErrWrongEndpointType = errors.New("endpoint type does not correspond with bind type")
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !linux || android
|
//go:build !(linux && !android)
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
|
|
@ -22,7 +22,7 @@ func (e *StdNetEndpoint) SrcToString() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: macOS, FreeBSD and other BSDs likely do support the sticky sockets
|
// 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.
|
// ports and require testing.
|
||||||
|
|
||||||
// getSrcFromControl parses the control for PKTINFO and if found updates ep with
|
// getSrcFromControl parses the control for PKTINFO and if found updates ep with
|
||||||
|
|
@ -35,8 +35,17 @@ func getSrcFromControl(control []byte, ep *StdNetEndpoint) {
|
||||||
func setSrcControl(control *[]byte, ep *StdNetEndpoint) {
|
func setSrcControl(control *[]byte, ep *StdNetEndpoint) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// stickyControlSize returns the recommended buffer size for pooling sticky
|
// 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
|
||||||
// offloading control data.
|
// offloading control data.
|
||||||
const stickyControlSize = 0
|
const controlSize = 0
|
||||||
|
|
||||||
const StdNetSupportsStickySockets = false
|
const StdNetSupportsStickySockets = false
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package conn
|
package conn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
|
@ -105,8 +106,54 @@ func setSrcControl(control *[]byte, ep *StdNetEndpoint) {
|
||||||
*control = append(*control, ep.src...)
|
*control = append(*control, ep.src...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stickyControlSize returns the recommended buffer size for pooling sticky
|
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
|
||||||
// offloading control data.
|
// offloading control data.
|
||||||
var stickyControlSize = unix.CmsgSpace(unix.SizeofInet6Pktinfo)
|
var controlSize = unix.CmsgSpace(unix.SizeofInet6Pktinfo) + unix.CmsgSpace(sizeOfGSOData)
|
||||||
|
|
||||||
const StdNetSupportsStickySockets = true
|
const StdNetSupportsStickySockets = true
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"github.com/sagernet/sing/common/control"
|
||||||
"syscall"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it is
|
// UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it is
|
||||||
|
|
@ -17,27 +16,6 @@ import (
|
||||||
// around this limitation)
|
// around this limitation)
|
||||||
const socketBufferSize = 7 << 20
|
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
|
// controlFns is a list of functions that are called from the listen config
|
||||||
// that can apply socket options.
|
// that can apply socket options.
|
||||||
var controlFns = []controlFn{}
|
var controlFns []control.Func
|
||||||
|
|
||||||
// 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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,17 @@ func init() {
|
||||||
|
|
||||||
// Attempt to enable UDP_GRO
|
// Attempt to enable UDP_GRO
|
||||||
func(network, address string, c syscall.RawConn) error {
|
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" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
c.Control(func(fd uintptr) {
|
c.Control(func(fd uintptr) {
|
||||||
_ = unix.SetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_GRO, 1)
|
_ = unix.SetsockoptInt(int(fd), unix.IPPROTO_UDP, socketOptionUDPGRO, 1)
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
//go:build !windows && !linux && !wasm
|
//go:build !windows && !linux && !wasm && !plan9 && !tamago
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,13 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
||||||
func NewDefaultBind() Bind { return NewStdNetBind() }
|
import "github.com/sagernet/sing/common/control"
|
||||||
|
|
||||||
|
func NewDefaultBind(externalControl control.Func) Bind {
|
||||||
|
return NewStdNetBind(externalControl)
|
||||||
|
}
|
||||||
|
|
|
||||||
14
conn/erraddrinuse.go
Normal file
14
conn/erraddrinuse.go
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
//go:build !plan9
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package conn
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
errEADDRINUSE = syscall.EADDRINUSE
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,43 @@ package conn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"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) {
|
func supportsUDPOffload(conn *net.UDPConn) (txOffload, rxOffload bool) {
|
||||||
rc, err := conn.SyscallConn()
|
rc, err := conn.SyscallConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = rc.Control(func(fd uintptr) {
|
err = rc.Control(func(fd uintptr) {
|
||||||
_, errSyscall := unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_SEGMENT)
|
_, errSyscall := unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, socketOptionUDPSegment)
|
||||||
txOffload = errSyscall == nil
|
if errSyscall != nil {
|
||||||
opt, errSyscall := unix.GetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_GRO)
|
return
|
||||||
rxOffload = errSyscall == nil && opt == 1
|
}
|
||||||
|
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
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, false
|
return false, false
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
//go:build !linux
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
//go:build linux
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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)
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package conn
|
package conn
|
||||||
|
|
|
||||||
|
|
@ -1,266 +0,0 @@
|
||||||
//go:build linux && !android
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package winrio
|
package winrio
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -223,19 +223,11 @@ func (table *AllowedIPs) EntriesForPeer(peer *Peer, cb func(prefix netip.Prefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
func (node *trieEntry) remove() {
|
||||||
table.mutex.Lock()
|
|
||||||
defer table.mutex.Unlock()
|
|
||||||
|
|
||||||
var next *list.Element
|
|
||||||
for elem := peer.trieEntries.Front(); elem != nil; elem = next {
|
|
||||||
next = elem.Next()
|
|
||||||
node := elem.Value.(*trieEntry)
|
|
||||||
|
|
||||||
node.removeFromPeerEntries()
|
node.removeFromPeerEntries()
|
||||||
node.peer = nil
|
node.peer = nil
|
||||||
if node.child[0] != nil && node.child[1] != nil {
|
if node.child[0] != nil && node.child[1] != nil {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
bit := 0
|
bit := 0
|
||||||
if node.child[0] == nil {
|
if node.child[0] == nil {
|
||||||
|
|
@ -248,12 +240,12 @@ func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
||||||
*node.parent.parentBit = child
|
*node.parent.parentBit = child
|
||||||
if node.child[0] != nil || node.child[1] != nil || node.parent.parentBitType > 1 {
|
if node.child[0] != nil || node.child[1] != nil || node.parent.parentBitType > 1 {
|
||||||
node.zeroizePointers()
|
node.zeroizePointers()
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
parent := (*trieEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(node.parent.parentBit)) - unsafe.Offsetof(node.child) - unsafe.Sizeof(node.child[0])*uintptr(node.parent.parentBitType)))
|
parent := (*trieEntry)(unsafe.Pointer(uintptr(unsafe.Pointer(node.parent.parentBit)) - unsafe.Offsetof(node.child) - unsafe.Sizeof(node.child[0])*uintptr(node.parent.parentBitType)))
|
||||||
if parent.peer != nil {
|
if parent.peer != nil {
|
||||||
node.zeroizePointers()
|
node.zeroizePointers()
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
child = parent.child[node.parent.parentBitType^1]
|
child = parent.child[node.parent.parentBitType^1]
|
||||||
if child != nil {
|
if child != nil {
|
||||||
|
|
@ -262,6 +254,37 @@ func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
||||||
*parent.parent.parentBit = child
|
*parent.parent.parentBit = child
|
||||||
node.zeroizePointers()
|
node.zeroizePointers()
|
||||||
parent.zeroizePointers()
|
parent.zeroizePointers()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (table *AllowedIPs) Remove(prefix netip.Prefix, peer *Peer) {
|
||||||
|
table.mutex.Lock()
|
||||||
|
defer table.mutex.Unlock()
|
||||||
|
var node *trieEntry
|
||||||
|
var exact bool
|
||||||
|
|
||||||
|
if prefix.Addr().Is6() {
|
||||||
|
ip := prefix.Addr().As16()
|
||||||
|
node, exact = table.IPv6.nodePlacement(ip[:], uint8(prefix.Bits()))
|
||||||
|
} else if prefix.Addr().Is4() {
|
||||||
|
ip := prefix.Addr().As4()
|
||||||
|
node, exact = table.IPv4.nodePlacement(ip[:], uint8(prefix.Bits()))
|
||||||
|
} else {
|
||||||
|
panic(errors.New("removing unknown address type"))
|
||||||
|
}
|
||||||
|
if !exact || node == nil || peer != node.peer {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
||||||
|
table.mutex.Lock()
|
||||||
|
defer table.mutex.Unlock()
|
||||||
|
|
||||||
|
var next *list.Element
|
||||||
|
for elem := peer.trieEntries.Front(); elem != nil; elem = next {
|
||||||
|
next = elem.Next()
|
||||||
|
elem.Value.(*trieEntry).remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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
|
|
||||||
|
|
||||||
rand.Seed(1)
|
|
||||||
|
|
||||||
for n := 0; n < NumberOfPeers; n++ {
|
|
||||||
peers = append(peers, &Peer{})
|
|
||||||
}
|
|
||||||
|
|
||||||
for n := 0; n < NumberOfAddresses; n++ {
|
|
||||||
var addr4 [4]byte
|
|
||||||
rand.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
|
|
||||||
rand.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
|
|
||||||
rand.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
|
|
||||||
rand.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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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, addressLength int, b *testing.B) {
|
|
||||||
var trie *trieEntry
|
|
||||||
var peers []*Peer
|
|
||||||
root := parentIndirection{&trie, 2}
|
|
||||||
|
|
||||||
rand.Seed(1)
|
|
||||||
|
|
||||||
const AddressLength = 4
|
|
||||||
|
|
||||||
for n := 0; n < peerNumber; n++ {
|
|
||||||
peers = append(peers, &Peer{})
|
|
||||||
}
|
|
||||||
|
|
||||||
for n := 0; n < addressNumber; n++ {
|
|
||||||
var addr [AddressLength]byte
|
|
||||||
rand.Read(addr[:])
|
|
||||||
cidr := uint8(rand.Uint32() % (AddressLength * 8))
|
|
||||||
index := rand.Int() % peerNumber
|
|
||||||
root.insert(addr[:], cidr, peers[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
var addr [AddressLength]byte
|
|
||||||
rand.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/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
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -29,7 +29,7 @@ const (
|
||||||
const (
|
const (
|
||||||
MinMessageSize = MessageKeepaliveSize // minimum size of transport message (keepalive)
|
MinMessageSize = MessageKeepaliveSize // minimum size of transport message (keepalive)
|
||||||
MaxMessageSize = MaxSegmentSize // maximum size of transport message
|
MaxMessageSize = MaxSegmentSize // maximum size of transport message
|
||||||
MaxContentSize = MaxSegmentSize - MessageTransportSize // maximum size of transport message content
|
MaxContentSize = MaxSegmentSize - MessageTransportSize - MessageEncapsulatingTransportSize // maximum size of transport message content
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Implementation constants */
|
/* Implementation constants */
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -118,6 +118,7 @@ func (st *CookieChecker) CreateReply(
|
||||||
msg []byte,
|
msg []byte,
|
||||||
recv uint32,
|
recv uint32,
|
||||||
src []byte,
|
src []byte,
|
||||||
|
msgType uint32,
|
||||||
) (*MessageCookieReply, error) {
|
) (*MessageCookieReply, error) {
|
||||||
st.RLock()
|
st.RLock()
|
||||||
|
|
||||||
|
|
@ -153,7 +154,7 @@ func (st *CookieChecker) CreateReply(
|
||||||
smac1 := smac2 - blake2s.Size128
|
smac1 := smac2 - blake2s.Size128
|
||||||
|
|
||||||
reply := new(MessageCookieReply)
|
reply := new(MessageCookieReply)
|
||||||
reply.Type = MessageCookieReplyType
|
reply.Type = msgType
|
||||||
reply.Receiver = recv
|
reply.Receiver = recv
|
||||||
|
|
||||||
_, err := rand.Read(reply.Nonce[:])
|
_, err := rand.Read(reply.Nonce[:])
|
||||||
|
|
|
||||||
|
|
@ -1,190 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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)
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +1,23 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
"github.com/sagernet/sing/service"
|
||||||
"golang.zx2c4.com/wireguard/ratelimiter"
|
"github.com/sagernet/sing/service/pause"
|
||||||
"golang.zx2c4.com/wireguard/rwcancel"
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"github.com/sagernet/wireguard-go/ratelimiter"
|
||||||
|
"github.com/sagernet/wireguard-go/rwcancel"
|
||||||
|
"github.com/sagernet/wireguard-go/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
|
|
@ -89,6 +92,30 @@ type Device struct {
|
||||||
ipcMutex sync.RWMutex
|
ipcMutex sync.RWMutex
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
log *Logger
|
log *Logger
|
||||||
|
pauseManager pause.Manager
|
||||||
|
|
||||||
|
// lx: AmneziaWG obfuscation state (grafted from amneziawg-go).
|
||||||
|
junk struct {
|
||||||
|
min int
|
||||||
|
max int
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
headers struct {
|
||||||
|
init *magicHeader
|
||||||
|
cookie *magicHeader
|
||||||
|
response *magicHeader
|
||||||
|
transport *magicHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
paddings struct {
|
||||||
|
init int
|
||||||
|
response int
|
||||||
|
cookie int
|
||||||
|
transport int
|
||||||
|
}
|
||||||
|
|
||||||
|
ipackets [5]*obfChain
|
||||||
}
|
}
|
||||||
|
|
||||||
// deviceState represents the state of a Device.
|
// deviceState represents the state of a Device.
|
||||||
|
|
@ -162,7 +189,8 @@ func (device *Device) changeState(want deviceState) (err error) {
|
||||||
err = errDown
|
err = errDown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
device.log.Verbosef("Interface state was %s, requested %s, now %s", old, want, device.deviceState())
|
device.log.Verbosef(
|
||||||
|
"Interface state was %s, requested %s, now %s", old, want, device.deviceState())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,8 +309,9 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
|
func NewDevice(ctx context.Context, tunDevice tun.Device, bind conn.Bind, logger *Logger, workers int) *Device {
|
||||||
device := new(Device)
|
device := new(Device)
|
||||||
|
device.pauseManager = service.FromContext[pause.Manager](ctx)
|
||||||
device.state.state.Store(uint32(deviceStateDown))
|
device.state.state.Store(uint32(deviceStateDown))
|
||||||
device.closed = make(chan struct{})
|
device.closed = make(chan struct{})
|
||||||
device.log = logger
|
device.log = logger
|
||||||
|
|
@ -298,6 +327,11 @@ func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
|
||||||
device.rate.limiter.Init()
|
device.rate.limiter.Init()
|
||||||
device.indexTable.Init()
|
device.indexTable.Init()
|
||||||
|
|
||||||
|
device.headers.init = &magicHeader{start: MessageInitiationType, end: MessageInitiationType}
|
||||||
|
device.headers.response = &magicHeader{start: MessageResponseType, end: MessageResponseType}
|
||||||
|
device.headers.cookie = &magicHeader{start: MessageCookieReplyType, end: MessageCookieReplyType}
|
||||||
|
device.headers.transport = &magicHeader{start: MessageTransportType, end: MessageTransportType}
|
||||||
|
|
||||||
device.PopulatePools()
|
device.PopulatePools()
|
||||||
|
|
||||||
// create queues
|
// create queues
|
||||||
|
|
@ -308,10 +342,12 @@ func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
|
||||||
|
|
||||||
// start workers
|
// start workers
|
||||||
|
|
||||||
cpus := runtime.NumCPU()
|
if workers == 0 {
|
||||||
|
workers = runtime.NumCPU()
|
||||||
|
}
|
||||||
device.state.stopping.Wait()
|
device.state.stopping.Wait()
|
||||||
device.queue.encryption.wg.Add(cpus) // One for each RoutineHandshake
|
device.queue.encryption.wg.Add(workers) // One for each RoutineHandshake
|
||||||
for i := 0; i < cpus; i++ {
|
for i := 0; i < workers; i++ {
|
||||||
go device.RoutineEncryption(i + 1)
|
go device.RoutineEncryption(i + 1)
|
||||||
go device.RoutineDecryption(i + 1)
|
go device.RoutineDecryption(i + 1)
|
||||||
go device.RoutineHandshake(i + 1)
|
go device.RoutineHandshake(i + 1)
|
||||||
|
|
|
||||||
|
|
@ -1,476 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"runtime/pprof"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
|
||||||
"golang.zx2c4.com/wireguard/conn/bindtest"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
"golang.zx2c4.com/wireguard/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) (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()
|
|
||||||
|
|
||||||
cfgs[0] = uapiCfg(
|
|
||||||
"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",
|
|
||||||
)
|
|
||||||
endpointCfgs[0] = uapiCfg(
|
|
||||||
"public_key", hex.EncodeToString(pub2[:]),
|
|
||||||
"endpoint", "127.0.0.1:%d",
|
|
||||||
)
|
|
||||||
cfgs[1] = uapiCfg(
|
|
||||||
"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",
|
|
||||||
)
|
|
||||||
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(5 * 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) (pair testPair) {
|
|
||||||
cfg, endpointCfg := genConfigs(tb)
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/replay"
|
"github.com/sagernet/wireguard-go/replay"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Due to limitations in Go and /x/crypto there is currently
|
/* Due to limitations in Go and /x/crypto there is currently
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
63
device/magic-header.go
Normal file
63
device/magic-header.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type magicHeader struct {
|
||||||
|
start uint32
|
||||||
|
end uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMagicHeader(spec string) (*magicHeader, error) {
|
||||||
|
parts := strings.Split(spec, "-")
|
||||||
|
if len(parts) < 1 || len(parts) > 2 {
|
||||||
|
return nil, errors.New("bad format")
|
||||||
|
}
|
||||||
|
|
||||||
|
start, err := strconv.ParseUint(parts[0], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s: %w", parts[0], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var end uint64
|
||||||
|
if len(parts) > 1 {
|
||||||
|
end, err = strconv.ParseUint(parts[1], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse %s: %w", parts[1], err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
end = start
|
||||||
|
}
|
||||||
|
|
||||||
|
if end < start {
|
||||||
|
return nil, errors.New("wrong range specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &magicHeader{
|
||||||
|
start: uint32(start),
|
||||||
|
end: uint32(end),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *magicHeader) GenSpec() string {
|
||||||
|
if h.start == h.end {
|
||||||
|
return fmt.Sprintf("%d", h.start)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d-%d", h.start, h.end)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *magicHeader) Validate(val uint32) bool {
|
||||||
|
return h.start <= val && val <= h.end
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *magicHeader) Generate() uint32 {
|
||||||
|
high := int64(h.end - h.start + 1)
|
||||||
|
r, _ := rand.Int(rand.Reader, big.NewInt(high))
|
||||||
|
return h.start + uint32(r.Int64())
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,22 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
|
"github.com/sagernet/wireguard-go/tai64n"
|
||||||
"golang.org/x/crypto/blake2s"
|
"golang.org/x/crypto/blake2s"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"golang.org/x/crypto/poly1305"
|
"golang.org/x/crypto/poly1305"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/tai64n"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type handshakeState int
|
type handshakeState int
|
||||||
|
|
@ -53,10 +54,11 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MessageInitiationType = 1
|
MessageUnknownType uint32 = 0
|
||||||
MessageResponseType = 2
|
MessageInitiationType uint32 = 1
|
||||||
MessageCookieReplyType = 3
|
MessageResponseType uint32 = 2
|
||||||
MessageTransportType = 4
|
MessageCookieReplyType uint32 = 3
|
||||||
|
MessageTransportType uint32 = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -64,6 +66,7 @@ const (
|
||||||
MessageResponseSize = 92 // size of response message
|
MessageResponseSize = 92 // size of response message
|
||||||
MessageCookieReplySize = 64 // size of cookie reply message
|
MessageCookieReplySize = 64 // size of cookie reply message
|
||||||
MessageTransportHeaderSize = 16 // size of data preceding content in transport 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
|
MessageTransportSize = MessageTransportHeaderSize + poly1305.TagSize // size of empty transport
|
||||||
MessageKeepaliveSize = MessageTransportSize // size of keepalive
|
MessageKeepaliveSize = MessageTransportSize // size of keepalive
|
||||||
MessageHandshakeSize = MessageInitiationSize // size of largest handshake related message
|
MessageHandshakeSize = MessageInitiationSize // size of largest handshake related message
|
||||||
|
|
@ -115,6 +118,98 @@ type MessageCookieReply struct {
|
||||||
Cookie [blake2s.Size128 + poly1305.TagSize]byte
|
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 {
|
type Handshake struct {
|
||||||
state handshakeState
|
state handshakeState
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
|
|
@ -124,7 +219,7 @@ type Handshake struct {
|
||||||
localEphemeral NoisePrivateKey // ephemeral secret key
|
localEphemeral NoisePrivateKey // ephemeral secret key
|
||||||
localIndex uint32 // used to clear hash-table
|
localIndex uint32 // used to clear hash-table
|
||||||
remoteIndex uint32 // index for sending
|
remoteIndex uint32 // index for sending
|
||||||
remoteStatic NoisePublicKey // long term key
|
remoteStatic NoisePublicKey // long term key, never changes, can be accessed without mutex
|
||||||
remoteEphemeral NoisePublicKey // ephemeral public key
|
remoteEphemeral NoisePublicKey // ephemeral public key
|
||||||
precomputedStaticStatic [NoisePublicKeySize]byte // precomputed shared secret
|
precomputedStaticStatic [NoisePublicKeySize]byte // precomputed shared secret
|
||||||
lastTimestamp tai64n.Timestamp
|
lastTimestamp tai64n.Timestamp
|
||||||
|
|
@ -193,8 +288,10 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
||||||
|
|
||||||
handshake.mixHash(handshake.remoteStatic[:])
|
handshake.mixHash(handshake.remoteStatic[:])
|
||||||
|
|
||||||
|
msgType := device.headers.init.Generate()
|
||||||
|
|
||||||
msg := MessageInitiation{
|
msg := MessageInitiation{
|
||||||
Type: MessageInitiationType,
|
Type: msgType,
|
||||||
Ephemeral: handshake.localEphemeral.publicKey(),
|
Ephemeral: handshake.localEphemeral.publicKey(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,7 +341,7 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
||||||
return &msg, nil
|
return &msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation, endpoint conn.Endpoint) *Peer {
|
||||||
var (
|
var (
|
||||||
hash [blake2s.Size]byte
|
hash [blake2s.Size]byte
|
||||||
chainKey [blake2s.Size]byte
|
chainKey [blake2s.Size]byte
|
||||||
|
|
@ -278,6 +375,11 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
||||||
|
|
||||||
// lookup peer
|
// lookup peer
|
||||||
|
|
||||||
|
initEP, ok := endpoint.(conn.InitiationAwareEndpoint)
|
||||||
|
if ok {
|
||||||
|
initEP.InitiationMessagePublicKey(peerPK)
|
||||||
|
}
|
||||||
|
|
||||||
peer := device.LookupPeer(peerPK)
|
peer := device.LookupPeer(peerPK)
|
||||||
if peer == nil || !peer.isRunning.Load() {
|
if peer == nil || !peer.isRunning.Load() {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -367,7 +469,7 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg MessageResponse
|
var msg MessageResponse
|
||||||
msg.Type = MessageResponseType
|
msg.Type = device.headers.response.Generate()
|
||||||
msg.Sender = handshake.localIndex
|
msg.Sender = handshake.localIndex
|
||||||
msg.Receiver = handshake.remoteIndex
|
msg.Receiver = handshake.remoteIndex
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,179 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
|
||||||
"golang.zx2c4.com/wireguard/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)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
140
device/obf.go
Normal file
140
device/obf.go
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type obfBuilder func(val string) (obf, error)
|
||||||
|
|
||||||
|
var obfBuilders = map[string]obfBuilder{
|
||||||
|
"b": newBytesObf,
|
||||||
|
"t": newTimestampObf,
|
||||||
|
"r": newRandObf,
|
||||||
|
"rc": newRandCharObf,
|
||||||
|
"rd": newRandDigitsObf,
|
||||||
|
"d": newDataObf,
|
||||||
|
"ds": newDataStringObf,
|
||||||
|
"dz": newDataSizeObf,
|
||||||
|
}
|
||||||
|
|
||||||
|
type obf interface {
|
||||||
|
Obfuscate(dst, src []byte)
|
||||||
|
Deobfuscate(dst, src []byte) bool
|
||||||
|
ObfuscatedLen(srcLen int) int
|
||||||
|
DeobfuscatedLen(srcLen int) int
|
||||||
|
}
|
||||||
|
|
||||||
|
type obfChain struct {
|
||||||
|
Spec string
|
||||||
|
obfs []obf
|
||||||
|
}
|
||||||
|
|
||||||
|
func newObfChain(spec string) (*obfChain, error) {
|
||||||
|
var (
|
||||||
|
obfs []obf
|
||||||
|
errs []error
|
||||||
|
)
|
||||||
|
|
||||||
|
remaining := spec[:]
|
||||||
|
for {
|
||||||
|
start := strings.IndexByte(remaining, '<')
|
||||||
|
if start == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
end := strings.IndexByte(remaining[start:], '>')
|
||||||
|
if end == -1 {
|
||||||
|
return nil, errors.New("missing enclosing >")
|
||||||
|
}
|
||||||
|
end += start
|
||||||
|
|
||||||
|
tag := remaining[start+1 : end]
|
||||||
|
parts := strings.Fields(tag)
|
||||||
|
if len(parts) == 0 {
|
||||||
|
errs = append(errs, errors.New("empty tag"))
|
||||||
|
remaining = remaining[end+1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := parts[0]
|
||||||
|
builder, ok := obfBuilders[key]
|
||||||
|
if !ok {
|
||||||
|
errs = append(errs, fmt.Errorf("unknown tag <%s>", key))
|
||||||
|
remaining = remaining[end+1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val := ""
|
||||||
|
if len(parts) > 1 {
|
||||||
|
val = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
o, err := builder(val)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("failed to build <%s>: %w", key, err))
|
||||||
|
remaining = remaining[end+1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
obfs = append(obfs, o)
|
||||||
|
remaining = remaining[end+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &obfChain{
|
||||||
|
Spec: spec,
|
||||||
|
obfs: obfs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfChain) Obfuscate(dst, src []byte) {
|
||||||
|
written := 0
|
||||||
|
for _, o := range c.obfs {
|
||||||
|
obfLen := o.ObfuscatedLen(len(src))
|
||||||
|
o.Obfuscate(dst[written:written+obfLen], src)
|
||||||
|
written += obfLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfChain) Deobfuscate(dst, src []byte) bool {
|
||||||
|
dynamicLen := len(src) - c.ObfuscatedLen(0)
|
||||||
|
|
||||||
|
written, read := 0, 0
|
||||||
|
|
||||||
|
for _, o := range c.obfs {
|
||||||
|
deobfLen := o.DeobfuscatedLen(dynamicLen)
|
||||||
|
obfLen := o.ObfuscatedLen(deobfLen)
|
||||||
|
|
||||||
|
if !o.Deobfuscate(dst[written:written+deobfLen], src[read:read+obfLen]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
written += deobfLen
|
||||||
|
read += obfLen
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfChain) ObfuscatedLen(n int) int {
|
||||||
|
total := 0
|
||||||
|
for _, o := range c.obfs {
|
||||||
|
total += o.ObfuscatedLen(n)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *obfChain) DeobfuscatedLen(n int) int {
|
||||||
|
dynamicLen := n - c.ObfuscatedLen(0)
|
||||||
|
|
||||||
|
total := 0
|
||||||
|
for _, o := range c.obfs {
|
||||||
|
total += o.DeobfuscatedLen(dynamicLen)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
47
device/obf_bytes.go
Normal file
47
device/obf_bytes.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newBytesObf(val string) (obf, error) {
|
||||||
|
val = strings.TrimPrefix(val, "0x")
|
||||||
|
|
||||||
|
if len(val) == 0 {
|
||||||
|
return nil, errors.New("empty argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(val)%2 != 0 {
|
||||||
|
return nil, errors.New("odd amount of symbols")
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes, err := hex.DecodeString(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bytesObf{data: bytes}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type bytesObf struct {
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bytesObf) Obfuscate(dst, src []byte) {
|
||||||
|
copy(dst, o.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bytesObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
return bytes.Equal(o.data, src[:o.ObfuscatedLen(0)])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bytesObf) ObfuscatedLen(srcLen int) int {
|
||||||
|
return len(o.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *bytesObf) DeobfuscatedLen(srcLen int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
25
device/obf_data.go
Normal file
25
device/obf_data.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
func newDataObf(val string) (obf, error) {
|
||||||
|
return &dataObf{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dataObf struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obf *dataObf) Obfuscate(dst, src []byte) {
|
||||||
|
copy(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obf *dataObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
copy(dst, src)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataObf) ObfuscatedLen(n int) int {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataObf) DeobfuscatedLen(n int) int {
|
||||||
|
return n
|
||||||
|
}
|
||||||
38
device/obf_datasize.go
Normal file
38
device/obf_datasize.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func newDataSizeObf(val string) (obf, error) {
|
||||||
|
length, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dataSizeObf{
|
||||||
|
length: length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dataSizeObf struct {
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataSizeObf) Obfuscate(dst, src []byte) {
|
||||||
|
srcLen := len(src)
|
||||||
|
for i := o.length - 1; i >= 0; i-- {
|
||||||
|
dst[i] = byte(srcLen & 0xFF)
|
||||||
|
srcLen >>= 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataSizeObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataSizeObf) ObfuscatedLen(srcLen int) int {
|
||||||
|
return o.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataSizeObf) DeobfuscatedLen(srcLen int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
29
device/obf_datastring.go
Normal file
29
device/obf_datastring.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDataStringObf(val string) (obf, error) {
|
||||||
|
return &dataStringObf{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dataStringObf struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataStringObf) Obfuscate(dst, src []byte) {
|
||||||
|
base64.RawStdEncoding.Encode(dst, src)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataStringObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
base64.RawStdEncoding.Decode(dst, src)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataStringObf) ObfuscatedLen(n int) int {
|
||||||
|
return base64.RawStdEncoding.EncodedLen(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *dataStringObf) DeobfuscatedLen(n int) int {
|
||||||
|
return base64.RawStdEncoding.DecodedLen(n)
|
||||||
|
}
|
||||||
39
device/obf_rand.go
Normal file
39
device/obf_rand.go
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRandObf(val string) (obf, error) {
|
||||||
|
length, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &randObf{
|
||||||
|
length: length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type randObf struct {
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randObf) Obfuscate(dst, src []byte) {
|
||||||
|
rand.Read(dst[:o.length])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
// there is no way to validate randomness :)
|
||||||
|
// assume that it is always true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randObf) ObfuscatedLen(n int) int {
|
||||||
|
return o.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randObf) DeobfuscatedLen(n int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
48
device/obf_randchars.go
Normal file
48
device/obf_randchars.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const chars52 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
func newRandCharObf(val string) (obf, error) {
|
||||||
|
length, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &randCharObf{
|
||||||
|
length: length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type randCharObf struct {
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randCharObf) Obfuscate(dst, src []byte) {
|
||||||
|
rand.Read(dst[:o.length])
|
||||||
|
for i := range dst[:o.length] {
|
||||||
|
dst[i] = chars52[dst[i]%52]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randCharObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
for _, b := range src[:o.length] {
|
||||||
|
if !unicode.IsLetter(rune(b)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randCharObf) ObfuscatedLen(n int) int {
|
||||||
|
return o.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randCharObf) DeobfuscatedLen(n int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
48
device/obf_randdigits.go
Normal file
48
device/obf_randdigits.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const digits10 = "0123456789"
|
||||||
|
|
||||||
|
func newRandDigitsObf(val string) (obf, error) {
|
||||||
|
length, err := strconv.Atoi(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &randDigitObf{
|
||||||
|
length: length,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type randDigitObf struct {
|
||||||
|
length int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randDigitObf) Obfuscate(dst, src []byte) {
|
||||||
|
rand.Read(dst[:o.length])
|
||||||
|
for i := range dst[:o.length] {
|
||||||
|
dst[i] = digits10[dst[i]%10]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randDigitObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
for _, b := range src[:o.length] {
|
||||||
|
if !unicode.IsDigit(rune(b)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randDigitObf) ObfuscatedLen(n int) int {
|
||||||
|
return o.length
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *randDigitObf) DeobfuscatedLen(n int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
31
device/obf_timestamp.go
Normal file
31
device/obf_timestamp.go
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTimestampObf(_ string) (obf, error) {
|
||||||
|
return ×tampObf{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type timestampObf struct{}
|
||||||
|
|
||||||
|
func (o *timestampObf) Obfuscate(dst, src []byte) {
|
||||||
|
t := uint32(time.Now().Unix())
|
||||||
|
binary.BigEndian.PutUint32(dst, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *timestampObf) Deobfuscate(dst, src []byte) bool {
|
||||||
|
// replay attack check?
|
||||||
|
// requires time to be always synchronized
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *timestampObf) ObfuscatedLen(n int) int {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *timestampObf) DeobfuscatedLen(n int) int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
|
|
@ -113,6 +113,9 @@ func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
|
||||||
return peer, nil
|
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 {
|
func (peer *Peer) SendBuffers(buffers [][]byte) error {
|
||||||
peer.device.net.RLock()
|
peer.device.net.RLock()
|
||||||
defer peer.device.net.RUnlock()
|
defer peer.device.net.RUnlock()
|
||||||
|
|
@ -133,7 +136,7 @@ func (peer *Peer) SendBuffers(buffers [][]byte) error {
|
||||||
}
|
}
|
||||||
peer.endpoint.Unlock()
|
peer.endpoint.Unlock()
|
||||||
|
|
||||||
err := peer.device.net.bind.Send(buffers, endpoint)
|
err := peer.device.net.bind.Send(buffers, endpoint, MessageEncapsulatingTransportSize)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var totalLen uint64
|
var totalLen uint64
|
||||||
for _, b := range buffers {
|
for _, b := range buffers {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WaitPool struct {
|
type WaitPool struct {
|
||||||
pool sync.Pool
|
pool sync.Pool
|
||||||
cond sync.Cond
|
cond sync.Cond
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
count atomic.Uint32
|
count uint32 // Get calls not yet Put back
|
||||||
max uint32
|
max uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,10 +26,10 @@ func NewWaitPool(max uint32, new func() any) *WaitPool {
|
||||||
func (p *WaitPool) Get() any {
|
func (p *WaitPool) Get() any {
|
||||||
if p.max != 0 {
|
if p.max != 0 {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
for p.count.Load() >= p.max {
|
for p.count >= p.max {
|
||||||
p.cond.Wait()
|
p.cond.Wait()
|
||||||
}
|
}
|
||||||
p.count.Add(1)
|
p.count++
|
||||||
p.lock.Unlock()
|
p.lock.Unlock()
|
||||||
}
|
}
|
||||||
return p.pool.Get()
|
return p.pool.Get()
|
||||||
|
|
@ -41,7 +40,9 @@ func (p *WaitPool) Put(x any) {
|
||||||
if p.max == 0 {
|
if p.max == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.count.Add(^uint32(0))
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
p.count--
|
||||||
p.cond.Signal()
|
p.cond.Signal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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() {
|
|
||||||
count := p.count.Load()
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import "golang.zx2c4.com/wireguard/conn"
|
import "github.com/sagernet/wireguard-go/conn"
|
||||||
|
|
||||||
/* Reduce memory consumption for Android */
|
/* Reduce memory consumption for Android */
|
||||||
|
|
||||||
|
|
@ -14,6 +14,6 @@ const (
|
||||||
QueueOutboundSize = 1024
|
QueueOutboundSize = 1024
|
||||||
QueueInboundSize = 1024
|
QueueInboundSize = 1024
|
||||||
QueueHandshakeSize = 1024
|
QueueHandshakeSize = 1024
|
||||||
MaxSegmentSize = (1 << 16) - 1 // largest possible UDP datagram
|
MaxSegmentSize = 2200
|
||||||
PreallocatedBuffersPerPool = 4096
|
PreallocatedBuffersPerPool = 4096
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import "golang.zx2c4.com/wireguard/conn"
|
import "github.com/sagernet/wireguard-go/conn"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
QueueStagedSize = conn.IdealBatchSize
|
QueueStagedSize = conn.IdealBatchSize
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
//go:build !race
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
const raceEnabled = false
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
//go:build race
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
const raceEnabled = true
|
|
||||||
|
|
@ -1,22 +1,21 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type QueueHandshakeElement struct {
|
type QueueHandshakeElement struct {
|
||||||
|
|
@ -70,7 +69,10 @@ func (peer *Peer) keepKeyFreshReceiving() {
|
||||||
* Every time the bind is updated a new routine is started for
|
* Every time the bind is updated a new routine is started for
|
||||||
* IPv4 and IPv6 (separately)
|
* IPv4 and IPv6 (separately)
|
||||||
*/
|
*/
|
||||||
func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.ReceiveFunc) {
|
func (device *Device) RoutineReceiveIncoming(
|
||||||
|
maxBatchSize int,
|
||||||
|
recv conn.ReceiveFunc,
|
||||||
|
) {
|
||||||
recvName := recv.PrettyName()
|
recvName := recv.PrettyName()
|
||||||
defer func() {
|
defer func() {
|
||||||
device.log.Verbosef("Routine: receive incoming %s - stopped", recvName)
|
device.log.Verbosef("Routine: receive incoming %s - stopped", recvName)
|
||||||
|
|
@ -94,13 +96,13 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||||
elemsByPeer = make(map[*Peer]*QueueInboundElementsContainer, maxBatchSize)
|
elemsByPeer = make(map[*Peer]*QueueInboundElementsContainer, maxBatchSize)
|
||||||
)
|
)
|
||||||
|
|
||||||
for i := range bufsArrs {
|
for i := range maxBatchSize {
|
||||||
bufsArrs[i] = device.GetMessageBuffer()
|
bufsArrs[i] = device.GetMessageBuffer()
|
||||||
bufs[i] = bufsArrs[i][:]
|
bufs[i] = bufsArrs[i][:]
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
for i := 0; i < maxBatchSize; i++ {
|
for i := range maxBatchSize {
|
||||||
if bufsArrs[i] != nil {
|
if bufsArrs[i] != nil {
|
||||||
device.PutMessageBuffer(bufsArrs[i])
|
device.PutMessageBuffer(bufsArrs[i])
|
||||||
}
|
}
|
||||||
|
|
@ -133,9 +135,14 @@ func (device *Device) RoutineReceiveIncoming(maxBatchSize int, recv conn.Receive
|
||||||
}
|
}
|
||||||
|
|
||||||
// check size of packet
|
// check size of packet
|
||||||
|
|
||||||
packet := bufsArrs[i][:size]
|
packet := bufsArrs[i][:size]
|
||||||
msgType := binary.LittleEndian.Uint32(packet[:4])
|
|
||||||
|
// get message padding and type based on information from S1-S4 and H1-H4
|
||||||
|
msgType, padding := device.DeterminePacketTypeAndPadding(packet, MessageUnknownType)
|
||||||
|
if padding > 0 {
|
||||||
|
copy(packet, packet[padding:])
|
||||||
|
packet = packet[:len(packet)-padding]
|
||||||
|
}
|
||||||
|
|
||||||
switch msgType {
|
switch msgType {
|
||||||
|
|
||||||
|
|
@ -277,7 +284,6 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
device.log.Verbosef("Routine: handshake worker %d - started", id)
|
device.log.Verbosef("Routine: handshake worker %d - started", id)
|
||||||
|
|
||||||
for elem := range device.queue.handshake.c {
|
for elem := range device.queue.handshake.c {
|
||||||
|
|
||||||
// handle cookie fields and ratelimiting
|
// handle cookie fields and ratelimiting
|
||||||
|
|
||||||
switch elem.msgType {
|
switch elem.msgType {
|
||||||
|
|
@ -287,8 +293,7 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
// unmarshal packet
|
// unmarshal packet
|
||||||
|
|
||||||
var reply MessageCookieReply
|
var reply MessageCookieReply
|
||||||
reader := bytes.NewReader(elem.packet)
|
err := reply.unmarshal(elem.packet)
|
||||||
err := binary.Read(reader, binary.LittleEndian, &reply)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
device.log.Verbosef("Failed to decode cookie reply")
|
device.log.Verbosef("Failed to decode cookie reply")
|
||||||
goto skip
|
goto skip
|
||||||
|
|
@ -305,9 +310,14 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
// consume reply
|
// consume reply
|
||||||
|
|
||||||
if peer := entry.peer; peer.isRunning.Load() {
|
if peer := entry.peer; peer.isRunning.Load() {
|
||||||
device.log.Verbosef("Receiving cookie response from %s", elem.endpoint.DstToString())
|
device.log.Verbosef(
|
||||||
|
"Receiving cookie response from %s",
|
||||||
|
elem.endpoint.DstToString(),
|
||||||
|
)
|
||||||
if !peer.cookieGenerator.ConsumeReply(&reply) {
|
if !peer.cookieGenerator.ConsumeReply(&reply) {
|
||||||
device.log.Verbosef("Could not decrypt invalid cookie response")
|
device.log.Verbosef(
|
||||||
|
"Could not decrypt invalid cookie response",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,20 +359,18 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
|
|
||||||
switch elem.msgType {
|
switch elem.msgType {
|
||||||
case MessageInitiationType:
|
case MessageInitiationType:
|
||||||
|
|
||||||
// unmarshal
|
// unmarshal
|
||||||
|
|
||||||
var msg MessageInitiation
|
var msg MessageInitiation
|
||||||
reader := bytes.NewReader(elem.packet)
|
err := msg.unmarshal(elem.packet)
|
||||||
err := binary.Read(reader, binary.LittleEndian, &msg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
device.log.Errorf("Failed to decode initiation message")
|
device.log.Errorf("Failed to decode initiation message")
|
||||||
goto skip
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// consume initiation
|
// have to reassign msgType for ranged msgType to work
|
||||||
|
msg.Type = elem.msgType
|
||||||
|
|
||||||
peer := device.ConsumeMessageInitiation(&msg)
|
peer := device.ConsumeMessageInitiation(&msg, elem.endpoint)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
device.log.Verbosef("Received invalid initiation message from %s", elem.endpoint.DstToString())
|
device.log.Verbosef("Received invalid initiation message from %s", elem.endpoint.DstToString())
|
||||||
goto skip
|
goto skip
|
||||||
|
|
@ -386,13 +394,15 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
// unmarshal
|
// unmarshal
|
||||||
|
|
||||||
var msg MessageResponse
|
var msg MessageResponse
|
||||||
reader := bytes.NewReader(elem.packet)
|
err := msg.unmarshal(elem.packet)
|
||||||
err := binary.Read(reader, binary.LittleEndian, &msg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
device.log.Errorf("Failed to decode response message")
|
device.log.Errorf("Failed to decode response message")
|
||||||
goto skip
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// have to reassign msgType for ranged msgType to work
|
||||||
|
msg.Type = elem.msgType
|
||||||
|
|
||||||
// consume response
|
// consume response
|
||||||
|
|
||||||
peer := device.ConsumeMessageResponse(&msg)
|
peer := device.ConsumeMessageResponse(&msg)
|
||||||
|
|
@ -415,7 +425,6 @@ func (device *Device) RoutineHandshake(id int) {
|
||||||
// derive keypair
|
// derive keypair
|
||||||
|
|
||||||
err = peer.BeginSymmetricSession()
|
err = peer.BeginSymmetricSession()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
device.log.Errorf("%v - Failed to derive keypair: %v", peer, err)
|
device.log.Errorf("%v - Failed to derive keypair: %v", peer, err)
|
||||||
goto skip
|
goto skip
|
||||||
|
|
@ -464,6 +473,9 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
||||||
peer.timersHandshakeComplete()
|
peer.timersHandshakeComplete()
|
||||||
peer.SendStagedPackets()
|
peer.SendStagedPackets()
|
||||||
}
|
}
|
||||||
|
if ep, ok := elem.endpoint.(conn.PeerAwareEndpoint); ok {
|
||||||
|
ep.FromPeer(peer.handshake.remoteStatic)
|
||||||
|
}
|
||||||
rxBytesLen += uint64(len(elem.packet) + MinMessageSize)
|
rxBytesLen += uint64(len(elem.packet) + MinMessageSize)
|
||||||
|
|
||||||
if len(elem.packet) == 0 {
|
if len(elem.packet) == 0 {
|
||||||
|
|
@ -507,11 +519,28 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
device.log.Verbosef("Packet with invalid IP version from %v", peer)
|
device.log.Verbosef(
|
||||||
|
"Packet with invalid IP version from %v",
|
||||||
|
peer,
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
bufs = append(bufs, elem.buffer[:MessageTransportOffsetContent+len(elem.packet)])
|
bufs = append(
|
||||||
|
bufs,
|
||||||
|
elem.buffer[:MessageTransportOffsetContent+len(elem.packet)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
peer.rxBytes.Add(rxBytesLen)
|
||||||
|
|
@ -538,3 +567,57 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
|
||||||
device.PutInboundElementsContainer(elemsContainer)
|
device.PutInboundElementsContainer(elemsContainer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (device *Device) DeterminePacketTypeAndPadding(packet []byte, expectedType uint32) (uint32, int) {
|
||||||
|
size := len(packet)
|
||||||
|
|
||||||
|
if expectedType == MessageUnknownType || expectedType == MessageInitiationType {
|
||||||
|
padding := device.paddings.init
|
||||||
|
header := device.headers.init
|
||||||
|
|
||||||
|
if size == padding+MessageInitiationSize {
|
||||||
|
data := packet[padding:]
|
||||||
|
if header.Validate(binary.LittleEndian.Uint32(data)) {
|
||||||
|
return MessageInitiationType, padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedType == MessageUnknownType || expectedType == MessageResponseType {
|
||||||
|
padding := device.paddings.response
|
||||||
|
header := device.headers.response
|
||||||
|
|
||||||
|
if size == padding+MessageResponseSize {
|
||||||
|
data := packet[padding:]
|
||||||
|
if header.Validate(binary.LittleEndian.Uint32(data)) {
|
||||||
|
return MessageResponseType, padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedType == MessageUnknownType || expectedType == MessageCookieReplyType {
|
||||||
|
padding := device.paddings.cookie
|
||||||
|
header := device.headers.cookie
|
||||||
|
|
||||||
|
if size == padding+MessageCookieReplySize {
|
||||||
|
data := packet[padding:]
|
||||||
|
if header.Validate(binary.LittleEndian.Uint32(data)) {
|
||||||
|
return MessageCookieReplyType, padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedType == MessageUnknownType || expectedType == MessageTransportType {
|
||||||
|
padding := device.paddings.transport
|
||||||
|
header := device.headers.transport
|
||||||
|
|
||||||
|
if size >= padding+MessageTransportHeaderSize {
|
||||||
|
data := packet[padding:]
|
||||||
|
if header.Validate(binary.LittleEndian.Uint32(data)) {
|
||||||
|
return MessageTransportType, padding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MessageUnknownType, 0
|
||||||
|
}
|
||||||
|
|
|
||||||
117
device/send.go
117
device/send.go
|
|
@ -1,24 +1,26 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
|
"github.com/sagernet/wireguard-go/tun"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Outbound flow
|
/* Outbound flow
|
||||||
|
|
@ -47,7 +49,11 @@ import (
|
||||||
|
|
||||||
type QueueOutboundElement struct {
|
type QueueOutboundElement struct {
|
||||||
buffer *[MaxMessageSize]byte // slice holding the packet data
|
buffer *[MaxMessageSize]byte // slice holding the packet data
|
||||||
packet []byte // slice of "buffer" (always!)
|
// packet is always a slice of "buffer". The starting offset in buffer
|
||||||
|
// is either:
|
||||||
|
// a) MessageEncapsulatingTransportSize+MessageTransportHeaderSize (plaintext)
|
||||||
|
// b) 0 (post-encryption)
|
||||||
|
packet []byte
|
||||||
nonce uint64 // nonce for encryption
|
nonce uint64 // nonce for encryption
|
||||||
keypair *Keypair // keypair for encryption
|
keypair *Keypair // keypair for encryption
|
||||||
peer *Peer // related peer
|
peer *Peer // related peer
|
||||||
|
|
@ -124,6 +130,29 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sendBuffer [][]byte
|
||||||
|
|
||||||
|
for _, ipacket := range peer.device.ipackets {
|
||||||
|
if ipacket != nil {
|
||||||
|
buf := make([]byte, ipacket.ObfuscatedLen(0))
|
||||||
|
ipacket.Obfuscate(buf, nil)
|
||||||
|
sendBuffer = append(sendBuffer, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jc := peer.device.junk.count
|
||||||
|
jmin := peer.device.junk.min
|
||||||
|
jmax := peer.device.junk.max
|
||||||
|
|
||||||
|
for i := 0; i < jc; i++ {
|
||||||
|
nBig, _ := rand.Int(rand.Reader, big.NewInt(int64(jmax-jmin+1)))
|
||||||
|
n := int(nBig.Int64()) + jmin
|
||||||
|
|
||||||
|
buf := make([]byte, n)
|
||||||
|
rand.Read(buf)
|
||||||
|
sendBuffer = append(sendBuffer, buf)
|
||||||
|
}
|
||||||
|
|
||||||
var buf [MessageInitiationSize]byte
|
var buf [MessageInitiationSize]byte
|
||||||
writer := bytes.NewBuffer(buf[:0])
|
writer := bytes.NewBuffer(buf[:0])
|
||||||
binary.Write(writer, binary.LittleEndian, msg)
|
binary.Write(writer, binary.LittleEndian, msg)
|
||||||
|
|
@ -133,7 +162,16 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||||
peer.timersAnyAuthenticatedPacketTraversal()
|
peer.timersAnyAuthenticatedPacketTraversal()
|
||||||
peer.timersAnyAuthenticatedPacketSent()
|
peer.timersAnyAuthenticatedPacketSent()
|
||||||
|
|
||||||
err = peer.SendBuffers([][]byte{packet})
|
if padding := peer.device.paddings.init; padding > 0 {
|
||||||
|
buf := make([]byte, padding+len(packet))
|
||||||
|
rand.Read(buf[:padding])
|
||||||
|
copy(buf[padding:], packet)
|
||||||
|
packet = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
sendBuffer = append(sendBuffer, packet)
|
||||||
|
|
||||||
|
err = peer.SendBuffers(sendBuffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
peer.device.log.Errorf("%v - Failed to send handshake initiation: %v", peer, err)
|
peer.device.log.Errorf("%v - Failed to send handshake initiation: %v", peer, err)
|
||||||
}
|
}
|
||||||
|
|
@ -157,6 +195,7 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||||
|
|
||||||
var buf [MessageResponseSize]byte
|
var buf [MessageResponseSize]byte
|
||||||
writer := bytes.NewBuffer(buf[:0])
|
writer := bytes.NewBuffer(buf[:0])
|
||||||
|
|
||||||
binary.Write(writer, binary.LittleEndian, response)
|
binary.Write(writer, binary.LittleEndian, response)
|
||||||
packet := writer.Bytes()
|
packet := writer.Bytes()
|
||||||
peer.cookieGenerator.AddMacs(packet)
|
peer.cookieGenerator.AddMacs(packet)
|
||||||
|
|
@ -171,6 +210,13 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||||
peer.timersAnyAuthenticatedPacketTraversal()
|
peer.timersAnyAuthenticatedPacketTraversal()
|
||||||
peer.timersAnyAuthenticatedPacketSent()
|
peer.timersAnyAuthenticatedPacketSent()
|
||||||
|
|
||||||
|
if padding := peer.device.paddings.response; padding > 0 {
|
||||||
|
buf := make([]byte, padding+len(packet))
|
||||||
|
rand.Read(buf[:padding])
|
||||||
|
copy(buf[padding:], packet)
|
||||||
|
packet = buf
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: allocation could be avoided
|
// TODO: allocation could be avoided
|
||||||
err = peer.SendBuffers([][]byte{packet})
|
err = peer.SendBuffers([][]byte{packet})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -183,7 +229,14 @@ func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement)
|
||||||
device.log.Verbosef("Sending cookie response for denied handshake message for %v", initiatingElem.endpoint.DstToString())
|
device.log.Verbosef("Sending cookie response for denied handshake message for %v", initiatingElem.endpoint.DstToString())
|
||||||
|
|
||||||
sender := binary.LittleEndian.Uint32(initiatingElem.packet[4:8])
|
sender := binary.LittleEndian.Uint32(initiatingElem.packet[4:8])
|
||||||
reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.endpoint.DstToBytes())
|
msgType := device.headers.cookie.Generate()
|
||||||
|
|
||||||
|
reply, err := device.cookieChecker.CreateReply(
|
||||||
|
initiatingElem.packet,
|
||||||
|
sender,
|
||||||
|
initiatingElem.endpoint.DstToBytes(),
|
||||||
|
msgType,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
device.log.Errorf("Failed to create cookie reply: %v", err)
|
device.log.Errorf("Failed to create cookie reply: %v", err)
|
||||||
return err
|
return err
|
||||||
|
|
@ -192,8 +245,17 @@ func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement)
|
||||||
var buf [MessageCookieReplySize]byte
|
var buf [MessageCookieReplySize]byte
|
||||||
writer := bytes.NewBuffer(buf[:0])
|
writer := bytes.NewBuffer(buf[:0])
|
||||||
binary.Write(writer, binary.LittleEndian, reply)
|
binary.Write(writer, binary.LittleEndian, reply)
|
||||||
|
packet := writer.Bytes()
|
||||||
|
|
||||||
|
if padding := device.paddings.cookie; padding > 0 {
|
||||||
|
buf := make([]byte, padding+len(packet))
|
||||||
|
rand.Read(buf[:padding])
|
||||||
|
copy(buf[padding:], packet)
|
||||||
|
packet = buf
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: allocation could be avoided
|
// TODO: allocation could be avoided
|
||||||
device.net.bind.Send([][]byte{writer.Bytes()}, initiatingElem.endpoint)
|
device.net.bind.Send([][]byte{packet}, initiatingElem.endpoint, 0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,7 +287,7 @@ func (device *Device) RoutineReadFromTUN() {
|
||||||
elemsByPeer = make(map[*Peer]*QueueOutboundElementsContainer, batchSize)
|
elemsByPeer = make(map[*Peer]*QueueOutboundElementsContainer, batchSize)
|
||||||
count = 0
|
count = 0
|
||||||
sizes = make([]int, batchSize)
|
sizes = make([]int, batchSize)
|
||||||
offset = MessageTransportHeaderSize
|
offset = MessageEncapsulatingTransportSize + MessageTransportHeaderSize
|
||||||
)
|
)
|
||||||
|
|
||||||
for i := range elems {
|
for i := range elems {
|
||||||
|
|
@ -320,6 +382,30 @@ 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) {
|
func (peer *Peer) StagePackets(elems *QueueOutboundElementsContainer) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
@ -457,7 +543,9 @@ func (device *Device) RoutineEncryption(id int) {
|
||||||
fieldReceiver := header[4:8]
|
fieldReceiver := header[4:8]
|
||||||
fieldNonce := header[8:16]
|
fieldNonce := header[8:16]
|
||||||
|
|
||||||
binary.LittleEndian.PutUint32(fieldType, MessageTransportType)
|
msgType := device.headers.transport.Generate()
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(fieldType, msgType)
|
||||||
binary.LittleEndian.PutUint32(fieldReceiver, elem.keypair.remoteIndex)
|
binary.LittleEndian.PutUint32(fieldReceiver, elem.keypair.remoteIndex)
|
||||||
binary.LittleEndian.PutUint64(fieldNonce, elem.nonce)
|
binary.LittleEndian.PutUint64(fieldNonce, elem.nonce)
|
||||||
|
|
||||||
|
|
@ -506,6 +594,7 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
|
||||||
device.PutMessageBuffer(elem.buffer)
|
device.PutMessageBuffer(elem.buffer)
|
||||||
device.PutOutboundElement(elem)
|
device.PutOutboundElement(elem)
|
||||||
}
|
}
|
||||||
|
device.PutOutboundElementsContainer(elemsContainer)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dataSent := false
|
dataSent := false
|
||||||
|
|
@ -514,6 +603,15 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
|
||||||
if len(elem.packet) != MessageKeepaliveSize {
|
if len(elem.packet) != MessageKeepaliveSize {
|
||||||
dataSent = true
|
dataSent = true
|
||||||
}
|
}
|
||||||
|
if padding := device.paddings.transport; padding > 0 {
|
||||||
|
// elem.packet is stored at the start of elem.buffer
|
||||||
|
// with zero padding
|
||||||
|
for i := len(elem.packet) - 1; i >= 0; i-- {
|
||||||
|
elem.buffer[i+padding] = elem.buffer[i]
|
||||||
|
}
|
||||||
|
rand.Read(elem.buffer[:padding])
|
||||||
|
elem.packet = elem.buffer[:padding+len(elem.packet)]
|
||||||
|
}
|
||||||
bufs = append(bufs, elem.packet)
|
bufs = append(bufs, elem.packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,6 +622,7 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
|
||||||
if dataSent {
|
if dataSent {
|
||||||
peer.timersDataSent()
|
peer.timersDataSent()
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, elem := range elemsContainer.elems {
|
for _, elem := range elemsContainer.elems {
|
||||||
device.PutMessageBuffer(elem.buffer)
|
device.PutMessageBuffer(elem.buffer)
|
||||||
device.PutOutboundElement(elem)
|
device.PutOutboundElement(elem)
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
"golang.zx2c4.com/wireguard/rwcancel"
|
"github.com/sagernet/wireguard-go/rwcancel"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
|
func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,9 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
|
"github.com/sagernet/wireguard-go/rwcancel"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
|
||||||
"golang.zx2c4.com/wireguard/rwcancel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
|
func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* This is based heavily on timers.c from the kernel implementation.
|
* This is based heavily on timers.c from the kernel implementation.
|
||||||
*/
|
*/
|
||||||
|
|
@ -39,6 +39,9 @@ func (peer *Peer) NewTimer(expirationFunction func(*Peer)) *Timer {
|
||||||
timer.isPending = false
|
timer.isPending = false
|
||||||
timer.modifyingLock.Unlock()
|
timer.modifyingLock.Unlock()
|
||||||
|
|
||||||
|
if pauseManager := peer.device.pauseManager; pauseManager != nil {
|
||||||
|
pauseManager.WaitActive()
|
||||||
|
}
|
||||||
expirationFunction(peer)
|
expirationFunction(peer)
|
||||||
})
|
})
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ package device
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"github.com/sagernet/wireguard-go/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultMTU = 1420
|
const DefaultMTU = 1420
|
||||||
|
|
|
||||||
308
device/uapi.go
308
device/uapi.go
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/ipc"
|
"github.com/sagernet/wireguard-go/ipc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IPCError struct {
|
type IPCError struct {
|
||||||
|
|
@ -97,6 +97,56 @@ func (device *Device) IpcGetOperation(w io.Writer) error {
|
||||||
sendf("fwmark=%d", device.net.fwmark)
|
sendf("fwmark=%d", device.net.fwmark)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if device.junk.count != 0 {
|
||||||
|
sendf("jc=%d", device.junk.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.junk.min != 0 {
|
||||||
|
sendf("jmin=%d", device.junk.min)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.junk.max != 0 {
|
||||||
|
sendf("jmax=%d", device.junk.max)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.paddings.init != 0 {
|
||||||
|
sendf("s1=%d", device.paddings.init)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.paddings.response != 0 {
|
||||||
|
sendf("s2=%d", device.paddings.response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.paddings.cookie != 0 {
|
||||||
|
sendf("s3=%d", device.paddings.cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.paddings.transport != 0 {
|
||||||
|
sendf("s4=%d", device.paddings.transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.headers.init != nil {
|
||||||
|
sendf("h1=%s", device.headers.init.GenSpec())
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.headers.response != nil {
|
||||||
|
sendf("h2=%s", device.headers.response.GenSpec())
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.headers.cookie != nil {
|
||||||
|
sendf("h3=%s", device.headers.cookie.GenSpec())
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.headers.transport != nil {
|
||||||
|
sendf("h4=%s", device.headers.transport.GenSpec())
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ipacket := range device.ipackets {
|
||||||
|
if ipacket != nil {
|
||||||
|
sendf("i%d=%s", i+1, ipacket.Spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, peer := range device.peers.keyMap {
|
for _, peer := range device.peers.keyMap {
|
||||||
// Serialize peer state.
|
// Serialize peer state.
|
||||||
peer.handshake.mutex.RLock()
|
peer.handshake.mutex.RLock()
|
||||||
|
|
@ -147,6 +197,7 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
ipcDev := new(ipcSetDevice)
|
||||||
peer := new(ipcSetPeer)
|
peer := new(ipcSetPeer)
|
||||||
deviceConfig := true
|
deviceConfig := true
|
||||||
|
|
||||||
|
|
@ -155,12 +206,20 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if line == "" {
|
if line == "" {
|
||||||
// Blank line means terminate operation.
|
// Blank line means terminate operation.
|
||||||
|
err := ipcDev.mergeWithDevice(device)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to merge with device: %w", err)
|
||||||
|
}
|
||||||
peer.handlePostConfig()
|
peer.handlePostConfig()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
key, value, ok := strings.Cut(line, "=")
|
key, value, ok := strings.Cut(line, "=")
|
||||||
if !ok {
|
if !ok {
|
||||||
return ipcErrorf(ipc.IpcErrorProtocol, "failed to parse line %q", line)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorProtocol,
|
||||||
|
"failed to parse line %q",
|
||||||
|
line,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if key == "public_key" {
|
if key == "public_key" {
|
||||||
|
|
@ -186,6 +245,10 @@ func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
err = ipcDev.mergeWithDevice(device)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to merge with device: %w", err)
|
||||||
|
}
|
||||||
peer.handlePostConfig()
|
peer.handlePostConfig()
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
|
|
@ -235,11 +298,155 @@ func (device *Device) handleDeviceLine(key, value string) error {
|
||||||
|
|
||||||
case "replace_peers":
|
case "replace_peers":
|
||||||
if value != "true" {
|
if value != "true" {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set replace_peers, invalid value: %v", value)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"failed to set replace_peers, invalid value: %v",
|
||||||
|
value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
device.log.Verbosef("UAPI: Removing all peers")
|
device.log.Verbosef("UAPI: Removing all peers")
|
||||||
device.RemoveAllPeers()
|
device.RemoveAllPeers()
|
||||||
|
|
||||||
|
case "jc":
|
||||||
|
jc, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jc: %w", err)
|
||||||
|
}
|
||||||
|
if jc <= 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "jc must be a positive value")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating junk count")
|
||||||
|
device.junk.count = jc
|
||||||
|
|
||||||
|
case "jmin":
|
||||||
|
jmin, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jmin: %w", err)
|
||||||
|
}
|
||||||
|
if jmin <= 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "jmin must be a positive value")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating junk min")
|
||||||
|
device.junk.min = jmin
|
||||||
|
|
||||||
|
case "jmax":
|
||||||
|
jmax, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse jmax: %w", err)
|
||||||
|
}
|
||||||
|
if jmax <= 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "jmax must be a positive value")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating junk max")
|
||||||
|
device.junk.max = jmax
|
||||||
|
|
||||||
|
case "s1":
|
||||||
|
padding, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s1: %w", err)
|
||||||
|
}
|
||||||
|
if padding < 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "s1 must be non-negative")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating s1 padding")
|
||||||
|
device.paddings.init = padding
|
||||||
|
|
||||||
|
case "s2":
|
||||||
|
padding, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s2: %w", err)
|
||||||
|
}
|
||||||
|
if padding < 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "s2 must be non-negative")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating s2 padding")
|
||||||
|
device.paddings.response = padding
|
||||||
|
|
||||||
|
case "s3":
|
||||||
|
padding, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s3: %w", err)
|
||||||
|
}
|
||||||
|
if padding < 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "s3 must be non-negative")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating s3 padding")
|
||||||
|
device.paddings.cookie = padding
|
||||||
|
|
||||||
|
case "s4":
|
||||||
|
padding, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse s4: %w", err)
|
||||||
|
}
|
||||||
|
if padding < 0 {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "s4 must be non-negative")
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating s4 padding")
|
||||||
|
device.paddings.transport = padding
|
||||||
|
|
||||||
|
case "h1":
|
||||||
|
header, err := newMagicHeader(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H1: %w", err)
|
||||||
|
}
|
||||||
|
device.headers.init = header
|
||||||
|
|
||||||
|
case "h2":
|
||||||
|
header, err := newMagicHeader(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H2: %w", err)
|
||||||
|
}
|
||||||
|
device.headers.response = header
|
||||||
|
|
||||||
|
case "h3":
|
||||||
|
header, err := newMagicHeader(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H3: %w", err)
|
||||||
|
}
|
||||||
|
device.headers.cookie = header
|
||||||
|
|
||||||
|
case "h4":
|
||||||
|
header, err := newMagicHeader(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse H4: %w", err)
|
||||||
|
}
|
||||||
|
device.headers.transport = header
|
||||||
|
|
||||||
|
case "i1":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I1: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[0] = chain
|
||||||
|
|
||||||
|
case "i2":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I2: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[1] = chain
|
||||||
|
|
||||||
|
case "i3":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I3: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[2] = chain
|
||||||
|
|
||||||
|
case "i4":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I4: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[3] = chain
|
||||||
|
|
||||||
|
case "i5":
|
||||||
|
chain, err := newObfChain(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse I5: %w", err)
|
||||||
|
}
|
||||||
|
device.ipackets[4] = chain
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key)
|
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key)
|
||||||
}
|
}
|
||||||
|
|
@ -271,7 +478,10 @@ func (peer *ipcSetPeer) handlePostConfig() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) handlePublicKeyLine(peer *ipcSetPeer, value string) error {
|
func (device *Device) handlePublicKeyLine(
|
||||||
|
peer *ipcSetPeer,
|
||||||
|
value string,
|
||||||
|
) error {
|
||||||
// Load/create the peer we are configuring.
|
// Load/create the peer we are configuring.
|
||||||
var publicKey NoisePublicKey
|
var publicKey NoisePublicKey
|
||||||
err := publicKey.FromHex(value)
|
err := publicKey.FromHex(value)
|
||||||
|
|
@ -301,12 +511,19 @@ func (device *Device) handlePublicKeyLine(peer *ipcSetPeer, value string) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error {
|
func (device *Device) handlePeerLine(
|
||||||
|
peer *ipcSetPeer,
|
||||||
|
key, value string,
|
||||||
|
) error {
|
||||||
switch key {
|
switch key {
|
||||||
case "update_only":
|
case "update_only":
|
||||||
// allow disabling of creation
|
// allow disabling of creation
|
||||||
if value != "true" {
|
if value != "true" {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set update only, invalid value: %v", value)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"failed to set update only, invalid value: %v",
|
||||||
|
value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if peer.created && !peer.dummy {
|
if peer.created && !peer.dummy {
|
||||||
device.RemovePeer(peer.handshake.remoteStatic)
|
device.RemovePeer(peer.handshake.remoteStatic)
|
||||||
|
|
@ -352,7 +569,11 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error
|
||||||
|
|
||||||
secs, err := strconv.ParseUint(value, 10, 16)
|
secs, err := strconv.ParseUint(value, 10, 16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set persistent keepalive interval: %w", err)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"failed to set persistent keepalive interval: %w",
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
old := peer.persistentKeepaliveInterval.Swap(uint32(secs))
|
old := peer.persistentKeepaliveInterval.Swap(uint32(secs))
|
||||||
|
|
@ -363,7 +584,11 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error
|
||||||
case "replace_allowed_ips":
|
case "replace_allowed_ips":
|
||||||
device.log.Verbosef("%v - UAPI: Removing all allowedips", peer.Peer)
|
device.log.Verbosef("%v - UAPI: Removing all allowedips", peer.Peer)
|
||||||
if value != "true" {
|
if value != "true" {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to replace allowedips, invalid value: %v", value)
|
return ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"failed to replace allowedips, invalid value: %v",
|
||||||
|
value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if peer.dummy {
|
if peer.dummy {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -371,7 +596,14 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error
|
||||||
device.allowedips.RemoveByPeer(peer.Peer)
|
device.allowedips.RemoveByPeer(peer.Peer)
|
||||||
|
|
||||||
case "allowed_ip":
|
case "allowed_ip":
|
||||||
device.log.Verbosef("%v - UAPI: Adding allowedip", peer.Peer)
|
add := true
|
||||||
|
verb := "Adding"
|
||||||
|
if len(value) > 0 && value[0] == '-' {
|
||||||
|
add = false
|
||||||
|
verb = "Removing"
|
||||||
|
value = value[1:]
|
||||||
|
}
|
||||||
|
device.log.Verbosef("%v - UAPI: %s allowedip", peer.Peer, verb)
|
||||||
prefix, err := netip.ParsePrefix(value)
|
prefix, err := netip.ParsePrefix(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set allowed ip: %w", err)
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set allowed ip: %w", err)
|
||||||
|
|
@ -379,7 +611,11 @@ func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error
|
||||||
if peer.dummy {
|
if peer.dummy {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if add {
|
||||||
device.allowedips.Insert(prefix, peer.Peer)
|
device.allowedips.Insert(prefix, peer.Peer)
|
||||||
|
} else {
|
||||||
|
device.allowedips.Remove(prefix, peer.Peer)
|
||||||
|
}
|
||||||
|
|
||||||
case "protocol_version":
|
case "protocol_version":
|
||||||
if value != "1" {
|
if value != "1" {
|
||||||
|
|
@ -431,7 +667,11 @@ func (device *Device) IpcHandle(socket net.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if nextByte != '\n' {
|
if nextByte != '\n' {
|
||||||
err = ipcErrorf(ipc.IpcErrorInvalid, "trailing character in UAPI get: %q", nextByte)
|
err = ipcErrorf(
|
||||||
|
ipc.IpcErrorInvalid,
|
||||||
|
"trailing character in UAPI get: %q",
|
||||||
|
nextByte,
|
||||||
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
err = device.IpcGetOperation(buffered.Writer)
|
err = device.IpcGetOperation(buffered.Writer)
|
||||||
|
|
@ -455,3 +695,49 @@ func (device *Device) IpcHandle(socket net.Conn) {
|
||||||
buffered.Flush()
|
buffered.Flush()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ipcSetDevice struct {
|
||||||
|
headers struct {
|
||||||
|
init *magicHeader
|
||||||
|
response *magicHeader
|
||||||
|
cookie *magicHeader
|
||||||
|
transport *magicHeader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *ipcSetDevice) mergeWithDevice(device *Device) error {
|
||||||
|
if d.headers.init == nil {
|
||||||
|
d.headers.init = device.headers.init
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.headers.response == nil {
|
||||||
|
d.headers.response = device.headers.response
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.headers.cookie == nil {
|
||||||
|
d.headers.cookie = device.headers.cookie
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.headers.transport == nil {
|
||||||
|
d.headers.transport = device.headers.transport
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []*magicHeader{d.headers.init, d.headers.response, d.headers.cookie, d.headers.transport}
|
||||||
|
for i := 0; i < len(headers); i++ {
|
||||||
|
for j := i + 1; j < len(headers); j++ {
|
||||||
|
left := headers[i]
|
||||||
|
right := headers[j]
|
||||||
|
|
||||||
|
if left.start <= right.end && right.start <= left.end {
|
||||||
|
return errors.New("headers must not overlap")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
device.headers.init = d.headers.init
|
||||||
|
device.headers.response = d.headers.response
|
||||||
|
device.headers.cookie = d.headers.cookie
|
||||||
|
device.headers.transport = d.headers.transport
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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()
|
|
||||||
}
|
|
||||||
11
go.mod
11
go.mod
|
|
@ -1,16 +1,11 @@
|
||||||
module golang.zx2c4.com/wireguard
|
module github.com/sagernet/wireguard-go
|
||||||
|
|
||||||
go 1.20
|
go 1.24
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/sagernet/sing v0.7.10
|
||||||
golang.org/x/crypto v0.13.0
|
golang.org/x/crypto v0.13.0
|
||||||
golang.org/x/net v0.15.0
|
golang.org/x/net v0.15.0
|
||||||
golang.org/x/sys v0.12.0
|
golang.org/x/sys v0.12.0
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/google/btree v1.0.1 // indirect
|
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
8
go.sum
8
go.sum
|
|
@ -1,14 +1,10 @@
|
||||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
github.com/sagernet/sing v0.7.10 h1:2yPhZFx+EkyHPH8hXNezgyRSHyGY12CboId7CtwLROw=
|
||||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
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 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
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 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
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 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
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=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
|
||||||
|
|
|
||||||
|
|
@ -1,674 +0,0 @@
|
||||||
// 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"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"golang.zx2c4.com/wireguard/ipc/namedpipe"
|
|
||||||
)
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ipc
|
package ipc
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
//go:build wasm || plan9 || aix || solaris || illumos
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
||||||
|
|
@ -5,7 +7,7 @@
|
||||||
|
|
||||||
package ipc
|
package ipc
|
||||||
|
|
||||||
// Made up sentinel error codes for {js,wasip1}/wasm.
|
// Made up sentinel error codes for {js,wasip1}/wasm, and plan9.
|
||||||
const (
|
const (
|
||||||
IpcErrorIO = 1
|
IpcErrorIO = 1
|
||||||
IpcErrorInvalid = 2
|
IpcErrorInvalid = 2
|
||||||
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/wireguard-go/rwcancel"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"golang.zx2c4.com/wireguard/rwcancel"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UAPIListener struct {
|
type UAPIListener struct {
|
||||||
|
|
@ -84,7 +84,6 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
||||||
unix.IN_DELETE|
|
unix.IN_DELETE|
|
||||||
unix.IN_DELETE_SELF,
|
unix.IN_DELETE_SELF,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
ipc/uapi_tamago.go
Normal file
17
ipc/uapi_tamago.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
//go:build tamago
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipc
|
||||||
|
|
||||||
|
// Made up sentinel error codes for tamago platform.
|
||||||
|
const (
|
||||||
|
IpcErrorIO = 1
|
||||||
|
IpcErrorInvalid = 2
|
||||||
|
IpcErrorPortInUse = 3
|
||||||
|
IpcErrorUnknown = 4
|
||||||
|
IpcErrorProtocol = 5
|
||||||
|
)
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ipc
|
package ipc
|
||||||
|
|
@ -26,7 +26,7 @@ const (
|
||||||
|
|
||||||
// socketDirectory is variable because it is modified by a linker
|
// socketDirectory is variable because it is modified by a linker
|
||||||
// flag in wireguard-android.
|
// flag in wireguard-android.
|
||||||
var socketDirectory = "/var/run/wireguard"
|
var socketDirectory = "/var/run/amneziawg"
|
||||||
|
|
||||||
func sockPath(iface string) string {
|
func sockPath(iface string) string {
|
||||||
return fmt.Sprintf("%s/%s.sock", socketDirectory, iface)
|
return fmt.Sprintf("%s/%s.sock", socketDirectory, iface)
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ package ipc
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/wireguard-go/ipc/namedpipe"
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
"golang.zx2c4.com/wireguard/ipc/namedpipe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: replace these with actual standard windows error numbers from the win package
|
// TODO: replace these with actual standard windows error numbers from the win package
|
||||||
|
|
|
||||||
268
main.go
268
main.go
|
|
@ -1,268 +0,0 @@
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
|
||||||
"golang.zx2c4.com/wireguard/device"
|
|
||||||
"golang.zx2c4.com/wireguard/ipc"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
)
|
|
||||||
|
|
||||||
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 wireguard-go is not required because this │")
|
|
||||||
fmt.Fprintln(os.Stderr, "│ kernel has first class support for WireGuard. For │")
|
|
||||||
fmt.Fprintln(os.Stderr, "│ information on installing the kernel module, │")
|
|
||||||
fmt.Fprintln(os.Stderr, "│ please visit: │")
|
|
||||||
fmt.Fprintln(os.Stderr, "│ https://www.wireguard.com/install/ │")
|
|
||||||
fmt.Fprintln(os.Stderr, "│ │")
|
|
||||||
fmt.Fprintln(os.Stderr, "└──────────────────────────────────────────────────────┘")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if len(os.Args) == 2 && os.Args[1] == "--version" {
|
|
||||||
fmt.Printf("wireguard-go v%s\n\nUserspace WireGuard daemon for %s-%s.\nInformation available at https://www.wireguard.com.\nCopyright (C) Jason A. Donenfeld <Jason@zx2c4.com>.\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 wireguard-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")
|
|
||||||
}
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/conn"
|
|
||||||
"golang.zx2c4.com/wireguard/device"
|
|
||||||
"golang.zx2c4.com/wireguard/ipc"
|
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/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 WireGuard for Windows client, the repo you want is <https://git.zx2c4.com/wireguard-windows/>, which includes this code as a module.")
|
|
||||||
|
|
||||||
logger := device.NewLogger(
|
|
||||||
device.LogLevelVerbose,
|
|
||||||
fmt.Sprintf("(%s) ", interfaceName),
|
|
||||||
)
|
|
||||||
logger.Verbosef("Starting wireguard-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")
|
|
||||||
}
|
|
||||||
33
module_rename.py
Normal file
33
module_rename.py
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/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="")
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ratelimiter
|
package ratelimiter
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
23
remove-unused.sh
Executable file
23
remove-unused.sh
Executable file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/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"
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package replay implements an efficient anti-replay algorithm as specified in RFC 6479.
|
// Package replay implements an efficient anti-replay algorithm as specified in RFC 6479.
|
||||||
|
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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,8 +1,8 @@
|
||||||
//go:build !windows && !wasm
|
//go:build !windows && !wasm && !plan9 && !tamago
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Package rwcancel implements cancelable read/write operations on
|
// Package rwcancel implements cancelable read/write operations on
|
||||||
|
|
@ -64,7 +64,7 @@ func (rw *RWCancel) ReadyRead() bool {
|
||||||
|
|
||||||
func (rw *RWCancel) ReadyWrite() bool {
|
func (rw *RWCancel) ReadyWrite() bool {
|
||||||
closeFd := int32(rw.closingReader.Fd())
|
closeFd := int32(rw.closingReader.Fd())
|
||||||
pollFds := []unix.PollFd{{Fd: int32(rw.fd), Events: unix.POLLOUT}, {Fd: closeFd, Events: unix.POLLOUT}}
|
pollFds := []unix.PollFd{{Fd: int32(rw.fd), Events: unix.POLLOUT}, {Fd: closeFd, Events: unix.POLLIN}}
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
_, err = unix.Poll(pollFds, -1)
|
_, err = unix.Poll(pollFds, -1)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build windows || wasm
|
//go:build windows || wasm || plan9 || tamago
|
||||||
|
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tai64n
|
package tai64n
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2023 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
425
tests/netns.sh
|
|
@ -1,425 +0,0 @@
|
||||||
#!/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."
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue