Compare commits
459 Commits
0.0.201904
...
0.0.202110
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f87e87af0d | ||
|
|
ba9e364dab | ||
|
|
dfd688b6aa | ||
|
|
c01d52b66a | ||
|
|
82d2aa87aa | ||
|
|
982d5d2e84 | ||
|
|
642a56e165 | ||
|
|
bb745b2ea3 | ||
|
|
fcc601dbf0 | ||
|
|
217ac1016b | ||
|
|
eae5e0f3a3 | ||
|
|
2ef39d4754 | ||
|
|
3957e9b9dd | ||
|
|
bad6caeb82 | ||
|
|
c89f5ca665 | ||
|
|
15b24b6179 | ||
|
|
f9b48a961c | ||
|
|
d0cf96114f | ||
|
|
841756e328 | ||
|
|
c382222eab | ||
|
|
b41f4cc768 | ||
|
|
4a57024b94 | ||
|
|
64cb82f2b3 | ||
|
|
c27ff9b9f6 | ||
|
|
99e8b4ba60 | ||
|
|
bd83f0ac99 | ||
|
|
50d779833e | ||
|
|
a9b377e9e1 | ||
|
|
9087e444e6 | ||
|
|
25ad08a591 | ||
|
|
5846b62283 | ||
|
|
9844c74f67 | ||
|
|
4e9e5dad09 | ||
|
|
39e0b6dade | ||
|
|
7121927b87 | ||
|
|
326aec10af | ||
|
|
efb8818550 | ||
|
|
69b39db0b4 | ||
|
|
db733ccd65 | ||
|
|
a7aec4449f | ||
|
|
60a26371f4 | ||
|
|
a544776d70 | ||
|
|
69a42a4eef | ||
|
|
097af6e135 | ||
|
|
8246d251ea | ||
|
|
c9db4b7aaa | ||
|
|
3625f8d284 | ||
|
|
0687dc06c8 | ||
|
|
71aefa374d | ||
|
|
3d3e30beb8 | ||
|
|
b0e5b19969 | ||
|
|
3988821442 | ||
|
|
c7cd2c9eab | ||
|
|
54dbe2471f | ||
|
|
d2fd0c0cc0 | ||
|
|
5f6bbe4ae8 | ||
|
|
75526d6071 | ||
|
|
fbf97502cf | ||
|
|
10533c3e73 | ||
|
|
8ed83e0427 | ||
|
|
6228659a91 | ||
|
|
517f0703f5 | ||
|
|
204140016a | ||
|
|
822f5a6d70 | ||
|
|
02e419ed8a | ||
|
|
bc69a3fa60 | ||
|
|
12ce53271b | ||
|
|
5f0c8b942d | ||
|
|
c5f382624e | ||
|
|
6005c573e2 | ||
|
|
82f3e9e2af | ||
|
|
4885e7c954 | ||
|
|
497ba95de7 | ||
|
|
0eb7206295 | ||
|
|
20714ca472 | ||
|
|
c1e09f1927 | ||
|
|
79611c64e8 | ||
|
|
593658d975 | ||
|
|
3c11c0308e | ||
|
|
f9dac7099e | ||
|
|
9a29ae267c | ||
|
|
6603c05a4a | ||
|
|
a4f8e83d5d | ||
|
|
c69481f1b3 | ||
|
|
0f4809f366 | ||
|
|
fecb8f482a | ||
|
|
8bf4204d2e | ||
|
|
4e439ea10e | ||
|
|
7a0fb5bbb1 | ||
|
|
c7b7998619 | ||
|
|
ef8115f63b | ||
|
|
75e6d810ed | ||
|
|
747f5440bc | ||
|
|
aabc3770ba | ||
|
|
484a9fd324 | ||
|
|
5bf8d73127 | ||
|
|
587a2b2a20 | ||
|
|
6f08a10041 | ||
|
|
a97ef39cd4 | ||
|
|
c040dea798 | ||
|
|
5cdb862f15 | ||
|
|
da32fe328b | ||
|
|
4eab21a7b7 | ||
|
|
30b96ba083 | ||
|
|
78ebce6932 | ||
|
|
cae090d116 | ||
|
|
465261310b | ||
|
|
d117d42ae7 | ||
|
|
ecceaadd16 | ||
|
|
9e728c2eb0 | ||
|
|
eaf664e4e9 | ||
|
|
a816e8511e | ||
|
|
02138f1f81 | ||
|
|
d7bc7508e5 | ||
|
|
d6e76fdbd6 | ||
|
|
6ac1240821 | ||
|
|
4b5d15ec2b | ||
|
|
6548a682a9 | ||
|
|
a60e6dab76 | ||
|
|
d8dd1f254f | ||
|
|
57aadfcb14 | ||
|
|
af408eb940 | ||
|
|
15810daa22 | ||
|
|
d840445e9b | ||
|
|
675ff32e6c | ||
|
|
3516ccc1e2 | ||
|
|
0bcb822e5b | ||
|
|
da95677203 | ||
|
|
9c75f58f3d | ||
|
|
84a42aed63 | ||
|
|
4192036acd | ||
|
|
9c7bd73be2 | ||
|
|
01e176af3c | ||
|
|
91617b4c52 | ||
|
|
7258a8973d | ||
|
|
d9d547a3f3 | ||
|
|
c3bde5f590 | ||
|
|
fd63a233c9 | ||
|
|
8a374a35a0 | ||
|
|
4846070322 | ||
|
|
a9f80d8c58 | ||
|
|
de51129e33 | ||
|
|
beb25cc4fd | ||
|
|
9263014ed3 | ||
|
|
f0f27d7fd2 | ||
|
|
d4112d9096 | ||
|
|
bf3bb88851 | ||
|
|
6a128dde71 | ||
|
|
34c047c762 | ||
|
|
d4725bc456 | ||
|
|
1b092ce584 | ||
|
|
a11dec5dc1 | ||
|
|
ace50a0529 | ||
|
|
8cc99631d0 | ||
|
|
d669c78c43 | ||
|
|
7139279cd0 | ||
|
|
37efdcaccf | ||
|
|
d3a2b74df2 | ||
|
|
8114c9db5f | ||
|
|
e6ec3852a9 | ||
|
|
23b2790aa0 | ||
|
|
18e47795e5 | ||
|
|
a29767dda6 | ||
|
|
cecb41515d | ||
|
|
a9ce4b762c | ||
|
|
d8f2cc87ee | ||
|
|
2b8665f5f9 | ||
|
|
674a4675a1 | ||
|
|
87bdcb2ae4 | ||
|
|
37a239e736 | ||
|
|
6252de0db9 | ||
|
|
a029b942ae | ||
|
|
db3fa1409c | ||
|
|
675aae2423 | ||
|
|
fcc8ad05df | ||
|
|
1d4eb2727a | ||
|
|
294d3bedf9 | ||
|
|
86a58b51c0 | ||
|
|
6a2ecb581b | ||
|
|
f07177c762 | ||
|
|
b00b2c2951 | ||
|
|
7c5d1e355e | ||
|
|
a86492a567 | ||
|
|
7ee95e053c | ||
|
|
291dbcf1f0 | ||
|
|
abc88c82b1 | ||
|
|
23642a13be | ||
|
|
2fe19ce54d | ||
|
|
0cc15e7c7c | ||
|
|
48c3b87eb8 | ||
|
|
675955de5d | ||
|
|
ea6c1cd7e6 | ||
|
|
3b3de758ec | ||
|
|
29b0477585 | ||
|
|
85b4950579 | ||
|
|
8a30415555 | ||
|
|
cdaf4e9a76 | ||
|
|
3d83df9bf3 | ||
|
|
d664444928 | ||
|
|
1481e72107 | ||
|
|
d0f8e9477c | ||
|
|
b42e32047d | ||
|
|
b5f966ac24 | ||
|
|
a1c265b0c5 | ||
|
|
25b01723dd | ||
|
|
40dfc85def | ||
|
|
890cc06ed5 | ||
|
|
ad73ee78e9 | ||
|
|
e9edc16349 | ||
|
|
f7bbdc31a0 | ||
|
|
70861686d3 | ||
|
|
c8faa34cde | ||
|
|
2832e96339 | ||
|
|
63066ce406 | ||
|
|
e1fa1cc556 | ||
|
|
41cd68416c | ||
|
|
94b33ba705 | ||
|
|
ea8fbb5927 | ||
|
|
93a4313c3a | ||
|
|
db1edc7e91 | ||
|
|
fc0aabbae9 | ||
|
|
c9e4a859ae | ||
|
|
3591acba76 | ||
|
|
ca9edf1c63 | ||
|
|
347ce76bbc | ||
|
|
c4895658e6 | ||
|
|
d3ff2d6b62 | ||
|
|
01d3aaa7f4 | ||
|
|
b6303091fc | ||
|
|
c9fabbd5bf | ||
|
|
4cc7a7a455 | ||
|
|
da19db415a | ||
|
|
52c834c446 | ||
|
|
913f68ce38 | ||
|
|
60b3766b89 | ||
|
|
82128c47d9 | ||
|
|
c192b2eeec | ||
|
|
a3b231b31e | ||
|
|
65e03a9182 | ||
|
|
3e08b8aee0 | ||
|
|
5ca1218a5c | ||
|
|
3b490f30aa | ||
|
|
e6b7c4eef3 | ||
|
|
8ae09213a7 | ||
|
|
36dc8b6994 | ||
|
|
2057f19a61 | ||
|
|
58a8f05f50 | ||
|
|
0b54907a73 | ||
|
|
2c143dce0f | ||
|
|
22af3890f6 | ||
|
|
c8fe925020 | ||
|
|
0cfa3314ee | ||
|
|
bc3f505efa | ||
|
|
507f148e1c | ||
|
|
31b574ef99 | ||
|
|
3c41141fb4 | ||
|
|
4369db522b | ||
|
|
b84f1d4db2 | ||
|
|
dfb28757f7 | ||
|
|
00bcd865e6 | ||
|
|
f28a6d244b | ||
|
|
c403da6a39 | ||
|
|
d6de6f3ce6 | ||
|
|
59e556f24e | ||
|
|
31faf4c159 | ||
|
|
99eb7896be | ||
|
|
f60b3919be | ||
|
|
da9d300cf8 | ||
|
|
59c9929714 | ||
|
|
db0aa39b76 | ||
|
|
bc77de2aca | ||
|
|
c8596328e7 | ||
|
|
28c4d04304 | ||
|
|
fdba6c183a | ||
|
|
250b9795f3 | ||
|
|
d60857e1a7 | ||
|
|
2fb0a712f0 | ||
|
|
f2c6faad44 | ||
|
|
c76b818466 | ||
|
|
de374bfb44 | ||
|
|
1a1c3d0968 | ||
|
|
85a45a9651 | ||
|
|
abd287159e | ||
|
|
203554620d | ||
|
|
6aefb61355 | ||
|
|
3dce460c88 | ||
|
|
224bc9e60c | ||
|
|
9cd8909df2 | ||
|
|
ae88e2a2cd | ||
|
|
4739708ca4 | ||
|
|
b33219c2cf | ||
|
|
9cbcff10dd | ||
|
|
6ed56ff2df | ||
|
|
cb4bb63030 | ||
|
|
05b03c6750 | ||
|
|
caebdfe9d0 | ||
|
|
4fa2ea6a2d | ||
|
|
89dd065e53 | ||
|
|
ddfad453cf | ||
|
|
2b242f9393 | ||
|
|
4cdf805b29 | ||
|
|
f7d0edd2ec | ||
|
|
ffffbbcc8a | ||
|
|
47b02c618b | ||
|
|
fd23c66fcd | ||
|
|
ae492d1b35 | ||
|
|
95fbfccf60 | ||
|
|
c85e4a410f | ||
|
|
1b6c8ddbe8 | ||
|
|
0abb6b668c | ||
|
|
540d01e54a | ||
|
|
f2ea85e9f9 | ||
|
|
222f0f8000 | ||
|
|
1f146a5e7a | ||
|
|
f2501aa6c8 | ||
|
|
cb8d01f58a | ||
|
|
01f8ef4e84 | ||
|
|
70f6c42556 | ||
|
|
bb0b2514c0 | ||
|
|
7c97fdb1e3 | ||
|
|
84b5a4d83d | ||
|
|
4cd06c0925 | ||
|
|
d12eb91f9a | ||
|
|
73d3bd9cd5 | ||
|
|
f3dba4c194 | ||
|
|
7937840f96 | ||
|
|
e4b957183c | ||
|
|
950ca2ba8c | ||
|
|
df2bf34373 | ||
|
|
a12b765784 | ||
|
|
14df9c3e75 | ||
|
|
353f0956bc | ||
|
|
fa7763c268 | ||
|
|
d94bae8348 | ||
|
|
7689d09336 | ||
|
|
69c26dc258 | ||
|
|
e862131d3c | ||
|
|
da28a3e9f3 | ||
|
|
3bf3322b2c | ||
|
|
7305b4ce93 | ||
|
|
26fb615b11 | ||
|
|
7fbb24afaa | ||
|
|
d9008ac35c | ||
|
|
f8198c0428 | ||
|
|
0c540ad60e | ||
|
|
3cedc22d7b | ||
|
|
68fea631d8 | ||
|
|
ef23100a4f | ||
|
|
eb786cd7c1 | ||
|
|
333de75370 | ||
|
|
d20459dc69 | ||
|
|
01786286c1 | ||
|
|
b16dba47a7 | ||
|
|
4be9630ddc | ||
|
|
4e3018a967 | ||
|
|
b4010123f7 | ||
|
|
1ff37e2b07 | ||
|
|
f5e54932e6 | ||
|
|
73698066d1 | ||
|
|
05ece4d167 | ||
|
|
6d78f89557 | ||
|
|
a2249449d6 | ||
|
|
eeeac287ef | ||
|
|
b5a7cbf069 | ||
|
|
50cd522cb0 | ||
|
|
5ba866a5c8 | ||
|
|
2f101fedec | ||
|
|
3341e2d444 | ||
|
|
1b550f6583 | ||
|
|
7bc0e11831 | ||
|
|
31ff9c02fe | ||
|
|
1e39c33ab1 | ||
|
|
6c50fedd8e | ||
|
|
298d759f3e | ||
|
|
4d5819183e | ||
|
|
9ea9a92117 | ||
|
|
2e24e7dcae | ||
|
|
a961aacc9f | ||
|
|
b0cf53b078 | ||
|
|
5c3d333f10 | ||
|
|
d8448f8a02 | ||
|
|
13abbdf14b | ||
|
|
f361e59001 | ||
|
|
b844f1b3cc | ||
|
|
dd8817f50e | ||
|
|
5e6eff81b6 | ||
|
|
c69d026649 | ||
|
|
1f48971a80 | ||
|
|
3371f8dac6 | ||
|
|
41fdbf0971 | ||
|
|
03eee4a778 | ||
|
|
700860f8e6 | ||
|
|
a304f69e0d | ||
|
|
baafe92888 | ||
|
|
a1a97d1e41 | ||
|
|
e924280baa | ||
|
|
bb3f1932fa | ||
|
|
eaf17becfa | ||
|
|
6d8b68c8f3 | ||
|
|
c2ed133df8 | ||
|
|
108c37a056 | ||
|
|
e4b0ef29a1 | ||
|
|
625e445b22 | ||
|
|
85b85e62e5 | ||
|
|
014f736480 | ||
|
|
43a4589043 | ||
|
|
8d76ac8cc4 | ||
|
|
18b6627f33 | ||
|
|
80ef2a42e6 | ||
|
|
da61947ec3 | ||
|
|
d9f995209c | ||
|
|
d0ab883ada | ||
|
|
32912dc778 | ||
|
|
d4034e5f8a | ||
|
|
fbcd995ec1 | ||
|
|
e7e286ba6c | ||
|
|
f70546bc2e | ||
|
|
6a0a3a5406 | ||
|
|
8fdcf5ee30 | ||
|
|
a74a29bc93 | ||
|
|
dc9bbec9db | ||
|
|
a6dbe4f475 | ||
|
|
c718f3940d | ||
|
|
95c70b8032 | ||
|
|
583ebe99f1 | ||
|
|
a6dd282600 | ||
|
|
7d5f5bcc0d | ||
|
|
3bf41b06ae | ||
|
|
3147f00089 | ||
|
|
6c1b66802f | ||
|
|
5669ed326f | ||
|
|
2d847a38a2 | ||
|
|
7a8553aef0 | ||
|
|
a6045ac042 | ||
|
|
1c92b48415 | ||
|
|
c267965bf8 | ||
|
|
1bf1dadf15 | ||
|
|
f9dcfccbb7 | ||
|
|
7e962a9932 | ||
|
|
586112b5d7 | ||
|
|
dcb8f1aa6b | ||
|
|
b16b0e4cf7 | ||
|
|
81ca08f1b3 | ||
|
|
2e988467c2 | ||
|
|
46dbf54040 | ||
|
|
247e14693a | ||
|
|
3945a299ff | ||
|
|
bb42ec7d18 | ||
|
|
f1dc167901 | ||
|
|
c7a26dfef3 | ||
|
|
d024393335 | ||
|
|
d9078fe772 | ||
|
|
d3dd991e4e | ||
|
|
5811447b38 | ||
|
|
e0a8c22aa6 | ||
|
|
0b77bf78cd | ||
|
|
ef5f3ad80a | ||
|
|
a291fdd746 | ||
|
|
d50e390904 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1 @@
|
|||||||
wireguard-go
|
wireguard-go
|
||||||
vendor
|
|
||||||
.gopath
|
|
||||||
ireallywantobuildon_linux.go
|
|
||||||
|
|||||||
27
Makefile
27
Makefile
@@ -1,33 +1,19 @@
|
|||||||
PREFIX ?= /usr
|
PREFIX ?= /usr
|
||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
BINDIR ?= $(PREFIX)/bin
|
BINDIR ?= $(PREFIX)/bin
|
||||||
export GOPATH ?= $(CURDIR)/.gopath
|
|
||||||
export GO111MODULE := on
|
export GO111MODULE := on
|
||||||
|
|
||||||
all: generate-version-and-build
|
all: generate-version-and-build
|
||||||
|
|
||||||
ifeq ($(shell go env GOOS)|$(wildcard .git),linux|)
|
|
||||||
$(error Do not build this for Linux. Instead use the Linux kernel module. See wireguard.com/install/ for more info.)
|
|
||||||
else
|
|
||||||
ireallywantobuildon_linux.go:
|
|
||||||
@printf "WARNING: This software is meant for use on non-Linux\nsystems. For Linux, please use the kernel module\ninstead. See wireguard.com/install/ for more info.\n\n" >&2
|
|
||||||
@printf 'package main\nconst UseTheKernelModuleInstead = 0xdeadbabe\n' > "$@"
|
|
||||||
clean-ireallywantobuildon_linux.go:
|
|
||||||
@rm -f ireallywantobuildon_linux.go
|
|
||||||
.PHONY: clean-ireallywantobuildon_linux.go
|
|
||||||
clean: clean-ireallywantobuildon_linux.go
|
|
||||||
wireguard-go: ireallywantobuildon_linux.go
|
|
||||||
endif
|
|
||||||
|
|
||||||
MAKEFLAGS += --no-print-directory
|
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 --dirty 2>/dev/null)" && \
|
||||||
ver="$$(printf 'package device\nconst WireGuardGoVersion = "%s"\n' "$$tag")" && \
|
ver="$$(printf 'package main\n\nconst Version = "%s"\n' "$$tag")" && \
|
||||||
[ "$$(cat device/version.go 2>/dev/null)" != "$$ver" ] && \
|
[ "$$(cat version.go 2>/dev/null)" != "$$ver" ] && \
|
||||||
echo "$$ver" > device/version.go && \
|
echo "$$ver" > version.go && \
|
||||||
git update-index --assume-unchanged device/version.go || true
|
git update-index --assume-unchanged version.go || true
|
||||||
@$(MAKE) wireguard-go
|
@$(MAKE) wireguard-go
|
||||||
|
|
||||||
wireguard-go: $(wildcard *.go) $(wildcard */*.go)
|
wireguard-go: $(wildcard *.go) $(wildcard */*.go)
|
||||||
@@ -36,7 +22,10 @@ wireguard-go: $(wildcard *.go) $(wildcard */*.go)
|
|||||||
install: wireguard-go
|
install: wireguard-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)/wireguard-go"
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -v ./...
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f wireguard-go
|
rm -f wireguard-go
|
||||||
|
|
||||||
.PHONY: all clean install generate-version-and-build
|
.PHONY: all clean test install generate-version-and-build
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
This is an implementation of WireGuard in Go.
|
This is an implementation of WireGuard in Go.
|
||||||
|
|
||||||
***WARNING:*** This is a work in progress and not ready for prime time, with no official "releases" yet. It is extremely rough around the edges and leaves much to be desired. There are bugs and we are not yet in a position to make claims about its security. Beware.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Most Linux kernel WireGuard users are used to adding an interface with `ip link add wg0 type wireguard`. With wireguard-go, instead simply run:
|
Most Linux kernel WireGuard users are used to adding an interface with `ip link add wg0 type wireguard`. With wireguard-go, instead simply run:
|
||||||
@@ -20,7 +18,7 @@ To run wireguard-go without forking to the background, pass `-f` or `--foregroun
|
|||||||
$ wireguard-go -f wg0
|
$ wireguard-go -f wg0
|
||||||
```
|
```
|
||||||
|
|
||||||
When an interface is running, you may use [`wg(8)`](https://git.zx2c4.com/WireGuard/about/src/tools/man/wg.8) to configure it, as well as the usual `ip(8)` and `ifconfig(8)` commands.
|
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.
|
||||||
|
|
||||||
To run with more logging you may set the environment variable `LOG_LEVEL=debug`.
|
To run with more logging you may set the environment variable `LOG_LEVEL=debug`.
|
||||||
|
|
||||||
@@ -28,7 +26,7 @@ To run with more logging you may set the environment variable `LOG_LEVEL=debug`.
|
|||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
This will run on Linux; however **YOU SHOULD NOT RUN THIS ON LINUX**. Instead use the kernel module; see the [installation page](https://www.wireguard.com/install/) for instructions.
|
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
|
### macOS
|
||||||
|
|
||||||
@@ -36,7 +34,7 @@ This runs on macOS using the utun driver. It does not yet support sticky sockets
|
|||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
It is currently a work in progress to strip out the beginnings of an experiment done with the OpenVPN tuntap driver and instead port to the new UWP APIs for tunnels. In other words, this does not *yet* work on 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
|
### FreeBSD
|
||||||
|
|
||||||
@@ -48,7 +46,7 @@ This will run on OpenBSD. It does not yet support sticky sockets. Fwmark is mapp
|
|||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
This requires an installation of [go](https://golang.org) ≥ 1.12.
|
This requires an installation of [go](https://golang.org) ≥ 1.16.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ git clone https://git.zx2c4.com/wireguard-go
|
$ git clone https://git.zx2c4.com/wireguard-go
|
||||||
@@ -58,7 +56,7 @@ $ make
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
Copyright (C) 2017-2021 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
|
||||||
|
|||||||
585
conn/bind_linux.go
Normal file
585
conn/bind_linux.go
Normal file
@@ -0,0 +1,585 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package conn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ipv4Source struct {
|
||||||
|
Src [4]byte
|
||||||
|
Ifindex int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipv6Source struct {
|
||||||
|
src [16]byte
|
||||||
|
// ifindex belongs in dst.ZoneId
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinuxSocketEndpoint struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
dst [unsafe.Sizeof(unix.SockaddrInet6{})]byte
|
||||||
|
src [unsafe.Sizeof(ipv6Source{})]byte
|
||||||
|
isV6 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (endpoint *LinuxSocketEndpoint) Src4() *ipv4Source { return endpoint.src4() }
|
||||||
|
func (endpoint *LinuxSocketEndpoint) Dst4() *unix.SockaddrInet4 { return endpoint.dst4() }
|
||||||
|
func (endpoint *LinuxSocketEndpoint) IsV6() bool { return endpoint.isV6 }
|
||||||
|
|
||||||
|
func (endpoint *LinuxSocketEndpoint) src4() *ipv4Source {
|
||||||
|
return (*ipv4Source)(unsafe.Pointer(&endpoint.src[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (endpoint *LinuxSocketEndpoint) src6() *ipv6Source {
|
||||||
|
return (*ipv6Source)(unsafe.Pointer(&endpoint.src[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (endpoint *LinuxSocketEndpoint) dst4() *unix.SockaddrInet4 {
|
||||||
|
return (*unix.SockaddrInet4)(unsafe.Pointer(&endpoint.dst[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (endpoint *LinuxSocketEndpoint) dst6() *unix.SockaddrInet6 {
|
||||||
|
return (*unix.SockaddrInet6)(unsafe.Pointer(&endpoint.dst[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinuxSocketBind uses sendmsg and recvmsg to implement a full bind with sticky sockets on Linux.
|
||||||
|
type LinuxSocketBind struct {
|
||||||
|
// mu guards sock4 and sock6 and the associated fds.
|
||||||
|
// As long as someone holds mu (read or write), the associated fds are valid.
|
||||||
|
mu sync.RWMutex
|
||||||
|
sock4 int
|
||||||
|
sock6 int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLinuxSocketBind() Bind { return &LinuxSocketBind{sock4: -1, sock6: -1} }
|
||||||
|
func NewDefaultBind() Bind { return NewLinuxSocketBind() }
|
||||||
|
|
||||||
|
var _ Endpoint = (*LinuxSocketEndpoint)(nil)
|
||||||
|
var _ Bind = (*LinuxSocketBind)(nil)
|
||||||
|
|
||||||
|
func (*LinuxSocketBind) ParseEndpoint(s string) (Endpoint, error) {
|
||||||
|
var end LinuxSocketEndpoint
|
||||||
|
addr, err := parseEndpoint(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv4 := addr.IP.To4()
|
||||||
|
if ipv4 != nil {
|
||||||
|
dst := end.dst4()
|
||||||
|
end.isV6 = false
|
||||||
|
dst.Port = addr.Port
|
||||||
|
copy(dst.Addr[:], ipv4)
|
||||||
|
end.ClearSrc()
|
||||||
|
return &end, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv6 := addr.IP.To16()
|
||||||
|
if ipv6 != nil {
|
||||||
|
zone, err := zoneToUint32(addr.Zone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dst := end.dst6()
|
||||||
|
end.isV6 = true
|
||||||
|
dst.Port = addr.Port
|
||||||
|
dst.ZoneId = zone
|
||||||
|
copy(dst.Addr[:], ipv6[:])
|
||||||
|
end.ClearSrc()
|
||||||
|
return &end, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("invalid IP address")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *LinuxSocketBind) Open(port uint16) ([]ReceiveFunc, uint16, error) {
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var newPort uint16
|
||||||
|
var tries int
|
||||||
|
|
||||||
|
if bind.sock4 != -1 || bind.sock6 != -1 {
|
||||||
|
return nil, 0, ErrBindAlreadyOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
originalPort := port
|
||||||
|
|
||||||
|
again:
|
||||||
|
port = originalPort
|
||||||
|
var sock4, sock6 int
|
||||||
|
// Attempt ipv6 bind, update port if successful.
|
||||||
|
sock6, newPort, err = create6(port)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
port = newPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt ipv4 bind, update port if successful.
|
||||||
|
sock4, newPort, err = create4(port)
|
||||||
|
if err != nil {
|
||||||
|
if originalPort == 0 && errors.Is(err, syscall.EADDRINUSE) && tries < 100 {
|
||||||
|
unix.Close(sock6)
|
||||||
|
tries++
|
||||||
|
goto again
|
||||||
|
}
|
||||||
|
if !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
unix.Close(sock6)
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
port = newPort
|
||||||
|
}
|
||||||
|
|
||||||
|
var fns []ReceiveFunc
|
||||||
|
if sock4 != -1 {
|
||||||
|
bind.sock4 = sock4
|
||||||
|
fns = append(fns, bind.receiveIPv4)
|
||||||
|
}
|
||||||
|
if sock6 != -1 {
|
||||||
|
bind.sock6 = sock6
|
||||||
|
fns = append(fns, bind.receiveIPv6)
|
||||||
|
}
|
||||||
|
if len(fns) == 0 {
|
||||||
|
return nil, 0, syscall.EAFNOSUPPORT
|
||||||
|
}
|
||||||
|
return fns, port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *LinuxSocketBind) SetMark(value uint32) error {
|
||||||
|
bind.mu.RLock()
|
||||||
|
defer bind.mu.RUnlock()
|
||||||
|
|
||||||
|
if bind.sock6 != -1 {
|
||||||
|
err := unix.SetsockoptInt(
|
||||||
|
bind.sock6,
|
||||||
|
unix.SOL_SOCKET,
|
||||||
|
unix.SO_MARK,
|
||||||
|
int(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bind.sock4 != -1 {
|
||||||
|
err := unix.SetsockoptInt(
|
||||||
|
bind.sock4,
|
||||||
|
unix.SOL_SOCKET,
|
||||||
|
unix.SO_MARK,
|
||||||
|
int(value),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *LinuxSocketBind) Close() error {
|
||||||
|
// Take a readlock to shut down the sockets...
|
||||||
|
bind.mu.RLock()
|
||||||
|
if bind.sock6 != -1 {
|
||||||
|
unix.Shutdown(bind.sock6, unix.SHUT_RDWR)
|
||||||
|
}
|
||||||
|
if bind.sock4 != -1 {
|
||||||
|
unix.Shutdown(bind.sock4, unix.SHUT_RDWR)
|
||||||
|
}
|
||||||
|
bind.mu.RUnlock()
|
||||||
|
// ...and a write lock to close the fd.
|
||||||
|
// This ensures that no one else is using the fd.
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
var err1, err2 error
|
||||||
|
if bind.sock6 != -1 {
|
||||||
|
err1 = unix.Close(bind.sock6)
|
||||||
|
bind.sock6 = -1
|
||||||
|
}
|
||||||
|
if bind.sock4 != -1 {
|
||||||
|
err2 = unix.Close(bind.sock4)
|
||||||
|
bind.sock4 = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *LinuxSocketBind) receiveIPv4(buf []byte) (int, Endpoint, error) {
|
||||||
|
bind.mu.RLock()
|
||||||
|
defer bind.mu.RUnlock()
|
||||||
|
if bind.sock4 == -1 {
|
||||||
|
return 0, nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
var end LinuxSocketEndpoint
|
||||||
|
n, err := receive4(bind.sock4, buf, &end)
|
||||||
|
return n, &end, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *LinuxSocketBind) receiveIPv6(buf []byte) (int, Endpoint, error) {
|
||||||
|
bind.mu.RLock()
|
||||||
|
defer bind.mu.RUnlock()
|
||||||
|
if bind.sock6 == -1 {
|
||||||
|
return 0, nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
var end LinuxSocketEndpoint
|
||||||
|
n, err := receive6(bind.sock6, buf, &end)
|
||||||
|
return n, &end, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *LinuxSocketBind) Send(buff []byte, end Endpoint) error {
|
||||||
|
nend, ok := end.(*LinuxSocketEndpoint)
|
||||||
|
if !ok {
|
||||||
|
return ErrWrongEndpointType
|
||||||
|
}
|
||||||
|
bind.mu.RLock()
|
||||||
|
defer bind.mu.RUnlock()
|
||||||
|
if !nend.isV6 {
|
||||||
|
if bind.sock4 == -1 {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return send4(bind.sock4, nend, buff)
|
||||||
|
} else {
|
||||||
|
if bind.sock6 == -1 {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return send6(bind.sock6, nend, buff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (end *LinuxSocketEndpoint) SrcIP() net.IP {
|
||||||
|
if !end.isV6 {
|
||||||
|
return net.IPv4(
|
||||||
|
end.src4().Src[0],
|
||||||
|
end.src4().Src[1],
|
||||||
|
end.src4().Src[2],
|
||||||
|
end.src4().Src[3],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return end.src6().src[:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (end *LinuxSocketEndpoint) DstIP() net.IP {
|
||||||
|
if !end.isV6 {
|
||||||
|
return net.IPv4(
|
||||||
|
end.dst4().Addr[0],
|
||||||
|
end.dst4().Addr[1],
|
||||||
|
end.dst4().Addr[2],
|
||||||
|
end.dst4().Addr[3],
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return end.dst6().Addr[:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (end *LinuxSocketEndpoint) DstToBytes() []byte {
|
||||||
|
if !end.isV6 {
|
||||||
|
return (*[unsafe.Offsetof(end.dst4().Addr) + unsafe.Sizeof(end.dst4().Addr)]byte)(unsafe.Pointer(end.dst4()))[:]
|
||||||
|
} else {
|
||||||
|
return (*[unsafe.Offsetof(end.dst6().Addr) + unsafe.Sizeof(end.dst6().Addr)]byte)(unsafe.Pointer(end.dst6()))[:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (end *LinuxSocketEndpoint) SrcToString() string {
|
||||||
|
return end.SrcIP().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (end *LinuxSocketEndpoint) DstToString() string {
|
||||||
|
var udpAddr net.UDPAddr
|
||||||
|
udpAddr.IP = end.DstIP()
|
||||||
|
if !end.isV6 {
|
||||||
|
udpAddr.Port = end.dst4().Port
|
||||||
|
} else {
|
||||||
|
udpAddr.Port = end.dst6().Port
|
||||||
|
}
|
||||||
|
return udpAddr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (end *LinuxSocketEndpoint) ClearDst() {
|
||||||
|
for i := range end.dst {
|
||||||
|
end.dst[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (end *LinuxSocketEndpoint) ClearSrc() {
|
||||||
|
for i := range end.src {
|
||||||
|
end.src[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func zoneToUint32(zone string) (uint32, error) {
|
||||||
|
if zone == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if intr, err := net.InterfaceByName(zone); err == nil {
|
||||||
|
return uint32(intr.Index), nil
|
||||||
|
}
|
||||||
|
n, err := strconv.ParseUint(zone, 10, 32)
|
||||||
|
return uint32(n), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func create4(port uint16) (int, uint16, error) {
|
||||||
|
|
||||||
|
// create socket
|
||||||
|
|
||||||
|
fd, err := unix.Socket(
|
||||||
|
unix.AF_INET,
|
||||||
|
unix.SOCK_DGRAM,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -1, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := unix.SockaddrInet4{
|
||||||
|
Port: int(port),
|
||||||
|
}
|
||||||
|
|
||||||
|
// set sockopts and bind
|
||||||
|
|
||||||
|
if err := func() error {
|
||||||
|
if err := unix.SetsockoptInt(
|
||||||
|
fd,
|
||||||
|
unix.IPPROTO_IP,
|
||||||
|
unix.IP_PKTINFO,
|
||||||
|
1,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return unix.Bind(fd, &addr)
|
||||||
|
}(); err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
|
return -1, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sa, err := unix.Getsockname(fd)
|
||||||
|
if err == nil {
|
||||||
|
addr.Port = sa.(*unix.SockaddrInet4).Port
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd, uint16(addr.Port), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func create6(port uint16) (int, uint16, error) {
|
||||||
|
|
||||||
|
// create socket
|
||||||
|
|
||||||
|
fd, err := unix.Socket(
|
||||||
|
unix.AF_INET6,
|
||||||
|
unix.SOCK_DGRAM,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -1, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set sockopts and bind
|
||||||
|
|
||||||
|
addr := unix.SockaddrInet6{
|
||||||
|
Port: int(port),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := func() error {
|
||||||
|
if err := unix.SetsockoptInt(
|
||||||
|
fd,
|
||||||
|
unix.IPPROTO_IPV6,
|
||||||
|
unix.IPV6_RECVPKTINFO,
|
||||||
|
1,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := unix.SetsockoptInt(
|
||||||
|
fd,
|
||||||
|
unix.IPPROTO_IPV6,
|
||||||
|
unix.IPV6_V6ONLY,
|
||||||
|
1,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return unix.Bind(fd, &addr)
|
||||||
|
|
||||||
|
}(); err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
|
return -1, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sa, err := unix.Getsockname(fd)
|
||||||
|
if err == nil {
|
||||||
|
addr.Port = sa.(*unix.SockaddrInet6).Port
|
||||||
|
}
|
||||||
|
|
||||||
|
return fd, uint16(addr.Port), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func send4(sock int, end *LinuxSocketEndpoint, buff []byte) error {
|
||||||
|
|
||||||
|
// construct message header
|
||||||
|
|
||||||
|
cmsg := struct {
|
||||||
|
cmsghdr unix.Cmsghdr
|
||||||
|
pktinfo unix.Inet4Pktinfo
|
||||||
|
}{
|
||||||
|
unix.Cmsghdr{
|
||||||
|
Level: unix.IPPROTO_IP,
|
||||||
|
Type: unix.IP_PKTINFO,
|
||||||
|
Len: unix.SizeofInet4Pktinfo + unix.SizeofCmsghdr,
|
||||||
|
},
|
||||||
|
unix.Inet4Pktinfo{
|
||||||
|
Spec_dst: end.src4().Src,
|
||||||
|
Ifindex: end.src4().Ifindex,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
end.mu.Lock()
|
||||||
|
_, err := unix.SendmsgN(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], end.dst4(), 0)
|
||||||
|
end.mu.Unlock()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear src and retry
|
||||||
|
|
||||||
|
if err == unix.EINVAL {
|
||||||
|
end.ClearSrc()
|
||||||
|
cmsg.pktinfo = unix.Inet4Pktinfo{}
|
||||||
|
end.mu.Lock()
|
||||||
|
_, err = unix.SendmsgN(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], end.dst4(), 0)
|
||||||
|
end.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func send6(sock int, end *LinuxSocketEndpoint, buff []byte) error {
|
||||||
|
|
||||||
|
// construct message header
|
||||||
|
|
||||||
|
cmsg := struct {
|
||||||
|
cmsghdr unix.Cmsghdr
|
||||||
|
pktinfo unix.Inet6Pktinfo
|
||||||
|
}{
|
||||||
|
unix.Cmsghdr{
|
||||||
|
Level: unix.IPPROTO_IPV6,
|
||||||
|
Type: unix.IPV6_PKTINFO,
|
||||||
|
Len: unix.SizeofInet6Pktinfo + unix.SizeofCmsghdr,
|
||||||
|
},
|
||||||
|
unix.Inet6Pktinfo{
|
||||||
|
Addr: end.src6().src,
|
||||||
|
Ifindex: end.dst6().ZoneId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmsg.pktinfo.Addr == [16]byte{} {
|
||||||
|
cmsg.pktinfo.Ifindex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
end.mu.Lock()
|
||||||
|
_, err := unix.SendmsgN(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], end.dst6(), 0)
|
||||||
|
end.mu.Unlock()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear src and retry
|
||||||
|
|
||||||
|
if err == unix.EINVAL {
|
||||||
|
end.ClearSrc()
|
||||||
|
cmsg.pktinfo = unix.Inet6Pktinfo{}
|
||||||
|
end.mu.Lock()
|
||||||
|
_, err = unix.SendmsgN(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], end.dst6(), 0)
|
||||||
|
end.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func receive4(sock int, buff []byte, end *LinuxSocketEndpoint) (int, error) {
|
||||||
|
|
||||||
|
// construct message header
|
||||||
|
|
||||||
|
var cmsg struct {
|
||||||
|
cmsghdr unix.Cmsghdr
|
||||||
|
pktinfo unix.Inet4Pktinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _, _, newDst, err := unix.Recvmsg(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], 0)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
end.isV6 = false
|
||||||
|
|
||||||
|
if newDst4, ok := newDst.(*unix.SockaddrInet4); ok {
|
||||||
|
*end.dst4() = *newDst4
|
||||||
|
}
|
||||||
|
|
||||||
|
// update source cache
|
||||||
|
|
||||||
|
if cmsg.cmsghdr.Level == unix.IPPROTO_IP &&
|
||||||
|
cmsg.cmsghdr.Type == unix.IP_PKTINFO &&
|
||||||
|
cmsg.cmsghdr.Len >= unix.SizeofInet4Pktinfo {
|
||||||
|
end.src4().Src = cmsg.pktinfo.Spec_dst
|
||||||
|
end.src4().Ifindex = cmsg.pktinfo.Ifindex
|
||||||
|
}
|
||||||
|
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func receive6(sock int, buff []byte, end *LinuxSocketEndpoint) (int, error) {
|
||||||
|
|
||||||
|
// construct message header
|
||||||
|
|
||||||
|
var cmsg struct {
|
||||||
|
cmsghdr unix.Cmsghdr
|
||||||
|
pktinfo unix.Inet6Pktinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
size, _, _, newDst, err := unix.Recvmsg(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], 0)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
end.isV6 = true
|
||||||
|
|
||||||
|
if newDst6, ok := newDst.(*unix.SockaddrInet6); ok {
|
||||||
|
*end.dst6() = *newDst6
|
||||||
|
}
|
||||||
|
|
||||||
|
// update source cache
|
||||||
|
|
||||||
|
if cmsg.cmsghdr.Level == unix.IPPROTO_IPV6 &&
|
||||||
|
cmsg.cmsghdr.Type == unix.IPV6_PKTINFO &&
|
||||||
|
cmsg.cmsghdr.Len >= unix.SizeofInet6Pktinfo {
|
||||||
|
end.src6().src = cmsg.pktinfo.Addr
|
||||||
|
end.dst6().ZoneId = cmsg.pktinfo.Ifindex
|
||||||
|
}
|
||||||
|
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
196
conn/bind_std.go
Normal file
196
conn/bind_std.go
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package conn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StdNetBind is meant to be a temporary solution on platforms for which
|
||||||
|
// the sticky socket / source caching behavior has not yet been implemented.
|
||||||
|
// It uses the Go's net package to implement networking.
|
||||||
|
// See LinuxSocketBind for a proper implementation on the Linux platform.
|
||||||
|
type StdNetBind struct {
|
||||||
|
mu sync.Mutex // protects following fields
|
||||||
|
ipv4 *net.UDPConn
|
||||||
|
ipv6 *net.UDPConn
|
||||||
|
blackhole4 bool
|
||||||
|
blackhole6 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStdNetBind() Bind { return &StdNetBind{} }
|
||||||
|
|
||||||
|
type StdNetEndpoint net.UDPAddr
|
||||||
|
|
||||||
|
var _ Bind = (*StdNetBind)(nil)
|
||||||
|
var _ Endpoint = (*StdNetEndpoint)(nil)
|
||||||
|
|
||||||
|
func (*StdNetBind) ParseEndpoint(s string) (Endpoint, error) {
|
||||||
|
addr, err := parseEndpoint(s)
|
||||||
|
return (*StdNetEndpoint)(addr), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StdNetEndpoint) ClearSrc() {}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) DstIP() net.IP {
|
||||||
|
return (*net.UDPAddr)(e).IP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) SrcIP() net.IP {
|
||||||
|
return nil // not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) DstToBytes() []byte {
|
||||||
|
addr := (*net.UDPAddr)(e)
|
||||||
|
out := addr.IP.To4()
|
||||||
|
if out == nil {
|
||||||
|
out = addr.IP
|
||||||
|
}
|
||||||
|
out = append(out, byte(addr.Port&0xff))
|
||||||
|
out = append(out, byte((addr.Port>>8)&0xff))
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) DstToString() string {
|
||||||
|
return (*net.UDPAddr)(e).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StdNetEndpoint) SrcToString() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenNet(network string, port int) (*net.UDPConn, int, error) {
|
||||||
|
conn, err := net.ListenUDP(network, &net.UDPAddr{Port: port})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve port.
|
||||||
|
laddr := conn.LocalAddr()
|
||||||
|
uaddr, err := net.ResolveUDPAddr(
|
||||||
|
laddr.Network(),
|
||||||
|
laddr.String(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return conn, uaddr.Port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *StdNetBind) Open(uport uint16) ([]ReceiveFunc, uint16, error) {
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var tries int
|
||||||
|
|
||||||
|
if bind.ipv4 != nil || bind.ipv6 != nil {
|
||||||
|
return nil, 0, ErrBindAlreadyOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to open ipv4 and ipv6 listeners on the same port.
|
||||||
|
// If uport is 0, we can retry on failure.
|
||||||
|
again:
|
||||||
|
port := int(uport)
|
||||||
|
var ipv4, ipv6 *net.UDPConn
|
||||||
|
|
||||||
|
ipv4, port, err = listenNet("udp4", port)
|
||||||
|
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen on the same port as we're using for ipv4.
|
||||||
|
ipv6, port, err = listenNet("udp6", port)
|
||||||
|
if uport == 0 && errors.Is(err, syscall.EADDRINUSE) && tries < 100 {
|
||||||
|
ipv4.Close()
|
||||||
|
tries++
|
||||||
|
goto again
|
||||||
|
}
|
||||||
|
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||||
|
ipv4.Close()
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
var fns []ReceiveFunc
|
||||||
|
if ipv4 != nil {
|
||||||
|
fns = append(fns, bind.makeReceiveIPv4(ipv4))
|
||||||
|
bind.ipv4 = ipv4
|
||||||
|
}
|
||||||
|
if ipv6 != nil {
|
||||||
|
fns = append(fns, bind.makeReceiveIPv6(ipv6))
|
||||||
|
bind.ipv6 = ipv6
|
||||||
|
}
|
||||||
|
if len(fns) == 0 {
|
||||||
|
return nil, 0, syscall.EAFNOSUPPORT
|
||||||
|
}
|
||||||
|
return fns, uint16(port), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *StdNetBind) Close() error {
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
|
||||||
|
var err1, err2 error
|
||||||
|
if bind.ipv4 != nil {
|
||||||
|
err1 = bind.ipv4.Close()
|
||||||
|
bind.ipv4 = nil
|
||||||
|
}
|
||||||
|
if bind.ipv6 != nil {
|
||||||
|
err2 = bind.ipv6.Close()
|
||||||
|
bind.ipv6 = nil
|
||||||
|
}
|
||||||
|
bind.blackhole4 = false
|
||||||
|
bind.blackhole6 = false
|
||||||
|
if err1 != nil {
|
||||||
|
return err1
|
||||||
|
}
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StdNetBind) makeReceiveIPv4(conn *net.UDPConn) ReceiveFunc {
|
||||||
|
return func(buff []byte) (int, Endpoint, error) {
|
||||||
|
n, endpoint, err := conn.ReadFromUDP(buff)
|
||||||
|
if endpoint != nil {
|
||||||
|
endpoint.IP = endpoint.IP.To4()
|
||||||
|
}
|
||||||
|
return n, (*StdNetEndpoint)(endpoint), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StdNetBind) makeReceiveIPv6(conn *net.UDPConn) ReceiveFunc {
|
||||||
|
return func(buff []byte) (int, Endpoint, error) {
|
||||||
|
n, endpoint, err := conn.ReadFromUDP(buff)
|
||||||
|
return n, (*StdNetEndpoint)(endpoint), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *StdNetBind) Send(buff []byte, endpoint Endpoint) error {
|
||||||
|
var err error
|
||||||
|
nend, ok := endpoint.(*StdNetEndpoint)
|
||||||
|
if !ok {
|
||||||
|
return ErrWrongEndpointType
|
||||||
|
}
|
||||||
|
|
||||||
|
bind.mu.Lock()
|
||||||
|
blackhole := bind.blackhole4
|
||||||
|
conn := bind.ipv4
|
||||||
|
if nend.IP.To4() == nil {
|
||||||
|
blackhole = bind.blackhole6
|
||||||
|
conn = bind.ipv6
|
||||||
|
}
|
||||||
|
bind.mu.Unlock()
|
||||||
|
|
||||||
|
if blackhole {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if conn == nil {
|
||||||
|
return syscall.EAFNOSUPPORT
|
||||||
|
}
|
||||||
|
_, err = conn.WriteToUDP(buff, (*net.UDPAddr)(nend))
|
||||||
|
return err
|
||||||
|
}
|
||||||
581
conn/bind_windows.go
Normal file
581
conn/bind_windows.go
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package conn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn/winrio"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
packetsPerRing = 1024
|
||||||
|
bytesPerPacket = 2048 - 32
|
||||||
|
receiveSpins = 15
|
||||||
|
)
|
||||||
|
|
||||||
|
type ringPacket struct {
|
||||||
|
addr WinRingEndpoint
|
||||||
|
data [bytesPerPacket]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type ringBuffer struct {
|
||||||
|
packets uintptr
|
||||||
|
head, tail uint32
|
||||||
|
id winrio.BufferId
|
||||||
|
iocp windows.Handle
|
||||||
|
isFull bool
|
||||||
|
cq winrio.Cq
|
||||||
|
mu sync.Mutex
|
||||||
|
overlapped windows.Overlapped
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *ringBuffer) Push() *ringPacket {
|
||||||
|
for rb.isFull {
|
||||||
|
panic("ring is full")
|
||||||
|
}
|
||||||
|
ret := (*ringPacket)(unsafe.Pointer(rb.packets + (uintptr(rb.tail%packetsPerRing) * unsafe.Sizeof(ringPacket{}))))
|
||||||
|
rb.tail += 1
|
||||||
|
if rb.tail%packetsPerRing == rb.head%packetsPerRing {
|
||||||
|
rb.isFull = true
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rb *ringBuffer) Return(count uint32) {
|
||||||
|
if rb.head%packetsPerRing == rb.tail%packetsPerRing && !rb.isFull {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rb.head += count
|
||||||
|
rb.isFull = false
|
||||||
|
}
|
||||||
|
|
||||||
|
type afWinRingBind struct {
|
||||||
|
sock windows.Handle
|
||||||
|
rx, tx ringBuffer
|
||||||
|
rq winrio.Rq
|
||||||
|
mu sync.Mutex
|
||||||
|
blackhole bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// WinRingBind uses Windows registered I/O for fast ring buffered networking.
|
||||||
|
type WinRingBind struct {
|
||||||
|
v4, v6 afWinRingBind
|
||||||
|
mu sync.RWMutex
|
||||||
|
isOpen uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultBind() Bind { return NewWinRingBind() }
|
||||||
|
|
||||||
|
func NewWinRingBind() Bind {
|
||||||
|
if !winrio.Initialize() {
|
||||||
|
return NewStdNetBind()
|
||||||
|
}
|
||||||
|
return new(WinRingBind)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WinRingEndpoint struct {
|
||||||
|
family uint16
|
||||||
|
data [30]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Bind = (*WinRingBind)(nil)
|
||||||
|
var _ Endpoint = (*WinRingEndpoint)(nil)
|
||||||
|
|
||||||
|
func (*WinRingBind) ParseEndpoint(s string) (Endpoint, error) {
|
||||||
|
host, port, err := net.SplitHostPort(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
host16, err := windows.UTF16PtrFromString(host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
port16, err := windows.UTF16PtrFromString(port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hints := windows.AddrinfoW{
|
||||||
|
Flags: windows.AI_NUMERICHOST,
|
||||||
|
Family: windows.AF_UNSPEC,
|
||||||
|
Socktype: windows.SOCK_DGRAM,
|
||||||
|
Protocol: windows.IPPROTO_UDP,
|
||||||
|
}
|
||||||
|
var addrinfo *windows.AddrinfoW
|
||||||
|
err = windows.GetAddrInfoW(host16, port16, &hints, &addrinfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer windows.FreeAddrInfoW(addrinfo)
|
||||||
|
if (addrinfo.Family != windows.AF_INET && addrinfo.Family != windows.AF_INET6) || addrinfo.Addrlen > unsafe.Sizeof(WinRingEndpoint{}) {
|
||||||
|
return nil, windows.ERROR_INVALID_ADDRESS
|
||||||
|
}
|
||||||
|
var dst [unsafe.Sizeof(WinRingEndpoint{})]byte
|
||||||
|
copy(dst[:], unsafe.Slice((*byte)(unsafe.Pointer(addrinfo.Addr)), addrinfo.Addrlen))
|
||||||
|
return (*WinRingEndpoint)(unsafe.Pointer(&dst[0])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*WinRingEndpoint) ClearSrc() {}
|
||||||
|
|
||||||
|
func (e *WinRingEndpoint) DstIP() net.IP {
|
||||||
|
switch e.family {
|
||||||
|
case windows.AF_INET:
|
||||||
|
return append([]byte{}, e.data[2:6]...)
|
||||||
|
case windows.AF_INET6:
|
||||||
|
return append([]byte{}, e.data[6:22]...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *WinRingEndpoint) SrcIP() net.IP {
|
||||||
|
return nil // not supported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *WinRingEndpoint) DstToBytes() []byte {
|
||||||
|
switch e.family {
|
||||||
|
case windows.AF_INET:
|
||||||
|
b := make([]byte, 0, 6)
|
||||||
|
b = append(b, e.data[2:6]...)
|
||||||
|
b = append(b, e.data[1], e.data[0])
|
||||||
|
return b
|
||||||
|
case windows.AF_INET6:
|
||||||
|
b := make([]byte, 0, 18)
|
||||||
|
b = append(b, e.data[6:22]...)
|
||||||
|
b = append(b, e.data[1], e.data[0])
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *WinRingEndpoint) DstToString() string {
|
||||||
|
switch e.family {
|
||||||
|
case windows.AF_INET:
|
||||||
|
addr := net.UDPAddr{IP: e.data[2:6], Port: int(binary.BigEndian.Uint16(e.data[0:2]))}
|
||||||
|
return addr.String()
|
||||||
|
case windows.AF_INET6:
|
||||||
|
var zone string
|
||||||
|
if scope := *(*uint32)(unsafe.Pointer(&e.data[22])); scope > 0 {
|
||||||
|
zone = strconv.FormatUint(uint64(scope), 10)
|
||||||
|
}
|
||||||
|
addr := net.UDPAddr{IP: e.data[6:22], Zone: zone, Port: int(binary.BigEndian.Uint16(e.data[0:2]))}
|
||||||
|
return addr.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *WinRingEndpoint) SrcToString() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ring *ringBuffer) CloseAndZero() {
|
||||||
|
if ring.cq != 0 {
|
||||||
|
winrio.CloseCompletionQueue(ring.cq)
|
||||||
|
ring.cq = 0
|
||||||
|
}
|
||||||
|
if ring.iocp != 0 {
|
||||||
|
windows.CloseHandle(ring.iocp)
|
||||||
|
ring.iocp = 0
|
||||||
|
}
|
||||||
|
if ring.id != 0 {
|
||||||
|
winrio.DeregisterBuffer(ring.id)
|
||||||
|
ring.id = 0
|
||||||
|
}
|
||||||
|
if ring.packets != 0 {
|
||||||
|
windows.VirtualFree(ring.packets, 0, windows.MEM_RELEASE)
|
||||||
|
ring.packets = 0
|
||||||
|
}
|
||||||
|
ring.head = 0
|
||||||
|
ring.tail = 0
|
||||||
|
ring.isFull = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *afWinRingBind) CloseAndZero() {
|
||||||
|
bind.rx.CloseAndZero()
|
||||||
|
bind.tx.CloseAndZero()
|
||||||
|
if bind.sock != 0 {
|
||||||
|
windows.CloseHandle(bind.sock)
|
||||||
|
bind.sock = 0
|
||||||
|
}
|
||||||
|
bind.blackhole = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *WinRingBind) closeAndZero() {
|
||||||
|
atomic.StoreUint32(&bind.isOpen, 0)
|
||||||
|
bind.v4.CloseAndZero()
|
||||||
|
bind.v6.CloseAndZero()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ring *ringBuffer) Open() error {
|
||||||
|
var err error
|
||||||
|
packetsLen := unsafe.Sizeof(ringPacket{}) * packetsPerRing
|
||||||
|
ring.packets, err = windows.VirtualAlloc(0, packetsLen, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_READWRITE)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ring.id, err = winrio.RegisterPointer(unsafe.Pointer(ring.packets), uint32(packetsLen))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ring.iocp, err = windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ring.cq, err = winrio.CreateIOCPCompletionQueue(packetsPerRing, ring.iocp, 0, &ring.overlapped)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *afWinRingBind) Open(family int32, sa windows.Sockaddr) (windows.Sockaddr, error) {
|
||||||
|
var err error
|
||||||
|
bind.sock, err = winrio.Socket(family, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = bind.rx.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = bind.tx.Open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bind.rq, err = winrio.CreateRequestQueue(bind.sock, packetsPerRing, 1, packetsPerRing, 1, bind.rx.cq, bind.tx.cq, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = windows.Bind(bind.sock, sa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sa, err = windows.Getsockname(bind.sock)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sa, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *WinRingBind) Open(port uint16) (recvFns []ReceiveFunc, selectedPort uint16, err error) {
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
bind.closeAndZero()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if atomic.LoadUint32(&bind.isOpen) != 0 {
|
||||||
|
return nil, 0, ErrBindAlreadyOpen
|
||||||
|
}
|
||||||
|
var sa windows.Sockaddr
|
||||||
|
sa, err = bind.v4.Open(windows.AF_INET, &windows.SockaddrInet4{Port: int(port)})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
sa, err = bind.v6.Open(windows.AF_INET6, &windows.SockaddrInet6{Port: sa.(*windows.SockaddrInet4).Port})
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
selectedPort = uint16(sa.(*windows.SockaddrInet6).Port)
|
||||||
|
for i := 0; i < packetsPerRing; i++ {
|
||||||
|
err = bind.v4.InsertReceiveRequest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
err = bind.v6.InsertReceiveRequest()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
atomic.StoreUint32(&bind.isOpen, 1)
|
||||||
|
return []ReceiveFunc{bind.receiveIPv4, bind.receiveIPv6}, selectedPort, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *WinRingBind) Close() error {
|
||||||
|
bind.mu.RLock()
|
||||||
|
if atomic.LoadUint32(&bind.isOpen) != 1 {
|
||||||
|
bind.mu.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
atomic.StoreUint32(&bind.isOpen, 2)
|
||||||
|
windows.PostQueuedCompletionStatus(bind.v4.rx.iocp, 0, 0, nil)
|
||||||
|
windows.PostQueuedCompletionStatus(bind.v4.tx.iocp, 0, 0, nil)
|
||||||
|
windows.PostQueuedCompletionStatus(bind.v6.rx.iocp, 0, 0, nil)
|
||||||
|
windows.PostQueuedCompletionStatus(bind.v6.tx.iocp, 0, 0, nil)
|
||||||
|
bind.mu.RUnlock()
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
bind.closeAndZero()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *WinRingBind) SetMark(mark uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *afWinRingBind) InsertReceiveRequest() error {
|
||||||
|
packet := bind.rx.Push()
|
||||||
|
dataBuffer := &winrio.Buffer{
|
||||||
|
Id: bind.rx.id,
|
||||||
|
Offset: uint32(uintptr(unsafe.Pointer(&packet.data[0])) - bind.rx.packets),
|
||||||
|
Length: uint32(len(packet.data)),
|
||||||
|
}
|
||||||
|
addressBuffer := &winrio.Buffer{
|
||||||
|
Id: bind.rx.id,
|
||||||
|
Offset: uint32(uintptr(unsafe.Pointer(&packet.addr)) - bind.rx.packets),
|
||||||
|
Length: uint32(unsafe.Sizeof(packet.addr)),
|
||||||
|
}
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
return winrio.ReceiveEx(bind.rq, dataBuffer, 1, nil, addressBuffer, nil, nil, 0, uintptr(unsafe.Pointer(packet)))
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname procyield runtime.procyield
|
||||||
|
func procyield(cycles uint32)
|
||||||
|
|
||||||
|
func (bind *afWinRingBind) Receive(buf []byte, isOpen *uint32) (int, Endpoint, error) {
|
||||||
|
if atomic.LoadUint32(isOpen) != 1 {
|
||||||
|
return 0, nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
bind.rx.mu.Lock()
|
||||||
|
defer bind.rx.mu.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var count uint32
|
||||||
|
var results [1]winrio.Result
|
||||||
|
retry:
|
||||||
|
count = 0
|
||||||
|
for tries := 0; count == 0 && tries < receiveSpins; tries++ {
|
||||||
|
if tries > 0 {
|
||||||
|
if atomic.LoadUint32(isOpen) != 1 {
|
||||||
|
return 0, nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
procyield(1)
|
||||||
|
}
|
||||||
|
count = winrio.DequeueCompletion(bind.rx.cq, results[:])
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
err = winrio.Notify(bind.rx.cq)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
var bytes uint32
|
||||||
|
var key uintptr
|
||||||
|
var overlapped *windows.Overlapped
|
||||||
|
err = windows.GetQueuedCompletionStatus(bind.rx.iocp, &bytes, &key, &overlapped, windows.INFINITE)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
if atomic.LoadUint32(isOpen) != 1 {
|
||||||
|
return 0, nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
count = winrio.DequeueCompletion(bind.rx.cq, results[:])
|
||||||
|
if count == 0 {
|
||||||
|
return 0, nil, io.ErrNoProgress
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bind.rx.Return(1)
|
||||||
|
err = bind.InsertReceiveRequest()
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
// We limit the MTU well below the 65k max for practicality, but this means a remote host can still send us
|
||||||
|
// huge packets. Just try again when this happens. The infinite loop this could cause is still limited to
|
||||||
|
// attacker bandwidth, just like the rest of the receive path.
|
||||||
|
if windows.Errno(results[0].Status) == windows.WSAEMSGSIZE {
|
||||||
|
if atomic.LoadUint32(isOpen) != 1 {
|
||||||
|
return 0, nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
goto retry
|
||||||
|
}
|
||||||
|
if results[0].Status != 0 {
|
||||||
|
return 0, nil, windows.Errno(results[0].Status)
|
||||||
|
}
|
||||||
|
packet := (*ringPacket)(unsafe.Pointer(uintptr(results[0].RequestContext)))
|
||||||
|
ep := packet.addr
|
||||||
|
n := copy(buf, packet.data[:results[0].BytesTransferred])
|
||||||
|
return n, &ep, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *WinRingBind) receiveIPv4(buf []byte) (int, Endpoint, error) {
|
||||||
|
bind.mu.RLock()
|
||||||
|
defer bind.mu.RUnlock()
|
||||||
|
return bind.v4.Receive(buf, &bind.isOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *WinRingBind) receiveIPv6(buf []byte) (int, Endpoint, error) {
|
||||||
|
bind.mu.RLock()
|
||||||
|
defer bind.mu.RUnlock()
|
||||||
|
return bind.v6.Receive(buf, &bind.isOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *afWinRingBind) Send(buf []byte, nend *WinRingEndpoint, isOpen *uint32) error {
|
||||||
|
if atomic.LoadUint32(isOpen) != 1 {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
if len(buf) > bytesPerPacket {
|
||||||
|
return io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
bind.tx.mu.Lock()
|
||||||
|
defer bind.tx.mu.Unlock()
|
||||||
|
var results [packetsPerRing]winrio.Result
|
||||||
|
count := winrio.DequeueCompletion(bind.tx.cq, results[:])
|
||||||
|
if count == 0 && bind.tx.isFull {
|
||||||
|
err := winrio.Notify(bind.tx.cq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var bytes uint32
|
||||||
|
var key uintptr
|
||||||
|
var overlapped *windows.Overlapped
|
||||||
|
err = windows.GetQueuedCompletionStatus(bind.tx.iocp, &bytes, &key, &overlapped, windows.INFINITE)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if atomic.LoadUint32(isOpen) != 1 {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
count = winrio.DequeueCompletion(bind.tx.cq, results[:])
|
||||||
|
if count == 0 {
|
||||||
|
return io.ErrNoProgress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
bind.tx.Return(count)
|
||||||
|
}
|
||||||
|
packet := bind.tx.Push()
|
||||||
|
packet.addr = *nend
|
||||||
|
copy(packet.data[:], buf)
|
||||||
|
dataBuffer := &winrio.Buffer{
|
||||||
|
Id: bind.tx.id,
|
||||||
|
Offset: uint32(uintptr(unsafe.Pointer(&packet.data[0])) - bind.tx.packets),
|
||||||
|
Length: uint32(len(buf)),
|
||||||
|
}
|
||||||
|
addressBuffer := &winrio.Buffer{
|
||||||
|
Id: bind.tx.id,
|
||||||
|
Offset: uint32(uintptr(unsafe.Pointer(&packet.addr)) - bind.tx.packets),
|
||||||
|
Length: uint32(unsafe.Sizeof(packet.addr)),
|
||||||
|
}
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
return winrio.SendEx(bind.rq, dataBuffer, 1, nil, addressBuffer, nil, nil, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *WinRingBind) Send(buf []byte, endpoint Endpoint) error {
|
||||||
|
nend, ok := endpoint.(*WinRingEndpoint)
|
||||||
|
if !ok {
|
||||||
|
return ErrWrongEndpointType
|
||||||
|
}
|
||||||
|
bind.mu.RLock()
|
||||||
|
defer bind.mu.RUnlock()
|
||||||
|
switch nend.family {
|
||||||
|
case windows.AF_INET:
|
||||||
|
if bind.v4.blackhole {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bind.v4.Send(buf, nend, &bind.isOpen)
|
||||||
|
case windows.AF_INET6:
|
||||||
|
if bind.v6.blackhole {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bind.v6.Send(buf, nend, &bind.isOpen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *StdNetBind) BindSocketToInterface4(interfaceIndex uint32, blackhole bool) error {
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
sysconn, err := bind.ipv4.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err2 := sysconn.Control(func(fd uintptr) {
|
||||||
|
err = bindSocketToInterface4(windows.Handle(fd), interfaceIndex)
|
||||||
|
})
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bind.blackhole4 = blackhole
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *StdNetBind) BindSocketToInterface6(interfaceIndex uint32, blackhole bool) error {
|
||||||
|
bind.mu.Lock()
|
||||||
|
defer bind.mu.Unlock()
|
||||||
|
sysconn, err := bind.ipv6.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err2 := sysconn.Control(func(fd uintptr) {
|
||||||
|
err = bindSocketToInterface6(windows.Handle(fd), interfaceIndex)
|
||||||
|
})
|
||||||
|
if err2 != nil {
|
||||||
|
return err2
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bind.blackhole6 = blackhole
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (bind *WinRingBind) BindSocketToInterface4(interfaceIndex uint32, blackhole bool) error {
|
||||||
|
bind.mu.RLock()
|
||||||
|
defer bind.mu.RUnlock()
|
||||||
|
if atomic.LoadUint32(&bind.isOpen) != 1 {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
err := bindSocketToInterface4(bind.v4.sock, interfaceIndex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bind.v4.blackhole = blackhole
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *WinRingBind) BindSocketToInterface6(interfaceIndex uint32, blackhole bool) error {
|
||||||
|
bind.mu.RLock()
|
||||||
|
defer bind.mu.RUnlock()
|
||||||
|
if atomic.LoadUint32(&bind.isOpen) != 1 {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
err := bindSocketToInterface6(bind.v6.sock, interfaceIndex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
bind.v6.blackhole = blackhole
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindSocketToInterface4(handle windows.Handle, interfaceIndex uint32) error {
|
||||||
|
const IP_UNICAST_IF = 31
|
||||||
|
/* MSDN says for IPv4 this needs to be in net byte order, so that it's like an IP address with leading zeros. */
|
||||||
|
var bytes [4]byte
|
||||||
|
binary.BigEndian.PutUint32(bytes[:], interfaceIndex)
|
||||||
|
interfaceIndex = *(*uint32)(unsafe.Pointer(&bytes[0]))
|
||||||
|
err := windows.SetsockoptInt(handle, windows.IPPROTO_IP, IP_UNICAST_IF, int(interfaceIndex))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bindSocketToInterface6(handle windows.Handle, interfaceIndex uint32) error {
|
||||||
|
const IPV6_UNICAST_IF = 31
|
||||||
|
return windows.SetsockoptInt(handle, windows.IPPROTO_IPV6, IPV6_UNICAST_IF, int(interfaceIndex))
|
||||||
|
}
|
||||||
131
conn/bindtest/bindtest.go
Normal file
131
conn/bindtest/bindtest.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bindtest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"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)
|
||||||
|
var _ 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() net.IP { return net.IPv4(127, 0, 0, 1) }
|
||||||
|
|
||||||
|
func (c ChannelEndpoint) SrcIP() net.IP { return nil }
|
||||||
|
|
||||||
|
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) SetMark(mark uint32) error { return nil }
|
||||||
|
|
||||||
|
func (c *ChannelBind) makeReceiveFunc(ch chan []byte) conn.ReceiveFunc {
|
||||||
|
return func(b []byte) (n int, ep conn.Endpoint, err error) {
|
||||||
|
select {
|
||||||
|
case <-c.closeSignal:
|
||||||
|
return 0, nil, net.ErrClosed
|
||||||
|
case rx := <-ch:
|
||||||
|
return copy(b, rx), c.target6, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChannelBind) Send(b []byte, ep conn.Endpoint) error {
|
||||||
|
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) {
|
||||||
|
_, port, err := net.SplitHostPort(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
i, err := strconv.ParseUint(port, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ChannelEndpoint(i), nil
|
||||||
|
}
|
||||||
34
conn/boundif_android.go
Normal file
34
conn/boundif_android.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package conn
|
||||||
|
|
||||||
|
func (bind *StdNetBind) PeekLookAtSocketFd4() (fd int, err error) {
|
||||||
|
sysconn, err := bind.ipv4.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
err = sysconn.Control(func(f uintptr) {
|
||||||
|
fd = int(f)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bind *StdNetBind) PeekLookAtSocketFd6() (fd int, err error) {
|
||||||
|
sysconn, err := bind.ipv6.SyscallConn()
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
err = sysconn.Control(func(f uintptr) {
|
||||||
|
fd = int(f)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
151
conn/conn.go
Normal file
151
conn/conn.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package conn implements WireGuard's network connections.
|
||||||
|
package conn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ReceiveFunc receives a single inbound packet from the network.
|
||||||
|
// It writes the data into b. n is the length of the packet.
|
||||||
|
// ep is the remote endpoint.
|
||||||
|
type ReceiveFunc func(b []byte) (n int, ep Endpoint, err error)
|
||||||
|
|
||||||
|
// A Bind listens on a port for both IPv6 and IPv4 UDP traffic.
|
||||||
|
//
|
||||||
|
// A Bind interface may also be a PeekLookAtSocketFd or BindSocketToInterface,
|
||||||
|
// depending on the platform-specific implementation.
|
||||||
|
type Bind interface {
|
||||||
|
// Open puts the Bind into a listening state on a given port and reports the actual
|
||||||
|
// port that it bound to. Passing zero results in a random selection.
|
||||||
|
// fns is the set of functions that will be called to receive packets.
|
||||||
|
Open(port uint16) (fns []ReceiveFunc, actualPort uint16, err error)
|
||||||
|
|
||||||
|
// Close closes the Bind listener.
|
||||||
|
// All fns returned by Open must return net.ErrClosed after a call to Close.
|
||||||
|
Close() error
|
||||||
|
|
||||||
|
// SetMark sets the mark for each packet sent through this Bind.
|
||||||
|
// This mark is passed to the kernel as the socket option SO_MARK.
|
||||||
|
SetMark(mark uint32) error
|
||||||
|
|
||||||
|
// Send writes a packet b to address ep.
|
||||||
|
Send(b []byte, ep Endpoint) error
|
||||||
|
|
||||||
|
// ParseEndpoint creates a new endpoint from a string.
|
||||||
|
ParseEndpoint(s string) (Endpoint, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindSocketToInterface is implemented by Bind objects that support being
|
||||||
|
// tied to a single network interface. Used by wireguard-windows.
|
||||||
|
type BindSocketToInterface interface {
|
||||||
|
BindSocketToInterface4(interfaceIndex uint32, blackhole bool) error
|
||||||
|
BindSocketToInterface6(interfaceIndex uint32, blackhole bool) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekLookAtSocketFd is implemented by Bind objects that support having their
|
||||||
|
// file descriptor peeked at. Used by wireguard-android.
|
||||||
|
type PeekLookAtSocketFd interface {
|
||||||
|
PeekLookAtSocketFd4() (fd int, err error)
|
||||||
|
PeekLookAtSocketFd6() (fd int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Endpoint maintains the source/destination caching for a peer.
|
||||||
|
//
|
||||||
|
// dst: the remote address of a peer ("endpoint" in uapi terminology)
|
||||||
|
// src: the local address from which datagrams originate going to the peer
|
||||||
|
type Endpoint interface {
|
||||||
|
ClearSrc() // clears the source address
|
||||||
|
SrcToString() string // returns the local source address (ip:port)
|
||||||
|
DstToString() string // returns the destination address (ip:port)
|
||||||
|
DstToBytes() []byte // used for mac2 cookie calculations
|
||||||
|
DstIP() net.IP
|
||||||
|
SrcIP() net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBindAlreadyOpen = errors.New("bind is already open")
|
||||||
|
ErrWrongEndpointType = errors.New("endpoint type does not correspond with bind type")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (fn ReceiveFunc) PrettyName() string {
|
||||||
|
name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
|
||||||
|
// 0. cheese/taco.beansIPv6.func12.func21218-fm
|
||||||
|
name = strings.TrimSuffix(name, "-fm")
|
||||||
|
// 1. cheese/taco.beansIPv6.func12.func21218
|
||||||
|
if idx := strings.LastIndexByte(name, '/'); idx != -1 {
|
||||||
|
name = name[idx+1:]
|
||||||
|
// 2. taco.beansIPv6.func12.func21218
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
var idx int
|
||||||
|
for idx = len(name) - 1; idx >= 0; idx-- {
|
||||||
|
if name[idx] < '0' || name[idx] > '9' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if idx == len(name)-1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const dotFunc = ".func"
|
||||||
|
if !strings.HasSuffix(name[:idx+1], dotFunc) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
name = name[:idx+1-len(dotFunc)]
|
||||||
|
// 3. taco.beansIPv6.func12
|
||||||
|
// 4. taco.beansIPv6
|
||||||
|
}
|
||||||
|
if idx := strings.LastIndexByte(name, '.'); idx != -1 {
|
||||||
|
name = name[idx+1:]
|
||||||
|
// 5. beansIPv6
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
return fmt.Sprintf("%p", fn)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(name, "IPv4") {
|
||||||
|
return "v4"
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(name, "IPv6") {
|
||||||
|
return "v6"
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEndpoint(s string) (*net.UDPAddr, error) {
|
||||||
|
// ensure that the host is an IP address
|
||||||
|
|
||||||
|
host, _, err := net.SplitHostPort(s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if i := strings.LastIndexByte(host, '%'); i > 0 && strings.IndexByte(host, ':') >= 0 {
|
||||||
|
// Remove the scope, if any. ResolveUDPAddr below will use it, but here we're just
|
||||||
|
// trying to make sure with a small sanity test that this is a real IP address and
|
||||||
|
// not something that's likely to incur DNS lookups.
|
||||||
|
host = host[:i]
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(host); ip == nil {
|
||||||
|
return nil, errors.New("Failed to parse IP address: " + host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse address and port
|
||||||
|
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ip4 := addr.IP.To4()
|
||||||
|
if ip4 != nil {
|
||||||
|
addr.IP = ip4
|
||||||
|
}
|
||||||
|
return addr, err
|
||||||
|
}
|
||||||
10
conn/default.go
Normal file
10
conn/default.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build !linux && !windows
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package conn
|
||||||
|
|
||||||
|
func NewDefaultBind() Bind { return NewStdNetBind() }
|
||||||
12
conn/mark_default.go
Normal file
12
conn/mark_default.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !linux && !openbsd && !freebsd
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package conn
|
||||||
|
|
||||||
|
func (bind *StdNetBind) SetMark(mark uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
// +build android openbsd freebsd
|
//go:build linux || openbsd || freebsd
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package conn
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var fwmarkIoctl int
|
var fwmarkIoctl int
|
||||||
@@ -25,7 +26,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bind *nativeBind) SetMark(mark uint32) error {
|
func (bind *StdNetBind) SetMark(mark uint32) error {
|
||||||
var operr error
|
var operr error
|
||||||
if fwmarkIoctl == 0 {
|
if fwmarkIoctl == 0 {
|
||||||
return nil
|
return nil
|
||||||
252
conn/winrio/rio_windows.go
Normal file
252
conn/winrio/rio_windows.go
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package winrio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MsgDontNotify = 1
|
||||||
|
MsgDefer = 2
|
||||||
|
MsgWaitAll = 4
|
||||||
|
MsgCommitOnly = 8
|
||||||
|
|
||||||
|
MaxCqSize = 0x8000000
|
||||||
|
|
||||||
|
invalidBufferId = 0xFFFFFFFF
|
||||||
|
invalidCq = 0
|
||||||
|
invalidRq = 0
|
||||||
|
corruptCq = 0xFFFFFFFF
|
||||||
|
)
|
||||||
|
|
||||||
|
var extensionFunctionTable struct {
|
||||||
|
cbSize uint32
|
||||||
|
rioReceive uintptr
|
||||||
|
rioReceiveEx uintptr
|
||||||
|
rioSend uintptr
|
||||||
|
rioSendEx uintptr
|
||||||
|
rioCloseCompletionQueue uintptr
|
||||||
|
rioCreateCompletionQueue uintptr
|
||||||
|
rioCreateRequestQueue uintptr
|
||||||
|
rioDequeueCompletion uintptr
|
||||||
|
rioDeregisterBuffer uintptr
|
||||||
|
rioNotify uintptr
|
||||||
|
rioRegisterBuffer uintptr
|
||||||
|
rioResizeCompletionQueue uintptr
|
||||||
|
rioResizeRequestQueue uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cq uintptr
|
||||||
|
|
||||||
|
type Rq uintptr
|
||||||
|
|
||||||
|
type BufferId uintptr
|
||||||
|
|
||||||
|
type Buffer struct {
|
||||||
|
Id BufferId
|
||||||
|
Offset uint32
|
||||||
|
Length uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Status int32
|
||||||
|
BytesTransferred uint32
|
||||||
|
SocketContext uint64
|
||||||
|
RequestContext uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type notificationCompletionType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
eventCompletion notificationCompletionType = 1
|
||||||
|
iocpCompletion notificationCompletionType = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type eventNotificationCompletion struct {
|
||||||
|
completionType notificationCompletionType
|
||||||
|
event windows.Handle
|
||||||
|
notifyReset uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type iocpNotificationCompletion struct {
|
||||||
|
completionType notificationCompletionType
|
||||||
|
iocp windows.Handle
|
||||||
|
key uintptr
|
||||||
|
overlapped *windows.Overlapped
|
||||||
|
}
|
||||||
|
|
||||||
|
var initialized sync.Once
|
||||||
|
var available bool
|
||||||
|
|
||||||
|
func Initialize() bool {
|
||||||
|
initialized.Do(func() {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
socket windows.Handle
|
||||||
|
cq Cq
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if maj, _, _ := windows.RtlGetNtVersionNumbers(); maj <= 7 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Registered I/O is unavailable: %v", err)
|
||||||
|
}()
|
||||||
|
socket, err = Socket(windows.AF_INET, windows.SOCK_DGRAM, windows.IPPROTO_UDP)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer windows.CloseHandle(socket)
|
||||||
|
var WSAID_MULTIPLE_RIO = &windows.GUID{0x8509e081, 0x96dd, 0x4005, [8]byte{0xb1, 0x65, 0x9e, 0x2e, 0xe8, 0xc7, 0x9e, 0x3f}}
|
||||||
|
const SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER = 0xc8000024
|
||||||
|
ob := uint32(0)
|
||||||
|
err = windows.WSAIoctl(socket, SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER,
|
||||||
|
(*byte)(unsafe.Pointer(WSAID_MULTIPLE_RIO)), uint32(unsafe.Sizeof(*WSAID_MULTIPLE_RIO)),
|
||||||
|
(*byte)(unsafe.Pointer(&extensionFunctionTable)), uint32(unsafe.Sizeof(extensionFunctionTable)),
|
||||||
|
&ob, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// While we should be able to stop here, after getting the function pointers, some anti-virus actually causes
|
||||||
|
// failures in RIOCreateRequestQueue, so keep going to be certain this is supported.
|
||||||
|
var iocp windows.Handle
|
||||||
|
iocp, err = windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer windows.CloseHandle(iocp)
|
||||||
|
var overlapped windows.Overlapped
|
||||||
|
cq, err = CreateIOCPCompletionQueue(2, iocp, 0, &overlapped)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer CloseCompletionQueue(cq)
|
||||||
|
_, err = CreateRequestQueue(socket, 1, 1, 1, 1, cq, cq, 0)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
available = true
|
||||||
|
})
|
||||||
|
return available
|
||||||
|
}
|
||||||
|
|
||||||
|
func Socket(af, typ, proto int32) (windows.Handle, error) {
|
||||||
|
return windows.WSASocket(af, typ, proto, nil, 0, windows.WSA_FLAG_REGISTERED_IO)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloseCompletionQueue(cq Cq) {
|
||||||
|
_, _, _ = syscall.Syscall(extensionFunctionTable.rioCloseCompletionQueue, 1, uintptr(cq), 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateEventCompletionQueue(queueSize uint32, event windows.Handle, notifyReset bool) (Cq, error) {
|
||||||
|
notificationCompletion := &eventNotificationCompletion{
|
||||||
|
completionType: eventCompletion,
|
||||||
|
event: event,
|
||||||
|
}
|
||||||
|
if notifyReset {
|
||||||
|
notificationCompletion.notifyReset = 1
|
||||||
|
}
|
||||||
|
ret, _, err := syscall.Syscall(extensionFunctionTable.rioCreateCompletionQueue, 2, uintptr(queueSize), uintptr(unsafe.Pointer(notificationCompletion)), 0)
|
||||||
|
if ret == invalidCq {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return Cq(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateIOCPCompletionQueue(queueSize uint32, iocp windows.Handle, key uintptr, overlapped *windows.Overlapped) (Cq, error) {
|
||||||
|
notificationCompletion := &iocpNotificationCompletion{
|
||||||
|
completionType: iocpCompletion,
|
||||||
|
iocp: iocp,
|
||||||
|
key: key,
|
||||||
|
overlapped: overlapped,
|
||||||
|
}
|
||||||
|
ret, _, err := syscall.Syscall(extensionFunctionTable.rioCreateCompletionQueue, 2, uintptr(queueSize), uintptr(unsafe.Pointer(notificationCompletion)), 0)
|
||||||
|
if ret == invalidCq {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return Cq(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePolledCompletionQueue(queueSize uint32) (Cq, error) {
|
||||||
|
ret, _, err := syscall.Syscall(extensionFunctionTable.rioCreateCompletionQueue, 2, uintptr(queueSize), 0, 0)
|
||||||
|
if ret == invalidCq {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return Cq(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateRequestQueue(socket windows.Handle, maxOutstandingReceive, maxReceiveDataBuffers, maxOutstandingSend, maxSendDataBuffers uint32, receiveCq, sendCq Cq, socketContext uintptr) (Rq, error) {
|
||||||
|
ret, _, err := syscall.Syscall9(extensionFunctionTable.rioCreateRequestQueue, 8, uintptr(socket), uintptr(maxOutstandingReceive), uintptr(maxReceiveDataBuffers), uintptr(maxOutstandingSend), uintptr(maxSendDataBuffers), uintptr(receiveCq), uintptr(sendCq), socketContext, 0)
|
||||||
|
if ret == invalidRq {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return Rq(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DequeueCompletion(cq Cq, results []Result) uint32 {
|
||||||
|
var array uintptr
|
||||||
|
if len(results) > 0 {
|
||||||
|
array = uintptr(unsafe.Pointer(&results[0]))
|
||||||
|
}
|
||||||
|
ret, _, _ := syscall.Syscall(extensionFunctionTable.rioDequeueCompletion, 3, uintptr(cq), array, uintptr(len(results)))
|
||||||
|
if ret == corruptCq {
|
||||||
|
panic("cq is corrupt")
|
||||||
|
}
|
||||||
|
return uint32(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeregisterBuffer(id BufferId) {
|
||||||
|
_, _, _ = syscall.Syscall(extensionFunctionTable.rioDeregisterBuffer, 1, uintptr(id), 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterBuffer(buffer []byte) (BufferId, error) {
|
||||||
|
var buf unsafe.Pointer
|
||||||
|
if len(buffer) > 0 {
|
||||||
|
buf = unsafe.Pointer(&buffer[0])
|
||||||
|
}
|
||||||
|
return RegisterPointer(buf, uint32(len(buffer)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterPointer(ptr unsafe.Pointer, size uint32) (BufferId, error) {
|
||||||
|
ret, _, err := syscall.Syscall(extensionFunctionTable.rioRegisterBuffer, 2, uintptr(ptr), uintptr(size), 0)
|
||||||
|
if ret == invalidBufferId {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return BufferId(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendEx(rq Rq, buf *Buffer, dataBufferCount uint32, localAddress, remoteAddress, controlContext, flags *Buffer, sflags uint32, requestContext uintptr) error {
|
||||||
|
ret, _, err := syscall.Syscall9(extensionFunctionTable.rioSendEx, 9, uintptr(rq), uintptr(unsafe.Pointer(buf)), uintptr(dataBufferCount), uintptr(unsafe.Pointer(localAddress)), uintptr(unsafe.Pointer(remoteAddress)), uintptr(unsafe.Pointer(controlContext)), uintptr(unsafe.Pointer(flags)), uintptr(sflags), requestContext)
|
||||||
|
if ret == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReceiveEx(rq Rq, buf *Buffer, dataBufferCount uint32, localAddress, remoteAddress, controlContext, flags *Buffer, sflags uint32, requestContext uintptr) error {
|
||||||
|
ret, _, err := syscall.Syscall9(extensionFunctionTable.rioReceiveEx, 9, uintptr(rq), uintptr(unsafe.Pointer(buf)), uintptr(dataBufferCount), uintptr(unsafe.Pointer(localAddress)), uintptr(unsafe.Pointer(remoteAddress)), uintptr(unsafe.Pointer(controlContext)), uintptr(unsafe.Pointer(flags)), uintptr(sflags), requestContext)
|
||||||
|
if ret == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Notify(cq Cq) error {
|
||||||
|
ret, _, _ := syscall.Syscall(extensionFunctionTable.rioNotify, 1, uintptr(cq), 0, 0)
|
||||||
|
if ret != 0 {
|
||||||
|
return windows.Errno(ret)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
65
device/alignment_test.go
Normal file
65
device/alignment_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkAlignment(t *testing.T, name string, offset uintptr) {
|
||||||
|
t.Helper()
|
||||||
|
if offset%8 != 0 {
|
||||||
|
t.Errorf("offset of %q within struct is %d bytes, which does not align to 64-bit word boundaries (missing %d bytes). Atomic operations will crash on 32-bit systems.", name, offset, 8-(offset%8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestPeerAlignment checks that atomically-accessed fields are
|
||||||
|
// aligned to 64-bit boundaries, as required by the atomic package.
|
||||||
|
//
|
||||||
|
// Unfortunately, violating this rule on 32-bit platforms results in a
|
||||||
|
// hard segfault at runtime.
|
||||||
|
func TestPeerAlignment(t *testing.T) {
|
||||||
|
var p Peer
|
||||||
|
|
||||||
|
typ := reflect.TypeOf(&p).Elem()
|
||||||
|
t.Logf("Peer type size: %d, with fields:", typ.Size())
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := typ.Field(i)
|
||||||
|
t.Logf("\t%30s\toffset=%3v\t(type size=%3d, align=%d)",
|
||||||
|
field.Name,
|
||||||
|
field.Offset,
|
||||||
|
field.Type.Size(),
|
||||||
|
field.Type.Align(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAlignment(t, "Peer.stats", unsafe.Offsetof(p.stats))
|
||||||
|
checkAlignment(t, "Peer.isRunning", unsafe.Offsetof(p.isRunning))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDeviceAlignment checks that atomically-accessed fields are
|
||||||
|
// aligned to 64-bit boundaries, as required by the atomic package.
|
||||||
|
//
|
||||||
|
// Unfortunately, violating this rule on 32-bit platforms results in a
|
||||||
|
// hard segfault at runtime.
|
||||||
|
func TestDeviceAlignment(t *testing.T) {
|
||||||
|
var d Device
|
||||||
|
|
||||||
|
typ := reflect.TypeOf(&d).Elem()
|
||||||
|
t.Logf("Device type size: %d, with fields:", typ.Size())
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := typ.Field(i)
|
||||||
|
t.Logf("\t%30s\toffset=%3v\t(type size=%3d, align=%d)",
|
||||||
|
field.Name,
|
||||||
|
field.Offset,
|
||||||
|
field.Type.Size(),
|
||||||
|
field.Type.Align(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
checkAlignment(t, "Device.rate.underLoadUntil", unsafe.Offsetof(d.rate)+unsafe.Offsetof(d.rate.underLoadUntil))
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/list"
|
||||||
"errors"
|
"errors"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"net"
|
"net"
|
||||||
@@ -13,16 +14,20 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type parentIndirection struct {
|
||||||
|
parentBit **trieEntry
|
||||||
|
parentBitType uint8
|
||||||
|
}
|
||||||
|
|
||||||
type trieEntry struct {
|
type trieEntry struct {
|
||||||
cidr uint
|
peer *Peer
|
||||||
child [2]*trieEntry
|
child [2]*trieEntry
|
||||||
bits net.IP
|
parent parentIndirection
|
||||||
peer *Peer
|
cidr uint8
|
||||||
|
bitAtByte uint8
|
||||||
// index of "branching" bit
|
bitAtShift uint8
|
||||||
|
bits net.IP
|
||||||
bit_at_byte uint
|
perPeerElem *list.Element
|
||||||
bit_at_shift uint
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isLittleEndian() bool {
|
func isLittleEndian() bool {
|
||||||
@@ -46,128 +51,170 @@ func swapU64(i uint64) uint64 {
|
|||||||
return bits.ReverseBytes64(i)
|
return bits.ReverseBytes64(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func commonBits(ip1 net.IP, ip2 net.IP) uint {
|
func commonBits(ip1 net.IP, ip2 net.IP) uint8 {
|
||||||
size := len(ip1)
|
size := len(ip1)
|
||||||
if size == net.IPv4len {
|
if size == net.IPv4len {
|
||||||
a := (*uint32)(unsafe.Pointer(&ip1[0]))
|
a := (*uint32)(unsafe.Pointer(&ip1[0]))
|
||||||
b := (*uint32)(unsafe.Pointer(&ip2[0]))
|
b := (*uint32)(unsafe.Pointer(&ip2[0]))
|
||||||
x := *a ^ *b
|
x := *a ^ *b
|
||||||
return uint(bits.LeadingZeros32(swapU32(x)))
|
return uint8(bits.LeadingZeros32(swapU32(x)))
|
||||||
} else if size == net.IPv6len {
|
} else if size == net.IPv6len {
|
||||||
a := (*uint64)(unsafe.Pointer(&ip1[0]))
|
a := (*uint64)(unsafe.Pointer(&ip1[0]))
|
||||||
b := (*uint64)(unsafe.Pointer(&ip2[0]))
|
b := (*uint64)(unsafe.Pointer(&ip2[0]))
|
||||||
x := *a ^ *b
|
x := *a ^ *b
|
||||||
if x != 0 {
|
if x != 0 {
|
||||||
return uint(bits.LeadingZeros64(swapU64(x)))
|
return uint8(bits.LeadingZeros64(swapU64(x)))
|
||||||
}
|
}
|
||||||
a = (*uint64)(unsafe.Pointer(&ip1[8]))
|
a = (*uint64)(unsafe.Pointer(&ip1[8]))
|
||||||
b = (*uint64)(unsafe.Pointer(&ip2[8]))
|
b = (*uint64)(unsafe.Pointer(&ip2[8]))
|
||||||
x = *a ^ *b
|
x = *a ^ *b
|
||||||
return 64 + uint(bits.LeadingZeros64(swapU64(x)))
|
return 64 + uint8(bits.LeadingZeros64(swapU64(x)))
|
||||||
} else {
|
} else {
|
||||||
panic("Wrong size bit string")
|
panic("Wrong size bit string")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *trieEntry) removeByPeer(p *Peer) *trieEntry {
|
func (node *trieEntry) addToPeerEntries() {
|
||||||
if node == nil {
|
node.perPeerElem = node.peer.trieEntries.PushBack(node)
|
||||||
return node
|
}
|
||||||
|
|
||||||
|
func (node *trieEntry) removeFromPeerEntries() {
|
||||||
|
if node.perPeerElem != nil {
|
||||||
|
node.peer.trieEntries.Remove(node.perPeerElem)
|
||||||
|
node.perPeerElem = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// walk recursively
|
|
||||||
|
|
||||||
node.child[0] = node.child[0].removeByPeer(p)
|
|
||||||
node.child[1] = node.child[1].removeByPeer(p)
|
|
||||||
|
|
||||||
if node.peer != p {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove peer & merge
|
|
||||||
|
|
||||||
node.peer = nil
|
|
||||||
if node.child[0] == nil {
|
|
||||||
return node.child[1]
|
|
||||||
}
|
|
||||||
return node.child[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *trieEntry) choose(ip net.IP) byte {
|
func (node *trieEntry) choose(ip net.IP) byte {
|
||||||
return (ip[node.bit_at_byte] >> node.bit_at_shift) & 1
|
return (ip[node.bitAtByte] >> node.bitAtShift) & 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *trieEntry) insert(ip net.IP, cidr uint, peer *Peer) *trieEntry {
|
func (node *trieEntry) maskSelf() {
|
||||||
|
mask := net.CIDRMask(int(node.cidr), len(node.bits)*8)
|
||||||
// at leaf
|
for i := 0; i < len(mask); i++ {
|
||||||
|
node.bits[i] &= mask[i]
|
||||||
if node == nil {
|
|
||||||
return &trieEntry{
|
|
||||||
bits: ip,
|
|
||||||
peer: peer,
|
|
||||||
cidr: cidr,
|
|
||||||
bit_at_byte: cidr / 8,
|
|
||||||
bit_at_shift: 7 - (cidr % 8),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// traverse deeper
|
func (node *trieEntry) zeroizePointers() {
|
||||||
|
// Make the garbage collector's life slightly easier
|
||||||
|
node.peer = nil
|
||||||
|
node.child[0] = nil
|
||||||
|
node.child[1] = nil
|
||||||
|
node.parent.parentBit = nil
|
||||||
|
}
|
||||||
|
|
||||||
common := commonBits(node.bits, ip)
|
func (node *trieEntry) nodePlacement(ip net.IP, cidr uint8) (parent *trieEntry, exact bool) {
|
||||||
if node.cidr <= cidr && common >= node.cidr {
|
for node != nil && node.cidr <= cidr && commonBits(node.bits, ip) >= node.cidr {
|
||||||
if node.cidr == cidr {
|
parent = node
|
||||||
node.peer = peer
|
if parent.cidr == cidr {
|
||||||
return node
|
exact = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
bit := node.choose(ip)
|
bit := node.choose(ip)
|
||||||
node.child[bit] = node.child[bit].insert(ip, cidr, peer)
|
node = node.child[bit]
|
||||||
return node
|
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// split node
|
func (trie parentIndirection) insert(ip net.IP, cidr uint8, peer *Peer) {
|
||||||
|
if *trie.parentBit == nil {
|
||||||
|
node := &trieEntry{
|
||||||
|
peer: peer,
|
||||||
|
parent: trie,
|
||||||
|
bits: ip,
|
||||||
|
cidr: cidr,
|
||||||
|
bitAtByte: cidr / 8,
|
||||||
|
bitAtShift: 7 - (cidr % 8),
|
||||||
|
}
|
||||||
|
node.maskSelf()
|
||||||
|
node.addToPeerEntries()
|
||||||
|
*trie.parentBit = node
|
||||||
|
return
|
||||||
|
}
|
||||||
|
node, exact := (*trie.parentBit).nodePlacement(ip, cidr)
|
||||||
|
if exact {
|
||||||
|
node.removeFromPeerEntries()
|
||||||
|
node.peer = peer
|
||||||
|
node.addToPeerEntries()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
newNode := &trieEntry{
|
newNode := &trieEntry{
|
||||||
bits: ip,
|
peer: peer,
|
||||||
peer: peer,
|
bits: ip,
|
||||||
cidr: cidr,
|
cidr: cidr,
|
||||||
bit_at_byte: cidr / 8,
|
bitAtByte: cidr / 8,
|
||||||
bit_at_shift: 7 - (cidr % 8),
|
bitAtShift: 7 - (cidr % 8),
|
||||||
}
|
}
|
||||||
|
newNode.maskSelf()
|
||||||
|
newNode.addToPeerEntries()
|
||||||
|
|
||||||
cidr = min(cidr, common)
|
var down *trieEntry
|
||||||
|
if node == nil {
|
||||||
// check for shorter prefix
|
down = *trie.parentBit
|
||||||
|
} else {
|
||||||
|
bit := node.choose(ip)
|
||||||
|
down = node.child[bit]
|
||||||
|
if down == nil {
|
||||||
|
newNode.parent = parentIndirection{&node.child[bit], bit}
|
||||||
|
node.child[bit] = newNode
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common := commonBits(down.bits, ip)
|
||||||
|
if common < cidr {
|
||||||
|
cidr = common
|
||||||
|
}
|
||||||
|
parent := node
|
||||||
|
|
||||||
if newNode.cidr == cidr {
|
if newNode.cidr == cidr {
|
||||||
bit := newNode.choose(node.bits)
|
bit := newNode.choose(down.bits)
|
||||||
newNode.child[bit] = node
|
down.parent = parentIndirection{&newNode.child[bit], bit}
|
||||||
return newNode
|
newNode.child[bit] = down
|
||||||
|
if parent == nil {
|
||||||
|
newNode.parent = trie
|
||||||
|
*trie.parentBit = newNode
|
||||||
|
} else {
|
||||||
|
bit := parent.choose(newNode.bits)
|
||||||
|
newNode.parent = parentIndirection{&parent.child[bit], bit}
|
||||||
|
parent.child[bit] = newNode
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new parent for node & newNode
|
node = &trieEntry{
|
||||||
|
bits: append([]byte{}, newNode.bits...),
|
||||||
parent := &trieEntry{
|
cidr: cidr,
|
||||||
bits: ip,
|
bitAtByte: cidr / 8,
|
||||||
peer: nil,
|
bitAtShift: 7 - (cidr % 8),
|
||||||
cidr: cidr,
|
|
||||||
bit_at_byte: cidr / 8,
|
|
||||||
bit_at_shift: 7 - (cidr % 8),
|
|
||||||
}
|
}
|
||||||
|
node.maskSelf()
|
||||||
|
|
||||||
bit := parent.choose(ip)
|
bit := node.choose(down.bits)
|
||||||
parent.child[bit] = newNode
|
down.parent = parentIndirection{&node.child[bit], bit}
|
||||||
parent.child[bit^1] = node
|
node.child[bit] = down
|
||||||
|
bit = node.choose(newNode.bits)
|
||||||
return parent
|
newNode.parent = parentIndirection{&node.child[bit], bit}
|
||||||
|
node.child[bit] = newNode
|
||||||
|
if parent == nil {
|
||||||
|
node.parent = trie
|
||||||
|
*trie.parentBit = node
|
||||||
|
} else {
|
||||||
|
bit := parent.choose(node.bits)
|
||||||
|
node.parent = parentIndirection{&parent.child[bit], bit}
|
||||||
|
parent.child[bit] = node
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *trieEntry) lookup(ip net.IP) *Peer {
|
func (node *trieEntry) lookup(ip net.IP) *Peer {
|
||||||
var found *Peer
|
var found *Peer
|
||||||
size := uint(len(ip))
|
size := uint8(len(ip))
|
||||||
for node != nil && commonBits(node.bits, ip) >= node.cidr {
|
for node != nil && commonBits(node.bits, ip) >= node.cidr {
|
||||||
if node.peer != nil {
|
if node.peer != nil {
|
||||||
found = node.peer
|
found = node.peer
|
||||||
}
|
}
|
||||||
if node.bit_at_byte == size {
|
if node.bitAtByte == size {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
bit := node.choose(ip)
|
bit := node.choose(ip)
|
||||||
@@ -176,76 +223,89 @@ func (node *trieEntry) lookup(ip net.IP) *Peer {
|
|||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|
||||||
func (node *trieEntry) entriesForPeer(p *Peer, results []net.IPNet) []net.IPNet {
|
|
||||||
if node == nil {
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
if node.peer == p {
|
|
||||||
mask := net.CIDRMask(int(node.cidr), len(node.bits)*8)
|
|
||||||
results = append(results, net.IPNet{
|
|
||||||
Mask: mask,
|
|
||||||
IP: node.bits.Mask(mask),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
results = node.child[0].entriesForPeer(p, results)
|
|
||||||
results = node.child[1].entriesForPeer(p, results)
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
type AllowedIPs struct {
|
type AllowedIPs struct {
|
||||||
IPv4 *trieEntry
|
IPv4 *trieEntry
|
||||||
IPv6 *trieEntry
|
IPv6 *trieEntry
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *AllowedIPs) EntriesForPeer(peer *Peer) []net.IPNet {
|
func (table *AllowedIPs) EntriesForPeer(peer *Peer, cb func(ip net.IP, cidr uint8) bool) {
|
||||||
table.mutex.RLock()
|
table.mutex.RLock()
|
||||||
defer table.mutex.RUnlock()
|
defer table.mutex.RUnlock()
|
||||||
|
|
||||||
allowed := make([]net.IPNet, 0, 10)
|
for elem := peer.trieEntries.Front(); elem != nil; elem = elem.Next() {
|
||||||
allowed = table.IPv4.entriesForPeer(peer, allowed)
|
node := elem.Value.(*trieEntry)
|
||||||
allowed = table.IPv6.entriesForPeer(peer, allowed)
|
if !cb(node.bits, node.cidr) {
|
||||||
return allowed
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
func (table *AllowedIPs) Reset() {
|
|
||||||
table.mutex.Lock()
|
|
||||||
defer table.mutex.Unlock()
|
|
||||||
|
|
||||||
table.IPv4 = nil
|
|
||||||
table.IPv6 = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
||||||
table.mutex.Lock()
|
table.mutex.Lock()
|
||||||
defer table.mutex.Unlock()
|
defer table.mutex.Unlock()
|
||||||
|
|
||||||
table.IPv4 = table.IPv4.removeByPeer(peer)
|
var next *list.Element
|
||||||
table.IPv6 = table.IPv6.removeByPeer(peer)
|
for elem := peer.trieEntries.Front(); elem != nil; elem = next {
|
||||||
|
next = elem.Next()
|
||||||
|
node := elem.Value.(*trieEntry)
|
||||||
|
|
||||||
|
node.removeFromPeerEntries()
|
||||||
|
node.peer = nil
|
||||||
|
if node.child[0] != nil && node.child[1] != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bit := 0
|
||||||
|
if node.child[0] == nil {
|
||||||
|
bit = 1
|
||||||
|
}
|
||||||
|
child := node.child[bit]
|
||||||
|
if child != nil {
|
||||||
|
child.parent = node.parent
|
||||||
|
}
|
||||||
|
*node.parent.parentBit = child
|
||||||
|
if node.child[0] != nil || node.child[1] != nil || node.parent.parentBitType > 1 {
|
||||||
|
node.zeroizePointers()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
node.zeroizePointers()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
child = parent.child[node.parent.parentBitType^1]
|
||||||
|
if child != nil {
|
||||||
|
child.parent = parent.parent
|
||||||
|
}
|
||||||
|
*parent.parent.parentBit = child
|
||||||
|
node.zeroizePointers()
|
||||||
|
parent.zeroizePointers()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *AllowedIPs) Insert(ip net.IP, cidr uint, peer *Peer) {
|
func (table *AllowedIPs) Insert(ip net.IP, cidr uint8, peer *Peer) {
|
||||||
table.mutex.Lock()
|
table.mutex.Lock()
|
||||||
defer table.mutex.Unlock()
|
defer table.mutex.Unlock()
|
||||||
|
|
||||||
switch len(ip) {
|
switch len(ip) {
|
||||||
case net.IPv6len:
|
case net.IPv6len:
|
||||||
table.IPv6 = table.IPv6.insert(ip, cidr, peer)
|
parentIndirection{&table.IPv6, 2}.insert(ip, cidr, peer)
|
||||||
case net.IPv4len:
|
case net.IPv4len:
|
||||||
table.IPv4 = table.IPv4.insert(ip, cidr, peer)
|
parentIndirection{&table.IPv4, 2}.insert(ip, cidr, peer)
|
||||||
default:
|
default:
|
||||||
panic(errors.New("inserting unknown address type"))
|
panic(errors.New("inserting unknown address type"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *AllowedIPs) LookupIPv4(address []byte) *Peer {
|
func (table *AllowedIPs) Lookup(address []byte) *Peer {
|
||||||
table.mutex.RLock()
|
table.mutex.RLock()
|
||||||
defer table.mutex.RUnlock()
|
defer table.mutex.RUnlock()
|
||||||
return table.IPv4.lookup(address)
|
switch len(address) {
|
||||||
}
|
case net.IPv6len:
|
||||||
|
return table.IPv6.lookup(address)
|
||||||
func (table *AllowedIPs) LookupIPv6(address []byte) *Peer {
|
case net.IPv4len:
|
||||||
table.mutex.RLock()
|
return table.IPv4.lookup(address)
|
||||||
defer table.mutex.RUnlock()
|
default:
|
||||||
return table.IPv6.lookup(address)
|
panic(errors.New("looking up unknown address type"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NumberOfPeers = 100
|
NumberOfPeers = 100
|
||||||
NumberOfAddresses = 250
|
NumberOfPeerRemovals = 4
|
||||||
NumberOfTests = 10000
|
NumberOfAddresses = 250
|
||||||
|
NumberOfTests = 10000
|
||||||
)
|
)
|
||||||
|
|
||||||
type SlowNode struct {
|
type SlowNode struct {
|
||||||
peer *Peer
|
peer *Peer
|
||||||
cidr uint
|
cidr uint8
|
||||||
bits []byte
|
bits []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +39,7 @@ func (r SlowRouter) Swap(i, j int) {
|
|||||||
r[i], r[j] = r[j], r[i]
|
r[i], r[j] = r[j], r[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r SlowRouter) Insert(addr []byte, cidr uint, peer *Peer) SlowRouter {
|
func (r SlowRouter) Insert(addr []byte, cidr uint8, peer *Peer) SlowRouter {
|
||||||
for _, t := range r {
|
for _, t := range r {
|
||||||
if t.cidr == cidr && commonBits(t.bits, addr) >= cidr {
|
if t.cidr == cidr && commonBits(t.bits, addr) >= cidr {
|
||||||
t.peer = peer
|
t.peer = peer
|
||||||
@@ -64,68 +66,75 @@ func (r SlowRouter) Lookup(addr []byte) *Peer {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrieRandomIPv4(t *testing.T) {
|
func (r SlowRouter) RemoveByPeer(peer *Peer) SlowRouter {
|
||||||
var trie *trieEntry
|
n := 0
|
||||||
var slow SlowRouter
|
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 peers []*Peer
|
||||||
|
var allowedIPs AllowedIPs
|
||||||
|
|
||||||
rand.Seed(1)
|
rand.Seed(1)
|
||||||
|
|
||||||
const AddressLength = 4
|
for n := 0; n < NumberOfPeers; n++ {
|
||||||
|
|
||||||
for n := 0; n < NumberOfPeers; n += 1 {
|
|
||||||
peers = append(peers, &Peer{})
|
peers = append(peers, &Peer{})
|
||||||
}
|
}
|
||||||
|
|
||||||
for n := 0; n < NumberOfAddresses; n += 1 {
|
for n := 0; n < NumberOfAddresses; n++ {
|
||||||
var addr [AddressLength]byte
|
var addr4 [4]byte
|
||||||
rand.Read(addr[:])
|
rand.Read(addr4[:])
|
||||||
cidr := uint(rand.Uint32() % (AddressLength * 8))
|
cidr := uint8(rand.Intn(32) + 1)
|
||||||
index := rand.Int() % NumberOfPeers
|
index := rand.Intn(NumberOfPeers)
|
||||||
trie = trie.insert(addr[:], cidr, peers[index])
|
allowedIPs.Insert(addr4[:], cidr, peers[index])
|
||||||
slow = slow.Insert(addr[:], 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(addr6[:], cidr, peers[index])
|
||||||
|
slow6 = slow6.Insert(addr6[:], cidr, peers[index])
|
||||||
}
|
}
|
||||||
|
|
||||||
for n := 0; n < NumberOfTests; n += 1 {
|
var p int
|
||||||
var addr [AddressLength]byte
|
for p = 0; ; p++ {
|
||||||
rand.Read(addr[:])
|
for n := 0; n < NumberOfTests; n++ {
|
||||||
peer1 := slow.Lookup(addr[:])
|
var addr4 [4]byte
|
||||||
peer2 := trie.lookup(addr[:])
|
rand.Read(addr4[:])
|
||||||
if peer1 != peer2 {
|
peer1 := slow4.Lookup(addr4[:])
|
||||||
t.Error("Trie did not match naive implementation, for:", addr)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrieRandomIPv6(t *testing.T) {
|
var addr6 [16]byte
|
||||||
var trie *trieEntry
|
rand.Read(addr6[:])
|
||||||
var slow SlowRouter
|
peer1 = slow6.Lookup(addr6[:])
|
||||||
var peers []*Peer
|
peer2 = allowedIPs.Lookup(addr6[:])
|
||||||
|
if peer1 != peer2 {
|
||||||
rand.Seed(1)
|
t.Errorf("Trie did not match naive implementation, for %v: want %p, got %p", net.IP(addr6[:]), peer1, peer2)
|
||||||
|
}
|
||||||
const AddressLength = 16
|
|
||||||
|
|
||||||
for n := 0; n < NumberOfPeers; n += 1 {
|
|
||||||
peers = append(peers, &Peer{})
|
|
||||||
}
|
|
||||||
|
|
||||||
for n := 0; n < NumberOfAddresses; n += 1 {
|
|
||||||
var addr [AddressLength]byte
|
|
||||||
rand.Read(addr[:])
|
|
||||||
cidr := uint(rand.Uint32() % (AddressLength * 8))
|
|
||||||
index := rand.Int() % NumberOfPeers
|
|
||||||
trie = trie.insert(addr[:], cidr, peers[index])
|
|
||||||
slow = slow.Insert(addr[:], cidr, peers[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
for n := 0; n < NumberOfTests; n += 1 {
|
|
||||||
var addr [AddressLength]byte
|
|
||||||
rand.Read(addr[:])
|
|
||||||
peer1 := slow.Lookup(addr[:])
|
|
||||||
peer2 := trie.lookup(addr[:])
|
|
||||||
if peer1 != peer2 {
|
|
||||||
t.Error("Trie did not match naive implementation, for:", addr)
|
|
||||||
}
|
}
|
||||||
|
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,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -11,33 +11,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Todo: More comprehensive
|
|
||||||
*/
|
|
||||||
|
|
||||||
type testPairCommonBits struct {
|
type testPairCommonBits struct {
|
||||||
s1 []byte
|
s1 []byte
|
||||||
s2 []byte
|
s2 []byte
|
||||||
match uint
|
match uint8
|
||||||
}
|
|
||||||
|
|
||||||
type testPairTrieInsert struct {
|
|
||||||
key []byte
|
|
||||||
cidr uint
|
|
||||||
peer *Peer
|
|
||||||
}
|
|
||||||
|
|
||||||
type testPairTrieLookup struct {
|
|
||||||
key []byte
|
|
||||||
peer *Peer
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTrie(t *testing.T, p *trieEntry) {
|
|
||||||
if p == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Log(p)
|
|
||||||
printTrie(t, p.child[0])
|
|
||||||
printTrie(t, p.child[1])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCommonBits(t *testing.T) {
|
func TestCommonBits(t *testing.T) {
|
||||||
@@ -65,24 +42,25 @@ func TestCommonBits(t *testing.T) {
|
|||||||
func benchmarkTrie(peerNumber int, addressNumber int, addressLength int, b *testing.B) {
|
func benchmarkTrie(peerNumber int, addressNumber int, addressLength int, b *testing.B) {
|
||||||
var trie *trieEntry
|
var trie *trieEntry
|
||||||
var peers []*Peer
|
var peers []*Peer
|
||||||
|
root := parentIndirection{&trie, 2}
|
||||||
|
|
||||||
rand.Seed(1)
|
rand.Seed(1)
|
||||||
|
|
||||||
const AddressLength = 4
|
const AddressLength = 4
|
||||||
|
|
||||||
for n := 0; n < peerNumber; n += 1 {
|
for n := 0; n < peerNumber; n++ {
|
||||||
peers = append(peers, &Peer{})
|
peers = append(peers, &Peer{})
|
||||||
}
|
}
|
||||||
|
|
||||||
for n := 0; n < addressNumber; n += 1 {
|
for n := 0; n < addressNumber; n++ {
|
||||||
var addr [AddressLength]byte
|
var addr [AddressLength]byte
|
||||||
rand.Read(addr[:])
|
rand.Read(addr[:])
|
||||||
cidr := uint(rand.Uint32() % (AddressLength * 8))
|
cidr := uint8(rand.Uint32() % (AddressLength * 8))
|
||||||
index := rand.Int() % peerNumber
|
index := rand.Int() % peerNumber
|
||||||
trie = trie.insert(addr[:], cidr, peers[index])
|
root.insert(addr[:], cidr, peers[index])
|
||||||
}
|
}
|
||||||
|
|
||||||
for n := 0; n < b.N; n += 1 {
|
for n := 0; n < b.N; n++ {
|
||||||
var addr [AddressLength]byte
|
var addr [AddressLength]byte
|
||||||
rand.Read(addr[:])
|
rand.Read(addr[:])
|
||||||
trie.lookup(addr[:])
|
trie.lookup(addr[:])
|
||||||
@@ -117,21 +95,21 @@ func TestTrieIPv4(t *testing.T) {
|
|||||||
g := &Peer{}
|
g := &Peer{}
|
||||||
h := &Peer{}
|
h := &Peer{}
|
||||||
|
|
||||||
var trie *trieEntry
|
var allowedIPs AllowedIPs
|
||||||
|
|
||||||
insert := func(peer *Peer, a, b, c, d byte, cidr uint) {
|
insert := func(peer *Peer, a, b, c, d byte, cidr uint8) {
|
||||||
trie = trie.insert([]byte{a, b, c, d}, cidr, peer)
|
allowedIPs.Insert([]byte{a, b, c, d}, cidr, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEQ := func(peer *Peer, a, b, c, d byte) {
|
assertEQ := func(peer *Peer, a, b, c, d byte) {
|
||||||
p := trie.lookup([]byte{a, b, c, d})
|
p := allowedIPs.Lookup([]byte{a, b, c, d})
|
||||||
if p != peer {
|
if p != peer {
|
||||||
t.Error("Assert EQ failed")
|
t.Error("Assert EQ failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNEQ := func(peer *Peer, a, b, c, d byte) {
|
assertNEQ := func(peer *Peer, a, b, c, d byte) {
|
||||||
p := trie.lookup([]byte{a, b, c, d})
|
p := allowedIPs.Lookup([]byte{a, b, c, d})
|
||||||
if p == peer {
|
if p == peer {
|
||||||
t.Error("Assert NEQ failed")
|
t.Error("Assert NEQ failed")
|
||||||
}
|
}
|
||||||
@@ -173,7 +151,7 @@ func TestTrieIPv4(t *testing.T) {
|
|||||||
assertEQ(a, 192, 0, 0, 0)
|
assertEQ(a, 192, 0, 0, 0)
|
||||||
assertEQ(a, 255, 0, 0, 0)
|
assertEQ(a, 255, 0, 0, 0)
|
||||||
|
|
||||||
trie = trie.removeByPeer(a)
|
allowedIPs.RemoveByPeer(a)
|
||||||
|
|
||||||
assertNEQ(a, 1, 0, 0, 0)
|
assertNEQ(a, 1, 0, 0, 0)
|
||||||
assertNEQ(a, 64, 0, 0, 0)
|
assertNEQ(a, 64, 0, 0, 0)
|
||||||
@@ -181,12 +159,21 @@ func TestTrieIPv4(t *testing.T) {
|
|||||||
assertNEQ(a, 192, 0, 0, 0)
|
assertNEQ(a, 192, 0, 0, 0)
|
||||||
assertNEQ(a, 255, 0, 0, 0)
|
assertNEQ(a, 255, 0, 0, 0)
|
||||||
|
|
||||||
trie = nil
|
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, 16)
|
||||||
insert(a, 192, 168, 0, 0, 24)
|
insert(a, 192, 168, 0, 0, 24)
|
||||||
|
|
||||||
trie = trie.removeByPeer(a)
|
allowedIPs.RemoveByPeer(a)
|
||||||
|
|
||||||
assertNEQ(a, 192, 168, 0, 1)
|
assertNEQ(a, 192, 168, 0, 1)
|
||||||
}
|
}
|
||||||
@@ -204,7 +191,7 @@ func TestTrieIPv6(t *testing.T) {
|
|||||||
g := &Peer{}
|
g := &Peer{}
|
||||||
h := &Peer{}
|
h := &Peer{}
|
||||||
|
|
||||||
var trie *trieEntry
|
var allowedIPs AllowedIPs
|
||||||
|
|
||||||
expand := func(a uint32) []byte {
|
expand := func(a uint32) []byte {
|
||||||
var out [4]byte
|
var out [4]byte
|
||||||
@@ -215,13 +202,13 @@ func TestTrieIPv6(t *testing.T) {
|
|||||||
return out[:]
|
return out[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
insert := func(peer *Peer, a, b, c, d uint32, cidr uint) {
|
insert := func(peer *Peer, a, b, c, d uint32, cidr uint8) {
|
||||||
var addr []byte
|
var addr []byte
|
||||||
addr = append(addr, expand(a)...)
|
addr = append(addr, expand(a)...)
|
||||||
addr = append(addr, expand(b)...)
|
addr = append(addr, expand(b)...)
|
||||||
addr = append(addr, expand(c)...)
|
addr = append(addr, expand(c)...)
|
||||||
addr = append(addr, expand(d)...)
|
addr = append(addr, expand(d)...)
|
||||||
trie = trie.insert(addr, cidr, peer)
|
allowedIPs.Insert(addr, cidr, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEQ := func(peer *Peer, a, b, c, d uint32) {
|
assertEQ := func(peer *Peer, a, b, c, d uint32) {
|
||||||
@@ -230,7 +217,7 @@ func TestTrieIPv6(t *testing.T) {
|
|||||||
addr = append(addr, expand(b)...)
|
addr = append(addr, expand(b)...)
|
||||||
addr = append(addr, expand(c)...)
|
addr = append(addr, expand(c)...)
|
||||||
addr = append(addr, expand(d)...)
|
addr = append(addr, expand(d)...)
|
||||||
p := trie.lookup(addr)
|
p := allowedIPs.Lookup(addr)
|
||||||
if p != peer {
|
if p != peer {
|
||||||
t.Error("Assert EQ failed")
|
t.Error("Assert EQ failed")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
)
|
||||||
|
|
||||||
type DummyDatagram struct {
|
type DummyDatagram struct {
|
||||||
msg []byte
|
msg []byte
|
||||||
endpoint Endpoint
|
endpoint conn.Endpoint
|
||||||
world bool // better type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DummyBind struct {
|
type DummyBind struct {
|
||||||
in6 chan DummyDatagram
|
in6 chan DummyDatagram
|
||||||
ou6 chan DummyDatagram
|
|
||||||
in4 chan DummyDatagram
|
in4 chan DummyDatagram
|
||||||
ou4 chan DummyDatagram
|
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ func (b *DummyBind) SetMark(v uint32) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DummyBind) ReceiveIPv6(buff []byte) (int, Endpoint, error) {
|
func (b *DummyBind) ReceiveIPv6(buff []byte) (int, conn.Endpoint, error) {
|
||||||
datagram, ok := <-b.in6
|
datagram, ok := <-b.in6
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, nil, errors.New("closed")
|
return 0, nil, errors.New("closed")
|
||||||
@@ -34,7 +35,7 @@ func (b *DummyBind) ReceiveIPv6(buff []byte) (int, Endpoint, error) {
|
|||||||
return len(datagram.msg), datagram.endpoint, nil
|
return len(datagram.msg), datagram.endpoint, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DummyBind) ReceiveIPv4(buff []byte) (int, Endpoint, error) {
|
func (b *DummyBind) ReceiveIPv4(buff []byte) (int, conn.Endpoint, error) {
|
||||||
datagram, ok := <-b.in4
|
datagram, ok := <-b.in4
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, nil, errors.New("closed")
|
return 0, nil, errors.New("closed")
|
||||||
@@ -50,6 +51,6 @@ func (b *DummyBind) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DummyBind) Send(buff []byte, end Endpoint) error {
|
func (b *DummyBind) Send(buff []byte, end conn.Endpoint) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
func (device *Device) PeekLookAtSocketFd4() (fd int, err error) {
|
|
||||||
sysconn, err := device.net.bind.(*nativeBind).ipv4.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = sysconn.Control(func(f uintptr) {
|
|
||||||
fd = int(f)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (device *Device) PeekLookAtSocketFd6() (fd int, err error) {
|
|
||||||
sysconn, err := device.net.bind.(*nativeBind).ipv6.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = sysconn.Control(func(f uintptr) {
|
|
||||||
fd = int(f)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (device *Device) BindSocketToInterface4(interfaceIndex uint32) error {
|
|
||||||
sysconn, err := device.net.bind.(*nativeBind).ipv4.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err2 := sysconn.Control(func(fd uintptr) {
|
|
||||||
err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, int(interfaceIndex))
|
|
||||||
})
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (device *Device) BindSocketToInterface6(interfaceIndex uint32) error {
|
|
||||||
sysconn, err := device.net.bind.(*nativeBind).ipv4.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err2 := sysconn.Control(func(fd uintptr) {
|
|
||||||
err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, int(interfaceIndex))
|
|
||||||
})
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
sockoptIP_UNICAST_IF = 31
|
|
||||||
sockoptIPV6_UNICAST_IF = 31
|
|
||||||
)
|
|
||||||
|
|
||||||
func (device *Device) BindSocketToInterface4(interfaceIndex uint32) error {
|
|
||||||
/* MSDN says for IPv4 this needs to be in net byte order, so that it's like an IP address with leading zeros. */
|
|
||||||
bytes := make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(bytes, interfaceIndex)
|
|
||||||
interfaceIndex = *(*uint32)(unsafe.Pointer(&bytes[0]))
|
|
||||||
|
|
||||||
sysconn, err := device.net.bind.(*nativeBind).ipv4.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err2 := sysconn.Control(func(fd uintptr) {
|
|
||||||
err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IP, sockoptIP_UNICAST_IF, int(interfaceIndex))
|
|
||||||
})
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (device *Device) BindSocketToInterface6(interfaceIndex uint32) error {
|
|
||||||
sysconn, err := device.net.bind.(*nativeBind).ipv6.SyscallConn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err2 := sysconn.Control(func(fd uintptr) {
|
|
||||||
err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, sockoptIPV6_UNICAST_IF, int(interfaceIndex))
|
|
||||||
})
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
131
device/channels.go
Normal file
131
device/channels.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An outboundQueue is a channel of QueueOutboundElements awaiting encryption.
|
||||||
|
// An outboundQueue is ref-counted using its wg field.
|
||||||
|
// An outboundQueue created with newOutboundQueue has one reference.
|
||||||
|
// Every additional writer must call wg.Add(1).
|
||||||
|
// Every completed writer must call wg.Done().
|
||||||
|
// When no further writers will be added,
|
||||||
|
// call wg.Done to remove the initial reference.
|
||||||
|
// When the refcount hits 0, the queue's channel is closed.
|
||||||
|
type outboundQueue struct {
|
||||||
|
c chan *QueueOutboundElement
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func newOutboundQueue() *outboundQueue {
|
||||||
|
q := &outboundQueue{
|
||||||
|
c: make(chan *QueueOutboundElement, QueueOutboundSize),
|
||||||
|
}
|
||||||
|
q.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
q.wg.Wait()
|
||||||
|
close(q.c)
|
||||||
|
}()
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// A inboundQueue is similar to an outboundQueue; see those docs.
|
||||||
|
type inboundQueue struct {
|
||||||
|
c chan *QueueInboundElement
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInboundQueue() *inboundQueue {
|
||||||
|
q := &inboundQueue{
|
||||||
|
c: make(chan *QueueInboundElement, QueueInboundSize),
|
||||||
|
}
|
||||||
|
q.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
q.wg.Wait()
|
||||||
|
close(q.c)
|
||||||
|
}()
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
// A handshakeQueue is similar to an outboundQueue; see those docs.
|
||||||
|
type handshakeQueue struct {
|
||||||
|
c chan QueueHandshakeElement
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHandshakeQueue() *handshakeQueue {
|
||||||
|
q := &handshakeQueue{
|
||||||
|
c: make(chan QueueHandshakeElement, QueueHandshakeSize),
|
||||||
|
}
|
||||||
|
q.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
q.wg.Wait()
|
||||||
|
close(q.c)
|
||||||
|
}()
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
type autodrainingInboundQueue struct {
|
||||||
|
c chan *QueueInboundElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAutodrainingInboundQueue returns a channel that will be drained when it gets GC'd.
|
||||||
|
// It is useful in cases in which is it hard to manage the lifetime of the channel.
|
||||||
|
// The returned channel must not be closed. Senders should signal shutdown using
|
||||||
|
// some other means, such as sending a sentinel nil values.
|
||||||
|
func newAutodrainingInboundQueue(device *Device) *autodrainingInboundQueue {
|
||||||
|
q := &autodrainingInboundQueue{
|
||||||
|
c: make(chan *QueueInboundElement, QueueInboundSize),
|
||||||
|
}
|
||||||
|
runtime.SetFinalizer(q, device.flushInboundQueue)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) flushInboundQueue(q *autodrainingInboundQueue) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case elem := <-q.c:
|
||||||
|
elem.Lock()
|
||||||
|
device.PutMessageBuffer(elem.buffer)
|
||||||
|
device.PutInboundElement(elem)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type autodrainingOutboundQueue struct {
|
||||||
|
c chan *QueueOutboundElement
|
||||||
|
}
|
||||||
|
|
||||||
|
// newAutodrainingOutboundQueue returns a channel that will be drained when it gets GC'd.
|
||||||
|
// It is useful in cases in which is it hard to manage the lifetime of the channel.
|
||||||
|
// The returned channel must not be closed. Senders should signal shutdown using
|
||||||
|
// some other means, such as sending a sentinel nil values.
|
||||||
|
// All sends to the channel must be best-effort, because there may be no receivers.
|
||||||
|
func newAutodrainingOutboundQueue(device *Device) *autodrainingOutboundQueue {
|
||||||
|
q := &autodrainingOutboundQueue{
|
||||||
|
c: make(chan *QueueOutboundElement, QueueOutboundSize),
|
||||||
|
}
|
||||||
|
runtime.SetFinalizer(q, device.flushOutboundQueue)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) flushOutboundQueue(q *autodrainingOutboundQueue) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case elem := <-q.c:
|
||||||
|
elem.Lock()
|
||||||
|
device.PutMessageBuffer(elem.buffer)
|
||||||
|
device.PutOutboundElement(elem)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
180
device/conn.go
180
device/conn.go
@@ -1,180 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"golang.org/x/net/ipv4"
|
|
||||||
"golang.org/x/net/ipv6"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ConnRoutineNumber = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
/* A Bind handles listening on a port for both IPv6 and IPv4 UDP traffic
|
|
||||||
*/
|
|
||||||
type Bind interface {
|
|
||||||
SetMark(value uint32) error
|
|
||||||
ReceiveIPv6(buff []byte) (int, Endpoint, error)
|
|
||||||
ReceiveIPv4(buff []byte) (int, Endpoint, error)
|
|
||||||
Send(buff []byte, end Endpoint) error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
/* An Endpoint maintains the source/destination caching for a peer
|
|
||||||
*
|
|
||||||
* dst : the remote address of a peer ("endpoint" in uapi terminology)
|
|
||||||
* src : the local address from which datagrams originate going to the peer
|
|
||||||
*/
|
|
||||||
type Endpoint interface {
|
|
||||||
ClearSrc() // clears the source address
|
|
||||||
SrcToString() string // returns the local source address (ip:port)
|
|
||||||
DstToString() string // returns the destination address (ip:port)
|
|
||||||
DstToBytes() []byte // used for mac2 cookie calculations
|
|
||||||
DstIP() net.IP
|
|
||||||
SrcIP() net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEndpoint(s string) (*net.UDPAddr, error) {
|
|
||||||
|
|
||||||
// ensure that the host is an IP address
|
|
||||||
|
|
||||||
host, _, err := net.SplitHostPort(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ip := net.ParseIP(host); ip == nil {
|
|
||||||
return nil, errors.New("Failed to parse IP address: " + host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse address and port
|
|
||||||
|
|
||||||
addr, err := net.ResolveUDPAddr("udp", s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ip4 := addr.IP.To4()
|
|
||||||
if ip4 != nil {
|
|
||||||
addr.IP = ip4
|
|
||||||
}
|
|
||||||
return addr, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func unsafeCloseBind(device *Device) error {
|
|
||||||
var err error
|
|
||||||
netc := &device.net
|
|
||||||
if netc.bind != nil {
|
|
||||||
err = netc.bind.Close()
|
|
||||||
netc.bind = nil
|
|
||||||
}
|
|
||||||
netc.stopping.Wait()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (device *Device) BindSetMark(mark uint32) error {
|
|
||||||
|
|
||||||
device.net.Lock()
|
|
||||||
defer device.net.Unlock()
|
|
||||||
|
|
||||||
// check if modified
|
|
||||||
|
|
||||||
if device.net.fwmark == mark {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// update fwmark on existing bind
|
|
||||||
|
|
||||||
device.net.fwmark = mark
|
|
||||||
if device.isUp.Get() && device.net.bind != nil {
|
|
||||||
if err := device.net.bind.SetMark(mark); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear cached source addresses
|
|
||||||
|
|
||||||
device.peers.RLock()
|
|
||||||
for _, peer := range device.peers.keyMap {
|
|
||||||
peer.Lock()
|
|
||||||
defer peer.Unlock()
|
|
||||||
if peer.endpoint != nil {
|
|
||||||
peer.endpoint.ClearSrc()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
device.peers.RUnlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (device *Device) BindUpdate() error {
|
|
||||||
|
|
||||||
device.net.Lock()
|
|
||||||
defer device.net.Unlock()
|
|
||||||
|
|
||||||
// close existing sockets
|
|
||||||
|
|
||||||
if err := unsafeCloseBind(device); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// open new sockets
|
|
||||||
|
|
||||||
if device.isUp.Get() {
|
|
||||||
|
|
||||||
// bind to new port
|
|
||||||
|
|
||||||
var err error
|
|
||||||
netc := &device.net
|
|
||||||
netc.bind, netc.port, err = CreateBind(netc.port, device)
|
|
||||||
if err != nil {
|
|
||||||
netc.bind = nil
|
|
||||||
netc.port = 0
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// set fwmark
|
|
||||||
|
|
||||||
if netc.fwmark != 0 {
|
|
||||||
err = netc.bind.SetMark(netc.fwmark)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear cached source addresses
|
|
||||||
|
|
||||||
device.peers.RLock()
|
|
||||||
for _, peer := range device.peers.keyMap {
|
|
||||||
peer.Lock()
|
|
||||||
defer peer.Unlock()
|
|
||||||
if peer.endpoint != nil {
|
|
||||||
peer.endpoint.ClearSrc()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
device.peers.RUnlock()
|
|
||||||
|
|
||||||
// start receiving routines
|
|
||||||
|
|
||||||
device.net.starting.Add(ConnRoutineNumber)
|
|
||||||
device.net.stopping.Add(ConnRoutineNumber)
|
|
||||||
go device.RoutineReceiveIncoming(ipv4.Version, netc.bind)
|
|
||||||
go device.RoutineReceiveIncoming(ipv6.Version, netc.bind)
|
|
||||||
device.net.starting.Wait()
|
|
||||||
|
|
||||||
device.log.Debug.Println("UDP bind has been updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (device *Device) BindClose() error {
|
|
||||||
device.net.Lock()
|
|
||||||
err := unsafeCloseBind(device)
|
|
||||||
device.net.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
// +build !linux android
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* This code is meant to be a temporary solution
|
|
||||||
* on platforms for which the sticky socket / source caching behavior
|
|
||||||
* has not yet been implemented.
|
|
||||||
*
|
|
||||||
* See conn_linux.go for an implementation on the linux platform.
|
|
||||||
*/
|
|
||||||
|
|
||||||
type nativeBind struct {
|
|
||||||
ipv4 *net.UDPConn
|
|
||||||
ipv6 *net.UDPConn
|
|
||||||
}
|
|
||||||
|
|
||||||
type NativeEndpoint net.UDPAddr
|
|
||||||
|
|
||||||
var _ Bind = (*nativeBind)(nil)
|
|
||||||
var _ Endpoint = (*NativeEndpoint)(nil)
|
|
||||||
|
|
||||||
func CreateEndpoint(s string) (Endpoint, error) {
|
|
||||||
addr, err := parseEndpoint(s)
|
|
||||||
return (*NativeEndpoint)(addr), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ *NativeEndpoint) ClearSrc() {}
|
|
||||||
|
|
||||||
func (e *NativeEndpoint) DstIP() net.IP {
|
|
||||||
return (*net.UDPAddr)(e).IP
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *NativeEndpoint) SrcIP() net.IP {
|
|
||||||
return nil // not supported
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *NativeEndpoint) DstToBytes() []byte {
|
|
||||||
addr := (*net.UDPAddr)(e)
|
|
||||||
out := addr.IP.To4()
|
|
||||||
if out == nil {
|
|
||||||
out = addr.IP
|
|
||||||
}
|
|
||||||
out = append(out, byte(addr.Port&0xff))
|
|
||||||
out = append(out, byte((addr.Port>>8)&0xff))
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *NativeEndpoint) DstToString() string {
|
|
||||||
return (*net.UDPAddr)(e).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *NativeEndpoint) SrcToString() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func listenNet(network string, port int) (*net.UDPConn, int, error) {
|
|
||||||
|
|
||||||
// listen
|
|
||||||
|
|
||||||
conn, err := net.ListenUDP(network, &net.UDPAddr{Port: port})
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve port
|
|
||||||
|
|
||||||
laddr := conn.LocalAddr()
|
|
||||||
uaddr, err := net.ResolveUDPAddr(
|
|
||||||
laddr.Network(),
|
|
||||||
laddr.String(),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
return conn, uaddr.Port, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractErrno(err error) error {
|
|
||||||
opErr, ok := err.(*net.OpError)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
syscallErr, ok := opErr.Err.(*os.SyscallError)
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return syscallErr.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBind(uport uint16, device *Device) (Bind, uint16, error) {
|
|
||||||
var err error
|
|
||||||
var bind nativeBind
|
|
||||||
|
|
||||||
port := int(uport)
|
|
||||||
|
|
||||||
bind.ipv4, port, err = listenNet("udp4", port)
|
|
||||||
if err != nil && extractErrno(err) != syscall.EAFNOSUPPORT {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bind.ipv6, port, err = listenNet("udp6", port)
|
|
||||||
if err != nil && extractErrno(err) != syscall.EAFNOSUPPORT {
|
|
||||||
bind.ipv4.Close()
|
|
||||||
bind.ipv4 = nil
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bind, uint16(port), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) Close() error {
|
|
||||||
var err1, err2 error
|
|
||||||
if bind.ipv4 != nil {
|
|
||||||
err1 = bind.ipv4.Close()
|
|
||||||
}
|
|
||||||
if bind.ipv6 != nil {
|
|
||||||
err2 = bind.ipv6.Close()
|
|
||||||
}
|
|
||||||
if err1 != nil {
|
|
||||||
return err1
|
|
||||||
}
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) ReceiveIPv4(buff []byte) (int, Endpoint, error) {
|
|
||||||
if bind.ipv4 == nil {
|
|
||||||
return 0, nil, syscall.EAFNOSUPPORT
|
|
||||||
}
|
|
||||||
n, endpoint, err := bind.ipv4.ReadFromUDP(buff)
|
|
||||||
if endpoint != nil {
|
|
||||||
endpoint.IP = endpoint.IP.To4()
|
|
||||||
}
|
|
||||||
return n, (*NativeEndpoint)(endpoint), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) ReceiveIPv6(buff []byte) (int, Endpoint, error) {
|
|
||||||
if bind.ipv6 == nil {
|
|
||||||
return 0, nil, syscall.EAFNOSUPPORT
|
|
||||||
}
|
|
||||||
n, endpoint, err := bind.ipv6.ReadFromUDP(buff)
|
|
||||||
return n, (*NativeEndpoint)(endpoint), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) Send(buff []byte, endpoint Endpoint) error {
|
|
||||||
var err error
|
|
||||||
nend := endpoint.(*NativeEndpoint)
|
|
||||||
if nend.IP.To4() != nil {
|
|
||||||
if bind.ipv4 == nil {
|
|
||||||
return syscall.EAFNOSUPPORT
|
|
||||||
}
|
|
||||||
_, err = bind.ipv4.WriteToUDP(buff, (*net.UDPAddr)(nend))
|
|
||||||
} else {
|
|
||||||
if bind.ipv6 == nil {
|
|
||||||
return syscall.EAFNOSUPPORT
|
|
||||||
}
|
|
||||||
_, err = bind.ipv6.WriteToUDP(buff, (*net.UDPAddr)(nend))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,746 +0,0 @@
|
|||||||
// +build !android
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* This implements userspace semantics of "sticky sockets", modeled after
|
|
||||||
* WireGuard's kernelspace implementation. This is more or less a straight port
|
|
||||||
* of the sticky-sockets.c example code:
|
|
||||||
* https://git.zx2c4.com/WireGuard/tree/contrib/examples/sticky-sockets/sticky-sockets.c
|
|
||||||
*
|
|
||||||
* Currently there is no way to achieve this within the net package:
|
|
||||||
* See e.g. https://github.com/golang/go/issues/17930
|
|
||||||
* So this code is remains platform dependent.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"golang.zx2c4.com/wireguard/rwcancel"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
FD_ERR = -1
|
|
||||||
)
|
|
||||||
|
|
||||||
type IPv4Source struct {
|
|
||||||
src [4]byte
|
|
||||||
ifindex int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type IPv6Source struct {
|
|
||||||
src [16]byte
|
|
||||||
//ifindex belongs in dst.ZoneId
|
|
||||||
}
|
|
||||||
|
|
||||||
type NativeEndpoint struct {
|
|
||||||
dst [unsafe.Sizeof(unix.SockaddrInet6{})]byte
|
|
||||||
src [unsafe.Sizeof(IPv6Source{})]byte
|
|
||||||
isV6 bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (endpoint *NativeEndpoint) src4() *IPv4Source {
|
|
||||||
return (*IPv4Source)(unsafe.Pointer(&endpoint.src[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (endpoint *NativeEndpoint) src6() *IPv6Source {
|
|
||||||
return (*IPv6Source)(unsafe.Pointer(&endpoint.src[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (endpoint *NativeEndpoint) dst4() *unix.SockaddrInet4 {
|
|
||||||
return (*unix.SockaddrInet4)(unsafe.Pointer(&endpoint.dst[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (endpoint *NativeEndpoint) dst6() *unix.SockaddrInet6 {
|
|
||||||
return (*unix.SockaddrInet6)(unsafe.Pointer(&endpoint.dst[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
type nativeBind struct {
|
|
||||||
sock4 int
|
|
||||||
sock6 int
|
|
||||||
netlinkSock int
|
|
||||||
netlinkCancel *rwcancel.RWCancel
|
|
||||||
lastMark uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Endpoint = (*NativeEndpoint)(nil)
|
|
||||||
var _ Bind = (*nativeBind)(nil)
|
|
||||||
|
|
||||||
func CreateEndpoint(s string) (Endpoint, error) {
|
|
||||||
var end NativeEndpoint
|
|
||||||
addr, err := parseEndpoint(s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ipv4 := addr.IP.To4()
|
|
||||||
if ipv4 != nil {
|
|
||||||
dst := end.dst4()
|
|
||||||
end.isV6 = false
|
|
||||||
dst.Port = addr.Port
|
|
||||||
copy(dst.Addr[:], ipv4)
|
|
||||||
end.ClearSrc()
|
|
||||||
return &end, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ipv6 := addr.IP.To16()
|
|
||||||
if ipv6 != nil {
|
|
||||||
zone, err := zoneToUint32(addr.Zone)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dst := end.dst6()
|
|
||||||
end.isV6 = true
|
|
||||||
dst.Port = addr.Port
|
|
||||||
dst.ZoneId = zone
|
|
||||||
copy(dst.Addr[:], ipv6[:])
|
|
||||||
end.ClearSrc()
|
|
||||||
return &end, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("Invalid IP address")
|
|
||||||
}
|
|
||||||
|
|
||||||
func createNetlinkRouteSocket() (int, error) {
|
|
||||||
sock, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW, unix.NETLINK_ROUTE)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
saddr := &unix.SockaddrNetlink{
|
|
||||||
Family: unix.AF_NETLINK,
|
|
||||||
Groups: uint32(1 << (unix.RTNLGRP_IPV4_ROUTE - 1)),
|
|
||||||
}
|
|
||||||
err = unix.Bind(sock, saddr)
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(sock)
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
return sock, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBind(port uint16, device *Device) (*nativeBind, uint16, error) {
|
|
||||||
var err error
|
|
||||||
var bind nativeBind
|
|
||||||
var newPort uint16
|
|
||||||
|
|
||||||
bind.netlinkSock, err = createNetlinkRouteSocket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
bind.netlinkCancel, err = rwcancel.NewRWCancel(bind.netlinkSock)
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(bind.netlinkSock)
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
go bind.routineRouteListener(device)
|
|
||||||
|
|
||||||
// attempt ipv6 bind, update port if succesful
|
|
||||||
|
|
||||||
bind.sock6, newPort, err = create6(port)
|
|
||||||
if err != nil {
|
|
||||||
if err != syscall.EAFNOSUPPORT {
|
|
||||||
bind.netlinkCancel.Cancel()
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
port = newPort
|
|
||||||
}
|
|
||||||
|
|
||||||
// attempt ipv4 bind, update port if succesful
|
|
||||||
|
|
||||||
bind.sock4, newPort, err = create4(port)
|
|
||||||
if err != nil {
|
|
||||||
if err != syscall.EAFNOSUPPORT {
|
|
||||||
bind.netlinkCancel.Cancel()
|
|
||||||
unix.Close(bind.sock6)
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
port = newPort
|
|
||||||
}
|
|
||||||
|
|
||||||
if bind.sock4 == FD_ERR && bind.sock6 == FD_ERR {
|
|
||||||
return nil, 0, errors.New("ipv4 and ipv6 not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bind, port, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) SetMark(value uint32) error {
|
|
||||||
if bind.sock6 != -1 {
|
|
||||||
err := unix.SetsockoptInt(
|
|
||||||
bind.sock6,
|
|
||||||
unix.SOL_SOCKET,
|
|
||||||
unix.SO_MARK,
|
|
||||||
int(value),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if bind.sock4 != -1 {
|
|
||||||
err := unix.SetsockoptInt(
|
|
||||||
bind.sock4,
|
|
||||||
unix.SOL_SOCKET,
|
|
||||||
unix.SO_MARK,
|
|
||||||
int(value),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bind.lastMark = value
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeUnblock(fd int) error {
|
|
||||||
// shutdown to unblock readers and writers
|
|
||||||
unix.Shutdown(fd, unix.SHUT_RDWR)
|
|
||||||
return unix.Close(fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) Close() error {
|
|
||||||
var err1, err2, err3 error
|
|
||||||
if bind.sock6 != -1 {
|
|
||||||
err1 = closeUnblock(bind.sock6)
|
|
||||||
}
|
|
||||||
if bind.sock4 != -1 {
|
|
||||||
err2 = closeUnblock(bind.sock4)
|
|
||||||
}
|
|
||||||
err3 = bind.netlinkCancel.Cancel()
|
|
||||||
|
|
||||||
if err1 != nil {
|
|
||||||
return err1
|
|
||||||
}
|
|
||||||
if err2 != nil {
|
|
||||||
return err2
|
|
||||||
}
|
|
||||||
return err3
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) ReceiveIPv6(buff []byte) (int, Endpoint, error) {
|
|
||||||
var end NativeEndpoint
|
|
||||||
if bind.sock6 == -1 {
|
|
||||||
return 0, nil, syscall.EAFNOSUPPORT
|
|
||||||
}
|
|
||||||
n, err := receive6(
|
|
||||||
bind.sock6,
|
|
||||||
buff,
|
|
||||||
&end,
|
|
||||||
)
|
|
||||||
return n, &end, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) ReceiveIPv4(buff []byte) (int, Endpoint, error) {
|
|
||||||
var end NativeEndpoint
|
|
||||||
if bind.sock4 == -1 {
|
|
||||||
return 0, nil, syscall.EAFNOSUPPORT
|
|
||||||
}
|
|
||||||
n, err := receive4(
|
|
||||||
bind.sock4,
|
|
||||||
buff,
|
|
||||||
&end,
|
|
||||||
)
|
|
||||||
return n, &end, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) Send(buff []byte, end Endpoint) error {
|
|
||||||
nend := end.(*NativeEndpoint)
|
|
||||||
if !nend.isV6 {
|
|
||||||
if bind.sock4 == -1 {
|
|
||||||
return syscall.EAFNOSUPPORT
|
|
||||||
}
|
|
||||||
return send4(bind.sock4, nend, buff)
|
|
||||||
} else {
|
|
||||||
if bind.sock6 == -1 {
|
|
||||||
return syscall.EAFNOSUPPORT
|
|
||||||
}
|
|
||||||
return send6(bind.sock6, nend, buff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (end *NativeEndpoint) SrcIP() net.IP {
|
|
||||||
if !end.isV6 {
|
|
||||||
return net.IPv4(
|
|
||||||
end.src4().src[0],
|
|
||||||
end.src4().src[1],
|
|
||||||
end.src4().src[2],
|
|
||||||
end.src4().src[3],
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return end.src6().src[:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (end *NativeEndpoint) DstIP() net.IP {
|
|
||||||
if !end.isV6 {
|
|
||||||
return net.IPv4(
|
|
||||||
end.dst4().Addr[0],
|
|
||||||
end.dst4().Addr[1],
|
|
||||||
end.dst4().Addr[2],
|
|
||||||
end.dst4().Addr[3],
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return end.dst6().Addr[:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (end *NativeEndpoint) DstToBytes() []byte {
|
|
||||||
if !end.isV6 {
|
|
||||||
return (*[unsafe.Offsetof(end.dst4().Addr) + unsafe.Sizeof(end.dst4().Addr)]byte)(unsafe.Pointer(end.dst4()))[:]
|
|
||||||
} else {
|
|
||||||
return (*[unsafe.Offsetof(end.dst6().Addr) + unsafe.Sizeof(end.dst6().Addr)]byte)(unsafe.Pointer(end.dst6()))[:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (end *NativeEndpoint) SrcToString() string {
|
|
||||||
return end.SrcIP().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (end *NativeEndpoint) DstToString() string {
|
|
||||||
var udpAddr net.UDPAddr
|
|
||||||
udpAddr.IP = end.DstIP()
|
|
||||||
if !end.isV6 {
|
|
||||||
udpAddr.Port = end.dst4().Port
|
|
||||||
} else {
|
|
||||||
udpAddr.Port = end.dst6().Port
|
|
||||||
}
|
|
||||||
return udpAddr.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (end *NativeEndpoint) ClearDst() {
|
|
||||||
for i := range end.dst {
|
|
||||||
end.dst[i] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (end *NativeEndpoint) ClearSrc() {
|
|
||||||
for i := range end.src {
|
|
||||||
end.src[i] = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func zoneToUint32(zone string) (uint32, error) {
|
|
||||||
if zone == "" {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
if intr, err := net.InterfaceByName(zone); err == nil {
|
|
||||||
return uint32(intr.Index), nil
|
|
||||||
}
|
|
||||||
n, err := strconv.ParseUint(zone, 10, 32)
|
|
||||||
return uint32(n), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func create4(port uint16) (int, uint16, error) {
|
|
||||||
|
|
||||||
// create socket
|
|
||||||
|
|
||||||
fd, err := unix.Socket(
|
|
||||||
unix.AF_INET,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return FD_ERR, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := unix.SockaddrInet4{
|
|
||||||
Port: int(port),
|
|
||||||
}
|
|
||||||
|
|
||||||
// set sockopts and bind
|
|
||||||
|
|
||||||
if err := func() error {
|
|
||||||
if err := unix.SetsockoptInt(
|
|
||||||
fd,
|
|
||||||
unix.SOL_SOCKET,
|
|
||||||
unix.SO_REUSEADDR,
|
|
||||||
1,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := unix.SetsockoptInt(
|
|
||||||
fd,
|
|
||||||
unix.IPPROTO_IP,
|
|
||||||
unix.IP_PKTINFO,
|
|
||||||
1,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return unix.Bind(fd, &addr)
|
|
||||||
}(); err != nil {
|
|
||||||
unix.Close(fd)
|
|
||||||
return FD_ERR, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fd, uint16(addr.Port), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func create6(port uint16) (int, uint16, error) {
|
|
||||||
|
|
||||||
// create socket
|
|
||||||
|
|
||||||
fd, err := unix.Socket(
|
|
||||||
unix.AF_INET6,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return FD_ERR, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// set sockopts and bind
|
|
||||||
|
|
||||||
addr := unix.SockaddrInet6{
|
|
||||||
Port: int(port),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := func() error {
|
|
||||||
|
|
||||||
if err := unix.SetsockoptInt(
|
|
||||||
fd,
|
|
||||||
unix.SOL_SOCKET,
|
|
||||||
unix.SO_REUSEADDR,
|
|
||||||
1,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := unix.SetsockoptInt(
|
|
||||||
fd,
|
|
||||||
unix.IPPROTO_IPV6,
|
|
||||||
unix.IPV6_RECVPKTINFO,
|
|
||||||
1,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := unix.SetsockoptInt(
|
|
||||||
fd,
|
|
||||||
unix.IPPROTO_IPV6,
|
|
||||||
unix.IPV6_V6ONLY,
|
|
||||||
1,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return unix.Bind(fd, &addr)
|
|
||||||
|
|
||||||
}(); err != nil {
|
|
||||||
unix.Close(fd)
|
|
||||||
return FD_ERR, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fd, uint16(addr.Port), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func send4(sock int, end *NativeEndpoint, buff []byte) error {
|
|
||||||
|
|
||||||
// construct message header
|
|
||||||
|
|
||||||
cmsg := struct {
|
|
||||||
cmsghdr unix.Cmsghdr
|
|
||||||
pktinfo unix.Inet4Pktinfo
|
|
||||||
}{
|
|
||||||
unix.Cmsghdr{
|
|
||||||
Level: unix.IPPROTO_IP,
|
|
||||||
Type: unix.IP_PKTINFO,
|
|
||||||
Len: unix.SizeofInet4Pktinfo + unix.SizeofCmsghdr,
|
|
||||||
},
|
|
||||||
unix.Inet4Pktinfo{
|
|
||||||
Spec_dst: end.src4().src,
|
|
||||||
Ifindex: end.src4().ifindex,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := unix.SendmsgN(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], end.dst4(), 0)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear src and retry
|
|
||||||
|
|
||||||
if err == unix.EINVAL {
|
|
||||||
end.ClearSrc()
|
|
||||||
cmsg.pktinfo = unix.Inet4Pktinfo{}
|
|
||||||
_, err = unix.SendmsgN(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], end.dst4(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func send6(sock int, end *NativeEndpoint, buff []byte) error {
|
|
||||||
|
|
||||||
// construct message header
|
|
||||||
|
|
||||||
cmsg := struct {
|
|
||||||
cmsghdr unix.Cmsghdr
|
|
||||||
pktinfo unix.Inet6Pktinfo
|
|
||||||
}{
|
|
||||||
unix.Cmsghdr{
|
|
||||||
Level: unix.IPPROTO_IPV6,
|
|
||||||
Type: unix.IPV6_PKTINFO,
|
|
||||||
Len: unix.SizeofInet6Pktinfo + unix.SizeofCmsghdr,
|
|
||||||
},
|
|
||||||
unix.Inet6Pktinfo{
|
|
||||||
Addr: end.src6().src,
|
|
||||||
Ifindex: end.dst6().ZoneId,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmsg.pktinfo.Addr == [16]byte{} {
|
|
||||||
cmsg.pktinfo.Ifindex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := unix.SendmsgN(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], end.dst6(), 0)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear src and retry
|
|
||||||
|
|
||||||
if err == unix.EINVAL {
|
|
||||||
end.ClearSrc()
|
|
||||||
cmsg.pktinfo = unix.Inet6Pktinfo{}
|
|
||||||
_, err = unix.SendmsgN(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], end.dst6(), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func receive4(sock int, buff []byte, end *NativeEndpoint) (int, error) {
|
|
||||||
|
|
||||||
// contruct message header
|
|
||||||
|
|
||||||
var cmsg struct {
|
|
||||||
cmsghdr unix.Cmsghdr
|
|
||||||
pktinfo unix.Inet4Pktinfo
|
|
||||||
}
|
|
||||||
|
|
||||||
size, _, _, newDst, err := unix.Recvmsg(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], 0)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
end.isV6 = false
|
|
||||||
|
|
||||||
if newDst4, ok := newDst.(*unix.SockaddrInet4); ok {
|
|
||||||
*end.dst4() = *newDst4
|
|
||||||
}
|
|
||||||
|
|
||||||
// update source cache
|
|
||||||
|
|
||||||
if cmsg.cmsghdr.Level == unix.IPPROTO_IP &&
|
|
||||||
cmsg.cmsghdr.Type == unix.IP_PKTINFO &&
|
|
||||||
cmsg.cmsghdr.Len >= unix.SizeofInet4Pktinfo {
|
|
||||||
end.src4().src = cmsg.pktinfo.Spec_dst
|
|
||||||
end.src4().ifindex = cmsg.pktinfo.Ifindex
|
|
||||||
}
|
|
||||||
|
|
||||||
return size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func receive6(sock int, buff []byte, end *NativeEndpoint) (int, error) {
|
|
||||||
|
|
||||||
// contruct message header
|
|
||||||
|
|
||||||
var cmsg struct {
|
|
||||||
cmsghdr unix.Cmsghdr
|
|
||||||
pktinfo unix.Inet6Pktinfo
|
|
||||||
}
|
|
||||||
|
|
||||||
size, _, _, newDst, err := unix.Recvmsg(sock, buff, (*[unsafe.Sizeof(cmsg)]byte)(unsafe.Pointer(&cmsg))[:], 0)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
end.isV6 = true
|
|
||||||
|
|
||||||
if newDst6, ok := newDst.(*unix.SockaddrInet6); ok {
|
|
||||||
*end.dst6() = *newDst6
|
|
||||||
}
|
|
||||||
|
|
||||||
// update source cache
|
|
||||||
|
|
||||||
if cmsg.cmsghdr.Level == unix.IPPROTO_IPV6 &&
|
|
||||||
cmsg.cmsghdr.Type == unix.IPV6_PKTINFO &&
|
|
||||||
cmsg.cmsghdr.Len >= unix.SizeofInet6Pktinfo {
|
|
||||||
end.src6().src = cmsg.pktinfo.Addr
|
|
||||||
end.dst6().ZoneId = cmsg.pktinfo.Ifindex
|
|
||||||
}
|
|
||||||
|
|
||||||
return size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bind *nativeBind) routineRouteListener(device *Device) {
|
|
||||||
type peerEndpointPtr struct {
|
|
||||||
peer *Peer
|
|
||||||
endpoint *Endpoint
|
|
||||||
}
|
|
||||||
var reqPeer map[uint32]peerEndpointPtr
|
|
||||||
var reqPeerLock sync.Mutex
|
|
||||||
|
|
||||||
defer unix.Close(bind.netlinkSock)
|
|
||||||
|
|
||||||
for msg := make([]byte, 1<<16); ; {
|
|
||||||
var err error
|
|
||||||
var msgn int
|
|
||||||
for {
|
|
||||||
msgn, _, _, _, err = unix.Recvmsg(bind.netlinkSock, msg[:], nil, 0)
|
|
||||||
if err == nil || !rwcancel.RetryAfterError(err) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !bind.netlinkCancel.ReadyRead() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for remain := msg[:msgn]; len(remain) >= unix.SizeofNlMsghdr; {
|
|
||||||
|
|
||||||
hdr := *(*unix.NlMsghdr)(unsafe.Pointer(&remain[0]))
|
|
||||||
|
|
||||||
if uint(hdr.Len) > uint(len(remain)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch hdr.Type {
|
|
||||||
case unix.RTM_NEWROUTE, unix.RTM_DELROUTE:
|
|
||||||
if hdr.Seq <= MaxPeers && hdr.Seq > 0 {
|
|
||||||
if uint(len(remain)) < uint(hdr.Len) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if hdr.Len > unix.SizeofNlMsghdr+unix.SizeofRtMsg {
|
|
||||||
attr := remain[unix.SizeofNlMsghdr+unix.SizeofRtMsg:]
|
|
||||||
for {
|
|
||||||
if uint(len(attr)) < uint(unix.SizeofRtAttr) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
attrhdr := *(*unix.RtAttr)(unsafe.Pointer(&attr[0]))
|
|
||||||
if attrhdr.Len < unix.SizeofRtAttr || uint(len(attr)) < uint(attrhdr.Len) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if attrhdr.Type == unix.RTA_OIF && attrhdr.Len == unix.SizeofRtAttr+4 {
|
|
||||||
ifidx := *(*uint32)(unsafe.Pointer(&attr[unix.SizeofRtAttr]))
|
|
||||||
reqPeerLock.Lock()
|
|
||||||
if reqPeer == nil {
|
|
||||||
reqPeerLock.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pePtr, ok := reqPeer[hdr.Seq]
|
|
||||||
reqPeerLock.Unlock()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pePtr.peer.Lock()
|
|
||||||
if &pePtr.peer.endpoint != pePtr.endpoint {
|
|
||||||
pePtr.peer.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if uint32(pePtr.peer.endpoint.(*NativeEndpoint).src4().ifindex) == ifidx {
|
|
||||||
pePtr.peer.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pePtr.peer.endpoint.(*NativeEndpoint).ClearSrc()
|
|
||||||
pePtr.peer.Unlock()
|
|
||||||
}
|
|
||||||
attr = attr[attrhdr.Len:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
reqPeerLock.Lock()
|
|
||||||
reqPeer = make(map[uint32]peerEndpointPtr)
|
|
||||||
reqPeerLock.Unlock()
|
|
||||||
go func() {
|
|
||||||
device.peers.RLock()
|
|
||||||
i := uint32(1)
|
|
||||||
for _, peer := range device.peers.keyMap {
|
|
||||||
peer.RLock()
|
|
||||||
if peer.endpoint == nil || peer.endpoint.(*NativeEndpoint) == nil {
|
|
||||||
peer.RUnlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if peer.endpoint.(*NativeEndpoint).isV6 || peer.endpoint.(*NativeEndpoint).src4().ifindex == 0 {
|
|
||||||
peer.RUnlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nlmsg := struct {
|
|
||||||
hdr unix.NlMsghdr
|
|
||||||
msg unix.RtMsg
|
|
||||||
dsthdr unix.RtAttr
|
|
||||||
dst [4]byte
|
|
||||||
srchdr unix.RtAttr
|
|
||||||
src [4]byte
|
|
||||||
markhdr unix.RtAttr
|
|
||||||
mark uint32
|
|
||||||
}{
|
|
||||||
unix.NlMsghdr{
|
|
||||||
Type: uint16(unix.RTM_GETROUTE),
|
|
||||||
Flags: unix.NLM_F_REQUEST,
|
|
||||||
Seq: i,
|
|
||||||
},
|
|
||||||
unix.RtMsg{
|
|
||||||
Family: unix.AF_INET,
|
|
||||||
Dst_len: 32,
|
|
||||||
Src_len: 32,
|
|
||||||
},
|
|
||||||
unix.RtAttr{
|
|
||||||
Len: 8,
|
|
||||||
Type: unix.RTA_DST,
|
|
||||||
},
|
|
||||||
peer.endpoint.(*NativeEndpoint).dst4().Addr,
|
|
||||||
unix.RtAttr{
|
|
||||||
Len: 8,
|
|
||||||
Type: unix.RTA_SRC,
|
|
||||||
},
|
|
||||||
peer.endpoint.(*NativeEndpoint).src4().src,
|
|
||||||
unix.RtAttr{
|
|
||||||
Len: 8,
|
|
||||||
Type: 0x10, //unix.RTA_MARK TODO: add this to x/sys/unix
|
|
||||||
},
|
|
||||||
uint32(bind.lastMark),
|
|
||||||
}
|
|
||||||
nlmsg.hdr.Len = uint32(unsafe.Sizeof(nlmsg))
|
|
||||||
reqPeerLock.Lock()
|
|
||||||
reqPeer[i] = peerEndpointPtr{
|
|
||||||
peer: peer,
|
|
||||||
endpoint: &peer.endpoint,
|
|
||||||
}
|
|
||||||
reqPeerLock.Unlock()
|
|
||||||
peer.RUnlock()
|
|
||||||
i++
|
|
||||||
_, err := bind.netlinkCancel.Write((*[unsafe.Sizeof(nlmsg)]byte)(unsafe.Pointer(&nlmsg))[:])
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
device.peers.RUnlock()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
remain = remain[hdr.Len:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
/* Specification constants */
|
/* Specification constants */
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RekeyAfterMessages = (1 << 64) - (1 << 16) - 1
|
RekeyAfterMessages = (1 << 60)
|
||||||
RejectAfterMessages = (1 << 64) - (1 << 4) - 1
|
RejectAfterMessages = (1 << 64) - (1 << 13) - 1
|
||||||
RekeyAfterTime = time.Second * 120
|
RekeyAfterTime = time.Second * 120
|
||||||
RekeyAttemptTime = time.Second * 90
|
RekeyAttemptTime = time.Second * 90
|
||||||
RekeyTimeout = time.Second * 5
|
RekeyTimeout = time.Second * 5
|
||||||
@@ -22,7 +22,7 @@ const (
|
|||||||
RejectAfterTime = time.Second * 180
|
RejectAfterTime = time.Second * 180
|
||||||
KeepaliveTimeout = time.Second * 10
|
KeepaliveTimeout = time.Second * 10
|
||||||
CookieRefreshTime = time.Second * 120
|
CookieRefreshTime = time.Second * 120
|
||||||
HandshakeInitationRate = time.Second / 20
|
HandshakeInitationRate = time.Second / 50
|
||||||
PaddingMultiple = 16
|
PaddingMultiple = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,7 +35,6 @@ const (
|
|||||||
/* Implementation constants */
|
/* Implementation constants */
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UnderLoadQueueSize = QueueHandshakeSize / 8
|
|
||||||
UnderLoadAfterTime = time.Second // how long does the device remain under load after detected
|
UnderLoadAfterTime = time.Second // how long does the device remain under load after detected
|
||||||
MaxPeers = 1 << 16 // maximum number of configured peers
|
MaxPeers = 1 << 16 // maximum number of configured peers
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -8,10 +8,11 @@ package device
|
|||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"golang.org/x/crypto/blake2s"
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/blake2s"
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CookieChecker struct {
|
type CookieChecker struct {
|
||||||
@@ -86,7 +87,7 @@ func (st *CookieChecker) CheckMAC2(msg []byte, src []byte) bool {
|
|||||||
st.RLock()
|
st.RLock()
|
||||||
defer st.RUnlock()
|
defer st.RUnlock()
|
||||||
|
|
||||||
if time.Now().Sub(st.mac2.secretSet) > CookieRefreshTime {
|
if time.Since(st.mac2.secretSet) > CookieRefreshTime {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ func (st *CookieChecker) CreateReply(
|
|||||||
|
|
||||||
// refresh cookie secret
|
// refresh cookie secret
|
||||||
|
|
||||||
if time.Now().Sub(st.mac2.secretSet) > CookieRefreshTime {
|
if time.Since(st.mac2.secretSet) > CookieRefreshTime {
|
||||||
st.RUnlock()
|
st.RUnlock()
|
||||||
st.Lock()
|
st.Lock()
|
||||||
_, err := rand.Read(st.mac2.secret[:])
|
_, err := rand.Read(st.mac2.secret[:])
|
||||||
@@ -238,7 +239,7 @@ func (st *CookieGenerator) AddMacs(msg []byte) {
|
|||||||
|
|
||||||
// set mac2
|
// set mac2
|
||||||
|
|
||||||
if time.Now().Sub(st.mac2.cookieSet) > CookieRefreshTime {
|
if time.Since(st.mac2.cookieSet) > CookieRefreshTime {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|||||||
538
device/device.go
538
device/device.go
@@ -1,46 +1,49 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.zx2c4.com/wireguard/ratelimiter"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
DeviceRoutineNumberPerCPU = 3
|
"golang.zx2c4.com/wireguard/ratelimiter"
|
||||||
DeviceRoutineNumberAdditional = 2
|
"golang.zx2c4.com/wireguard/rwcancel"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
isUp AtomicBool // device is (going) up
|
|
||||||
isClosed AtomicBool // device is closed? (acting as guard)
|
|
||||||
log *Logger
|
|
||||||
|
|
||||||
// synchronized resources (locks acquired in order)
|
|
||||||
|
|
||||||
state struct {
|
state struct {
|
||||||
starting sync.WaitGroup
|
// state holds the device's state. It is accessed atomically.
|
||||||
|
// Use the device.deviceState method to read it.
|
||||||
|
// device.deviceState does not acquire the mutex, so it captures only a snapshot.
|
||||||
|
// During state transitions, the state variable is updated before the device itself.
|
||||||
|
// The state is thus either the current state of the device or
|
||||||
|
// the intended future state of the device.
|
||||||
|
// For example, while executing a call to Up, state will be deviceStateUp.
|
||||||
|
// There is no guarantee that that intended future state of the device
|
||||||
|
// will become the actual state; Up can fail.
|
||||||
|
// The device can also change state multiple times between time of check and time of use.
|
||||||
|
// Unsynchronized uses of state must therefore be advisory/best-effort only.
|
||||||
|
state uint32 // actually a deviceState, but typed uint32 for convenience
|
||||||
|
// stopping blocks until all inputs to Device have been closed.
|
||||||
stopping sync.WaitGroup
|
stopping sync.WaitGroup
|
||||||
|
// mu protects state changes.
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
changing AtomicBool
|
|
||||||
current bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
net struct {
|
net struct {
|
||||||
starting sync.WaitGroup
|
|
||||||
stopping sync.WaitGroup
|
stopping sync.WaitGroup
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
bind Bind // bind interface
|
bind conn.Bind // bind interface
|
||||||
port uint16 // listening port
|
netlinkCancel *rwcancel.RWCancel
|
||||||
fwmark uint32 // mark value (0 = disabled)
|
port uint16 // listening port
|
||||||
|
fwmark uint32 // mark value (0 = disabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
staticIdentity struct {
|
staticIdentity struct {
|
||||||
@@ -49,168 +52,190 @@ type Device struct {
|
|||||||
publicKey NoisePublicKey
|
publicKey NoisePublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
peers struct {
|
rate struct {
|
||||||
sync.RWMutex
|
underLoadUntil int64
|
||||||
keyMap map[NoisePublicKey]*Peer
|
limiter ratelimiter.Ratelimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
// unprotected / "self-synchronising resources"
|
peers struct {
|
||||||
|
sync.RWMutex // protects keyMap
|
||||||
|
keyMap map[NoisePublicKey]*Peer
|
||||||
|
}
|
||||||
|
|
||||||
allowedips AllowedIPs
|
allowedips AllowedIPs
|
||||||
indexTable IndexTable
|
indexTable IndexTable
|
||||||
cookieChecker CookieChecker
|
cookieChecker CookieChecker
|
||||||
|
|
||||||
rate struct {
|
|
||||||
underLoadUntil atomic.Value
|
|
||||||
limiter ratelimiter.Ratelimiter
|
|
||||||
}
|
|
||||||
|
|
||||||
pool struct {
|
pool struct {
|
||||||
messageBufferPool *sync.Pool
|
messageBuffers *WaitPool
|
||||||
messageBufferReuseChan chan *[MaxMessageSize]byte
|
inboundElements *WaitPool
|
||||||
inboundElementPool *sync.Pool
|
outboundElements *WaitPool
|
||||||
inboundElementReuseChan chan *QueueInboundElement
|
|
||||||
outboundElementPool *sync.Pool
|
|
||||||
outboundElementReuseChan chan *QueueOutboundElement
|
|
||||||
}
|
}
|
||||||
|
|
||||||
queue struct {
|
queue struct {
|
||||||
encryption chan *QueueOutboundElement
|
encryption *outboundQueue
|
||||||
decryption chan *QueueInboundElement
|
decryption *inboundQueue
|
||||||
handshake chan QueueHandshakeElement
|
handshake *handshakeQueue
|
||||||
}
|
|
||||||
|
|
||||||
signals struct {
|
|
||||||
stop chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tun struct {
|
tun struct {
|
||||||
device tun.TUNDevice
|
device tun.Device
|
||||||
mtu int32
|
mtu int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcMutex sync.RWMutex
|
||||||
|
closed chan struct{}
|
||||||
|
log *Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Converts the peer into a "zombie", which remains in the peer map,
|
// deviceState represents the state of a Device.
|
||||||
* but processes no packets and does not exists in the routing table.
|
// There are three states: down, up, closed.
|
||||||
*
|
// Transitions:
|
||||||
* Must hold device.peers.Mutex
|
//
|
||||||
*/
|
// down -----+
|
||||||
func unsafeRemovePeer(device *Device, peer *Peer, key NoisePublicKey) {
|
// ↑↓ ↓
|
||||||
|
// up -> closed
|
||||||
|
//
|
||||||
|
type deviceState uint32
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/tools/cmd/stringer -type deviceState -trimprefix=deviceState
|
||||||
|
const (
|
||||||
|
deviceStateDown deviceState = iota
|
||||||
|
deviceStateUp
|
||||||
|
deviceStateClosed
|
||||||
|
)
|
||||||
|
|
||||||
|
// deviceState returns device.state.state as a deviceState
|
||||||
|
// See those docs for how to interpret this value.
|
||||||
|
func (device *Device) deviceState() deviceState {
|
||||||
|
return deviceState(atomic.LoadUint32(&device.state.state))
|
||||||
|
}
|
||||||
|
|
||||||
|
// isClosed reports whether the device is closed (or is closing).
|
||||||
|
// See device.state.state comments for how to interpret this value.
|
||||||
|
func (device *Device) isClosed() bool {
|
||||||
|
return device.deviceState() == deviceStateClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// isUp reports whether the device is up (or is attempting to come up).
|
||||||
|
// See device.state.state comments for how to interpret this value.
|
||||||
|
func (device *Device) isUp() bool {
|
||||||
|
return device.deviceState() == deviceStateUp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must hold device.peers.Lock()
|
||||||
|
func removePeerLocked(device *Device, peer *Peer, key NoisePublicKey) {
|
||||||
// stop routing and processing of packets
|
// stop routing and processing of packets
|
||||||
|
|
||||||
device.allowedips.RemoveByPeer(peer)
|
device.allowedips.RemoveByPeer(peer)
|
||||||
peer.Stop()
|
peer.Stop()
|
||||||
|
|
||||||
// remove from peer map
|
// remove from peer map
|
||||||
|
|
||||||
delete(device.peers.keyMap, key)
|
delete(device.peers.keyMap, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func deviceUpdateState(device *Device) {
|
// changeState attempts to change the device state to match want.
|
||||||
|
func (device *Device) changeState(want deviceState) (err error) {
|
||||||
// check if state already being updated (guard)
|
|
||||||
|
|
||||||
if device.state.changing.Swap(true) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// compare to current state of device
|
|
||||||
|
|
||||||
device.state.Lock()
|
device.state.Lock()
|
||||||
|
defer device.state.Unlock()
|
||||||
newIsUp := device.isUp.Get()
|
old := device.deviceState()
|
||||||
|
if old == deviceStateClosed {
|
||||||
if newIsUp == device.state.current {
|
// once closed, always closed
|
||||||
device.state.changing.Set(false)
|
device.log.Verbosef("Interface closed, ignored requested state %s", want)
|
||||||
device.state.Unlock()
|
return nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
switch want {
|
||||||
// change state of device
|
case old:
|
||||||
|
return nil
|
||||||
switch newIsUp {
|
case deviceStateUp:
|
||||||
case true:
|
atomic.StoreUint32(&device.state.state, uint32(deviceStateUp))
|
||||||
if err := device.BindUpdate(); err != nil {
|
err = device.upLocked()
|
||||||
device.isUp.Set(false)
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
device.peers.RLock()
|
fallthrough // up failed; bring the device all the way back down
|
||||||
for _, peer := range device.peers.keyMap {
|
case deviceStateDown:
|
||||||
peer.Start()
|
atomic.StoreUint32(&device.state.state, uint32(deviceStateDown))
|
||||||
if peer.persistentKeepaliveInterval > 0 {
|
errDown := device.downLocked()
|
||||||
peer.SendKeepalive()
|
if err == nil {
|
||||||
}
|
err = errDown
|
||||||
}
|
}
|
||||||
device.peers.RUnlock()
|
|
||||||
|
|
||||||
case false:
|
|
||||||
device.BindClose()
|
|
||||||
device.peers.RLock()
|
|
||||||
for _, peer := range device.peers.keyMap {
|
|
||||||
peer.Stop()
|
|
||||||
}
|
|
||||||
device.peers.RUnlock()
|
|
||||||
}
|
}
|
||||||
|
device.log.Verbosef("Interface state was %s, requested %s, now %s", old, want, device.deviceState())
|
||||||
// update state variables
|
return
|
||||||
|
|
||||||
device.state.current = newIsUp
|
|
||||||
device.state.changing.Set(false)
|
|
||||||
device.state.Unlock()
|
|
||||||
|
|
||||||
// check for state change in the mean time
|
|
||||||
|
|
||||||
deviceUpdateState(device)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) Up() {
|
// upLocked attempts to bring the device up and reports whether it succeeded.
|
||||||
|
// The caller must hold device.state.mu and is responsible for updating device.state.state.
|
||||||
// closed device cannot be brought up
|
func (device *Device) upLocked() error {
|
||||||
|
if err := device.BindUpdate(); err != nil {
|
||||||
if device.isClosed.Get() {
|
device.log.Errorf("Unable to update bind: %v", err)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
device.isUp.Set(true)
|
device.peers.RLock()
|
||||||
deviceUpdateState(device)
|
for _, peer := range device.peers.keyMap {
|
||||||
|
peer.Start()
|
||||||
|
if atomic.LoadUint32(&peer.persistentKeepaliveInterval) > 0 {
|
||||||
|
peer.SendKeepalive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
device.peers.RUnlock()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) Down() {
|
// downLocked attempts to bring the device down.
|
||||||
device.isUp.Set(false)
|
// The caller must hold device.state.mu and is responsible for updating device.state.state.
|
||||||
deviceUpdateState(device)
|
func (device *Device) downLocked() error {
|
||||||
|
err := device.BindClose()
|
||||||
|
if err != nil {
|
||||||
|
device.log.Errorf("Bind close failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
device.peers.RLock()
|
||||||
|
for _, peer := range device.peers.keyMap {
|
||||||
|
peer.Stop()
|
||||||
|
}
|
||||||
|
device.peers.RUnlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) Up() error {
|
||||||
|
return device.changeState(deviceStateUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) Down() error {
|
||||||
|
return device.changeState(deviceStateDown)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) IsUnderLoad() bool {
|
func (device *Device) IsUnderLoad() bool {
|
||||||
|
|
||||||
// check if currently under load
|
// check if currently under load
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
underLoad := len(device.queue.handshake) >= UnderLoadQueueSize
|
underLoad := len(device.queue.handshake.c) >= QueueHandshakeSize/8
|
||||||
if underLoad {
|
if underLoad {
|
||||||
device.rate.underLoadUntil.Store(now.Add(UnderLoadAfterTime))
|
atomic.StoreInt64(&device.rate.underLoadUntil, now.Add(UnderLoadAfterTime).UnixNano())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if recently under load
|
// check if recently under load
|
||||||
|
return atomic.LoadInt64(&device.rate.underLoadUntil) > now.UnixNano()
|
||||||
until := device.rate.underLoadUntil.Load().(time.Time)
|
|
||||||
return until.After(now)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
||||||
|
|
||||||
// lock required resources
|
// lock required resources
|
||||||
|
|
||||||
device.staticIdentity.Lock()
|
device.staticIdentity.Lock()
|
||||||
defer device.staticIdentity.Unlock()
|
defer device.staticIdentity.Unlock()
|
||||||
|
|
||||||
|
if sk.Equals(device.staticIdentity.privateKey) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
device.peers.Lock()
|
device.peers.Lock()
|
||||||
defer device.peers.Unlock()
|
defer device.peers.Unlock()
|
||||||
|
|
||||||
|
lockedPeers := make([]*Peer, 0, len(device.peers.keyMap))
|
||||||
for _, peer := range device.peers.keyMap {
|
for _, peer := range device.peers.keyMap {
|
||||||
peer.handshake.mutex.RLock()
|
peer.handshake.mutex.RLock()
|
||||||
defer peer.handshake.mutex.RUnlock()
|
lockedPeers = append(lockedPeers, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove peers with matching public keys
|
// remove peers with matching public keys
|
||||||
@@ -218,7 +243,9 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
|||||||
publicKey := sk.publicKey()
|
publicKey := sk.publicKey()
|
||||||
for key, peer := range device.peers.keyMap {
|
for key, peer := range device.peers.keyMap {
|
||||||
if peer.handshake.remoteStatic.Equals(publicKey) {
|
if peer.handshake.remoteStatic.Equals(publicKey) {
|
||||||
unsafeRemovePeer(device, peer, key)
|
peer.handshake.mutex.RUnlock()
|
||||||
|
removePeerLocked(device, peer, key)
|
||||||
|
peer.handshake.mutex.RLock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,85 +257,63 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
|||||||
|
|
||||||
// do static-static DH pre-computations
|
// do static-static DH pre-computations
|
||||||
|
|
||||||
rmKey := device.staticIdentity.privateKey.IsZero()
|
expiredPeers := make([]*Peer, 0, len(device.peers.keyMap))
|
||||||
|
for _, peer := range device.peers.keyMap {
|
||||||
for key, peer := range device.peers.keyMap {
|
|
||||||
|
|
||||||
handshake := &peer.handshake
|
handshake := &peer.handshake
|
||||||
|
handshake.precomputedStaticStatic = device.staticIdentity.privateKey.sharedSecret(handshake.remoteStatic)
|
||||||
|
expiredPeers = append(expiredPeers, peer)
|
||||||
|
}
|
||||||
|
|
||||||
if rmKey {
|
for _, peer := range lockedPeers {
|
||||||
handshake.precomputedStaticStatic = [NoisePublicKeySize]byte{}
|
peer.handshake.mutex.RUnlock()
|
||||||
} else {
|
}
|
||||||
handshake.precomputedStaticStatic = device.staticIdentity.privateKey.sharedSecret(handshake.remoteStatic)
|
for _, peer := range expiredPeers {
|
||||||
}
|
peer.ExpireCurrentKeypairs()
|
||||||
|
|
||||||
if isZero(handshake.precomputedStaticStatic[:]) {
|
|
||||||
unsafeRemovePeer(device, peer, key)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDevice(tunDevice tun.TUNDevice, logger *Logger) *Device {
|
func NewDevice(tunDevice tun.Device, bind conn.Bind, logger *Logger) *Device {
|
||||||
device := new(Device)
|
device := new(Device)
|
||||||
|
device.state.state = uint32(deviceStateDown)
|
||||||
device.isUp.Set(false)
|
device.closed = make(chan struct{})
|
||||||
device.isClosed.Set(false)
|
|
||||||
|
|
||||||
device.log = logger
|
device.log = logger
|
||||||
|
device.net.bind = bind
|
||||||
device.tun.device = tunDevice
|
device.tun.device = tunDevice
|
||||||
mtu, err := device.tun.device.MTU()
|
mtu, err := device.tun.device.MTU()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Println("Trouble determining MTU, assuming default:", err)
|
device.log.Errorf("Trouble determining MTU, assuming default: %v", err)
|
||||||
mtu = DefaultMTU
|
mtu = DefaultMTU
|
||||||
}
|
}
|
||||||
device.tun.mtu = int32(mtu)
|
device.tun.mtu = int32(mtu)
|
||||||
|
|
||||||
device.peers.keyMap = make(map[NoisePublicKey]*Peer)
|
device.peers.keyMap = make(map[NoisePublicKey]*Peer)
|
||||||
|
|
||||||
device.rate.limiter.Init()
|
device.rate.limiter.Init()
|
||||||
device.rate.underLoadUntil.Store(time.Time{})
|
|
||||||
|
|
||||||
device.indexTable.Init()
|
device.indexTable.Init()
|
||||||
device.allowedips.Reset()
|
|
||||||
|
|
||||||
device.PopulatePools()
|
device.PopulatePools()
|
||||||
|
|
||||||
// create queues
|
// create queues
|
||||||
|
|
||||||
device.queue.handshake = make(chan QueueHandshakeElement, QueueHandshakeSize)
|
device.queue.handshake = newHandshakeQueue()
|
||||||
device.queue.encryption = make(chan *QueueOutboundElement, QueueOutboundSize)
|
device.queue.encryption = newOutboundQueue()
|
||||||
device.queue.decryption = make(chan *QueueInboundElement, QueueInboundSize)
|
device.queue.decryption = newInboundQueue()
|
||||||
|
|
||||||
// prepare signals
|
|
||||||
|
|
||||||
device.signals.stop = make(chan struct{})
|
|
||||||
|
|
||||||
// prepare net
|
|
||||||
|
|
||||||
device.net.port = 0
|
|
||||||
device.net.bind = nil
|
|
||||||
|
|
||||||
// start workers
|
// start workers
|
||||||
|
|
||||||
cpus := runtime.NumCPU()
|
cpus := runtime.NumCPU()
|
||||||
device.state.starting.Wait()
|
|
||||||
device.state.stopping.Wait()
|
device.state.stopping.Wait()
|
||||||
device.state.stopping.Add(DeviceRoutineNumberPerCPU*cpus + DeviceRoutineNumberAdditional)
|
device.queue.encryption.wg.Add(cpus) // One for each RoutineHandshake
|
||||||
device.state.starting.Add(DeviceRoutineNumberPerCPU*cpus + DeviceRoutineNumberAdditional)
|
for i := 0; i < cpus; i++ {
|
||||||
for i := 0; i < cpus; i += 1 {
|
go device.RoutineEncryption(i + 1)
|
||||||
go device.RoutineEncryption()
|
go device.RoutineDecryption(i + 1)
|
||||||
go device.RoutineDecryption()
|
go device.RoutineHandshake(i + 1)
|
||||||
go device.RoutineHandshake()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
device.state.stopping.Add(1) // RoutineReadFromTUN
|
||||||
|
device.queue.encryption.wg.Add(1) // RoutineReadFromTUN
|
||||||
go device.RoutineReadFromTUN()
|
go device.RoutineReadFromTUN()
|
||||||
go device.RoutineTUNEventReader()
|
go device.RoutineTUNEventReader()
|
||||||
|
|
||||||
device.state.starting.Wait()
|
|
||||||
|
|
||||||
return device
|
return device
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,12 +327,11 @@ func (device *Device) LookupPeer(pk NoisePublicKey) *Peer {
|
|||||||
func (device *Device) RemovePeer(key NoisePublicKey) {
|
func (device *Device) RemovePeer(key NoisePublicKey) {
|
||||||
device.peers.Lock()
|
device.peers.Lock()
|
||||||
defer device.peers.Unlock()
|
defer device.peers.Unlock()
|
||||||
|
|
||||||
// stop peer and remove from routing
|
// stop peer and remove from routing
|
||||||
|
|
||||||
peer, ok := device.peers.keyMap[key]
|
peer, ok := device.peers.keyMap[key]
|
||||||
if ok {
|
if ok {
|
||||||
unsafeRemovePeer(device, peer, key)
|
removePeerLocked(device, peer, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,61 +340,179 @@ func (device *Device) RemoveAllPeers() {
|
|||||||
defer device.peers.Unlock()
|
defer device.peers.Unlock()
|
||||||
|
|
||||||
for key, peer := range device.peers.keyMap {
|
for key, peer := range device.peers.keyMap {
|
||||||
unsafeRemovePeer(device, peer, key)
|
removePeerLocked(device, peer, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
device.peers.keyMap = make(map[NoisePublicKey]*Peer)
|
device.peers.keyMap = make(map[NoisePublicKey]*Peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) FlushPacketQueues() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case elem, ok := <-device.queue.decryption:
|
|
||||||
if ok {
|
|
||||||
elem.Drop()
|
|
||||||
}
|
|
||||||
case elem, ok := <-device.queue.encryption:
|
|
||||||
if ok {
|
|
||||||
elem.Drop()
|
|
||||||
}
|
|
||||||
case <-device.queue.handshake:
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (device *Device) Close() {
|
func (device *Device) Close() {
|
||||||
if device.isClosed.Swap(true) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
device.state.starting.Wait()
|
|
||||||
|
|
||||||
device.log.Info.Println("Device closing")
|
|
||||||
device.state.changing.Set(true)
|
|
||||||
device.state.Lock()
|
device.state.Lock()
|
||||||
defer device.state.Unlock()
|
defer device.state.Unlock()
|
||||||
|
if device.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
atomic.StoreUint32(&device.state.state, uint32(deviceStateClosed))
|
||||||
|
device.log.Verbosef("Device closing")
|
||||||
|
|
||||||
device.tun.device.Close()
|
device.tun.device.Close()
|
||||||
device.BindClose()
|
device.downLocked()
|
||||||
|
|
||||||
device.isUp.Set(false)
|
|
||||||
|
|
||||||
close(device.signals.stop)
|
|
||||||
|
|
||||||
|
// Remove peers before closing queues,
|
||||||
|
// because peers assume that queues are active.
|
||||||
device.RemoveAllPeers()
|
device.RemoveAllPeers()
|
||||||
|
|
||||||
|
// We kept a reference to the encryption and decryption queues,
|
||||||
|
// in case we started any new peers that might write to them.
|
||||||
|
// No new peers are coming; we are done with these queues.
|
||||||
|
device.queue.encryption.wg.Done()
|
||||||
|
device.queue.decryption.wg.Done()
|
||||||
|
device.queue.handshake.wg.Done()
|
||||||
device.state.stopping.Wait()
|
device.state.stopping.Wait()
|
||||||
device.FlushPacketQueues()
|
|
||||||
|
|
||||||
device.rate.limiter.Close()
|
device.rate.limiter.Close()
|
||||||
|
|
||||||
device.state.changing.Set(false)
|
device.log.Verbosef("Device closed")
|
||||||
device.log.Info.Println("Interface closed")
|
close(device.closed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) Wait() chan struct{} {
|
func (device *Device) Wait() chan struct{} {
|
||||||
return device.signals.stop
|
return device.closed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) SendKeepalivesToPeersWithCurrentKeypair() {
|
||||||
|
if !device.isUp() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
device.peers.RLock()
|
||||||
|
for _, peer := range device.peers.keyMap {
|
||||||
|
peer.keypairs.RLock()
|
||||||
|
sendKeepalive := peer.keypairs.current != nil && !peer.keypairs.current.created.Add(RejectAfterTime).Before(time.Now())
|
||||||
|
peer.keypairs.RUnlock()
|
||||||
|
if sendKeepalive {
|
||||||
|
peer.SendKeepalive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
device.peers.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeBindLocked closes the device's net.bind.
|
||||||
|
// The caller must hold the net mutex.
|
||||||
|
func closeBindLocked(device *Device) error {
|
||||||
|
var err error
|
||||||
|
netc := &device.net
|
||||||
|
if netc.netlinkCancel != nil {
|
||||||
|
netc.netlinkCancel.Cancel()
|
||||||
|
}
|
||||||
|
if netc.bind != nil {
|
||||||
|
err = netc.bind.Close()
|
||||||
|
}
|
||||||
|
netc.stopping.Wait()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) Bind() conn.Bind {
|
||||||
|
device.net.Lock()
|
||||||
|
defer device.net.Unlock()
|
||||||
|
return device.net.bind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) BindSetMark(mark uint32) error {
|
||||||
|
device.net.Lock()
|
||||||
|
defer device.net.Unlock()
|
||||||
|
|
||||||
|
// check if modified
|
||||||
|
if device.net.fwmark == mark {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// update fwmark on existing bind
|
||||||
|
device.net.fwmark = mark
|
||||||
|
if device.isUp() && device.net.bind != nil {
|
||||||
|
if err := device.net.bind.SetMark(mark); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear cached source addresses
|
||||||
|
device.peers.RLock()
|
||||||
|
for _, peer := range device.peers.keyMap {
|
||||||
|
peer.Lock()
|
||||||
|
defer peer.Unlock()
|
||||||
|
if peer.endpoint != nil {
|
||||||
|
peer.endpoint.ClearSrc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
device.peers.RUnlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) BindUpdate() error {
|
||||||
|
device.net.Lock()
|
||||||
|
defer device.net.Unlock()
|
||||||
|
|
||||||
|
// close existing sockets
|
||||||
|
if err := closeBindLocked(device); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// open new sockets
|
||||||
|
if !device.isUp() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind to new port
|
||||||
|
var err error
|
||||||
|
var recvFns []conn.ReceiveFunc
|
||||||
|
netc := &device.net
|
||||||
|
recvFns, netc.port, err = netc.bind.Open(netc.port)
|
||||||
|
if err != nil {
|
||||||
|
netc.port = 0
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
netc.netlinkCancel, err = device.startRouteListener(netc.bind)
|
||||||
|
if err != nil {
|
||||||
|
netc.bind.Close()
|
||||||
|
netc.port = 0
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// set fwmark
|
||||||
|
if netc.fwmark != 0 {
|
||||||
|
err = netc.bind.SetMark(netc.fwmark)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear cached source addresses
|
||||||
|
device.peers.RLock()
|
||||||
|
for _, peer := range device.peers.keyMap {
|
||||||
|
peer.Lock()
|
||||||
|
defer peer.Unlock()
|
||||||
|
if peer.endpoint != nil {
|
||||||
|
peer.endpoint.ClearSrc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
device.peers.RUnlock()
|
||||||
|
|
||||||
|
// start receiving routines
|
||||||
|
device.net.stopping.Add(len(recvFns))
|
||||||
|
device.queue.decryption.wg.Add(len(recvFns)) // each RoutineReceiveIncoming goroutine writes to device.queue.decryption
|
||||||
|
device.queue.handshake.wg.Add(len(recvFns)) // each RoutineReceiveIncoming goroutine writes to device.queue.handshake
|
||||||
|
for _, fn := range recvFns {
|
||||||
|
go device.RoutineReceiveIncoming(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
device.log.Verbosef("UDP bind has been updated")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) BindClose() error {
|
||||||
|
device.net.Lock()
|
||||||
|
err := closeBindLocked(device)
|
||||||
|
device.net.Unlock()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,407 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
/* Create two device instances and simulate full WireGuard interaction
|
import (
|
||||||
* without network dependencies
|
"bytes"
|
||||||
*/
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
import "testing"
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/conn/bindtest"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/tuntest"
|
||||||
|
)
|
||||||
|
|
||||||
func TestDevice(t *testing.T) {
|
// uapiCfg returns a string that contains cfg formatted use with IpcSet.
|
||||||
|
// cfg is a series of alternating key/value strings.
|
||||||
// prepare tun devices for generating traffic
|
// uapiCfg exists because editors and humans like to insert
|
||||||
|
// whitespace into configs, which can cause failures, some of which are silent.
|
||||||
tun1, err := CreateDummyTUN("tun1")
|
// For example, a leading blank newline causes the remainder
|
||||||
if err != nil {
|
// of the config to be silently ignored.
|
||||||
t.Error("failed to create tun:", err.Error())
|
func uapiCfg(cfg ...string) string {
|
||||||
|
if len(cfg)%2 != 0 {
|
||||||
|
panic("odd number of args to uapiReader")
|
||||||
}
|
}
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
tun2, err := CreateDummyTUN("tun2")
|
for i, s := range cfg {
|
||||||
if err != nil {
|
buf.WriteString(s)
|
||||||
t.Error("failed to create tun:", err.Error())
|
sep := byte('\n')
|
||||||
|
if i%2 == 0 {
|
||||||
|
sep = '='
|
||||||
|
}
|
||||||
|
buf.WriteByte(sep)
|
||||||
}
|
}
|
||||||
|
return buf.String()
|
||||||
_ = tun1
|
}
|
||||||
_ = tun2
|
|
||||||
|
// genConfigs generates a pair of configs that connect to each other.
|
||||||
// prepare endpoints
|
// The configs use distinct, probably-usable ports.
|
||||||
|
func genConfigs(tb testing.TB) (cfgs [2]string, endpointCfgs [2]string) {
|
||||||
end1, err := CreateDummyEndpoint()
|
var key1, key2 NoisePrivateKey
|
||||||
if err != nil {
|
_, err := rand.Read(key1[:])
|
||||||
t.Error("failed to create endpoint:", err.Error())
|
if err != nil {
|
||||||
}
|
tb.Errorf("unable to generate private key random bytes: %v", err)
|
||||||
|
}
|
||||||
end2, err := CreateDummyEndpoint()
|
_, err = rand.Read(key2[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("failed to create endpoint:", err.Error())
|
tb.Errorf("unable to generate private key random bytes: %v", err)
|
||||||
}
|
}
|
||||||
|
pub1, pub2 := key1.publicKey(), key2.publicKey()
|
||||||
_ = end1
|
|
||||||
_ = end2
|
cfgs[0] = uapiCfg(
|
||||||
|
"private_key", hex.EncodeToString(key1[:]),
|
||||||
// create binds
|
"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 net.IP
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = net.IPv4(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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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 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 := atomic.AddUint64(&recv, 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 atomic.LoadUint64(&recv) != 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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
16
device/devicestate_string.go
Normal file
16
device/devicestate_string.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// Code generated by "stringer -type deviceState -trimprefix=deviceState"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
const _deviceState_name = "DownUpClosed"
|
||||||
|
|
||||||
|
var _deviceState_index = [...]uint8{0, 4, 6, 12}
|
||||||
|
|
||||||
|
func (i deviceState) String() string {
|
||||||
|
if i >= deviceState(len(_deviceState_index)-1) {
|
||||||
|
return "deviceState(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _deviceState_name[_deviceState_index[i]:_deviceState_index[i+1]]
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexTableEntry struct {
|
type IndexTableEntry struct {
|
||||||
@@ -25,7 +25,8 @@ type IndexTable struct {
|
|||||||
func randUint32() (uint32, error) {
|
func randUint32() (uint32, error) {
|
||||||
var integer [4]byte
|
var integer [4]byte
|
||||||
_, err := rand.Read(integer[:])
|
_, err := rand.Read(integer[:])
|
||||||
return *(*uint32)(unsafe.Pointer(&integer[0])), err
|
// Arbitrary endianness; both are intrinsified by the Go compiler.
|
||||||
|
return binary.LittleEndian.Uint32(integer[:]), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (table *IndexTable) Init() {
|
func (table *IndexTable) Init() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"golang.org/x/crypto/blake2s"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/blake2s"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KDFTest struct {
|
type KDFTest struct {
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"golang.zx2c4.com/wireguard/replay"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/replay"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Due to limitations in Go and /x/crypto there is currently
|
/* Due to limitations in Go and /x/crypto there is currently
|
||||||
@@ -20,10 +23,10 @@ import (
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
type Keypair struct {
|
type Keypair struct {
|
||||||
sendNonce uint64
|
sendNonce uint64 // accessed atomically
|
||||||
send cipher.AEAD
|
send cipher.AEAD
|
||||||
receive cipher.AEAD
|
receive cipher.AEAD
|
||||||
replayFilter replay.ReplayFilter
|
replayFilter replay.Filter
|
||||||
isInitiator bool
|
isInitiator bool
|
||||||
created time.Time
|
created time.Time
|
||||||
localIndex uint32
|
localIndex uint32
|
||||||
@@ -37,6 +40,14 @@ type Keypairs struct {
|
|||||||
next *Keypair
|
next *Keypair
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (kp *Keypairs) storeNext(next *Keypair) {
|
||||||
|
atomic.StorePointer((*unsafe.Pointer)((unsafe.Pointer)(&kp.next)), (unsafe.Pointer)(next))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kp *Keypairs) loadNext() *Keypair {
|
||||||
|
return (*Keypair)(atomic.LoadPointer((*unsafe.Pointer)((unsafe.Pointer)(&kp.next))))
|
||||||
|
}
|
||||||
|
|
||||||
func (kp *Keypairs) Current() *Keypair {
|
func (kp *Keypairs) Current() *Keypair {
|
||||||
kp.RLock()
|
kp.RLock()
|
||||||
defer kp.RUnlock()
|
defer kp.RUnlock()
|
||||||
|
|||||||
@@ -1,59 +1,48 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A Logger provides logging for a Device.
|
||||||
|
// The functions are Printf-style functions.
|
||||||
|
// They must be safe for concurrent use.
|
||||||
|
// They do not require a trailing newline in the format.
|
||||||
|
// If nil, that level of logging will be silent.
|
||||||
|
type Logger struct {
|
||||||
|
Verbosef func(format string, args ...interface{})
|
||||||
|
Errorf func(format string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log levels for use with NewLogger.
|
||||||
const (
|
const (
|
||||||
LogLevelSilent = iota
|
LogLevelSilent = iota
|
||||||
LogLevelError
|
LogLevelError
|
||||||
LogLevelInfo
|
LogLevelVerbose
|
||||||
LogLevelDebug
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Logger struct {
|
// Function for use in Logger for discarding logged lines.
|
||||||
Debug *log.Logger
|
func DiscardLogf(format string, args ...interface{}) {}
|
||||||
Info *log.Logger
|
|
||||||
Error *log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// NewLogger constructs a Logger that writes to stdout.
|
||||||
|
// It logs at the specified log level and above.
|
||||||
|
// It decorates log lines with the log level, date, time, and prepend.
|
||||||
func NewLogger(level int, prepend string) *Logger {
|
func NewLogger(level int, prepend string) *Logger {
|
||||||
output := os.Stdout
|
logger := &Logger{DiscardLogf, DiscardLogf}
|
||||||
logger := new(Logger)
|
logf := func(prefix string) func(string, ...interface{}) {
|
||||||
|
return log.New(os.Stdout, prefix+": "+prepend, log.Ldate|log.Ltime).Printf
|
||||||
logErr, logInfo, logDebug := func() (io.Writer, io.Writer, io.Writer) {
|
}
|
||||||
if level >= LogLevelDebug {
|
if level >= LogLevelVerbose {
|
||||||
return output, output, output
|
logger.Verbosef = logf("DEBUG")
|
||||||
}
|
}
|
||||||
if level >= LogLevelInfo {
|
if level >= LogLevelError {
|
||||||
return output, output, ioutil.Discard
|
logger.Errorf = logf("ERROR")
|
||||||
}
|
}
|
||||||
if level >= LogLevelError {
|
|
||||||
return output, ioutil.Discard, ioutil.Discard
|
|
||||||
}
|
|
||||||
return ioutil.Discard, ioutil.Discard, ioutil.Discard
|
|
||||||
}()
|
|
||||||
|
|
||||||
logger.Debug = log.New(logDebug,
|
|
||||||
"DEBUG: "+prepend,
|
|
||||||
log.Ldate|log.Ltime,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.Info = log.New(logInfo,
|
|
||||||
"INFO: "+prepend,
|
|
||||||
log.Ldate|log.Ltime,
|
|
||||||
)
|
|
||||||
logger.Error = log.New(logErr,
|
|
||||||
"ERROR: "+prepend,
|
|
||||||
log.Ldate|log.Ltime,
|
|
||||||
)
|
|
||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
// +build !linux,!openbsd,!freebsd
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package device
|
|
||||||
|
|
||||||
func (bind *nativeBind) SetMark(mark uint32) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -39,10 +39,3 @@ func (a *AtomicBool) Set(val bool) {
|
|||||||
}
|
}
|
||||||
atomic.StoreInt32(&a.int32, flag)
|
atomic.StoreInt32(&a.int32, flag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func min(a, b uint) uint {
|
|
||||||
if a > b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|||||||
16
device/mobilequirks.go
Normal file
16
device/mobilequirks.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
func (device *Device) DisableSomeRoamingForBrokenMobileSemantics() {
|
||||||
|
device.peers.RLock()
|
||||||
|
for _, peer := range device.peers.keyMap {
|
||||||
|
peer.Lock()
|
||||||
|
peer.disableRoaming = peer.endpoint != nil
|
||||||
|
peer.Unlock()
|
||||||
|
}
|
||||||
|
device.peers.RUnlock()
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -9,9 +9,10 @@ import (
|
|||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"hash"
|
||||||
|
|
||||||
"golang.org/x/crypto/blake2s"
|
"golang.org/x/crypto/blake2s"
|
||||||
"golang.org/x/crypto/curve25519"
|
"golang.org/x/crypto/curve25519"
|
||||||
"hash"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/* KDF related functions.
|
/* KDF related functions.
|
||||||
@@ -41,7 +42,6 @@ func HMAC2(sum *[blake2s.Size]byte, key, in0, in1 []byte) {
|
|||||||
func KDF1(t0 *[blake2s.Size]byte, key, input []byte) {
|
func KDF1(t0 *[blake2s.Size]byte, key, input []byte) {
|
||||||
HMAC1(t0, key, input)
|
HMAC1(t0, key, input)
|
||||||
HMAC1(t0, t0[:], []byte{0x1})
|
HMAC1(t0, t0[:], []byte{0x1})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func KDF2(t0, t1 *[blake2s.Size]byte, key, input []byte) {
|
func KDF2(t0, t1 *[blake2s.Size]byte, key, input []byte) {
|
||||||
@@ -50,7 +50,6 @@ func KDF2(t0, t1 *[blake2s.Size]byte, key, input []byte) {
|
|||||||
HMAC1(t0, prk[:], []byte{0x1})
|
HMAC1(t0, prk[:], []byte{0x1})
|
||||||
HMAC2(t1, prk[:], t0[:], []byte{0x2})
|
HMAC2(t1, prk[:], t0[:], []byte{0x2})
|
||||||
setZero(prk[:])
|
setZero(prk[:])
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func KDF3(t0, t1, t2 *[blake2s.Size]byte, key, input []byte) {
|
func KDF3(t0, t1, t2 *[blake2s.Size]byte, key, input []byte) {
|
||||||
@@ -60,7 +59,6 @@ func KDF3(t0, t1, t2 *[blake2s.Size]byte, key, input []byte) {
|
|||||||
HMAC2(t1, prk[:], t0[:], []byte{0x2})
|
HMAC2(t1, prk[:], t0[:], []byte{0x2})
|
||||||
HMAC2(t2, prk[:], t1[:], []byte{0x3})
|
HMAC2(t2, prk[:], t1[:], []byte{0x3})
|
||||||
setZero(prk[:])
|
setZero(prk[:])
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isZero(val []byte) bool {
|
func isZero(val []byte) bool {
|
||||||
|
|||||||
@@ -1,28 +1,50 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"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"
|
"golang.zx2c4.com/wireguard/tai64n"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type handshakeState int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HandshakeZeroed = iota
|
handshakeZeroed = handshakeState(iota)
|
||||||
HandshakeInitiationCreated
|
handshakeInitiationCreated
|
||||||
HandshakeInitiationConsumed
|
handshakeInitiationConsumed
|
||||||
HandshakeResponseCreated
|
handshakeResponseCreated
|
||||||
HandshakeResponseConsumed
|
handshakeResponseConsumed
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (hs handshakeState) String() string {
|
||||||
|
switch hs {
|
||||||
|
case handshakeZeroed:
|
||||||
|
return "handshakeZeroed"
|
||||||
|
case handshakeInitiationCreated:
|
||||||
|
return "handshakeInitiationCreated"
|
||||||
|
case handshakeInitiationConsumed:
|
||||||
|
return "handshakeInitiationConsumed"
|
||||||
|
case handshakeResponseCreated:
|
||||||
|
return "handshakeResponseCreated"
|
||||||
|
case handshakeResponseConsumed:
|
||||||
|
return "handshakeResponseConsumed"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Handshake(UNKNOWN:%d)", int(hs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NoiseConstruction = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
|
NoiseConstruction = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
|
||||||
WGIdentifier = "WireGuard v1 zx2c4 Jason@zx2c4.com"
|
WGIdentifier = "WireGuard v1 zx2c4 Jason@zx2c4.com"
|
||||||
@@ -38,13 +60,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MessageInitiationSize = 148 // size of handshake initation message
|
MessageInitiationSize = 148 // size of handshake initiation message
|
||||||
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 preceeding content in transport message
|
MessageTransportHeaderSize = 16 // size of data preceding content in transport message
|
||||||
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 releated message
|
MessageHandshakeSize = MessageInitiationSize // size of largest handshake related message
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -94,11 +116,11 @@ type MessageCookieReply struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Handshake struct {
|
type Handshake struct {
|
||||||
state int
|
state handshakeState
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
hash [blake2s.Size]byte // hash value
|
hash [blake2s.Size]byte // hash value
|
||||||
chainKey [blake2s.Size]byte // chain key
|
chainKey [blake2s.Size]byte // chain key
|
||||||
presharedKey NoiseSymmetricKey // psk
|
presharedKey NoisePresharedKey // psk
|
||||||
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
|
||||||
@@ -134,7 +156,7 @@ func (h *Handshake) Clear() {
|
|||||||
setZero(h.chainKey[:])
|
setZero(h.chainKey[:])
|
||||||
setZero(h.hash[:])
|
setZero(h.hash[:])
|
||||||
h.localIndex = 0
|
h.localIndex = 0
|
||||||
h.state = HandshakeZeroed
|
h.state = handshakeZeroed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handshake) mixHash(data []byte) {
|
func (h *Handshake) mixHash(data []byte) {
|
||||||
@@ -153,6 +175,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, error) {
|
func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, error) {
|
||||||
|
var errZeroECDHResult = errors.New("ECDH returned all zeros")
|
||||||
|
|
||||||
device.staticIdentity.RLock()
|
device.staticIdentity.RLock()
|
||||||
defer device.staticIdentity.RUnlock()
|
defer device.staticIdentity.RUnlock()
|
||||||
@@ -161,12 +184,7 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
|||||||
handshake.mutex.Lock()
|
handshake.mutex.Lock()
|
||||||
defer handshake.mutex.Unlock()
|
defer handshake.mutex.Unlock()
|
||||||
|
|
||||||
if isZero(handshake.precomputedStaticStatic[:]) {
|
|
||||||
return nil, errors.New("static shared secret is zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
// create ephemeral key
|
// create ephemeral key
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
handshake.hash = InitialHash
|
handshake.hash = InitialHash
|
||||||
handshake.chainKey = InitialChainKey
|
handshake.chainKey = InitialChainKey
|
||||||
@@ -175,59 +193,56 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// assign index
|
|
||||||
|
|
||||||
device.indexTable.Delete(handshake.localIndex)
|
|
||||||
handshake.localIndex, err = device.indexTable.NewIndexForHandshake(peer, handshake)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
handshake.mixHash(handshake.remoteStatic[:])
|
handshake.mixHash(handshake.remoteStatic[:])
|
||||||
|
|
||||||
msg := MessageInitiation{
|
msg := MessageInitiation{
|
||||||
Type: MessageInitiationType,
|
Type: MessageInitiationType,
|
||||||
Ephemeral: handshake.localEphemeral.publicKey(),
|
Ephemeral: handshake.localEphemeral.publicKey(),
|
||||||
Sender: handshake.localIndex,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handshake.mixKey(msg.Ephemeral[:])
|
handshake.mixKey(msg.Ephemeral[:])
|
||||||
handshake.mixHash(msg.Ephemeral[:])
|
handshake.mixHash(msg.Ephemeral[:])
|
||||||
|
|
||||||
// encrypt static key
|
// encrypt static key
|
||||||
|
ss := handshake.localEphemeral.sharedSecret(handshake.remoteStatic)
|
||||||
func() {
|
if isZero(ss[:]) {
|
||||||
var key [chacha20poly1305.KeySize]byte
|
return nil, errZeroECDHResult
|
||||||
ss := handshake.localEphemeral.sharedSecret(handshake.remoteStatic)
|
}
|
||||||
KDF2(
|
var key [chacha20poly1305.KeySize]byte
|
||||||
&handshake.chainKey,
|
KDF2(
|
||||||
&key,
|
&handshake.chainKey,
|
||||||
handshake.chainKey[:],
|
&key,
|
||||||
ss[:],
|
handshake.chainKey[:],
|
||||||
)
|
ss[:],
|
||||||
aead, _ := chacha20poly1305.New(key[:])
|
)
|
||||||
aead.Seal(msg.Static[:0], ZeroNonce[:], device.staticIdentity.publicKey[:], handshake.hash[:])
|
aead, _ := chacha20poly1305.New(key[:])
|
||||||
}()
|
aead.Seal(msg.Static[:0], ZeroNonce[:], device.staticIdentity.publicKey[:], handshake.hash[:])
|
||||||
handshake.mixHash(msg.Static[:])
|
handshake.mixHash(msg.Static[:])
|
||||||
|
|
||||||
// encrypt timestamp
|
// encrypt timestamp
|
||||||
|
if isZero(handshake.precomputedStaticStatic[:]) {
|
||||||
|
return nil, errZeroECDHResult
|
||||||
|
}
|
||||||
|
KDF2(
|
||||||
|
&handshake.chainKey,
|
||||||
|
&key,
|
||||||
|
handshake.chainKey[:],
|
||||||
|
handshake.precomputedStaticStatic[:],
|
||||||
|
)
|
||||||
timestamp := tai64n.Now()
|
timestamp := tai64n.Now()
|
||||||
func() {
|
aead, _ = chacha20poly1305.New(key[:])
|
||||||
var key [chacha20poly1305.KeySize]byte
|
aead.Seal(msg.Timestamp[:0], ZeroNonce[:], timestamp[:], handshake.hash[:])
|
||||||
KDF2(
|
|
||||||
&handshake.chainKey,
|
// assign index
|
||||||
&key,
|
device.indexTable.Delete(handshake.localIndex)
|
||||||
handshake.chainKey[:],
|
msg.Sender, err = device.indexTable.NewIndexForHandshake(peer, handshake)
|
||||||
handshake.precomputedStaticStatic[:],
|
if err != nil {
|
||||||
)
|
return nil, err
|
||||||
aead, _ := chacha20poly1305.New(key[:])
|
}
|
||||||
aead.Seal(msg.Timestamp[:0], ZeroNonce[:], timestamp[:], handshake.hash[:])
|
handshake.localIndex = msg.Sender
|
||||||
}()
|
|
||||||
|
|
||||||
handshake.mixHash(msg.Timestamp[:])
|
handshake.mixHash(msg.Timestamp[:])
|
||||||
handshake.state = HandshakeInitiationCreated
|
handshake.state = handshakeInitiationCreated
|
||||||
return &msg, nil
|
return &msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,16 +264,16 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
|||||||
mixKey(&chainKey, &InitialChainKey, msg.Ephemeral[:])
|
mixKey(&chainKey, &InitialChainKey, msg.Ephemeral[:])
|
||||||
|
|
||||||
// decrypt static key
|
// decrypt static key
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var peerPK NoisePublicKey
|
var peerPK NoisePublicKey
|
||||||
func() {
|
var key [chacha20poly1305.KeySize]byte
|
||||||
var key [chacha20poly1305.KeySize]byte
|
ss := device.staticIdentity.privateKey.sharedSecret(msg.Ephemeral)
|
||||||
ss := device.staticIdentity.privateKey.sharedSecret(msg.Ephemeral)
|
if isZero(ss[:]) {
|
||||||
KDF2(&chainKey, &key, chainKey[:], ss[:])
|
return nil
|
||||||
aead, _ := chacha20poly1305.New(key[:])
|
}
|
||||||
_, err = aead.Open(peerPK[:0], ZeroNonce[:], msg.Static[:], hash[:])
|
KDF2(&chainKey, &key, chainKey[:], ss[:])
|
||||||
}()
|
aead, _ := chacha20poly1305.New(key[:])
|
||||||
|
_, err = aead.Open(peerPK[:0], ZeroNonce[:], msg.Static[:], hash[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -272,23 +287,24 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handshake := &peer.handshake
|
handshake := &peer.handshake
|
||||||
if isZero(handshake.precomputedStaticStatic[:]) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify identity
|
// verify identity
|
||||||
|
|
||||||
var timestamp tai64n.Timestamp
|
var timestamp tai64n.Timestamp
|
||||||
var key [chacha20poly1305.KeySize]byte
|
|
||||||
|
|
||||||
handshake.mutex.RLock()
|
handshake.mutex.RLock()
|
||||||
|
|
||||||
|
if isZero(handshake.precomputedStaticStatic[:]) {
|
||||||
|
handshake.mutex.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
KDF2(
|
KDF2(
|
||||||
&chainKey,
|
&chainKey,
|
||||||
&key,
|
&key,
|
||||||
chainKey[:],
|
chainKey[:],
|
||||||
handshake.precomputedStaticStatic[:],
|
handshake.precomputedStaticStatic[:],
|
||||||
)
|
)
|
||||||
aead, _ := chacha20poly1305.New(key[:])
|
aead, _ = chacha20poly1305.New(key[:])
|
||||||
_, err = aead.Open(timestamp[:0], ZeroNonce[:], msg.Timestamp[:], hash[:])
|
_, err = aead.Open(timestamp[:0], ZeroNonce[:], msg.Timestamp[:], hash[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handshake.mutex.RUnlock()
|
handshake.mutex.RUnlock()
|
||||||
@@ -298,11 +314,15 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
|||||||
|
|
||||||
// protect against replay & flood
|
// protect against replay & flood
|
||||||
|
|
||||||
var ok bool
|
replay := !timestamp.After(handshake.lastTimestamp)
|
||||||
ok = timestamp.After(handshake.lastTimestamp)
|
flood := time.Since(handshake.lastInitiationConsumption) <= HandshakeInitationRate
|
||||||
ok = ok && time.Now().Sub(handshake.lastInitiationConsumption) > HandshakeInitationRate
|
|
||||||
handshake.mutex.RUnlock()
|
handshake.mutex.RUnlock()
|
||||||
if !ok {
|
if replay {
|
||||||
|
device.log.Verbosef("%v - ConsumeMessageInitiation: handshake replay @ %v", peer, timestamp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if flood {
|
||||||
|
device.log.Verbosef("%v - ConsumeMessageInitiation: handshake flood", peer)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,9 +334,14 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
|||||||
handshake.chainKey = chainKey
|
handshake.chainKey = chainKey
|
||||||
handshake.remoteIndex = msg.Sender
|
handshake.remoteIndex = msg.Sender
|
||||||
handshake.remoteEphemeral = msg.Ephemeral
|
handshake.remoteEphemeral = msg.Ephemeral
|
||||||
handshake.lastTimestamp = timestamp
|
if timestamp.After(handshake.lastTimestamp) {
|
||||||
handshake.lastInitiationConsumption = time.Now()
|
handshake.lastTimestamp = timestamp
|
||||||
handshake.state = HandshakeInitiationConsumed
|
}
|
||||||
|
now := time.Now()
|
||||||
|
if now.After(handshake.lastInitiationConsumption) {
|
||||||
|
handshake.lastInitiationConsumption = now
|
||||||
|
}
|
||||||
|
handshake.state = handshakeInitiationConsumed
|
||||||
|
|
||||||
handshake.mutex.Unlock()
|
handshake.mutex.Unlock()
|
||||||
|
|
||||||
@@ -331,7 +356,7 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
|
|||||||
handshake.mutex.Lock()
|
handshake.mutex.Lock()
|
||||||
defer handshake.mutex.Unlock()
|
defer handshake.mutex.Unlock()
|
||||||
|
|
||||||
if handshake.state != HandshakeInitiationConsumed {
|
if handshake.state != handshakeInitiationConsumed {
|
||||||
return nil, errors.New("handshake initiation must be consumed first")
|
return nil, errors.New("handshake initiation must be consumed first")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +412,7 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
|
|||||||
handshake.mixHash(msg.Empty[:])
|
handshake.mixHash(msg.Empty[:])
|
||||||
}()
|
}()
|
||||||
|
|
||||||
handshake.state = HandshakeResponseCreated
|
handshake.state = handshakeResponseCreated
|
||||||
|
|
||||||
return &msg, nil
|
return &msg, nil
|
||||||
}
|
}
|
||||||
@@ -417,7 +442,7 @@ func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
|
|||||||
handshake.mutex.RLock()
|
handshake.mutex.RLock()
|
||||||
defer handshake.mutex.RUnlock()
|
defer handshake.mutex.RUnlock()
|
||||||
|
|
||||||
if handshake.state != HandshakeInitiationCreated {
|
if handshake.state != handshakeInitiationCreated {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +503,7 @@ func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
|
|||||||
handshake.hash = hash
|
handshake.hash = hash
|
||||||
handshake.chainKey = chainKey
|
handshake.chainKey = chainKey
|
||||||
handshake.remoteIndex = msg.Sender
|
handshake.remoteIndex = msg.Sender
|
||||||
handshake.state = HandshakeResponseConsumed
|
handshake.state = handshakeResponseConsumed
|
||||||
|
|
||||||
handshake.mutex.Unlock()
|
handshake.mutex.Unlock()
|
||||||
|
|
||||||
@@ -503,7 +528,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
|||||||
var sendKey [chacha20poly1305.KeySize]byte
|
var sendKey [chacha20poly1305.KeySize]byte
|
||||||
var recvKey [chacha20poly1305.KeySize]byte
|
var recvKey [chacha20poly1305.KeySize]byte
|
||||||
|
|
||||||
if handshake.state == HandshakeResponseConsumed {
|
if handshake.state == handshakeResponseConsumed {
|
||||||
KDF2(
|
KDF2(
|
||||||
&sendKey,
|
&sendKey,
|
||||||
&recvKey,
|
&recvKey,
|
||||||
@@ -511,7 +536,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
|||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
isInitiator = true
|
isInitiator = true
|
||||||
} else if handshake.state == HandshakeResponseCreated {
|
} else if handshake.state == handshakeResponseCreated {
|
||||||
KDF2(
|
KDF2(
|
||||||
&recvKey,
|
&recvKey,
|
||||||
&sendKey,
|
&sendKey,
|
||||||
@@ -520,7 +545,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
|||||||
)
|
)
|
||||||
isInitiator = false
|
isInitiator = false
|
||||||
} else {
|
} else {
|
||||||
return errors.New("invalid state for keypair derivation")
|
return fmt.Errorf("invalid state for keypair derivation: %v", handshake.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// zero handshake
|
// zero handshake
|
||||||
@@ -528,7 +553,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
|||||||
setZero(handshake.chainKey[:])
|
setZero(handshake.chainKey[:])
|
||||||
setZero(handshake.hash[:]) // Doesn't necessarily need to be zeroed. Could be used for something interesting down the line.
|
setZero(handshake.hash[:]) // Doesn't necessarily need to be zeroed. Could be used for something interesting down the line.
|
||||||
setZero(handshake.localEphemeral[:])
|
setZero(handshake.localEphemeral[:])
|
||||||
peer.handshake.state = HandshakeZeroed
|
peer.handshake.state = handshakeZeroed
|
||||||
|
|
||||||
// create AEAD instances
|
// create AEAD instances
|
||||||
|
|
||||||
@@ -540,8 +565,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
|||||||
setZero(recvKey[:])
|
setZero(recvKey[:])
|
||||||
|
|
||||||
keypair.created = time.Now()
|
keypair.created = time.Now()
|
||||||
keypair.sendNonce = 0
|
keypair.replayFilter.Reset()
|
||||||
keypair.replayFilter.Init()
|
|
||||||
keypair.isInitiator = isInitiator
|
keypair.isInitiator = isInitiator
|
||||||
keypair.localIndex = peer.handshake.localIndex
|
keypair.localIndex = peer.handshake.localIndex
|
||||||
keypair.remoteIndex = peer.handshake.remoteIndex
|
keypair.remoteIndex = peer.handshake.remoteIndex
|
||||||
@@ -558,12 +582,12 @@ func (peer *Peer) BeginSymmetricSession() error {
|
|||||||
defer keypairs.Unlock()
|
defer keypairs.Unlock()
|
||||||
|
|
||||||
previous := keypairs.previous
|
previous := keypairs.previous
|
||||||
next := keypairs.next
|
next := keypairs.loadNext()
|
||||||
current := keypairs.current
|
current := keypairs.current
|
||||||
|
|
||||||
if isInitiator {
|
if isInitiator {
|
||||||
if next != nil {
|
if next != nil {
|
||||||
keypairs.next = nil
|
keypairs.storeNext(nil)
|
||||||
keypairs.previous = next
|
keypairs.previous = next
|
||||||
device.DeleteKeypair(current)
|
device.DeleteKeypair(current)
|
||||||
} else {
|
} else {
|
||||||
@@ -572,7 +596,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
|||||||
device.DeleteKeypair(previous)
|
device.DeleteKeypair(previous)
|
||||||
keypairs.current = keypair
|
keypairs.current = keypair
|
||||||
} else {
|
} else {
|
||||||
keypairs.next = keypair
|
keypairs.storeNext(keypair)
|
||||||
device.DeleteKeypair(next)
|
device.DeleteKeypair(next)
|
||||||
keypairs.previous = nil
|
keypairs.previous = nil
|
||||||
device.DeleteKeypair(previous)
|
device.DeleteKeypair(previous)
|
||||||
@@ -583,18 +607,19 @@ func (peer *Peer) BeginSymmetricSession() error {
|
|||||||
|
|
||||||
func (peer *Peer) ReceivedWithKeypair(receivedKeypair *Keypair) bool {
|
func (peer *Peer) ReceivedWithKeypair(receivedKeypair *Keypair) bool {
|
||||||
keypairs := &peer.keypairs
|
keypairs := &peer.keypairs
|
||||||
if keypairs.next != receivedKeypair {
|
|
||||||
|
if keypairs.loadNext() != receivedKeypair {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
keypairs.Lock()
|
keypairs.Lock()
|
||||||
defer keypairs.Unlock()
|
defer keypairs.Unlock()
|
||||||
if keypairs.next != receivedKeypair {
|
if keypairs.loadNext() != receivedKeypair {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
old := keypairs.previous
|
old := keypairs.previous
|
||||||
keypairs.previous = keypairs.current
|
keypairs.previous = keypairs.current
|
||||||
peer.device.DeleteKeypair(old)
|
peer.device.DeleteKeypair(old)
|
||||||
keypairs.current = keypairs.next
|
keypairs.current = keypairs.loadNext()
|
||||||
keypairs.next = nil
|
keypairs.storeNext(nil)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -9,18 +9,18 @@ import (
|
|||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NoisePublicKeySize = 32
|
NoisePublicKeySize = 32
|
||||||
NoisePrivateKeySize = 32
|
NoisePrivateKeySize = 32
|
||||||
|
NoisePresharedKeySize = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
NoisePublicKey [NoisePublicKeySize]byte
|
NoisePublicKey [NoisePublicKeySize]byte
|
||||||
NoisePrivateKey [NoisePrivateKeySize]byte
|
NoisePrivateKey [NoisePrivateKeySize]byte
|
||||||
NoiseSymmetricKey [chacha20poly1305.KeySize]byte
|
NoisePresharedKey [NoisePresharedKeySize]byte
|
||||||
NoiseNonce uint64 // padded to 12-bytes
|
NoiseNonce uint64 // padded to 12-bytes
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,18 +51,19 @@ func (key *NoisePrivateKey) FromHex(src string) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key NoisePrivateKey) ToHex() string {
|
func (key *NoisePrivateKey) FromMaybeZeroHex(src string) (err error) {
|
||||||
return hex.EncodeToString(key[:])
|
err = loadExactHex(key[:], src)
|
||||||
|
if key.IsZero() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key.clamp()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key *NoisePublicKey) FromHex(src string) error {
|
func (key *NoisePublicKey) FromHex(src string) error {
|
||||||
return loadExactHex(key[:], src)
|
return loadExactHex(key[:], src)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key NoisePublicKey) ToHex() string {
|
|
||||||
return hex.EncodeToString(key[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key NoisePublicKey) IsZero() bool {
|
func (key NoisePublicKey) IsZero() bool {
|
||||||
var zero NoisePublicKey
|
var zero NoisePublicKey
|
||||||
return key.Equals(zero)
|
return key.Equals(zero)
|
||||||
@@ -72,10 +73,6 @@ func (key NoisePublicKey) Equals(tar NoisePublicKey) bool {
|
|||||||
return subtle.ConstantTimeCompare(key[:], tar[:]) == 1
|
return subtle.ConstantTimeCompare(key[:], tar[:]) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key *NoiseSymmetricKey) FromHex(src string) error {
|
func (key *NoisePresharedKey) FromHex(src string) error {
|
||||||
return loadExactHex(key[:], src)
|
return loadExactHex(key[:], src)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (key NoiseSymmetricKey) ToHex() string {
|
|
||||||
return hex.EncodeToString(key[:])
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -9,6 +9,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/tuntest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCurveWrappers(t *testing.T) {
|
func TestCurveWrappers(t *testing.T) {
|
||||||
@@ -29,6 +32,30 @@ func TestCurveWrappers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestNoiseHandshake(t *testing.T) {
|
||||||
dev1 := randDevice(t)
|
dev1 := randDevice(t)
|
||||||
dev2 := randDevice(t)
|
dev2 := randDevice(t)
|
||||||
@@ -36,8 +63,14 @@ func TestNoiseHandshake(t *testing.T) {
|
|||||||
defer dev1.Close()
|
defer dev1.Close()
|
||||||
defer dev2.Close()
|
defer dev2.Close()
|
||||||
|
|
||||||
peer1, _ := dev2.NewPeer(dev1.staticIdentity.privateKey.publicKey())
|
peer1, err := dev2.NewPeer(dev1.staticIdentity.privateKey.publicKey())
|
||||||
peer2, _ := dev1.NewPeer(dev2.staticIdentity.privateKey.publicKey())
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
peer2, err := dev1.NewPeer(dev2.staticIdentity.privateKey.publicKey())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
assertEqual(
|
assertEqual(
|
||||||
t,
|
t,
|
||||||
@@ -113,7 +146,7 @@ func TestNoiseHandshake(t *testing.T) {
|
|||||||
t.Fatal("failed to derive keypair for peer 2", err)
|
t.Fatal("failed to derive keypair for peer 2", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
key1 := peer1.keypairs.next
|
key1 := peer1.keypairs.loadNext()
|
||||||
key2 := peer2.keypairs.current
|
key2 := peer2.keypairs.current
|
||||||
|
|
||||||
// encrypting / decryption test
|
// encrypting / decryption test
|
||||||
|
|||||||
212
device/peer.go
212
device/peer.go
@@ -1,38 +1,42 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"container/list"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
PeerRoutineNumber = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
isRunning AtomicBool
|
isRunning AtomicBool
|
||||||
sync.RWMutex // Mostly protects endpoint, but is generally taken whenever we modify peer
|
sync.RWMutex // Mostly protects endpoint, but is generally taken whenever we modify peer
|
||||||
keypairs Keypairs
|
keypairs Keypairs
|
||||||
handshake Handshake
|
handshake Handshake
|
||||||
device *Device
|
device *Device
|
||||||
endpoint Endpoint
|
endpoint conn.Endpoint
|
||||||
persistentKeepaliveInterval uint16
|
stopping sync.WaitGroup // routines pending stop
|
||||||
|
|
||||||
// This must be 64-bit aligned, so make sure the above members come out to even alignment and pad accordingly
|
// These fields are accessed with atomic operations, which must be
|
||||||
|
// 64-bit aligned even on 32-bit platforms. Go guarantees that an
|
||||||
|
// allocated struct will be 64-bit aligned. So we place
|
||||||
|
// atomically-accessed fields up front, so that they can share in
|
||||||
|
// this alignment before smaller fields throw it off.
|
||||||
stats struct {
|
stats struct {
|
||||||
txBytes uint64 // bytes send to peer (endpoint)
|
txBytes uint64 // bytes send to peer (endpoint)
|
||||||
rxBytes uint64 // bytes received from peer
|
rxBytes uint64 // bytes received from peer
|
||||||
lastHandshakeNano int64 // nano seconds since epoch
|
lastHandshakeNano int64 // nano seconds since epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disableRoaming bool
|
||||||
|
|
||||||
timers struct {
|
timers struct {
|
||||||
retransmitHandshake *Timer
|
retransmitHandshake *Timer
|
||||||
sendKeepalive *Timer
|
sendKeepalive *Timer
|
||||||
@@ -44,36 +48,27 @@ type Peer struct {
|
|||||||
sentLastMinuteHandshake AtomicBool
|
sentLastMinuteHandshake AtomicBool
|
||||||
}
|
}
|
||||||
|
|
||||||
signals struct {
|
state struct {
|
||||||
newKeypairArrived chan struct{}
|
sync.Mutex // protects against concurrent Start/Stop
|
||||||
flushNonceQueue chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
queue struct {
|
queue struct {
|
||||||
nonce chan *QueueOutboundElement // nonce / pre-handshake queue
|
staged chan *QueueOutboundElement // staged packets before a handshake is available
|
||||||
outbound chan *QueueOutboundElement // sequential ordering of work
|
outbound *autodrainingOutboundQueue // sequential ordering of udp transmission
|
||||||
inbound chan *QueueInboundElement // sequential ordering of work
|
inbound *autodrainingInboundQueue // sequential ordering of tun writing
|
||||||
packetInNonceQueueIsAwaitingKey AtomicBool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
routines struct {
|
cookieGenerator CookieGenerator
|
||||||
sync.Mutex // held when stopping / starting routines
|
trieEntries list.List
|
||||||
starting sync.WaitGroup // routines pending start
|
persistentKeepaliveInterval uint32 // accessed atomically
|
||||||
stopping sync.WaitGroup // routines pending stop
|
|
||||||
stop chan struct{} // size 0, stop all go routines in peer
|
|
||||||
}
|
|
||||||
|
|
||||||
cookieGenerator CookieGenerator
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
|
func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
|
||||||
|
if device.isClosed() {
|
||||||
if device.isClosed.Get() {
|
|
||||||
return nil, errors.New("device closed")
|
return nil, errors.New("device closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// lock resources
|
// lock resources
|
||||||
|
|
||||||
device.staticIdentity.RLock()
|
device.staticIdentity.RLock()
|
||||||
defer device.staticIdentity.RUnlock()
|
defer device.staticIdentity.RUnlock()
|
||||||
|
|
||||||
@@ -81,44 +76,43 @@ func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
|
|||||||
defer device.peers.Unlock()
|
defer device.peers.Unlock()
|
||||||
|
|
||||||
// check if over limit
|
// check if over limit
|
||||||
|
|
||||||
if len(device.peers.keyMap) >= MaxPeers {
|
if len(device.peers.keyMap) >= MaxPeers {
|
||||||
return nil, errors.New("too many peers")
|
return nil, errors.New("too many peers")
|
||||||
}
|
}
|
||||||
|
|
||||||
// create peer
|
// create peer
|
||||||
|
|
||||||
peer := new(Peer)
|
peer := new(Peer)
|
||||||
peer.Lock()
|
peer.Lock()
|
||||||
defer peer.Unlock()
|
defer peer.Unlock()
|
||||||
|
|
||||||
peer.cookieGenerator.Init(pk)
|
peer.cookieGenerator.Init(pk)
|
||||||
peer.device = device
|
peer.device = device
|
||||||
peer.isRunning.Set(false)
|
peer.queue.outbound = newAutodrainingOutboundQueue(device)
|
||||||
|
peer.queue.inbound = newAutodrainingInboundQueue(device)
|
||||||
|
peer.queue.staged = make(chan *QueueOutboundElement, QueueStagedSize)
|
||||||
|
|
||||||
// map public key
|
// map public key
|
||||||
|
|
||||||
_, ok := device.peers.keyMap[pk]
|
_, ok := device.peers.keyMap[pk]
|
||||||
if ok {
|
if ok {
|
||||||
return nil, errors.New("adding existing peer")
|
return nil, errors.New("adding existing peer")
|
||||||
}
|
}
|
||||||
device.peers.keyMap[pk] = peer
|
|
||||||
|
|
||||||
// pre-compute DH
|
// pre-compute DH
|
||||||
|
|
||||||
handshake := &peer.handshake
|
handshake := &peer.handshake
|
||||||
handshake.mutex.Lock()
|
handshake.mutex.Lock()
|
||||||
handshake.remoteStatic = pk
|
|
||||||
handshake.precomputedStaticStatic = device.staticIdentity.privateKey.sharedSecret(pk)
|
handshake.precomputedStaticStatic = device.staticIdentity.privateKey.sharedSecret(pk)
|
||||||
|
handshake.remoteStatic = pk
|
||||||
handshake.mutex.Unlock()
|
handshake.mutex.Unlock()
|
||||||
|
|
||||||
// reset endpoint
|
// reset endpoint
|
||||||
|
|
||||||
peer.endpoint = nil
|
peer.endpoint = nil
|
||||||
|
|
||||||
// start peer
|
// add
|
||||||
|
device.peers.keyMap[pk] = peer
|
||||||
|
|
||||||
if peer.device.isUp.Get() {
|
// start peer
|
||||||
|
peer.timersInit()
|
||||||
|
if peer.device.isUp() {
|
||||||
peer.Start()
|
peer.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +123,8 @@ func (peer *Peer) SendBuffer(buffer []byte) error {
|
|||||||
peer.device.net.RLock()
|
peer.device.net.RLock()
|
||||||
defer peer.device.net.RUnlock()
|
defer peer.device.net.RUnlock()
|
||||||
|
|
||||||
if peer.device.net.bind == nil {
|
if peer.device.isClosed() {
|
||||||
return errors.New("no bind")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.RLock()
|
peer.RLock()
|
||||||
@@ -140,64 +134,73 @@ func (peer *Peer) SendBuffer(buffer []byte) error {
|
|||||||
return errors.New("no known endpoint for peer")
|
return errors.New("no known endpoint for peer")
|
||||||
}
|
}
|
||||||
|
|
||||||
return peer.device.net.bind.Send(buffer, peer.endpoint)
|
err := peer.device.net.bind.Send(buffer, peer.endpoint)
|
||||||
|
if err == nil {
|
||||||
|
atomic.AddUint64(&peer.stats.txBytes, uint64(len(buffer)))
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *Peer) String() string {
|
func (peer *Peer) String() string {
|
||||||
base64Key := base64.StdEncoding.EncodeToString(peer.handshake.remoteStatic[:])
|
// The awful goo that follows is identical to:
|
||||||
abbreviatedKey := "invalid"
|
//
|
||||||
if len(base64Key) == 44 {
|
// base64Key := base64.StdEncoding.EncodeToString(peer.handshake.remoteStatic[:])
|
||||||
abbreviatedKey = base64Key[0:4] + "…" + base64Key[39:43]
|
// abbreviatedKey := base64Key[0:4] + "…" + base64Key[39:43]
|
||||||
|
// return fmt.Sprintf("peer(%s)", abbreviatedKey)
|
||||||
|
//
|
||||||
|
// except that it is considerably more efficient.
|
||||||
|
src := peer.handshake.remoteStatic
|
||||||
|
b64 := func(input byte) byte {
|
||||||
|
return input + 'A' + byte(((25-int(input))>>8)&6) - byte(((51-int(input))>>8)&75) - byte(((61-int(input))>>8)&15) + byte(((62-int(input))>>8)&3)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("peer(%s)", abbreviatedKey)
|
b := []byte("peer(____…____)")
|
||||||
|
const first = len("peer(")
|
||||||
|
const second = len("peer(____…")
|
||||||
|
b[first+0] = b64((src[0] >> 2) & 63)
|
||||||
|
b[first+1] = b64(((src[0] << 4) | (src[1] >> 4)) & 63)
|
||||||
|
b[first+2] = b64(((src[1] << 2) | (src[2] >> 6)) & 63)
|
||||||
|
b[first+3] = b64(src[2] & 63)
|
||||||
|
b[second+0] = b64(src[29] & 63)
|
||||||
|
b[second+1] = b64((src[30] >> 2) & 63)
|
||||||
|
b[second+2] = b64(((src[30] << 4) | (src[31] >> 4)) & 63)
|
||||||
|
b[second+3] = b64((src[31] << 2) & 63)
|
||||||
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *Peer) Start() {
|
func (peer *Peer) Start() {
|
||||||
|
|
||||||
// should never start a peer on a closed device
|
// should never start a peer on a closed device
|
||||||
|
if peer.device.isClosed() {
|
||||||
if peer.device.isClosed.Get() {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// prevent simultaneous start/stop operations
|
// prevent simultaneous start/stop operations
|
||||||
|
peer.state.Lock()
|
||||||
peer.routines.Lock()
|
defer peer.state.Unlock()
|
||||||
defer peer.routines.Unlock()
|
|
||||||
|
|
||||||
if peer.isRunning.Get() {
|
if peer.isRunning.Get() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
device := peer.device
|
device := peer.device
|
||||||
device.log.Debug.Println(peer, "- Starting...")
|
device.log.Verbosef("%v - Starting", peer)
|
||||||
|
|
||||||
// reset routine state
|
// reset routine state
|
||||||
|
peer.stopping.Wait()
|
||||||
|
peer.stopping.Add(2)
|
||||||
|
|
||||||
peer.routines.starting.Wait()
|
peer.handshake.mutex.Lock()
|
||||||
peer.routines.stopping.Wait()
|
|
||||||
peer.routines.stop = make(chan struct{})
|
|
||||||
peer.routines.starting.Add(PeerRoutineNumber)
|
|
||||||
peer.routines.stopping.Add(PeerRoutineNumber)
|
|
||||||
|
|
||||||
// prepare queues
|
|
||||||
|
|
||||||
peer.queue.nonce = make(chan *QueueOutboundElement, QueueOutboundSize)
|
|
||||||
peer.queue.outbound = make(chan *QueueOutboundElement, QueueOutboundSize)
|
|
||||||
peer.queue.inbound = make(chan *QueueInboundElement, QueueInboundSize)
|
|
||||||
|
|
||||||
peer.timersInit()
|
|
||||||
peer.handshake.lastSentHandshake = time.Now().Add(-(RekeyTimeout + time.Second))
|
peer.handshake.lastSentHandshake = time.Now().Add(-(RekeyTimeout + time.Second))
|
||||||
peer.signals.newKeypairArrived = make(chan struct{}, 1)
|
peer.handshake.mutex.Unlock()
|
||||||
peer.signals.flushNonceQueue = make(chan struct{}, 1)
|
|
||||||
|
|
||||||
// wait for routines to start
|
peer.device.queue.encryption.wg.Add(1) // keep encryption queue open for our writes
|
||||||
|
|
||||||
go peer.RoutineNonce()
|
peer.timersStart()
|
||||||
|
|
||||||
|
device.flushInboundQueue(peer.queue.inbound)
|
||||||
|
device.flushOutboundQueue(peer.queue.outbound)
|
||||||
go peer.RoutineSequentialSender()
|
go peer.RoutineSequentialSender()
|
||||||
go peer.RoutineSequentialReceiver()
|
go peer.RoutineSequentialReceiver()
|
||||||
|
|
||||||
peer.routines.starting.Wait()
|
|
||||||
peer.isRunning.Set(true)
|
peer.isRunning.Set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,10 +213,10 @@ func (peer *Peer) ZeroAndFlushAll() {
|
|||||||
keypairs.Lock()
|
keypairs.Lock()
|
||||||
device.DeleteKeypair(keypairs.previous)
|
device.DeleteKeypair(keypairs.previous)
|
||||||
device.DeleteKeypair(keypairs.current)
|
device.DeleteKeypair(keypairs.current)
|
||||||
device.DeleteKeypair(keypairs.next)
|
device.DeleteKeypair(keypairs.loadNext())
|
||||||
keypairs.previous = nil
|
keypairs.previous = nil
|
||||||
keypairs.current = nil
|
keypairs.current = nil
|
||||||
keypairs.next = nil
|
keypairs.storeNext(nil)
|
||||||
keypairs.Unlock()
|
keypairs.Unlock()
|
||||||
|
|
||||||
// clear handshake state
|
// clear handshake state
|
||||||
@@ -224,44 +227,51 @@ func (peer *Peer) ZeroAndFlushAll() {
|
|||||||
handshake.Clear()
|
handshake.Clear()
|
||||||
handshake.mutex.Unlock()
|
handshake.mutex.Unlock()
|
||||||
|
|
||||||
peer.FlushNonceQueue()
|
peer.FlushStagedPackets()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (peer *Peer) ExpireCurrentKeypairs() {
|
||||||
|
handshake := &peer.handshake
|
||||||
|
handshake.mutex.Lock()
|
||||||
|
peer.device.indexTable.Delete(handshake.localIndex)
|
||||||
|
handshake.Clear()
|
||||||
|
peer.handshake.lastSentHandshake = time.Now().Add(-(RekeyTimeout + time.Second))
|
||||||
|
handshake.mutex.Unlock()
|
||||||
|
|
||||||
|
keypairs := &peer.keypairs
|
||||||
|
keypairs.Lock()
|
||||||
|
if keypairs.current != nil {
|
||||||
|
atomic.StoreUint64(&keypairs.current.sendNonce, RejectAfterMessages)
|
||||||
|
}
|
||||||
|
if keypairs.next != nil {
|
||||||
|
next := keypairs.loadNext()
|
||||||
|
atomic.StoreUint64(&next.sendNonce, RejectAfterMessages)
|
||||||
|
}
|
||||||
|
keypairs.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *Peer) Stop() {
|
func (peer *Peer) Stop() {
|
||||||
|
peer.state.Lock()
|
||||||
// prevent simultaneous start/stop operations
|
defer peer.state.Unlock()
|
||||||
|
|
||||||
if !peer.isRunning.Swap(false) {
|
if !peer.isRunning.Swap(false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.routines.starting.Wait()
|
peer.device.log.Verbosef("%v - Stopping", peer)
|
||||||
|
|
||||||
peer.routines.Lock()
|
|
||||||
defer peer.routines.Unlock()
|
|
||||||
|
|
||||||
peer.device.log.Debug.Println(peer, "- Stopping...")
|
|
||||||
|
|
||||||
peer.timersStop()
|
peer.timersStop()
|
||||||
|
// Signal that RoutineSequentialSender and RoutineSequentialReceiver should exit.
|
||||||
// stop & wait for ongoing peer routines
|
peer.queue.inbound.c <- nil
|
||||||
|
peer.queue.outbound.c <- nil
|
||||||
close(peer.routines.stop)
|
peer.stopping.Wait()
|
||||||
peer.routines.stopping.Wait()
|
peer.device.queue.encryption.wg.Done() // no more writes to encryption queue from us
|
||||||
|
|
||||||
// close queues
|
|
||||||
|
|
||||||
close(peer.queue.nonce)
|
|
||||||
close(peer.queue.outbound)
|
|
||||||
close(peer.queue.inbound)
|
|
||||||
|
|
||||||
peer.ZeroAndFlushAll()
|
peer.ZeroAndFlushAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
var RoamingDisabled bool
|
func (peer *Peer) SetEndpointFromPacket(endpoint conn.Endpoint) {
|
||||||
|
if peer.disableRoaming {
|
||||||
func (peer *Peer) SetEndpointFromPacket(endpoint Endpoint) {
|
|
||||||
if RoamingDisabled {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
peer.Lock()
|
peer.Lock()
|
||||||
|
|||||||
123
device/pools.go
123
device/pools.go
@@ -1,89 +1,84 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WaitPool struct {
|
||||||
|
pool sync.Pool
|
||||||
|
cond sync.Cond
|
||||||
|
lock sync.Mutex
|
||||||
|
count uint32
|
||||||
|
max uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWaitPool(max uint32, new func() interface{}) *WaitPool {
|
||||||
|
p := &WaitPool{pool: sync.Pool{New: new}, max: max}
|
||||||
|
p.cond = sync.Cond{L: &p.lock}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WaitPool) Get() interface{} {
|
||||||
|
if p.max != 0 {
|
||||||
|
p.lock.Lock()
|
||||||
|
for atomic.LoadUint32(&p.count) >= p.max {
|
||||||
|
p.cond.Wait()
|
||||||
|
}
|
||||||
|
atomic.AddUint32(&p.count, 1)
|
||||||
|
p.lock.Unlock()
|
||||||
|
}
|
||||||
|
return p.pool.Get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *WaitPool) Put(x interface{}) {
|
||||||
|
p.pool.Put(x)
|
||||||
|
if p.max == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
atomic.AddUint32(&p.count, ^uint32(0))
|
||||||
|
p.cond.Signal()
|
||||||
|
}
|
||||||
|
|
||||||
func (device *Device) PopulatePools() {
|
func (device *Device) PopulatePools() {
|
||||||
if PreallocatedBuffersPerPool == 0 {
|
device.pool.messageBuffers = NewWaitPool(PreallocatedBuffersPerPool, func() interface{} {
|
||||||
device.pool.messageBufferPool = &sync.Pool{
|
return new([MaxMessageSize]byte)
|
||||||
New: func() interface{} {
|
})
|
||||||
return new([MaxMessageSize]byte)
|
device.pool.inboundElements = NewWaitPool(PreallocatedBuffersPerPool, func() interface{} {
|
||||||
},
|
return new(QueueInboundElement)
|
||||||
}
|
})
|
||||||
device.pool.inboundElementPool = &sync.Pool{
|
device.pool.outboundElements = NewWaitPool(PreallocatedBuffersPerPool, func() interface{} {
|
||||||
New: func() interface{} {
|
return new(QueueOutboundElement)
|
||||||
return new(QueueInboundElement)
|
})
|
||||||
},
|
|
||||||
}
|
|
||||||
device.pool.outboundElementPool = &sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
return new(QueueOutboundElement)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
device.pool.messageBufferReuseChan = make(chan *[MaxMessageSize]byte, PreallocatedBuffersPerPool)
|
|
||||||
for i := 0; i < PreallocatedBuffersPerPool; i += 1 {
|
|
||||||
device.pool.messageBufferReuseChan <- new([MaxMessageSize]byte)
|
|
||||||
}
|
|
||||||
device.pool.inboundElementReuseChan = make(chan *QueueInboundElement, PreallocatedBuffersPerPool)
|
|
||||||
for i := 0; i < PreallocatedBuffersPerPool; i += 1 {
|
|
||||||
device.pool.inboundElementReuseChan <- new(QueueInboundElement)
|
|
||||||
}
|
|
||||||
device.pool.outboundElementReuseChan = make(chan *QueueOutboundElement, PreallocatedBuffersPerPool)
|
|
||||||
for i := 0; i < PreallocatedBuffersPerPool; i += 1 {
|
|
||||||
device.pool.outboundElementReuseChan <- new(QueueOutboundElement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) GetMessageBuffer() *[MaxMessageSize]byte {
|
func (device *Device) GetMessageBuffer() *[MaxMessageSize]byte {
|
||||||
if PreallocatedBuffersPerPool == 0 {
|
return device.pool.messageBuffers.Get().(*[MaxMessageSize]byte)
|
||||||
return device.pool.messageBufferPool.Get().(*[MaxMessageSize]byte)
|
|
||||||
} else {
|
|
||||||
return <-device.pool.messageBufferReuseChan
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) PutMessageBuffer(msg *[MaxMessageSize]byte) {
|
func (device *Device) PutMessageBuffer(msg *[MaxMessageSize]byte) {
|
||||||
if PreallocatedBuffersPerPool == 0 {
|
device.pool.messageBuffers.Put(msg)
|
||||||
device.pool.messageBufferPool.Put(msg)
|
|
||||||
} else {
|
|
||||||
device.pool.messageBufferReuseChan <- msg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) GetInboundElement() *QueueInboundElement {
|
func (device *Device) GetInboundElement() *QueueInboundElement {
|
||||||
if PreallocatedBuffersPerPool == 0 {
|
return device.pool.inboundElements.Get().(*QueueInboundElement)
|
||||||
return device.pool.inboundElementPool.Get().(*QueueInboundElement)
|
|
||||||
} else {
|
|
||||||
return <-device.pool.inboundElementReuseChan
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) PutInboundElement(msg *QueueInboundElement) {
|
func (device *Device) PutInboundElement(elem *QueueInboundElement) {
|
||||||
if PreallocatedBuffersPerPool == 0 {
|
elem.clearPointers()
|
||||||
device.pool.inboundElementPool.Put(msg)
|
device.pool.inboundElements.Put(elem)
|
||||||
} else {
|
|
||||||
device.pool.inboundElementReuseChan <- msg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) GetOutboundElement() *QueueOutboundElement {
|
func (device *Device) GetOutboundElement() *QueueOutboundElement {
|
||||||
if PreallocatedBuffersPerPool == 0 {
|
return device.pool.outboundElements.Get().(*QueueOutboundElement)
|
||||||
return device.pool.outboundElementPool.Get().(*QueueOutboundElement)
|
|
||||||
} else {
|
|
||||||
return <-device.pool.outboundElementReuseChan
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) PutOutboundElement(msg *QueueOutboundElement) {
|
func (device *Device) PutOutboundElement(elem *QueueOutboundElement) {
|
||||||
if PreallocatedBuffersPerPool == 0 {
|
elem.clearPointers()
|
||||||
device.pool.outboundElementPool.Put(msg)
|
device.pool.outboundElements.Put(elem)
|
||||||
} else {
|
|
||||||
device.pool.outboundElementReuseChan <- msg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
88
device/pools_test.go
Normal file
88
device/pools_test.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 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
|
||||||
|
trials := int32(100000)
|
||||||
|
if raceEnabled {
|
||||||
|
// This test can be very slow with -race.
|
||||||
|
trials /= 10
|
||||||
|
}
|
||||||
|
workers := runtime.NumCPU() + 2
|
||||||
|
if workers-4 <= 0 {
|
||||||
|
t.Skip("Not enough cores")
|
||||||
|
}
|
||||||
|
p := NewWaitPool(uint32(workers-4), func() interface{} { return make([]byte, 16) })
|
||||||
|
wg.Add(workers)
|
||||||
|
max := uint32(0)
|
||||||
|
updateMax := func() {
|
||||||
|
count := atomic.LoadUint32(&p.count)
|
||||||
|
if count > p.max {
|
||||||
|
t.Errorf("count (%d) > max (%d)", count, p.max)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
old := atomic.LoadUint32(&max)
|
||||||
|
if count <= old {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if atomic.CompareAndSwapUint32(&max, old, count) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < workers; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for atomic.AddInt32(&trials, -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 != p.max {
|
||||||
|
t.Errorf("Actual maximum count (%d) != ideal maximum count (%d)", max, p.max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWaitPool(b *testing.B) {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
trials := int32(b.N)
|
||||||
|
workers := runtime.NumCPU() + 2
|
||||||
|
if workers-4 <= 0 {
|
||||||
|
b.Skip("Not enough cores")
|
||||||
|
}
|
||||||
|
p := NewWaitPool(uint32(workers-4), func() interface{} { return make([]byte, 16) })
|
||||||
|
wg.Add(workers)
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < workers; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for atomic.AddInt32(&trials, -1) > 0 {
|
||||||
|
x := p.Get()
|
||||||
|
time.Sleep(time.Duration(rand.Intn(100)) * time.Microsecond)
|
||||||
|
p.Put(x)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -8,6 +8,7 @@ package device
|
|||||||
/* Reduce memory consumption for Android */
|
/* Reduce memory consumption for Android */
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
QueueStagedSize = 128
|
||||||
QueueOutboundSize = 1024
|
QueueOutboundSize = 1024
|
||||||
QueueInboundSize = 1024
|
QueueInboundSize = 1024
|
||||||
QueueHandshakeSize = 1024
|
QueueHandshakeSize = 1024
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
// +build !android,!ios
|
//go:build !android && !ios && !windows
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
QueueStagedSize = 128
|
||||||
QueueOutboundSize = 1024
|
QueueOutboundSize = 1024
|
||||||
QueueInboundSize = 1024
|
QueueInboundSize = 1024
|
||||||
QueueHandshakeSize = 1024
|
QueueHandshakeSize = 1024
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
// +build ios
|
//go:build ios
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
/* Fit within memory limits for iOS's Network Extension API, which has stricter requirements */
|
// Fit within memory limits for iOS's Network Extension API, which has stricter requirements.
|
||||||
|
// These are vars instead of consts, because heavier network extensions might want to reduce
|
||||||
const (
|
// them further.
|
||||||
QueueOutboundSize = 1024
|
var (
|
||||||
QueueInboundSize = 1024
|
QueueStagedSize = 128
|
||||||
QueueHandshakeSize = 1024
|
QueueOutboundSize = 1024
|
||||||
MaxSegmentSize = 1700
|
QueueInboundSize = 1024
|
||||||
PreallocatedBuffersPerPool = 1024
|
QueueHandshakeSize = 1024
|
||||||
|
PreallocatedBuffersPerPool uint32 = 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MaxSegmentSize = 1700
|
||||||
|
|||||||
15
device/queueconstants_windows.go
Normal file
15
device/queueconstants_windows.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
const (
|
||||||
|
QueueStagedSize = 128
|
||||||
|
QueueOutboundSize = 1024
|
||||||
|
QueueInboundSize = 1024
|
||||||
|
QueueHandshakeSize = 1024
|
||||||
|
MaxSegmentSize = 2048 - 32 // largest possible UDP datagram
|
||||||
|
PreallocatedBuffersPerPool = 0 // Disable and allow for infinite memory growth
|
||||||
|
)
|
||||||
10
device/race_disabled_test.go
Normal file
10
device/race_disabled_test.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build !race
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
const raceEnabled = false
|
||||||
10
device/race_enabled_test.go
Normal file
10
device/race_enabled_test.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//go:build race
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
const raceEnabled = true
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -8,65 +8,44 @@ package device
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"errors"
|
||||||
"golang.org/x/net/ipv4"
|
|
||||||
"golang.org/x/net/ipv6"
|
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QueueHandshakeElement struct {
|
type QueueHandshakeElement struct {
|
||||||
msgType uint32
|
msgType uint32
|
||||||
packet []byte
|
packet []byte
|
||||||
endpoint Endpoint
|
endpoint conn.Endpoint
|
||||||
buffer *[MaxMessageSize]byte
|
buffer *[MaxMessageSize]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueueInboundElement struct {
|
type QueueInboundElement struct {
|
||||||
dropped int32
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
buffer *[MaxMessageSize]byte
|
buffer *[MaxMessageSize]byte
|
||||||
packet []byte
|
packet []byte
|
||||||
counter uint64
|
counter uint64
|
||||||
keypair *Keypair
|
keypair *Keypair
|
||||||
endpoint Endpoint
|
endpoint conn.Endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
func (elem *QueueInboundElement) Drop() {
|
// clearPointers clears elem fields that contain pointers.
|
||||||
atomic.StoreInt32(&elem.dropped, AtomicTrue)
|
// This makes the garbage collector's life easier and
|
||||||
}
|
// avoids accidentally keeping other objects around unnecessarily.
|
||||||
|
// It also reduces the possible collateral damage from use-after-free bugs.
|
||||||
func (elem *QueueInboundElement) IsDropped() bool {
|
func (elem *QueueInboundElement) clearPointers() {
|
||||||
return atomic.LoadInt32(&elem.dropped) == AtomicTrue
|
elem.buffer = nil
|
||||||
}
|
elem.packet = nil
|
||||||
|
elem.keypair = nil
|
||||||
func (device *Device) addToInboundAndDecryptionQueues(inboundQueue chan *QueueInboundElement, decryptionQueue chan *QueueInboundElement, element *QueueInboundElement) bool {
|
elem.endpoint = nil
|
||||||
select {
|
|
||||||
case inboundQueue <- element:
|
|
||||||
select {
|
|
||||||
case decryptionQueue <- element:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
element.Drop()
|
|
||||||
element.Unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
device.PutInboundElement(element)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (device *Device) addToHandshakeQueue(queue chan QueueHandshakeElement, element QueueHandshakeElement) bool {
|
|
||||||
select {
|
|
||||||
case queue <- element:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called when a new authenticated message has been received
|
/* Called when a new authenticated message has been received
|
||||||
@@ -78,7 +57,7 @@ func (peer *Peer) keepKeyFreshReceiving() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
keypair := peer.keypairs.Current()
|
keypair := peer.keypairs.Current()
|
||||||
if keypair != nil && keypair.isInitiator && time.Now().Sub(keypair.created) > (RejectAfterTime-KeepaliveTimeout-RekeyTimeout) {
|
if keypair != nil && keypair.isInitiator && time.Since(keypair.created) > (RejectAfterTime-KeepaliveTimeout-RekeyTimeout) {
|
||||||
peer.timers.sentLastMinuteHandshake.Set(true)
|
peer.timers.sentLastMinuteHandshake.Set(true)
|
||||||
peer.SendHandshakeInitiation(false)
|
peer.SendHandshakeInitiation(false)
|
||||||
}
|
}
|
||||||
@@ -89,44 +68,49 @@ 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(IP int, bind Bind) {
|
func (device *Device) RoutineReceiveIncoming(recv conn.ReceiveFunc) {
|
||||||
|
recvName := recv.PrettyName()
|
||||||
logDebug := device.log.Debug
|
|
||||||
defer func() {
|
defer func() {
|
||||||
logDebug.Println("Routine: receive incoming IPv" + strconv.Itoa(IP) + " - stopped")
|
device.log.Verbosef("Routine: receive incoming %s - stopped", recvName)
|
||||||
|
device.queue.decryption.wg.Done()
|
||||||
|
device.queue.handshake.wg.Done()
|
||||||
device.net.stopping.Done()
|
device.net.stopping.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
logDebug.Println("Routine: receive incoming IPv" + strconv.Itoa(IP) + " - started")
|
device.log.Verbosef("Routine: receive incoming %s - started", recvName)
|
||||||
device.net.starting.Done()
|
|
||||||
|
|
||||||
// receive datagrams until conn is closed
|
// receive datagrams until conn is closed
|
||||||
|
|
||||||
buffer := device.GetMessageBuffer()
|
buffer := device.GetMessageBuffer()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
size int
|
size int
|
||||||
endpoint Endpoint
|
endpoint conn.Endpoint
|
||||||
|
deathSpiral int
|
||||||
)
|
)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
size, endpoint, err = recv(buffer[:])
|
||||||
// read next datagram
|
|
||||||
|
|
||||||
switch IP {
|
|
||||||
case ipv4.Version:
|
|
||||||
size, endpoint, err = bind.ReceiveIPv4(buffer[:])
|
|
||||||
case ipv6.Version:
|
|
||||||
size, endpoint, err = bind.ReceiveIPv6(buffer[:])
|
|
||||||
default:
|
|
||||||
panic("invalid IP version")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
device.PutMessageBuffer(buffer)
|
device.PutMessageBuffer(buffer)
|
||||||
|
if errors.Is(err, net.ErrClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
device.log.Verbosef("Failed to receive %s packet: %v", recvName, err)
|
||||||
|
if neterr, ok := err.(net.Error); ok && !neterr.Temporary() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if deathSpiral < 10 {
|
||||||
|
deathSpiral++
|
||||||
|
time.Sleep(time.Second / 3)
|
||||||
|
buffer = device.GetMessageBuffer()
|
||||||
|
continue
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
deathSpiral = 0
|
||||||
|
|
||||||
if size < MinMessageSize {
|
if size < MinMessageSize {
|
||||||
continue
|
continue
|
||||||
@@ -174,20 +158,19 @@ func (device *Device) RoutineReceiveIncoming(IP int, bind Bind) {
|
|||||||
elem.packet = packet
|
elem.packet = packet
|
||||||
elem.buffer = buffer
|
elem.buffer = buffer
|
||||||
elem.keypair = keypair
|
elem.keypair = keypair
|
||||||
elem.dropped = AtomicFalse
|
|
||||||
elem.endpoint = endpoint
|
elem.endpoint = endpoint
|
||||||
elem.counter = 0
|
elem.counter = 0
|
||||||
elem.Mutex = sync.Mutex{}
|
elem.Mutex = sync.Mutex{}
|
||||||
elem.Lock()
|
elem.Lock()
|
||||||
|
|
||||||
// add to decryption queues
|
// add to decryption queues
|
||||||
|
|
||||||
if peer.isRunning.Get() {
|
if peer.isRunning.Get() {
|
||||||
if device.addToInboundAndDecryptionQueues(peer.queue.inbound, device.queue.decryption, elem) {
|
peer.queue.inbound.c <- elem
|
||||||
buffer = device.GetMessageBuffer()
|
device.queue.decryption.c <- elem
|
||||||
}
|
buffer = device.GetMessageBuffer()
|
||||||
|
} else {
|
||||||
|
device.PutInboundElement(elem)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
// otherwise it is a fixed size & handshake related packet
|
// otherwise it is a fixed size & handshake related packet
|
||||||
@@ -202,127 +185,63 @@ func (device *Device) RoutineReceiveIncoming(IP int, bind Bind) {
|
|||||||
okay = len(packet) == MessageCookieReplySize
|
okay = len(packet) == MessageCookieReplySize
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logDebug.Println("Received message with unknown type")
|
device.log.Verbosef("Received message with unknown type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if okay {
|
if okay {
|
||||||
if (device.addToHandshakeQueue(
|
select {
|
||||||
device.queue.handshake,
|
case device.queue.handshake.c <- QueueHandshakeElement{
|
||||||
QueueHandshakeElement{
|
msgType: msgType,
|
||||||
msgType: msgType,
|
buffer: buffer,
|
||||||
buffer: buffer,
|
packet: packet,
|
||||||
packet: packet,
|
endpoint: endpoint,
|
||||||
endpoint: endpoint,
|
}:
|
||||||
},
|
|
||||||
)) {
|
|
||||||
buffer = device.GetMessageBuffer()
|
buffer = device.GetMessageBuffer()
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) RoutineDecryption() {
|
func (device *Device) RoutineDecryption(id int) {
|
||||||
|
|
||||||
var nonce [chacha20poly1305.NonceSize]byte
|
var nonce [chacha20poly1305.NonceSize]byte
|
||||||
|
|
||||||
logDebug := device.log.Debug
|
defer device.log.Verbosef("Routine: decryption worker %d - stopped", id)
|
||||||
defer func() {
|
device.log.Verbosef("Routine: decryption worker %d - started", id)
|
||||||
logDebug.Println("Routine: decryption worker - stopped")
|
|
||||||
device.state.stopping.Done()
|
|
||||||
}()
|
|
||||||
logDebug.Println("Routine: decryption worker - started")
|
|
||||||
device.state.starting.Done()
|
|
||||||
|
|
||||||
for {
|
for elem := range device.queue.decryption.c {
|
||||||
select {
|
// split message into fields
|
||||||
case <-device.signals.stop:
|
counter := elem.packet[MessageTransportOffsetCounter:MessageTransportOffsetContent]
|
||||||
return
|
content := elem.packet[MessageTransportOffsetContent:]
|
||||||
|
|
||||||
case elem, ok := <-device.queue.decryption:
|
// decrypt and release to consumer
|
||||||
|
var err error
|
||||||
if !ok {
|
elem.counter = binary.LittleEndian.Uint64(counter)
|
||||||
return
|
// copy counter to nonce
|
||||||
}
|
binary.LittleEndian.PutUint64(nonce[0x4:0xc], elem.counter)
|
||||||
|
elem.packet, err = elem.keypair.receive.Open(
|
||||||
// check if dropped
|
content[:0],
|
||||||
|
nonce[:],
|
||||||
if elem.IsDropped() {
|
content,
|
||||||
continue
|
nil,
|
||||||
}
|
)
|
||||||
|
if err != nil {
|
||||||
// split message into fields
|
elem.packet = nil
|
||||||
|
|
||||||
counter := elem.packet[MessageTransportOffsetCounter:MessageTransportOffsetContent]
|
|
||||||
content := elem.packet[MessageTransportOffsetContent:]
|
|
||||||
|
|
||||||
// expand nonce
|
|
||||||
|
|
||||||
nonce[0x4] = counter[0x0]
|
|
||||||
nonce[0x5] = counter[0x1]
|
|
||||||
nonce[0x6] = counter[0x2]
|
|
||||||
nonce[0x7] = counter[0x3]
|
|
||||||
|
|
||||||
nonce[0x8] = counter[0x4]
|
|
||||||
nonce[0x9] = counter[0x5]
|
|
||||||
nonce[0xa] = counter[0x6]
|
|
||||||
nonce[0xb] = counter[0x7]
|
|
||||||
|
|
||||||
// decrypt and release to consumer
|
|
||||||
|
|
||||||
var err error
|
|
||||||
elem.counter = binary.LittleEndian.Uint64(counter)
|
|
||||||
elem.packet, err = elem.keypair.receive.Open(
|
|
||||||
content[:0],
|
|
||||||
nonce[:],
|
|
||||||
content,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
elem.Drop()
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
}
|
|
||||||
elem.Unlock()
|
|
||||||
}
|
}
|
||||||
|
elem.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handles incoming packets related to handshake
|
/* Handles incoming packets related to handshake
|
||||||
*/
|
*/
|
||||||
func (device *Device) RoutineHandshake() {
|
func (device *Device) RoutineHandshake(id int) {
|
||||||
|
|
||||||
logInfo := device.log.Info
|
|
||||||
logError := device.log.Error
|
|
||||||
logDebug := device.log.Debug
|
|
||||||
|
|
||||||
var elem QueueHandshakeElement
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
logDebug.Println("Routine: handshake worker - stopped")
|
device.log.Verbosef("Routine: handshake worker %d - stopped", id)
|
||||||
device.state.stopping.Done()
|
device.queue.encryption.wg.Done()
|
||||||
if elem.buffer != nil {
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
device.log.Verbosef("Routine: handshake worker %d - started", id)
|
||||||
|
|
||||||
logDebug.Println("Routine: handshake worker - started")
|
for elem := range device.queue.handshake.c {
|
||||||
device.state.starting.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if elem.buffer != nil {
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
elem.buffer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case elem, ok = <-device.queue.handshake:
|
|
||||||
case <-device.signals.stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle cookie fields and ratelimiting
|
// handle cookie fields and ratelimiting
|
||||||
|
|
||||||
@@ -336,8 +255,8 @@ func (device *Device) RoutineHandshake() {
|
|||||||
reader := bytes.NewReader(elem.packet)
|
reader := bytes.NewReader(elem.packet)
|
||||||
err := binary.Read(reader, binary.LittleEndian, &reply)
|
err := binary.Read(reader, binary.LittleEndian, &reply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logDebug.Println("Failed to decode cookie reply")
|
device.log.Verbosef("Failed to decode cookie reply")
|
||||||
return
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup peer from index
|
// lookup peer from index
|
||||||
@@ -345,27 +264,27 @@ func (device *Device) RoutineHandshake() {
|
|||||||
entry := device.indexTable.Lookup(reply.Receiver)
|
entry := device.indexTable.Lookup(reply.Receiver)
|
||||||
|
|
||||||
if entry.peer == nil {
|
if entry.peer == nil {
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// consume reply
|
// consume reply
|
||||||
|
|
||||||
if peer := entry.peer; peer.isRunning.Get() {
|
if peer := entry.peer; peer.isRunning.Get() {
|
||||||
logDebug.Println("Receiving cookie response from ", elem.endpoint.DstToString())
|
device.log.Verbosef("Receiving cookie response from %s", elem.endpoint.DstToString())
|
||||||
if !peer.cookieGenerator.ConsumeReply(&reply) {
|
if !peer.cookieGenerator.ConsumeReply(&reply) {
|
||||||
logDebug.Println("Could not decrypt invalid cookie response")
|
device.log.Verbosef("Could not decrypt invalid cookie response")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
goto skip
|
||||||
|
|
||||||
case MessageInitiationType, MessageResponseType:
|
case MessageInitiationType, MessageResponseType:
|
||||||
|
|
||||||
// check mac fields and maybe ratelimit
|
// check mac fields and maybe ratelimit
|
||||||
|
|
||||||
if !device.cookieChecker.CheckMAC1(elem.packet) {
|
if !device.cookieChecker.CheckMAC1(elem.packet) {
|
||||||
logDebug.Println("Received packet with invalid mac1")
|
device.log.Verbosef("Received packet with invalid mac1")
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// endpoints destination address is the source of the datagram
|
// endpoints destination address is the source of the datagram
|
||||||
@@ -376,19 +295,19 @@ func (device *Device) RoutineHandshake() {
|
|||||||
|
|
||||||
if !device.cookieChecker.CheckMAC2(elem.packet, elem.endpoint.DstToBytes()) {
|
if !device.cookieChecker.CheckMAC2(elem.packet, elem.endpoint.DstToBytes()) {
|
||||||
device.SendHandshakeCookie(&elem)
|
device.SendHandshakeCookie(&elem)
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// check ratelimiter
|
// check ratelimiter
|
||||||
|
|
||||||
if !device.rate.limiter.Allow(elem.endpoint.DstIP()) {
|
if !device.rate.limiter.Allow(elem.endpoint.DstIP()) {
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logError.Println("Invalid packet ended up in the handshake queue")
|
device.log.Errorf("Invalid packet ended up in the handshake queue")
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle handshake initiation/response content
|
// handle handshake initiation/response content
|
||||||
@@ -402,19 +321,16 @@ func (device *Device) RoutineHandshake() {
|
|||||||
reader := bytes.NewReader(elem.packet)
|
reader := bytes.NewReader(elem.packet)
|
||||||
err := binary.Read(reader, binary.LittleEndian, &msg)
|
err := binary.Read(reader, binary.LittleEndian, &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logError.Println("Failed to decode initiation message")
|
device.log.Errorf("Failed to decode initiation message")
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// consume initiation
|
// consume initiation
|
||||||
|
|
||||||
peer := device.ConsumeMessageInitiation(&msg)
|
peer := device.ConsumeMessageInitiation(&msg)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
logInfo.Println(
|
device.log.Verbosef("Received invalid initiation message from %s", elem.endpoint.DstToString())
|
||||||
"Received invalid initiation message from",
|
goto skip
|
||||||
elem.endpoint.DstToString(),
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update timers
|
// update timers
|
||||||
@@ -425,7 +341,8 @@ func (device *Device) RoutineHandshake() {
|
|||||||
// update endpoint
|
// update endpoint
|
||||||
peer.SetEndpointFromPacket(elem.endpoint)
|
peer.SetEndpointFromPacket(elem.endpoint)
|
||||||
|
|
||||||
logDebug.Println(peer, "- Received handshake initiation")
|
device.log.Verbosef("%v - Received handshake initiation", peer)
|
||||||
|
atomic.AddUint64(&peer.stats.rxBytes, uint64(len(elem.packet)))
|
||||||
|
|
||||||
peer.SendHandshakeResponse()
|
peer.SendHandshakeResponse()
|
||||||
|
|
||||||
@@ -437,25 +354,23 @@ func (device *Device) RoutineHandshake() {
|
|||||||
reader := bytes.NewReader(elem.packet)
|
reader := bytes.NewReader(elem.packet)
|
||||||
err := binary.Read(reader, binary.LittleEndian, &msg)
|
err := binary.Read(reader, binary.LittleEndian, &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logError.Println("Failed to decode response message")
|
device.log.Errorf("Failed to decode response message")
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// consume response
|
// consume response
|
||||||
|
|
||||||
peer := device.ConsumeMessageResponse(&msg)
|
peer := device.ConsumeMessageResponse(&msg)
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
logInfo.Println(
|
device.log.Verbosef("Received invalid response message from %s", elem.endpoint.DstToString())
|
||||||
"Received invalid response message from",
|
goto skip
|
||||||
elem.endpoint.DstToString(),
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update endpoint
|
// update endpoint
|
||||||
peer.SetEndpointFromPacket(elem.endpoint)
|
peer.SetEndpointFromPacket(elem.endpoint)
|
||||||
|
|
||||||
logDebug.Println(peer, "- Received handshake response")
|
device.log.Verbosef("%v - Received handshake response", peer)
|
||||||
|
atomic.AddUint64(&peer.stats.rxBytes, uint64(len(elem.packet)))
|
||||||
|
|
||||||
// update timers
|
// update timers
|
||||||
|
|
||||||
@@ -467,201 +382,110 @@ func (device *Device) RoutineHandshake() {
|
|||||||
err = peer.BeginSymmetricSession()
|
err = peer.BeginSymmetricSession()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logError.Println(peer, "- Failed to derive keypair:", err)
|
device.log.Errorf("%v - Failed to derive keypair: %v", peer, err)
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.timersSessionDerived()
|
peer.timersSessionDerived()
|
||||||
peer.timersHandshakeComplete()
|
peer.timersHandshakeComplete()
|
||||||
peer.SendKeepalive()
|
peer.SendKeepalive()
|
||||||
select {
|
|
||||||
case peer.signals.newKeypairArrived <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (peer *Peer) elementStopOrFlush(shouldFlush *bool) (stop bool, elemOk bool, elem *QueueInboundElement) {
|
|
||||||
if !*shouldFlush {
|
|
||||||
select {
|
|
||||||
case <-peer.routines.stop:
|
|
||||||
stop = true
|
|
||||||
return
|
|
||||||
case elem, elemOk = <-peer.queue.inbound:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
select {
|
|
||||||
case <-peer.routines.stop:
|
|
||||||
stop = true
|
|
||||||
return
|
|
||||||
case elem, elemOk = <-peer.queue.inbound:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
*shouldFlush = false
|
|
||||||
err := peer.device.tun.device.Flush()
|
|
||||||
if err != nil {
|
|
||||||
peer.device.log.Error.Printf("Unable to flush packets: %v", err)
|
|
||||||
}
|
|
||||||
return peer.elementStopOrFlush(shouldFlush)
|
|
||||||
}
|
}
|
||||||
|
skip:
|
||||||
|
device.PutMessageBuffer(elem.buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *Peer) RoutineSequentialReceiver() {
|
func (peer *Peer) RoutineSequentialReceiver() {
|
||||||
|
|
||||||
device := peer.device
|
device := peer.device
|
||||||
logInfo := device.log.Info
|
|
||||||
logError := device.log.Error
|
|
||||||
logDebug := device.log.Debug
|
|
||||||
|
|
||||||
var elem *QueueInboundElement
|
|
||||||
var ok bool
|
|
||||||
var stop bool
|
|
||||||
|
|
||||||
shouldFlush := false
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
logDebug.Println(peer, "- Routine: sequential receiver - stopped")
|
device.log.Verbosef("%v - Routine: sequential receiver - stopped", peer)
|
||||||
peer.routines.stopping.Done()
|
peer.stopping.Done()
|
||||||
if elem != nil {
|
|
||||||
if !elem.IsDropped() {
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
}
|
|
||||||
device.PutInboundElement(elem)
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
device.log.Verbosef("%v - Routine: sequential receiver - started", peer)
|
||||||
|
|
||||||
logDebug.Println(peer, "- Routine: sequential receiver - started")
|
for elem := range peer.queue.inbound.c {
|
||||||
|
if elem == nil {
|
||||||
peer.routines.starting.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if elem != nil {
|
|
||||||
if !elem.IsDropped() {
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
}
|
|
||||||
device.PutInboundElement(elem)
|
|
||||||
elem = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
stop, ok, elem = peer.elementStopOrFlush(&shouldFlush)
|
|
||||||
if stop || !ok {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
// wait for decryption
|
|
||||||
|
|
||||||
elem.Lock()
|
elem.Lock()
|
||||||
|
if elem.packet == nil {
|
||||||
if elem.IsDropped() {
|
// decryption failed
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for replay
|
|
||||||
|
|
||||||
if !elem.keypair.replayFilter.ValidateCounter(elem.counter, RejectAfterMessages) {
|
if !elem.keypair.replayFilter.ValidateCounter(elem.counter, RejectAfterMessages) {
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// update endpoint
|
|
||||||
peer.SetEndpointFromPacket(elem.endpoint)
|
peer.SetEndpointFromPacket(elem.endpoint)
|
||||||
|
|
||||||
// check if using new keypair
|
|
||||||
if peer.ReceivedWithKeypair(elem.keypair) {
|
if peer.ReceivedWithKeypair(elem.keypair) {
|
||||||
peer.timersHandshakeComplete()
|
peer.timersHandshakeComplete()
|
||||||
select {
|
peer.SendStagedPackets()
|
||||||
case peer.signals.newKeypairArrived <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.keepKeyFreshReceiving()
|
peer.keepKeyFreshReceiving()
|
||||||
peer.timersAnyAuthenticatedPacketTraversal()
|
peer.timersAnyAuthenticatedPacketTraversal()
|
||||||
peer.timersAnyAuthenticatedPacketReceived()
|
peer.timersAnyAuthenticatedPacketReceived()
|
||||||
|
atomic.AddUint64(&peer.stats.rxBytes, uint64(len(elem.packet)+MinMessageSize))
|
||||||
// check for keepalive
|
|
||||||
|
|
||||||
if len(elem.packet) == 0 {
|
if len(elem.packet) == 0 {
|
||||||
logDebug.Println(peer, "- Receiving keepalive packet")
|
device.log.Verbosef("%v - Receiving keepalive packet", peer)
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
peer.timersDataReceived()
|
peer.timersDataReceived()
|
||||||
|
|
||||||
// verify source and strip padding
|
|
||||||
|
|
||||||
switch elem.packet[0] >> 4 {
|
switch elem.packet[0] >> 4 {
|
||||||
case ipv4.Version:
|
case ipv4.Version:
|
||||||
|
|
||||||
// strip padding
|
|
||||||
|
|
||||||
if len(elem.packet) < ipv4.HeaderLen {
|
if len(elem.packet) < ipv4.HeaderLen {
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
field := elem.packet[IPv4offsetTotalLength : IPv4offsetTotalLength+2]
|
field := elem.packet[IPv4offsetTotalLength : IPv4offsetTotalLength+2]
|
||||||
length := binary.BigEndian.Uint16(field)
|
length := binary.BigEndian.Uint16(field)
|
||||||
if int(length) > len(elem.packet) || int(length) < ipv4.HeaderLen {
|
if int(length) > len(elem.packet) || int(length) < ipv4.HeaderLen {
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
elem.packet = elem.packet[:length]
|
elem.packet = elem.packet[:length]
|
||||||
|
|
||||||
// verify IPv4 source
|
|
||||||
|
|
||||||
src := elem.packet[IPv4offsetSrc : IPv4offsetSrc+net.IPv4len]
|
src := elem.packet[IPv4offsetSrc : IPv4offsetSrc+net.IPv4len]
|
||||||
if device.allowedips.LookupIPv4(src) != peer {
|
if device.allowedips.Lookup(src) != peer {
|
||||||
logInfo.Println(
|
device.log.Verbosef("IPv4 packet with disallowed source address from %v", peer)
|
||||||
"IPv4 packet with disallowed source address from",
|
goto skip
|
||||||
peer,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case ipv6.Version:
|
case ipv6.Version:
|
||||||
|
|
||||||
// strip padding
|
|
||||||
|
|
||||||
if len(elem.packet) < ipv6.HeaderLen {
|
if len(elem.packet) < ipv6.HeaderLen {
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
field := elem.packet[IPv6offsetPayloadLength : IPv6offsetPayloadLength+2]
|
field := elem.packet[IPv6offsetPayloadLength : IPv6offsetPayloadLength+2]
|
||||||
length := binary.BigEndian.Uint16(field)
|
length := binary.BigEndian.Uint16(field)
|
||||||
length += ipv6.HeaderLen
|
length += ipv6.HeaderLen
|
||||||
if int(length) > len(elem.packet) {
|
if int(length) > len(elem.packet) {
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
elem.packet = elem.packet[:length]
|
elem.packet = elem.packet[:length]
|
||||||
|
|
||||||
// verify IPv6 source
|
|
||||||
|
|
||||||
src := elem.packet[IPv6offsetSrc : IPv6offsetSrc+net.IPv6len]
|
src := elem.packet[IPv6offsetSrc : IPv6offsetSrc+net.IPv6len]
|
||||||
if device.allowedips.LookupIPv6(src) != peer {
|
if device.allowedips.Lookup(src) != peer {
|
||||||
logInfo.Println(
|
device.log.Verbosef("IPv6 packet with disallowed source address from %v", peer)
|
||||||
peer,
|
goto skip
|
||||||
"sent packet with disallowed IPv6 source",
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logInfo.Println("Packet with invalid IP version from", peer)
|
device.log.Verbosef("Packet with invalid IP version from %v", peer)
|
||||||
continue
|
goto skip
|
||||||
}
|
}
|
||||||
|
|
||||||
// write to tun device
|
_, err = device.tun.device.Write(elem.buffer[:MessageTransportOffsetContent+len(elem.packet)], MessageTransportOffsetContent)
|
||||||
|
if err != nil && !device.isClosed() {
|
||||||
offset := MessageTransportOffsetContent
|
device.log.Errorf("Failed to write packet to TUN device: %v", err)
|
||||||
atomic.AddUint64(&peer.stats.rxBytes, uint64(len(elem.packet)))
|
|
||||||
_, err := device.tun.device.Write(elem.buffer[:offset+len(elem.packet)], offset)
|
|
||||||
if err == nil {
|
|
||||||
shouldFlush = true
|
|
||||||
}
|
}
|
||||||
if err != nil && !device.isClosed.Get() {
|
if len(peer.queue.inbound.c) == 0 {
|
||||||
logError.Println("Failed to write packet to TUN device:", err)
|
err = device.tun.device.Flush()
|
||||||
|
if err != nil {
|
||||||
|
peer.device.log.Errorf("Unable to flush packets: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
skip:
|
||||||
|
device.PutMessageBuffer(elem.buffer)
|
||||||
|
device.PutInboundElement(elem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
513
device/send.go
513
device/send.go
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
@@ -8,13 +8,16 @@ package device
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"errors"
|
||||||
"golang.org/x/net/ipv4"
|
|
||||||
"golang.org/x/net/ipv6"
|
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Outbound flow
|
/* Outbound flow
|
||||||
@@ -42,7 +45,6 @@ import (
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
type QueueOutboundElement struct {
|
type QueueOutboundElement struct {
|
||||||
dropped int32
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
buffer *[MaxMessageSize]byte // slice holding the packet data
|
buffer *[MaxMessageSize]byte // slice holding the packet data
|
||||||
packet []byte // slice of "buffer" (always!)
|
packet []byte // slice of "buffer" (always!)
|
||||||
@@ -53,73 +55,38 @@ type QueueOutboundElement struct {
|
|||||||
|
|
||||||
func (device *Device) NewOutboundElement() *QueueOutboundElement {
|
func (device *Device) NewOutboundElement() *QueueOutboundElement {
|
||||||
elem := device.GetOutboundElement()
|
elem := device.GetOutboundElement()
|
||||||
elem.dropped = AtomicFalse
|
|
||||||
elem.buffer = device.GetMessageBuffer()
|
elem.buffer = device.GetMessageBuffer()
|
||||||
elem.Mutex = sync.Mutex{}
|
elem.Mutex = sync.Mutex{}
|
||||||
elem.nonce = 0
|
elem.nonce = 0
|
||||||
elem.keypair = nil
|
// keypair and peer were cleared (if necessary) by clearPointers.
|
||||||
elem.peer = nil
|
|
||||||
return elem
|
return elem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (elem *QueueOutboundElement) Drop() {
|
// clearPointers clears elem fields that contain pointers.
|
||||||
atomic.StoreInt32(&elem.dropped, AtomicTrue)
|
// This makes the garbage collector's life easier and
|
||||||
}
|
// avoids accidentally keeping other objects around unnecessarily.
|
||||||
|
// It also reduces the possible collateral damage from use-after-free bugs.
|
||||||
func (elem *QueueOutboundElement) IsDropped() bool {
|
func (elem *QueueOutboundElement) clearPointers() {
|
||||||
return atomic.LoadInt32(&elem.dropped) == AtomicTrue
|
elem.buffer = nil
|
||||||
}
|
elem.packet = nil
|
||||||
|
elem.keypair = nil
|
||||||
func addToNonceQueue(queue chan *QueueOutboundElement, element *QueueOutboundElement, device *Device) {
|
elem.peer = nil
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case queue <- element:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
select {
|
|
||||||
case old := <-queue:
|
|
||||||
device.PutMessageBuffer(old.buffer)
|
|
||||||
device.PutOutboundElement(old)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addToOutboundAndEncryptionQueues(outboundQueue chan *QueueOutboundElement, encryptionQueue chan *QueueOutboundElement, element *QueueOutboundElement) {
|
|
||||||
select {
|
|
||||||
case outboundQueue <- element:
|
|
||||||
select {
|
|
||||||
case encryptionQueue <- element:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
element.Drop()
|
|
||||||
element.peer.device.PutMessageBuffer(element.buffer)
|
|
||||||
element.Unlock()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
element.peer.device.PutMessageBuffer(element.buffer)
|
|
||||||
element.peer.device.PutOutboundElement(element)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Queues a keepalive if no packets are queued for peer
|
/* Queues a keepalive if no packets are queued for peer
|
||||||
*/
|
*/
|
||||||
func (peer *Peer) SendKeepalive() bool {
|
func (peer *Peer) SendKeepalive() {
|
||||||
if len(peer.queue.nonce) != 0 || peer.queue.packetInNonceQueueIsAwaitingKey.Get() || !peer.isRunning.Get() {
|
if len(peer.queue.staged) == 0 && peer.isRunning.Get() {
|
||||||
return false
|
elem := peer.device.NewOutboundElement()
|
||||||
}
|
select {
|
||||||
elem := peer.device.NewOutboundElement()
|
case peer.queue.staged <- elem:
|
||||||
elem.packet = nil
|
peer.device.log.Verbosef("%v - Sending keepalive packet", peer)
|
||||||
select {
|
default:
|
||||||
case peer.queue.nonce <- elem:
|
peer.device.PutMessageBuffer(elem.buffer)
|
||||||
peer.device.log.Debug.Println(peer, "- Sending keepalive packet")
|
peer.device.PutOutboundElement(elem)
|
||||||
return true
|
}
|
||||||
default:
|
|
||||||
peer.device.PutMessageBuffer(elem.buffer)
|
|
||||||
peer.device.PutOutboundElement(elem)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
peer.SendStagedPackets()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||||
@@ -128,25 +95,25 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
peer.handshake.mutex.RLock()
|
peer.handshake.mutex.RLock()
|
||||||
if time.Now().Sub(peer.handshake.lastSentHandshake) < RekeyTimeout {
|
if time.Since(peer.handshake.lastSentHandshake) < RekeyTimeout {
|
||||||
peer.handshake.mutex.RUnlock()
|
peer.handshake.mutex.RUnlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
peer.handshake.mutex.RUnlock()
|
peer.handshake.mutex.RUnlock()
|
||||||
|
|
||||||
peer.handshake.mutex.Lock()
|
peer.handshake.mutex.Lock()
|
||||||
if time.Now().Sub(peer.handshake.lastSentHandshake) < RekeyTimeout {
|
if time.Since(peer.handshake.lastSentHandshake) < RekeyTimeout {
|
||||||
peer.handshake.mutex.Unlock()
|
peer.handshake.mutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
peer.handshake.lastSentHandshake = time.Now()
|
peer.handshake.lastSentHandshake = time.Now()
|
||||||
peer.handshake.mutex.Unlock()
|
peer.handshake.mutex.Unlock()
|
||||||
|
|
||||||
peer.device.log.Debug.Println(peer, "- Sending handshake initiation")
|
peer.device.log.Verbosef("%v - Sending handshake initiation", peer)
|
||||||
|
|
||||||
msg, err := peer.device.CreateMessageInitiation(peer)
|
msg, err := peer.device.CreateMessageInitiation(peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
peer.device.log.Error.Println(peer, "- Failed to create initiation message:", err)
|
peer.device.log.Errorf("%v - Failed to create initiation message: %v", peer, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +128,7 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
|||||||
|
|
||||||
err = peer.SendBuffer(packet)
|
err = peer.SendBuffer(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
peer.device.log.Error.Println(peer, "- Failed to send handshake initiation", err)
|
peer.device.log.Errorf("%v - Failed to send handshake initiation: %v", peer, err)
|
||||||
}
|
}
|
||||||
peer.timersHandshakeInitiated()
|
peer.timersHandshakeInitiated()
|
||||||
|
|
||||||
@@ -173,11 +140,11 @@ func (peer *Peer) SendHandshakeResponse() error {
|
|||||||
peer.handshake.lastSentHandshake = time.Now()
|
peer.handshake.lastSentHandshake = time.Now()
|
||||||
peer.handshake.mutex.Unlock()
|
peer.handshake.mutex.Unlock()
|
||||||
|
|
||||||
peer.device.log.Debug.Println(peer, "- Sending handshake response")
|
peer.device.log.Verbosef("%v - Sending handshake response", peer)
|
||||||
|
|
||||||
response, err := peer.device.CreateMessageResponse(peer)
|
response, err := peer.device.CreateMessageResponse(peer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
peer.device.log.Error.Println(peer, "- Failed to create response message:", err)
|
peer.device.log.Errorf("%v - Failed to create response message: %v", peer, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +156,7 @@ func (peer *Peer) SendHandshakeResponse() error {
|
|||||||
|
|
||||||
err = peer.BeginSymmetricSession()
|
err = peer.BeginSymmetricSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
peer.device.log.Error.Println(peer, "- Failed to derive keypair:", err)
|
peer.device.log.Errorf("%v - Failed to derive keypair: %v", peer, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,19 +166,18 @@ func (peer *Peer) SendHandshakeResponse() error {
|
|||||||
|
|
||||||
err = peer.SendBuffer(packet)
|
err = peer.SendBuffer(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
peer.device.log.Error.Println(peer, "- Failed to send handshake response", err)
|
peer.device.log.Errorf("%v - Failed to send handshake response: %v", peer, err)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement) error {
|
func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement) error {
|
||||||
|
device.log.Verbosef("Sending cookie response for denied handshake message for %v", initiatingElem.endpoint.DstToString())
|
||||||
device.log.Debug.Println("Sending cookie response for denied handshake message for", 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())
|
reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.endpoint.DstToBytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
device.log.Error.Println("Failed to create cookie reply:", err)
|
device.log.Errorf("Failed to create cookie reply: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,10 +185,7 @@ func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement)
|
|||||||
writer := bytes.NewBuffer(buff[:0])
|
writer := bytes.NewBuffer(buff[:0])
|
||||||
binary.Write(writer, binary.LittleEndian, reply)
|
binary.Write(writer, binary.LittleEndian, reply)
|
||||||
device.net.bind.Send(writer.Bytes(), initiatingElem.endpoint)
|
device.net.bind.Send(writer.Bytes(), initiatingElem.endpoint)
|
||||||
if err != nil {
|
return nil
|
||||||
device.log.Error.Println("Failed to send cookie reply:", err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *Peer) keepKeyFreshSending() {
|
func (peer *Peer) keepKeyFreshSending() {
|
||||||
@@ -231,28 +194,24 @@ func (peer *Peer) keepKeyFreshSending() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
nonce := atomic.LoadUint64(&keypair.sendNonce)
|
nonce := atomic.LoadUint64(&keypair.sendNonce)
|
||||||
if nonce > RekeyAfterMessages || (keypair.isInitiator && time.Now().Sub(keypair.created) > RekeyAfterTime) {
|
if nonce > RekeyAfterMessages || (keypair.isInitiator && time.Since(keypair.created) > RekeyAfterTime) {
|
||||||
peer.SendHandshakeInitiation(false)
|
peer.SendHandshakeInitiation(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reads packets from the TUN and inserts
|
/* Reads packets from the TUN and inserts
|
||||||
* into nonce queue for peer
|
* into staged queue for peer
|
||||||
*
|
*
|
||||||
* Obs. Single instance per TUN device
|
* Obs. Single instance per TUN device
|
||||||
*/
|
*/
|
||||||
func (device *Device) RoutineReadFromTUN() {
|
func (device *Device) RoutineReadFromTUN() {
|
||||||
|
|
||||||
logDebug := device.log.Debug
|
|
||||||
logError := device.log.Error
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
logDebug.Println("Routine: TUN reader - stopped")
|
device.log.Verbosef("Routine: TUN reader - stopped")
|
||||||
device.state.stopping.Done()
|
device.state.stopping.Done()
|
||||||
|
device.queue.encryption.wg.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
logDebug.Println("Routine: TUN reader - started")
|
device.log.Verbosef("Routine: TUN reader - started")
|
||||||
device.state.starting.Done()
|
|
||||||
|
|
||||||
var elem *QueueOutboundElement
|
var elem *QueueOutboundElement
|
||||||
|
|
||||||
@@ -269,9 +228,11 @@ func (device *Device) RoutineReadFromTUN() {
|
|||||||
size, err := device.tun.device.Read(elem.buffer[:], offset)
|
size, err := device.tun.device.Read(elem.buffer[:], offset)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !device.isClosed.Get() {
|
if !device.isClosed() {
|
||||||
logError.Println("Failed to read packet from TUN device:", err)
|
if !errors.Is(err, os.ErrClosed) {
|
||||||
device.Close()
|
device.log.Errorf("Failed to read packet from TUN device: %v", err)
|
||||||
|
}
|
||||||
|
go device.Close()
|
||||||
}
|
}
|
||||||
device.PutMessageBuffer(elem.buffer)
|
device.PutMessageBuffer(elem.buffer)
|
||||||
device.PutOutboundElement(elem)
|
device.PutOutboundElement(elem)
|
||||||
@@ -293,250 +254,151 @@ func (device *Device) RoutineReadFromTUN() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dst := elem.packet[IPv4offsetDst : IPv4offsetDst+net.IPv4len]
|
dst := elem.packet[IPv4offsetDst : IPv4offsetDst+net.IPv4len]
|
||||||
peer = device.allowedips.LookupIPv4(dst)
|
peer = device.allowedips.Lookup(dst)
|
||||||
|
|
||||||
case ipv6.Version:
|
case ipv6.Version:
|
||||||
if len(elem.packet) < ipv6.HeaderLen {
|
if len(elem.packet) < ipv6.HeaderLen {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dst := elem.packet[IPv6offsetDst : IPv6offsetDst+net.IPv6len]
|
dst := elem.packet[IPv6offsetDst : IPv6offsetDst+net.IPv6len]
|
||||||
peer = device.allowedips.LookupIPv6(dst)
|
peer = device.allowedips.Lookup(dst)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logDebug.Println("Received packet with unknown IP version")
|
device.log.Verbosef("Received packet with unknown IP version")
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer == nil {
|
if peer == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert into nonce/pre-handshake queue
|
|
||||||
|
|
||||||
if peer.isRunning.Get() {
|
if peer.isRunning.Get() {
|
||||||
if peer.queue.packetInNonceQueueIsAwaitingKey.Get() {
|
peer.StagePacket(elem)
|
||||||
peer.SendHandshakeInitiation(false)
|
|
||||||
}
|
|
||||||
addToNonceQueue(peer.queue.nonce, elem, device)
|
|
||||||
elem = nil
|
elem = nil
|
||||||
|
peer.SendStagedPackets()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (peer *Peer) FlushNonceQueue() {
|
func (peer *Peer) StagePacket(elem *QueueOutboundElement) {
|
||||||
select {
|
for {
|
||||||
case peer.signals.flushNonceQueue <- struct{}{}:
|
select {
|
||||||
default:
|
case peer.queue.staged <- elem:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case tooOld := <-peer.queue.staged:
|
||||||
|
peer.device.PutMessageBuffer(tooOld.buffer)
|
||||||
|
peer.device.PutOutboundElement(tooOld)
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Queues packets when there is no handshake.
|
func (peer *Peer) SendStagedPackets() {
|
||||||
* Then assigns nonces to packets sequentially
|
top:
|
||||||
* and creates "work" structs for workers
|
if len(peer.queue.staged) == 0 || !peer.device.isUp() {
|
||||||
*
|
return
|
||||||
* Obs. A single instance per peer
|
|
||||||
*/
|
|
||||||
func (peer *Peer) RoutineNonce() {
|
|
||||||
var keypair *Keypair
|
|
||||||
|
|
||||||
device := peer.device
|
|
||||||
logDebug := device.log.Debug
|
|
||||||
|
|
||||||
flush := func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case elem := <-peer.queue.nonce:
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
device.PutOutboundElement(elem)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
keypair := peer.keypairs.Current()
|
||||||
flush()
|
if keypair == nil || atomic.LoadUint64(&keypair.sendNonce) >= RejectAfterMessages || time.Since(keypair.created) >= RejectAfterTime {
|
||||||
logDebug.Println(peer, "- Routine: nonce worker - stopped")
|
peer.SendHandshakeInitiation(false)
|
||||||
peer.queue.packetInNonceQueueIsAwaitingKey.Set(false)
|
return
|
||||||
peer.routines.stopping.Done()
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
peer.routines.starting.Done()
|
|
||||||
logDebug.Println(peer, "- Routine: nonce worker - started")
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
NextPacket:
|
|
||||||
peer.queue.packetInNonceQueueIsAwaitingKey.Set(false)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-peer.routines.stop:
|
case elem := <-peer.queue.staged:
|
||||||
return
|
|
||||||
|
|
||||||
case <-peer.signals.flushNonceQueue:
|
|
||||||
flush()
|
|
||||||
goto NextPacket
|
|
||||||
|
|
||||||
case elem, ok := <-peer.queue.nonce:
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure to always pick the newest key
|
|
||||||
|
|
||||||
for {
|
|
||||||
|
|
||||||
// check validity of newest key pair
|
|
||||||
|
|
||||||
keypair = peer.keypairs.Current()
|
|
||||||
if keypair != nil && keypair.sendNonce < RejectAfterMessages {
|
|
||||||
if time.Now().Sub(keypair.created) < RejectAfterTime {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
peer.queue.packetInNonceQueueIsAwaitingKey.Set(true)
|
|
||||||
|
|
||||||
// no suitable key pair, request for new handshake
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-peer.signals.newKeypairArrived:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
peer.SendHandshakeInitiation(false)
|
|
||||||
|
|
||||||
// wait for key to be established
|
|
||||||
|
|
||||||
logDebug.Println(peer, "- Awaiting keypair")
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-peer.signals.newKeypairArrived:
|
|
||||||
logDebug.Println(peer, "- Obtained awaited keypair")
|
|
||||||
|
|
||||||
case <-peer.signals.flushNonceQueue:
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
device.PutOutboundElement(elem)
|
|
||||||
flush()
|
|
||||||
goto NextPacket
|
|
||||||
|
|
||||||
case <-peer.routines.stop:
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
device.PutOutboundElement(elem)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
peer.queue.packetInNonceQueueIsAwaitingKey.Set(false)
|
|
||||||
|
|
||||||
// populate work element
|
|
||||||
|
|
||||||
elem.peer = peer
|
elem.peer = peer
|
||||||
elem.nonce = atomic.AddUint64(&keypair.sendNonce, 1) - 1
|
elem.nonce = atomic.AddUint64(&keypair.sendNonce, 1) - 1
|
||||||
|
|
||||||
// double check in case of race condition added by future code
|
|
||||||
|
|
||||||
if elem.nonce >= RejectAfterMessages {
|
if elem.nonce >= RejectAfterMessages {
|
||||||
atomic.StoreUint64(&keypair.sendNonce, RejectAfterMessages)
|
atomic.StoreUint64(&keypair.sendNonce, RejectAfterMessages)
|
||||||
device.PutMessageBuffer(elem.buffer)
|
peer.StagePacket(elem) // XXX: Out of order, but we can't front-load go chans
|
||||||
device.PutOutboundElement(elem)
|
goto top
|
||||||
goto NextPacket
|
|
||||||
}
|
}
|
||||||
|
|
||||||
elem.keypair = keypair
|
elem.keypair = keypair
|
||||||
elem.dropped = AtomicFalse
|
|
||||||
elem.Lock()
|
elem.Lock()
|
||||||
|
|
||||||
// add to parallel and sequential queue
|
// add to parallel and sequential queue
|
||||||
addToOutboundAndEncryptionQueues(peer.queue.outbound, device.queue.encryption, elem)
|
if peer.isRunning.Get() {
|
||||||
|
peer.queue.outbound.c <- elem
|
||||||
|
peer.device.queue.encryption.c <- elem
|
||||||
|
} else {
|
||||||
|
peer.device.PutMessageBuffer(elem.buffer)
|
||||||
|
peer.device.PutOutboundElement(elem)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (peer *Peer) FlushStagedPackets() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case elem := <-peer.queue.staged:
|
||||||
|
peer.device.PutMessageBuffer(elem.buffer)
|
||||||
|
peer.device.PutOutboundElement(elem)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculatePaddingSize(packetSize, mtu int) int {
|
||||||
|
lastUnit := packetSize
|
||||||
|
if mtu == 0 {
|
||||||
|
return ((lastUnit + PaddingMultiple - 1) & ^(PaddingMultiple - 1)) - lastUnit
|
||||||
|
}
|
||||||
|
if lastUnit > mtu {
|
||||||
|
lastUnit %= mtu
|
||||||
|
}
|
||||||
|
paddedSize := ((lastUnit + PaddingMultiple - 1) & ^(PaddingMultiple - 1))
|
||||||
|
if paddedSize > mtu {
|
||||||
|
paddedSize = mtu
|
||||||
|
}
|
||||||
|
return paddedSize - lastUnit
|
||||||
|
}
|
||||||
|
|
||||||
/* Encrypts the elements in the queue
|
/* Encrypts the elements in the queue
|
||||||
* and marks them for sequential consumption (by releasing the mutex)
|
* and marks them for sequential consumption (by releasing the mutex)
|
||||||
*
|
*
|
||||||
* Obs. One instance per core
|
* Obs. One instance per core
|
||||||
*/
|
*/
|
||||||
func (device *Device) RoutineEncryption() {
|
func (device *Device) RoutineEncryption(id int) {
|
||||||
|
var paddingZeros [PaddingMultiple]byte
|
||||||
var nonce [chacha20poly1305.NonceSize]byte
|
var nonce [chacha20poly1305.NonceSize]byte
|
||||||
|
|
||||||
logDebug := device.log.Debug
|
defer device.log.Verbosef("Routine: encryption worker %d - stopped", id)
|
||||||
|
device.log.Verbosef("Routine: encryption worker %d - started", id)
|
||||||
|
|
||||||
defer func() {
|
for elem := range device.queue.encryption.c {
|
||||||
for {
|
// populate header fields
|
||||||
select {
|
header := elem.buffer[:MessageTransportHeaderSize]
|
||||||
case elem, ok := <-device.queue.encryption:
|
|
||||||
if ok && !elem.IsDropped() {
|
|
||||||
elem.Drop()
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
elem.Unlock()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out:
|
|
||||||
logDebug.Println("Routine: encryption worker - stopped")
|
|
||||||
device.state.stopping.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
logDebug.Println("Routine: encryption worker - started")
|
fieldType := header[0:4]
|
||||||
device.state.starting.Done()
|
fieldReceiver := header[4:8]
|
||||||
|
fieldNonce := header[8:16]
|
||||||
|
|
||||||
for {
|
binary.LittleEndian.PutUint32(fieldType, MessageTransportType)
|
||||||
|
binary.LittleEndian.PutUint32(fieldReceiver, elem.keypair.remoteIndex)
|
||||||
|
binary.LittleEndian.PutUint64(fieldNonce, elem.nonce)
|
||||||
|
|
||||||
// fetch next element
|
// pad content to multiple of 16
|
||||||
|
paddingSize := calculatePaddingSize(len(elem.packet), int(atomic.LoadInt32(&device.tun.mtu)))
|
||||||
|
elem.packet = append(elem.packet, paddingZeros[:paddingSize]...)
|
||||||
|
|
||||||
select {
|
// encrypt content and release to consumer
|
||||||
case <-device.signals.stop:
|
|
||||||
return
|
|
||||||
|
|
||||||
case elem, ok := <-device.queue.encryption:
|
binary.LittleEndian.PutUint64(nonce[4:], elem.nonce)
|
||||||
|
elem.packet = elem.keypair.send.Seal(
|
||||||
if !ok {
|
header,
|
||||||
return
|
nonce[:],
|
||||||
}
|
elem.packet,
|
||||||
|
nil,
|
||||||
// check if dropped
|
)
|
||||||
|
elem.Unlock()
|
||||||
if elem.IsDropped() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate header fields
|
|
||||||
|
|
||||||
header := elem.buffer[:MessageTransportHeaderSize]
|
|
||||||
|
|
||||||
fieldType := header[0:4]
|
|
||||||
fieldReceiver := header[4:8]
|
|
||||||
fieldNonce := header[8:16]
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint32(fieldType, MessageTransportType)
|
|
||||||
binary.LittleEndian.PutUint32(fieldReceiver, elem.keypair.remoteIndex)
|
|
||||||
binary.LittleEndian.PutUint64(fieldNonce, elem.nonce)
|
|
||||||
|
|
||||||
// pad content to multiple of 16
|
|
||||||
|
|
||||||
mtu := int(atomic.LoadInt32(&device.tun.mtu))
|
|
||||||
lastUnit := len(elem.packet) % mtu
|
|
||||||
paddedSize := (lastUnit + PaddingMultiple - 1) & ^(PaddingMultiple - 1)
|
|
||||||
if paddedSize > mtu {
|
|
||||||
paddedSize = mtu
|
|
||||||
}
|
|
||||||
for i := len(elem.packet); i < paddedSize; i++ {
|
|
||||||
elem.packet = append(elem.packet, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// encrypt content and release to consumer
|
|
||||||
|
|
||||||
binary.LittleEndian.PutUint64(nonce[4:], elem.nonce)
|
|
||||||
elem.packet = elem.keypair.send.Seal(
|
|
||||||
header,
|
|
||||||
nonce[:],
|
|
||||||
elem.packet,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
elem.Unlock()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,73 +408,46 @@ func (device *Device) RoutineEncryption() {
|
|||||||
* The routine terminates then the outbound queue is closed.
|
* The routine terminates then the outbound queue is closed.
|
||||||
*/
|
*/
|
||||||
func (peer *Peer) RoutineSequentialSender() {
|
func (peer *Peer) RoutineSequentialSender() {
|
||||||
|
|
||||||
device := peer.device
|
device := peer.device
|
||||||
|
|
||||||
logDebug := device.log.Debug
|
|
||||||
logError := device.log.Error
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
for {
|
defer device.log.Verbosef("%v - Routine: sequential sender - stopped", peer)
|
||||||
select {
|
peer.stopping.Done()
|
||||||
case elem, ok := <-peer.queue.outbound:
|
|
||||||
if ok {
|
|
||||||
if !elem.IsDropped() {
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
|
||||||
elem.Drop()
|
|
||||||
}
|
|
||||||
device.PutOutboundElement(elem)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out:
|
|
||||||
logDebug.Println(peer, "- Routine: sequential sender - stopped")
|
|
||||||
peer.routines.stopping.Done()
|
|
||||||
}()
|
}()
|
||||||
|
device.log.Verbosef("%v - Routine: sequential sender - started", peer)
|
||||||
|
|
||||||
logDebug.Println(peer, "- Routine: sequential sender - started")
|
for elem := range peer.queue.outbound.c {
|
||||||
|
if elem == nil {
|
||||||
peer.routines.starting.Done()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
|
|
||||||
case <-peer.routines.stop:
|
|
||||||
return
|
return
|
||||||
|
}
|
||||||
case elem, ok := <-peer.queue.outbound:
|
elem.Lock()
|
||||||
|
if !peer.isRunning.Get() {
|
||||||
if !ok {
|
// peer has been stopped; return re-usable elems to the shared pool.
|
||||||
return
|
// This is an optimization only. It is possible for the peer to be stopped
|
||||||
}
|
// immediately after this check, in which case, elem will get processed.
|
||||||
|
// The timers and SendBuffer code are resilient to a few stragglers.
|
||||||
elem.Lock()
|
// TODO: rework peer shutdown order to ensure
|
||||||
if elem.IsDropped() {
|
// that we never accidentally keep timers alive longer than necessary.
|
||||||
device.PutOutboundElement(elem)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
peer.timersAnyAuthenticatedPacketTraversal()
|
|
||||||
peer.timersAnyAuthenticatedPacketSent()
|
|
||||||
|
|
||||||
// send message and return buffer to pool
|
|
||||||
|
|
||||||
length := uint64(len(elem.packet))
|
|
||||||
err := peer.SendBuffer(elem.packet)
|
|
||||||
device.PutMessageBuffer(elem.buffer)
|
device.PutMessageBuffer(elem.buffer)
|
||||||
device.PutOutboundElement(elem)
|
device.PutOutboundElement(elem)
|
||||||
if err != nil {
|
continue
|
||||||
logError.Println(peer, "- Failed to send data packet", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
atomic.AddUint64(&peer.stats.txBytes, length)
|
|
||||||
|
|
||||||
if len(elem.packet) != MessageKeepaliveSize {
|
|
||||||
peer.timersDataSent()
|
|
||||||
}
|
|
||||||
peer.keepKeyFreshSending()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
peer.timersAnyAuthenticatedPacketTraversal()
|
||||||
|
peer.timersAnyAuthenticatedPacketSent()
|
||||||
|
|
||||||
|
// send message and return buffer to pool
|
||||||
|
|
||||||
|
err := peer.SendBuffer(elem.packet)
|
||||||
|
if len(elem.packet) != MessageKeepaliveSize {
|
||||||
|
peer.timersDataSent()
|
||||||
|
}
|
||||||
|
device.PutMessageBuffer(elem.buffer)
|
||||||
|
device.PutOutboundElement(elem)
|
||||||
|
if err != nil {
|
||||||
|
device.log.Errorf("%v - Failed to send data packet: %v", peer, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.keepKeyFreshSending()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
device/sticky_default.go
Normal file
12
device/sticky_default.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/rwcancel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (device *Device) startRouteListener(bind conn.Bind) (*rwcancel.RWCancel, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
221
device/sticky_linux.go
Normal file
221
device/sticky_linux.go
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* This implements userspace semantics of "sticky sockets", modeled after
|
||||||
|
* WireGuard's kernelspace implementation. This is more or less a straight port
|
||||||
|
* of the sticky-sockets.c example code:
|
||||||
|
* https://git.zx2c4.com/WireGuard/tree/contrib/examples/sticky-sockets/sticky-sockets.c
|
||||||
|
*
|
||||||
|
* Currently there is no way to achieve this within the net package:
|
||||||
|
* See e.g. https://github.com/golang/go/issues/17930
|
||||||
|
* So this code is remains platform dependent.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"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) {
|
||||||
|
if _, ok := bind.(*conn.LinuxSocketBind); !ok {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
netlinkSock, err := createNetlinkRouteSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
netlinkCancel, err := rwcancel.NewRWCancel(netlinkSock)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(netlinkSock)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go device.routineRouteListener(bind, netlinkSock, netlinkCancel)
|
||||||
|
|
||||||
|
return netlinkCancel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) routineRouteListener(bind conn.Bind, netlinkSock int, netlinkCancel *rwcancel.RWCancel) {
|
||||||
|
type peerEndpointPtr struct {
|
||||||
|
peer *Peer
|
||||||
|
endpoint *conn.Endpoint
|
||||||
|
}
|
||||||
|
var reqPeer map[uint32]peerEndpointPtr
|
||||||
|
var reqPeerLock sync.Mutex
|
||||||
|
|
||||||
|
defer netlinkCancel.Close()
|
||||||
|
defer unix.Close(netlinkSock)
|
||||||
|
|
||||||
|
for msg := make([]byte, 1<<16); ; {
|
||||||
|
var err error
|
||||||
|
var msgn int
|
||||||
|
for {
|
||||||
|
msgn, _, _, _, err = unix.Recvmsg(netlinkSock, msg[:], nil, 0)
|
||||||
|
if err == nil || !rwcancel.RetryAfterError(err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !netlinkCancel.ReadyRead() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for remain := msg[:msgn]; len(remain) >= unix.SizeofNlMsghdr; {
|
||||||
|
|
||||||
|
hdr := *(*unix.NlMsghdr)(unsafe.Pointer(&remain[0]))
|
||||||
|
|
||||||
|
if uint(hdr.Len) > uint(len(remain)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch hdr.Type {
|
||||||
|
case unix.RTM_NEWROUTE, unix.RTM_DELROUTE:
|
||||||
|
if hdr.Seq <= MaxPeers && hdr.Seq > 0 {
|
||||||
|
if uint(len(remain)) < uint(hdr.Len) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if hdr.Len > unix.SizeofNlMsghdr+unix.SizeofRtMsg {
|
||||||
|
attr := remain[unix.SizeofNlMsghdr+unix.SizeofRtMsg:]
|
||||||
|
for {
|
||||||
|
if uint(len(attr)) < uint(unix.SizeofRtAttr) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
attrhdr := *(*unix.RtAttr)(unsafe.Pointer(&attr[0]))
|
||||||
|
if attrhdr.Len < unix.SizeofRtAttr || uint(len(attr)) < uint(attrhdr.Len) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if attrhdr.Type == unix.RTA_OIF && attrhdr.Len == unix.SizeofRtAttr+4 {
|
||||||
|
ifidx := *(*uint32)(unsafe.Pointer(&attr[unix.SizeofRtAttr]))
|
||||||
|
reqPeerLock.Lock()
|
||||||
|
if reqPeer == nil {
|
||||||
|
reqPeerLock.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pePtr, ok := reqPeer[hdr.Seq]
|
||||||
|
reqPeerLock.Unlock()
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pePtr.peer.Lock()
|
||||||
|
if &pePtr.peer.endpoint != pePtr.endpoint {
|
||||||
|
pePtr.peer.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if uint32(pePtr.peer.endpoint.(*conn.LinuxSocketEndpoint).Src4().Ifindex) == ifidx {
|
||||||
|
pePtr.peer.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pePtr.peer.endpoint.(*conn.LinuxSocketEndpoint).ClearSrc()
|
||||||
|
pePtr.peer.Unlock()
|
||||||
|
}
|
||||||
|
attr = attr[attrhdr.Len:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
reqPeerLock.Lock()
|
||||||
|
reqPeer = make(map[uint32]peerEndpointPtr)
|
||||||
|
reqPeerLock.Unlock()
|
||||||
|
go func() {
|
||||||
|
device.peers.RLock()
|
||||||
|
i := uint32(1)
|
||||||
|
for _, peer := range device.peers.keyMap {
|
||||||
|
peer.RLock()
|
||||||
|
if peer.endpoint == nil {
|
||||||
|
peer.RUnlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
nativeEP, _ := peer.endpoint.(*conn.LinuxSocketEndpoint)
|
||||||
|
if nativeEP == nil {
|
||||||
|
peer.RUnlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nativeEP.IsV6() || nativeEP.Src4().Ifindex == 0 {
|
||||||
|
peer.RUnlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nlmsg := struct {
|
||||||
|
hdr unix.NlMsghdr
|
||||||
|
msg unix.RtMsg
|
||||||
|
dsthdr unix.RtAttr
|
||||||
|
dst [4]byte
|
||||||
|
srchdr unix.RtAttr
|
||||||
|
src [4]byte
|
||||||
|
markhdr unix.RtAttr
|
||||||
|
mark uint32
|
||||||
|
}{
|
||||||
|
unix.NlMsghdr{
|
||||||
|
Type: uint16(unix.RTM_GETROUTE),
|
||||||
|
Flags: unix.NLM_F_REQUEST,
|
||||||
|
Seq: i,
|
||||||
|
},
|
||||||
|
unix.RtMsg{
|
||||||
|
Family: unix.AF_INET,
|
||||||
|
Dst_len: 32,
|
||||||
|
Src_len: 32,
|
||||||
|
},
|
||||||
|
unix.RtAttr{
|
||||||
|
Len: 8,
|
||||||
|
Type: unix.RTA_DST,
|
||||||
|
},
|
||||||
|
nativeEP.Dst4().Addr,
|
||||||
|
unix.RtAttr{
|
||||||
|
Len: 8,
|
||||||
|
Type: unix.RTA_SRC,
|
||||||
|
},
|
||||||
|
nativeEP.Src4().Src,
|
||||||
|
unix.RtAttr{
|
||||||
|
Len: 8,
|
||||||
|
Type: unix.RTA_MARK,
|
||||||
|
},
|
||||||
|
device.net.fwmark,
|
||||||
|
}
|
||||||
|
nlmsg.hdr.Len = uint32(unsafe.Sizeof(nlmsg))
|
||||||
|
reqPeerLock.Lock()
|
||||||
|
reqPeer[i] = peerEndpointPtr{
|
||||||
|
peer: peer,
|
||||||
|
endpoint: &peer.endpoint,
|
||||||
|
}
|
||||||
|
reqPeerLock.Unlock()
|
||||||
|
peer.RUnlock()
|
||||||
|
i++
|
||||||
|
_, err := netlinkCancel.Write((*[unsafe.Sizeof(nlmsg)]byte)(unsafe.Pointer(&nlmsg))[:])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
device.peers.RUnlock()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
remain = remain[hdr.Len:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNetlinkRouteSocket() (int, error) {
|
||||||
|
sock, err := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW, unix.NETLINK_ROUTE)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
saddr := &unix.SockaddrNetlink{
|
||||||
|
Family: unix.AF_NETLINK,
|
||||||
|
Groups: unix.RTMGRP_IPV4_ROUTE,
|
||||||
|
}
|
||||||
|
err = unix.Bind(sock, saddr)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(sock)
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return sock, nil
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 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.
|
||||||
*/
|
*/
|
||||||
@@ -14,10 +14,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* This Timer structure and related functions should roughly copy the interface of
|
// A Timer manages time-based aspects of the WireGuard protocol.
|
||||||
* the Linux kernel's struct timer_list.
|
// Timer roughly copies the interface of the Linux kernel's struct timer_list.
|
||||||
*/
|
|
||||||
|
|
||||||
type Timer struct {
|
type Timer struct {
|
||||||
*time.Timer
|
*time.Timer
|
||||||
modifyingLock sync.RWMutex
|
modifyingLock sync.RWMutex
|
||||||
@@ -29,18 +27,17 @@ func (peer *Peer) NewTimer(expirationFunction func(*Peer)) *Timer {
|
|||||||
timer := &Timer{}
|
timer := &Timer{}
|
||||||
timer.Timer = time.AfterFunc(time.Hour, func() {
|
timer.Timer = time.AfterFunc(time.Hour, func() {
|
||||||
timer.runningLock.Lock()
|
timer.runningLock.Lock()
|
||||||
|
defer timer.runningLock.Unlock()
|
||||||
|
|
||||||
timer.modifyingLock.Lock()
|
timer.modifyingLock.Lock()
|
||||||
if !timer.isPending {
|
if !timer.isPending {
|
||||||
timer.modifyingLock.Unlock()
|
timer.modifyingLock.Unlock()
|
||||||
timer.runningLock.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
timer.isPending = false
|
timer.isPending = false
|
||||||
timer.modifyingLock.Unlock()
|
timer.modifyingLock.Unlock()
|
||||||
|
|
||||||
expirationFunction(peer)
|
expirationFunction(peer)
|
||||||
timer.runningLock.Unlock()
|
|
||||||
})
|
})
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
return timer
|
return timer
|
||||||
@@ -74,12 +71,12 @@ func (timer *Timer) IsPending() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (peer *Peer) timersActive() bool {
|
func (peer *Peer) timersActive() bool {
|
||||||
return peer.isRunning.Get() && peer.device != nil && peer.device.isUp.Get() && len(peer.device.peers.keyMap) > 0
|
return peer.isRunning.Get() && peer.device != nil && peer.device.isUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
func expiredRetransmitHandshake(peer *Peer) {
|
func expiredRetransmitHandshake(peer *Peer) {
|
||||||
if atomic.LoadUint32(&peer.timers.handshakeAttempts) > MaxTimerHandshakes {
|
if atomic.LoadUint32(&peer.timers.handshakeAttempts) > MaxTimerHandshakes {
|
||||||
peer.device.log.Debug.Printf("%s - Handshake did not complete after %d attempts, giving up\n", peer, MaxTimerHandshakes+2)
|
peer.device.log.Verbosef("%s - Handshake did not complete after %d attempts, giving up", peer, MaxTimerHandshakes+2)
|
||||||
|
|
||||||
if peer.timersActive() {
|
if peer.timersActive() {
|
||||||
peer.timers.sendKeepalive.Del()
|
peer.timers.sendKeepalive.Del()
|
||||||
@@ -88,7 +85,7 @@ func expiredRetransmitHandshake(peer *Peer) {
|
|||||||
/* We drop all packets without a keypair and don't try again,
|
/* We drop all packets without a keypair and don't try again,
|
||||||
* if we try unsuccessfully for too long to make a handshake.
|
* if we try unsuccessfully for too long to make a handshake.
|
||||||
*/
|
*/
|
||||||
peer.FlushNonceQueue()
|
peer.FlushStagedPackets()
|
||||||
|
|
||||||
/* We set a timer for destroying any residue that might be left
|
/* We set a timer for destroying any residue that might be left
|
||||||
* of a partial exchange.
|
* of a partial exchange.
|
||||||
@@ -98,7 +95,7 @@ func expiredRetransmitHandshake(peer *Peer) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
atomic.AddUint32(&peer.timers.handshakeAttempts, 1)
|
atomic.AddUint32(&peer.timers.handshakeAttempts, 1)
|
||||||
peer.device.log.Debug.Printf("%s - Handshake did not complete after %d seconds, retrying (try %d)\n", peer, int(RekeyTimeout.Seconds()), atomic.LoadUint32(&peer.timers.handshakeAttempts)+1)
|
peer.device.log.Verbosef("%s - Handshake did not complete after %d seconds, retrying (try %d)", peer, int(RekeyTimeout.Seconds()), atomic.LoadUint32(&peer.timers.handshakeAttempts)+1)
|
||||||
|
|
||||||
/* We clear the endpoint address src address, in case this is the cause of trouble. */
|
/* We clear the endpoint address src address, in case this is the cause of trouble. */
|
||||||
peer.Lock()
|
peer.Lock()
|
||||||
@@ -122,7 +119,7 @@ func expiredSendKeepalive(peer *Peer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func expiredNewHandshake(peer *Peer) {
|
func expiredNewHandshake(peer *Peer) {
|
||||||
peer.device.log.Debug.Printf("%s - Retrying handshake because we stopped hearing back after %d seconds\n", peer, int((KeepaliveTimeout + RekeyTimeout).Seconds()))
|
peer.device.log.Verbosef("%s - Retrying handshake because we stopped hearing back after %d seconds", peer, int((KeepaliveTimeout + RekeyTimeout).Seconds()))
|
||||||
/* We clear the endpoint address src address, in case this is the cause of trouble. */
|
/* We clear the endpoint address src address, in case this is the cause of trouble. */
|
||||||
peer.Lock()
|
peer.Lock()
|
||||||
if peer.endpoint != nil {
|
if peer.endpoint != nil {
|
||||||
@@ -134,12 +131,12 @@ func expiredNewHandshake(peer *Peer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func expiredZeroKeyMaterial(peer *Peer) {
|
func expiredZeroKeyMaterial(peer *Peer) {
|
||||||
peer.device.log.Debug.Printf("%s - Removing all keys, since we haven't received a new one in %d seconds\n", peer, int((RejectAfterTime * 3).Seconds()))
|
peer.device.log.Verbosef("%s - Removing all keys, since we haven't received a new one in %d seconds", peer, int((RejectAfterTime * 3).Seconds()))
|
||||||
peer.ZeroAndFlushAll()
|
peer.ZeroAndFlushAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func expiredPersistentKeepalive(peer *Peer) {
|
func expiredPersistentKeepalive(peer *Peer) {
|
||||||
if peer.persistentKeepaliveInterval > 0 {
|
if atomic.LoadUint32(&peer.persistentKeepaliveInterval) > 0 {
|
||||||
peer.SendKeepalive()
|
peer.SendKeepalive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,7 +144,7 @@ func expiredPersistentKeepalive(peer *Peer) {
|
|||||||
/* Should be called after an authenticated data packet is sent. */
|
/* Should be called after an authenticated data packet is sent. */
|
||||||
func (peer *Peer) timersDataSent() {
|
func (peer *Peer) timersDataSent() {
|
||||||
if peer.timersActive() && !peer.timers.newHandshake.IsPending() {
|
if peer.timersActive() && !peer.timers.newHandshake.IsPending() {
|
||||||
peer.timers.newHandshake.Mod(KeepaliveTimeout + RekeyTimeout)
|
peer.timers.newHandshake.Mod(KeepaliveTimeout + RekeyTimeout + time.Millisecond*time.Duration(rand.Int31n(RekeyTimeoutJitterMaxMs)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,8 +199,9 @@ func (peer *Peer) timersSessionDerived() {
|
|||||||
|
|
||||||
/* Should be called before a packet with authentication -- keepalive, data, or handshake -- is sent, or after one is received. */
|
/* Should be called before a packet with authentication -- keepalive, data, or handshake -- is sent, or after one is received. */
|
||||||
func (peer *Peer) timersAnyAuthenticatedPacketTraversal() {
|
func (peer *Peer) timersAnyAuthenticatedPacketTraversal() {
|
||||||
if peer.persistentKeepaliveInterval > 0 && peer.timersActive() {
|
keepalive := atomic.LoadUint32(&peer.persistentKeepaliveInterval)
|
||||||
peer.timers.persistentKeepalive.Mod(time.Duration(peer.persistentKeepaliveInterval) * time.Second)
|
if keepalive > 0 && peer.timersActive() {
|
||||||
|
peer.timers.persistentKeepalive.Mod(time.Duration(keepalive) * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +211,9 @@ func (peer *Peer) timersInit() {
|
|||||||
peer.timers.newHandshake = peer.NewTimer(expiredNewHandshake)
|
peer.timers.newHandshake = peer.NewTimer(expiredNewHandshake)
|
||||||
peer.timers.zeroKeyMaterial = peer.NewTimer(expiredZeroKeyMaterial)
|
peer.timers.zeroKeyMaterial = peer.NewTimer(expiredZeroKeyMaterial)
|
||||||
peer.timers.persistentKeepalive = peer.NewTimer(expiredPersistentKeepalive)
|
peer.timers.persistentKeepalive = peer.NewTimer(expiredPersistentKeepalive)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (peer *Peer) timersStart() {
|
||||||
atomic.StoreUint32(&peer.timers.handshakeAttempts, 0)
|
atomic.StoreUint32(&peer.timers.handshakeAttempts, 0)
|
||||||
peer.timers.sentLastMinuteHandshake.Set(false)
|
peer.timers.sentLastMinuteHandshake.Set(false)
|
||||||
peer.timers.needAnotherKeepalive.Set(false)
|
peer.timers.needAnotherKeepalive.Set(false)
|
||||||
|
|||||||
@@ -1,55 +1,54 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"fmt"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DefaultMTU = 1420
|
const DefaultMTU = 1420
|
||||||
|
|
||||||
func (device *Device) RoutineTUNEventReader() {
|
func (device *Device) RoutineTUNEventReader() {
|
||||||
setUp := false
|
device.log.Verbosef("Routine: event worker - started")
|
||||||
logDebug := device.log.Debug
|
|
||||||
logInfo := device.log.Info
|
|
||||||
logError := device.log.Error
|
|
||||||
|
|
||||||
logDebug.Println("Routine: event worker - started")
|
|
||||||
device.state.starting.Done()
|
|
||||||
|
|
||||||
for event := range device.tun.device.Events() {
|
for event := range device.tun.device.Events() {
|
||||||
if event&tun.TUNEventMTUUpdate != 0 {
|
if event&tun.EventMTUUpdate != 0 {
|
||||||
mtu, err := device.tun.device.MTU()
|
mtu, err := device.tun.device.MTU()
|
||||||
old := atomic.LoadInt32(&device.tun.mtu)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logError.Println("Failed to load updated MTU of device:", err)
|
device.log.Errorf("Failed to load updated MTU of device: %v", err)
|
||||||
} else if int(old) != mtu {
|
continue
|
||||||
if mtu+MessageTransportSize > MaxMessageSize {
|
}
|
||||||
logInfo.Println("MTU updated:", mtu, "(too large)")
|
if mtu < 0 {
|
||||||
} else {
|
device.log.Errorf("MTU not updated to negative value: %v", mtu)
|
||||||
logInfo.Println("MTU updated:", mtu)
|
continue
|
||||||
}
|
}
|
||||||
atomic.StoreInt32(&device.tun.mtu, int32(mtu))
|
var tooLarge string
|
||||||
|
if mtu > MaxContentSize {
|
||||||
|
tooLarge = fmt.Sprintf(" (too large, capped at %v)", MaxContentSize)
|
||||||
|
mtu = MaxContentSize
|
||||||
|
}
|
||||||
|
old := atomic.SwapInt32(&device.tun.mtu, int32(mtu))
|
||||||
|
if int(old) != mtu {
|
||||||
|
device.log.Verbosef("MTU updated: %v%s", mtu, tooLarge)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if event&tun.TUNEventUp != 0 && !setUp {
|
if event&tun.EventUp != 0 {
|
||||||
logInfo.Println("Interface set up")
|
device.log.Verbosef("Interface up requested")
|
||||||
setUp = true
|
|
||||||
device.Up()
|
device.Up()
|
||||||
}
|
}
|
||||||
|
|
||||||
if event&tun.TUNEventDown != 0 && setUp {
|
if event&tun.EventDown != 0 {
|
||||||
logInfo.Println("Interface set down")
|
device.log.Verbosef("Interface down requested")
|
||||||
setUp = false
|
|
||||||
device.Down()
|
device.Down()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logDebug.Println("Routine: event worker - stopped")
|
device.log.Verbosef("Routine: event worker - stopped")
|
||||||
device.state.stopping.Done()
|
|
||||||
}
|
}
|
||||||
|
|||||||
650
device/uapi.go
650
device/uapi.go
@@ -1,38 +1,74 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package device
|
package device
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.zx2c4.com/wireguard/ipc"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/ipc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IPCError struct {
|
type IPCError struct {
|
||||||
int64
|
code int64 // error code
|
||||||
|
err error // underlying/wrapped error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s IPCError) Error() string {
|
func (s IPCError) Error() string {
|
||||||
return fmt.Sprintf("IPC error: %d", s.int64)
|
return fmt.Sprintf("IPC error %d: %v", s.code, s.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s IPCError) Unwrap() error {
|
||||||
|
return s.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s IPCError) ErrorCode() int64 {
|
func (s IPCError) ErrorCode() int64 {
|
||||||
return s.int64
|
return s.code
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) IpcGetOperation(socket *bufio.Writer) *IPCError {
|
func ipcErrorf(code int64, msg string, args ...interface{}) *IPCError {
|
||||||
lines := make([]string, 0, 100)
|
return &IPCError{code: code, err: fmt.Errorf(msg, args...)}
|
||||||
send := func(line string) {
|
}
|
||||||
lines = append(lines, line)
|
|
||||||
|
var byteBufferPool = &sync.Pool{
|
||||||
|
New: func() interface{} { return new(bytes.Buffer) },
|
||||||
|
}
|
||||||
|
|
||||||
|
// IpcGetOperation implements the WireGuard configuration protocol "get" operation.
|
||||||
|
// See https://www.wireguard.com/xplatform/#configuration-protocol for details.
|
||||||
|
func (device *Device) IpcGetOperation(w io.Writer) error {
|
||||||
|
device.ipcMutex.RLock()
|
||||||
|
defer device.ipcMutex.RUnlock()
|
||||||
|
|
||||||
|
buf := byteBufferPool.Get().(*bytes.Buffer)
|
||||||
|
buf.Reset()
|
||||||
|
defer byteBufferPool.Put(buf)
|
||||||
|
sendf := func(format string, args ...interface{}) {
|
||||||
|
fmt.Fprintf(buf, format, args...)
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
}
|
||||||
|
keyf := func(prefix string, key *[32]byte) {
|
||||||
|
buf.Grow(len(key)*2 + 2 + len(prefix))
|
||||||
|
buf.WriteString(prefix)
|
||||||
|
buf.WriteByte('=')
|
||||||
|
const hex = "0123456789abcdef"
|
||||||
|
for i := 0; i < len(key); i++ {
|
||||||
|
buf.WriteByte(hex[key[i]>>4])
|
||||||
|
buf.WriteByte(hex[key[i]&0xf])
|
||||||
|
}
|
||||||
|
buf.WriteByte('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
@@ -51,15 +87,15 @@ func (device *Device) IpcGetOperation(socket *bufio.Writer) *IPCError {
|
|||||||
// serialize device related values
|
// serialize device related values
|
||||||
|
|
||||||
if !device.staticIdentity.privateKey.IsZero() {
|
if !device.staticIdentity.privateKey.IsZero() {
|
||||||
send("private_key=" + device.staticIdentity.privateKey.ToHex())
|
keyf("private_key", (*[32]byte)(&device.staticIdentity.privateKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
if device.net.port != 0 {
|
if device.net.port != 0 {
|
||||||
send(fmt.Sprintf("listen_port=%d", device.net.port))
|
sendf("listen_port=%d", device.net.port)
|
||||||
}
|
}
|
||||||
|
|
||||||
if device.net.fwmark != 0 {
|
if device.net.fwmark != 0 {
|
||||||
send(fmt.Sprintf("fwmark=%d", device.net.fwmark))
|
sendf("fwmark=%d", device.net.fwmark)
|
||||||
}
|
}
|
||||||
|
|
||||||
// serialize each peer state
|
// serialize each peer state
|
||||||
@@ -68,315 +104,308 @@ func (device *Device) IpcGetOperation(socket *bufio.Writer) *IPCError {
|
|||||||
peer.RLock()
|
peer.RLock()
|
||||||
defer peer.RUnlock()
|
defer peer.RUnlock()
|
||||||
|
|
||||||
send("public_key=" + peer.handshake.remoteStatic.ToHex())
|
keyf("public_key", (*[32]byte)(&peer.handshake.remoteStatic))
|
||||||
send("preshared_key=" + peer.handshake.presharedKey.ToHex())
|
keyf("preshared_key", (*[32]byte)(&peer.handshake.presharedKey))
|
||||||
send("protocol_version=1")
|
sendf("protocol_version=1")
|
||||||
if peer.endpoint != nil {
|
if peer.endpoint != nil {
|
||||||
send("endpoint=" + peer.endpoint.DstToString())
|
sendf("endpoint=%s", peer.endpoint.DstToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
nano := atomic.LoadInt64(&peer.stats.lastHandshakeNano)
|
nano := atomic.LoadInt64(&peer.stats.lastHandshakeNano)
|
||||||
secs := nano / time.Second.Nanoseconds()
|
secs := nano / time.Second.Nanoseconds()
|
||||||
nano %= time.Second.Nanoseconds()
|
nano %= time.Second.Nanoseconds()
|
||||||
|
|
||||||
send(fmt.Sprintf("last_handshake_time_sec=%d", secs))
|
sendf("last_handshake_time_sec=%d", secs)
|
||||||
send(fmt.Sprintf("last_handshake_time_nsec=%d", nano))
|
sendf("last_handshake_time_nsec=%d", nano)
|
||||||
send(fmt.Sprintf("tx_bytes=%d", atomic.LoadUint64(&peer.stats.txBytes)))
|
sendf("tx_bytes=%d", atomic.LoadUint64(&peer.stats.txBytes))
|
||||||
send(fmt.Sprintf("rx_bytes=%d", atomic.LoadUint64(&peer.stats.rxBytes)))
|
sendf("rx_bytes=%d", atomic.LoadUint64(&peer.stats.rxBytes))
|
||||||
send(fmt.Sprintf("persistent_keepalive_interval=%d", peer.persistentKeepaliveInterval))
|
sendf("persistent_keepalive_interval=%d", atomic.LoadUint32(&peer.persistentKeepaliveInterval))
|
||||||
|
|
||||||
for _, ip := range device.allowedips.EntriesForPeer(peer) {
|
|
||||||
send("allowed_ip=" + ip.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
device.allowedips.EntriesForPeer(peer, func(ip net.IP, cidr uint8) bool {
|
||||||
|
sendf("allowed_ip=%s/%d", ip.String(), cidr)
|
||||||
|
return true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// send lines (does not require resource locks)
|
// send lines (does not require resource locks)
|
||||||
|
if _, err := w.Write(buf.Bytes()); err != nil {
|
||||||
for _, line := range lines {
|
return ipcErrorf(ipc.IpcErrorIO, "failed to write output: %w", err)
|
||||||
_, err := socket.WriteString(line + "\n")
|
|
||||||
if err != nil {
|
|
||||||
return &IPCError{ipc.IpcErrorIO}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (device *Device) IpcSetOperation(socket *bufio.Reader) *IPCError {
|
// IpcSetOperation implements the WireGuard configuration protocol "set" operation.
|
||||||
scanner := bufio.NewScanner(socket)
|
// See https://www.wireguard.com/xplatform/#configuration-protocol for details.
|
||||||
logError := device.log.Error
|
func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||||
logDebug := device.log.Debug
|
device.ipcMutex.Lock()
|
||||||
|
defer device.ipcMutex.Unlock()
|
||||||
|
|
||||||
var peer *Peer
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
device.log.Errorf("%v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
dummy := false
|
peer := new(ipcSetPeer)
|
||||||
deviceConfig := true
|
deviceConfig := true
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
|
|
||||||
// parse line
|
|
||||||
|
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if line == "" {
|
if line == "" {
|
||||||
|
// Blank line means terminate operation.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
parts := strings.Split(line, "=")
|
parts := strings.Split(line, "=")
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return &IPCError{ipc.IpcErrorProtocol}
|
return ipcErrorf(ipc.IpcErrorProtocol, "failed to parse line %q, found %d =-separated parts, want 2", line, len(parts))
|
||||||
}
|
}
|
||||||
key := parts[0]
|
key := parts[0]
|
||||||
value := parts[1]
|
value := parts[1]
|
||||||
|
|
||||||
/* device configuration */
|
if key == "public_key" {
|
||||||
|
if deviceConfig {
|
||||||
if deviceConfig {
|
|
||||||
|
|
||||||
switch key {
|
|
||||||
case "private_key":
|
|
||||||
var sk NoisePrivateKey
|
|
||||||
err := sk.FromHex(value)
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Failed to set private_key:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
logDebug.Println("UAPI: Updating private key")
|
|
||||||
device.SetPrivateKey(sk)
|
|
||||||
|
|
||||||
case "listen_port":
|
|
||||||
|
|
||||||
// parse port number
|
|
||||||
|
|
||||||
port, err := strconv.ParseUint(value, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Failed to parse listen_port:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update port and rebind
|
|
||||||
|
|
||||||
logDebug.Println("UAPI: Updating listen port")
|
|
||||||
|
|
||||||
device.net.Lock()
|
|
||||||
device.net.port = uint16(port)
|
|
||||||
device.net.Unlock()
|
|
||||||
|
|
||||||
if err := device.BindUpdate(); err != nil {
|
|
||||||
logError.Println("Failed to set listen_port:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorPortInUse}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "fwmark":
|
|
||||||
|
|
||||||
// parse fwmark field
|
|
||||||
|
|
||||||
fwmark, err := func() (uint32, error) {
|
|
||||||
if value == "" {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
mark, err := strconv.ParseUint(value, 10, 32)
|
|
||||||
return uint32(mark), err
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Invalid fwmark", err)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
|
|
||||||
logDebug.Println("UAPI: Updating fwmark")
|
|
||||||
|
|
||||||
if err := device.BindSetMark(uint32(fwmark)); err != nil {
|
|
||||||
logError.Println("Failed to update fwmark:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorPortInUse}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "public_key":
|
|
||||||
// switch to peer configuration
|
|
||||||
logDebug.Println("UAPI: Transition to peer configuration")
|
|
||||||
deviceConfig = false
|
deviceConfig = false
|
||||||
|
|
||||||
case "replace_peers":
|
|
||||||
if value != "true" {
|
|
||||||
logError.Println("Failed to set replace_peers, invalid value:", value)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
logDebug.Println("UAPI: Removing all peers")
|
|
||||||
device.RemoveAllPeers()
|
|
||||||
|
|
||||||
default:
|
|
||||||
logError.Println("Invalid UAPI device key:", key)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
}
|
||||||
|
peer.handlePostConfig()
|
||||||
|
// Load/create the peer we are now configuring.
|
||||||
|
err := device.handlePublicKeyLine(peer, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
/* peer configuration */
|
var err error
|
||||||
|
if deviceConfig {
|
||||||
if !deviceConfig {
|
err = device.handleDeviceLine(key, value)
|
||||||
|
} else {
|
||||||
switch key {
|
err = device.handlePeerLine(peer, key, value)
|
||||||
|
|
||||||
case "public_key":
|
|
||||||
var publicKey NoisePublicKey
|
|
||||||
err := publicKey.FromHex(value)
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Failed to get peer by public key:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore peer with public key of device
|
|
||||||
|
|
||||||
device.staticIdentity.RLock()
|
|
||||||
dummy = device.staticIdentity.publicKey.Equals(publicKey)
|
|
||||||
device.staticIdentity.RUnlock()
|
|
||||||
|
|
||||||
if dummy {
|
|
||||||
peer = &Peer{}
|
|
||||||
} else {
|
|
||||||
peer = device.LookupPeer(publicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
if peer == nil {
|
|
||||||
peer, err = device.NewPeer(publicKey)
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Failed to create new peer:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
logDebug.Println(peer, "- UAPI: Created")
|
|
||||||
}
|
|
||||||
|
|
||||||
case "remove":
|
|
||||||
|
|
||||||
// remove currently selected peer from device
|
|
||||||
|
|
||||||
if value != "true" {
|
|
||||||
logError.Println("Failed to set remove, invalid value:", value)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
if !dummy {
|
|
||||||
logDebug.Println(peer, "- UAPI: Removing")
|
|
||||||
device.RemovePeer(peer.handshake.remoteStatic)
|
|
||||||
}
|
|
||||||
peer = &Peer{}
|
|
||||||
dummy = true
|
|
||||||
|
|
||||||
case "preshared_key":
|
|
||||||
|
|
||||||
// update PSK
|
|
||||||
|
|
||||||
logDebug.Println(peer, "- UAPI: Updating preshared key")
|
|
||||||
|
|
||||||
peer.handshake.mutex.Lock()
|
|
||||||
err := peer.handshake.presharedKey.FromHex(value)
|
|
||||||
peer.handshake.mutex.Unlock()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Failed to set preshared key:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "endpoint":
|
|
||||||
|
|
||||||
// set endpoint destination
|
|
||||||
|
|
||||||
logDebug.Println(peer, "- UAPI: Updating endpoint")
|
|
||||||
|
|
||||||
err := func() error {
|
|
||||||
peer.Lock()
|
|
||||||
defer peer.Unlock()
|
|
||||||
endpoint, err := CreateEndpoint(value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
peer.endpoint = endpoint
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Failed to set endpoint:", err, ":", value)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "persistent_keepalive_interval":
|
|
||||||
|
|
||||||
// update persistent keepalive interval
|
|
||||||
|
|
||||||
logDebug.Println(peer, "- UAPI: Updating persistent keepalive interval")
|
|
||||||
|
|
||||||
secs, err := strconv.ParseUint(value, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Failed to set persistent keepalive interval:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
|
|
||||||
old := peer.persistentKeepaliveInterval
|
|
||||||
peer.persistentKeepaliveInterval = uint16(secs)
|
|
||||||
|
|
||||||
// send immediate keepalive if we're turning it on and before it wasn't on
|
|
||||||
|
|
||||||
if old == 0 && secs != 0 {
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Failed to get tun device status:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorIO}
|
|
||||||
}
|
|
||||||
if device.isUp.Get() && !dummy {
|
|
||||||
peer.SendKeepalive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "replace_allowed_ips":
|
|
||||||
|
|
||||||
logDebug.Println(peer, "- UAPI: Removing all allowedips")
|
|
||||||
|
|
||||||
if value != "true" {
|
|
||||||
logError.Println("Failed to replace allowedips, invalid value:", value)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
|
|
||||||
if dummy {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
device.allowedips.RemoveByPeer(peer)
|
|
||||||
|
|
||||||
case "allowed_ip":
|
|
||||||
|
|
||||||
logDebug.Println(peer, "- UAPI: Adding allowedip")
|
|
||||||
|
|
||||||
_, network, err := net.ParseCIDR(value)
|
|
||||||
if err != nil {
|
|
||||||
logError.Println("Failed to set allowed ip:", err)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
|
|
||||||
if dummy {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ones, _ := network.Mask.Size()
|
|
||||||
device.allowedips.Insert(network.IP, uint(ones), peer)
|
|
||||||
|
|
||||||
case "protocol_version":
|
|
||||||
|
|
||||||
if value != "1" {
|
|
||||||
logError.Println("Invalid protocol version:", value)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
logError.Println("Invalid UAPI peer key:", key)
|
|
||||||
return &IPCError{ipc.IpcErrorInvalid}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peer.handlePostConfig()
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorIO, "failed to read input: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) handleDeviceLine(key, value string) error {
|
||||||
|
switch key {
|
||||||
|
case "private_key":
|
||||||
|
var sk NoisePrivateKey
|
||||||
|
err := sk.FromMaybeZeroHex(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set private_key: %w", err)
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Updating private key")
|
||||||
|
device.SetPrivateKey(sk)
|
||||||
|
|
||||||
|
case "listen_port":
|
||||||
|
port, err := strconv.ParseUint(value, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse listen_port: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update port and rebind
|
||||||
|
device.log.Verbosef("UAPI: Updating listen port")
|
||||||
|
|
||||||
|
device.net.Lock()
|
||||||
|
device.net.port = uint16(port)
|
||||||
|
device.net.Unlock()
|
||||||
|
|
||||||
|
if err := device.BindUpdate(); err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set listen_port: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "fwmark":
|
||||||
|
mark, err := strconv.ParseUint(value, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "invalid fwmark: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
device.log.Verbosef("UAPI: Updating fwmark")
|
||||||
|
if err := device.BindSetMark(uint32(mark)); err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to update fwmark: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "replace_peers":
|
||||||
|
if value != "true" {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set replace_peers, invalid value: %v", value)
|
||||||
|
}
|
||||||
|
device.log.Verbosef("UAPI: Removing all peers")
|
||||||
|
device.RemoveAllPeers()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An ipcSetPeer is the current state of an IPC set operation on a peer.
|
||||||
|
type ipcSetPeer struct {
|
||||||
|
*Peer // Peer is the current peer being operated on
|
||||||
|
dummy bool // dummy reports whether this peer is a temporary, placeholder peer
|
||||||
|
created bool // new reports whether this is a newly created peer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (peer *ipcSetPeer) handlePostConfig() {
|
||||||
|
if peer.Peer != nil && !peer.dummy && peer.Peer.device.isUp() {
|
||||||
|
peer.SendStagedPackets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) handlePublicKeyLine(peer *ipcSetPeer, value string) error {
|
||||||
|
// Load/create the peer we are configuring.
|
||||||
|
var publicKey NoisePublicKey
|
||||||
|
err := publicKey.FromHex(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to get peer by public key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore peer with the same public key as this device.
|
||||||
|
device.staticIdentity.RLock()
|
||||||
|
peer.dummy = device.staticIdentity.publicKey.Equals(publicKey)
|
||||||
|
device.staticIdentity.RUnlock()
|
||||||
|
|
||||||
|
if peer.dummy {
|
||||||
|
peer.Peer = &Peer{}
|
||||||
|
} else {
|
||||||
|
peer.Peer = device.LookupPeer(publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.created = peer.Peer == nil
|
||||||
|
if peer.created {
|
||||||
|
peer.Peer, err = device.NewPeer(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to create new peer: %w", err)
|
||||||
|
}
|
||||||
|
device.log.Verbosef("%v - UAPI: Created", peer.Peer)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) handlePeerLine(peer *ipcSetPeer, key, value string) error {
|
||||||
|
switch key {
|
||||||
|
case "update_only":
|
||||||
|
// allow disabling of creation
|
||||||
|
if value != "true" {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set update only, invalid value: %v", value)
|
||||||
|
}
|
||||||
|
if peer.created && !peer.dummy {
|
||||||
|
device.RemovePeer(peer.handshake.remoteStatic)
|
||||||
|
peer.Peer = &Peer{}
|
||||||
|
peer.dummy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case "remove":
|
||||||
|
// remove currently selected peer from device
|
||||||
|
if value != "true" {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set remove, invalid value: %v", value)
|
||||||
|
}
|
||||||
|
if !peer.dummy {
|
||||||
|
device.log.Verbosef("%v - UAPI: Removing", peer.Peer)
|
||||||
|
device.RemovePeer(peer.handshake.remoteStatic)
|
||||||
|
}
|
||||||
|
peer.Peer = &Peer{}
|
||||||
|
peer.dummy = true
|
||||||
|
|
||||||
|
case "preshared_key":
|
||||||
|
device.log.Verbosef("%v - UAPI: Updating preshared key", peer.Peer)
|
||||||
|
|
||||||
|
peer.handshake.mutex.Lock()
|
||||||
|
err := peer.handshake.presharedKey.FromHex(value)
|
||||||
|
peer.handshake.mutex.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set preshared key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "endpoint":
|
||||||
|
device.log.Verbosef("%v - UAPI: Updating endpoint", peer.Peer)
|
||||||
|
endpoint, err := device.net.bind.ParseEndpoint(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set endpoint %v: %w", value, err)
|
||||||
|
}
|
||||||
|
peer.Lock()
|
||||||
|
defer peer.Unlock()
|
||||||
|
peer.endpoint = endpoint
|
||||||
|
|
||||||
|
case "persistent_keepalive_interval":
|
||||||
|
device.log.Verbosef("%v - UAPI: Updating persistent keepalive interval", peer.Peer)
|
||||||
|
|
||||||
|
secs, err := strconv.ParseUint(value, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set persistent keepalive interval: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
old := atomic.SwapUint32(&peer.persistentKeepaliveInterval, uint32(secs))
|
||||||
|
|
||||||
|
// Send immediate keepalive if we're turning it on and before it wasn't on.
|
||||||
|
if old == 0 && secs != 0 {
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorIO, "failed to get tun device status: %w", err)
|
||||||
|
}
|
||||||
|
if device.isUp() && !peer.dummy {
|
||||||
|
peer.SendKeepalive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "replace_allowed_ips":
|
||||||
|
device.log.Verbosef("%v - UAPI: Removing all allowedips", peer.Peer)
|
||||||
|
if value != "true" {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to replace allowedips, invalid value: %v", value)
|
||||||
|
}
|
||||||
|
if peer.dummy {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
device.allowedips.RemoveByPeer(peer.Peer)
|
||||||
|
|
||||||
|
case "allowed_ip":
|
||||||
|
device.log.Verbosef("%v - UAPI: Adding allowedip", peer.Peer)
|
||||||
|
|
||||||
|
_, network, err := net.ParseCIDR(value)
|
||||||
|
if err != nil {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set allowed ip: %w", err)
|
||||||
|
}
|
||||||
|
if peer.dummy {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ones, _ := network.Mask.Size()
|
||||||
|
device.allowedips.Insert(network.IP, uint8(ones), peer.Peer)
|
||||||
|
|
||||||
|
case "protocol_version":
|
||||||
|
if value != "1" {
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "invalid protocol version: %v", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI peer key: %v", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) IpcGet() (string, error) {
|
||||||
|
buf := new(strings.Builder)
|
||||||
|
if err := device.IpcGetOperation(buf); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (device *Device) IpcSet(uapiConf string) error {
|
||||||
|
return device.IpcSetOperation(strings.NewReader(uapiConf))
|
||||||
|
}
|
||||||
|
|
||||||
func (device *Device) IpcHandle(socket net.Conn) {
|
func (device *Device) IpcHandle(socket net.Conn) {
|
||||||
|
|
||||||
// create buffered read/writer
|
|
||||||
|
|
||||||
defer socket.Close()
|
defer socket.Close()
|
||||||
|
|
||||||
buffered := func(s io.ReadWriter) *bufio.ReadWriter {
|
buffered := func(s io.ReadWriter) *bufio.ReadWriter {
|
||||||
@@ -385,35 +414,44 @@ func (device *Device) IpcHandle(socket net.Conn) {
|
|||||||
return bufio.NewReadWriter(reader, writer)
|
return bufio.NewReadWriter(reader, writer)
|
||||||
}(socket)
|
}(socket)
|
||||||
|
|
||||||
defer buffered.Flush()
|
for {
|
||||||
|
op, err := buffered.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
op, err := buffered.ReadString('\n')
|
// handle operation
|
||||||
if err != nil {
|
switch op {
|
||||||
return
|
case "set=1\n":
|
||||||
}
|
err = device.IpcSetOperation(buffered.Reader)
|
||||||
|
case "get=1\n":
|
||||||
|
var nextByte byte
|
||||||
|
nextByte, err = buffered.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nextByte != '\n' {
|
||||||
|
err = ipcErrorf(ipc.IpcErrorInvalid, "trailing character in UAPI get: %q", nextByte)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err = device.IpcGetOperation(buffered.Writer)
|
||||||
|
default:
|
||||||
|
device.log.Errorf("invalid UAPI operation: %v", op)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// handle operation
|
// write status
|
||||||
|
var status *IPCError
|
||||||
var status *IPCError
|
if err != nil && !errors.As(err, &status) {
|
||||||
|
// shouldn't happen
|
||||||
switch op {
|
status = ipcErrorf(ipc.IpcErrorUnknown, "other UAPI error: %w", err)
|
||||||
case "set=1\n":
|
}
|
||||||
status = device.IpcSetOperation(buffered.Reader)
|
if status != nil {
|
||||||
|
device.log.Errorf("%v", status)
|
||||||
case "get=1\n":
|
fmt.Fprintf(buffered, "errno=%d\n\n", status.ErrorCode())
|
||||||
status = device.IpcGetOperation(buffered.Writer)
|
} else {
|
||||||
|
fmt.Fprintf(buffered, "errno=0\n\n")
|
||||||
default:
|
}
|
||||||
device.log.Error.Println("Invalid UAPI operation:", op)
|
buffered.Flush()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// write status
|
|
||||||
|
|
||||||
if status != nil {
|
|
||||||
device.log.Error.Println(status)
|
|
||||||
fmt.Fprintf(buffered, "errno=%d\n\n", status.ErrorCode())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(buffered, "errno=0\n\n")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
package device
|
|
||||||
|
|
||||||
const WireGuardGoVersion = "0.0.20190409"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// +build !android
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
const DoNotUseThisProgramOnLinux = UseTheKernelModuleInstead
|
|
||||||
|
|
||||||
// --------------------------------------------------------
|
|
||||||
// Do not use this on Linux. Instead use the kernel module.
|
|
||||||
// See wireguard.com/install for more information.
|
|
||||||
// --------------------------------------------------------
|
|
||||||
51
format_test.go
Normal file
51
format_test.go
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 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()
|
||||||
|
}
|
||||||
9
go.mod
9
go.mod
@@ -1,8 +1,9 @@
|
|||||||
module golang.zx2c4.com/wireguard
|
module golang.zx2c4.com/wireguard
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.4.12
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576
|
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476
|
||||||
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6
|
||||||
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54
|
|
||||||
)
|
)
|
||||||
|
|||||||
25
go.sum
25
go.sum
@@ -1,11 +1,14 @@
|
|||||||
github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||||
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4=
|
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476 h1:s5hu7bTnLKswvidgtqc4GwsW83m9LZu8UAqzmWOZtI4=
|
||||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/net v0.0.0-20210927181540-4e4d966f7476/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 h1:kcXqo9vE6fsZY5X5Rd7R1l7fTgnWaDCVmln65REefiE=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54 h1:xe1/2UUJRmA9iDglQSlkx8c5n3twv58+K0mPpC2zmhA=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
||||||
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -1,30 +1,19 @@
|
|||||||
// +build darwin freebsd openbsd
|
//go:build darwin || freebsd || openbsd
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ipc
|
package ipc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
|
||||||
|
|
||||||
var socketDirectory = "/var/run/wireguard"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
const (
|
|
||||||
IpcErrorIO = -int64(unix.EIO)
|
|
||||||
IpcErrorProtocol = -int64(unix.EPROTO)
|
|
||||||
IpcErrorInvalid = -int64(unix.EINVAL)
|
|
||||||
IpcErrorPortInUse = -int64(unix.EADDRINUSE)
|
|
||||||
socketName = "%s.sock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UAPIListener struct {
|
type UAPIListener struct {
|
||||||
@@ -83,10 +72,7 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
|||||||
unixListener.SetUnlinkOnClose(true)
|
unixListener.SetUnlinkOnClose(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
socketPath := path.Join(
|
socketPath := sockPath(name)
|
||||||
socketDirectory,
|
|
||||||
fmt.Sprintf(socketName, name),
|
|
||||||
)
|
|
||||||
|
|
||||||
// watch for deletion of socket
|
// watch for deletion of socket
|
||||||
|
|
||||||
@@ -145,58 +131,3 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
|||||||
|
|
||||||
return uapi, nil
|
return uapi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UAPIOpen(name string) (*os.File, error) {
|
|
||||||
|
|
||||||
// check if path exist
|
|
||||||
|
|
||||||
err := os.MkdirAll(socketDirectory, 0755)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// open UNIX socket
|
|
||||||
|
|
||||||
socketPath := path.Join(
|
|
||||||
socketDirectory,
|
|
||||||
fmt.Sprintf(socketName, name),
|
|
||||||
)
|
|
||||||
|
|
||||||
addr, err := net.ResolveUnixAddr("unix", socketPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldUmask := unix.Umask(0077)
|
|
||||||
listener, err := func() (*net.UnixListener, error) {
|
|
||||||
|
|
||||||
// initial connection attempt
|
|
||||||
|
|
||||||
listener, err := net.ListenUnix("unix", addr)
|
|
||||||
if err == nil {
|
|
||||||
return listener, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if socket already active
|
|
||||||
|
|
||||||
_, err = net.Dial("unix", socketPath)
|
|
||||||
if err == nil {
|
|
||||||
return nil, errors.New("unix socket in use")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup & attempt again
|
|
||||||
|
|
||||||
err = os.Remove(socketPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return net.ListenUnix("unix", addr)
|
|
||||||
}()
|
|
||||||
unix.Umask(oldUmask)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return listener.File()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,28 +1,16 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ipc
|
package ipc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"golang.zx2c4.com/wireguard/rwcancel"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
var socketDirectory = "/var/run/wireguard"
|
"golang.org/x/sys/unix"
|
||||||
|
"golang.zx2c4.com/wireguard/rwcancel"
|
||||||
const (
|
|
||||||
IpcErrorIO = -int64(unix.EIO)
|
|
||||||
IpcErrorProtocol = -int64(unix.EPROTO)
|
|
||||||
IpcErrorInvalid = -int64(unix.EINVAL)
|
|
||||||
IpcErrorPortInUse = -int64(unix.EADDRINUSE)
|
|
||||||
socketName = "%s.sock"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UAPIListener struct {
|
type UAPIListener struct {
|
||||||
@@ -83,10 +71,7 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
|||||||
|
|
||||||
// watch for deletion of socket
|
// watch for deletion of socket
|
||||||
|
|
||||||
socketPath := path.Join(
|
socketPath := sockPath(name)
|
||||||
socketDirectory,
|
|
||||||
fmt.Sprintf(socketName, name),
|
|
||||||
)
|
|
||||||
|
|
||||||
uapi.inotifyFd, err = unix.InotifyInit()
|
uapi.inotifyFd, err = unix.InotifyInit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -114,6 +99,7 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
|||||||
go func(l *UAPIListener) {
|
go func(l *UAPIListener) {
|
||||||
var buff [0]byte
|
var buff [0]byte
|
||||||
for {
|
for {
|
||||||
|
defer uapi.inotifyRWCancel.Close()
|
||||||
// start with lstat to avoid race condition
|
// start with lstat to avoid race condition
|
||||||
if _, err := os.Lstat(socketPath); os.IsNotExist(err) {
|
if _, err := os.Lstat(socketPath); os.IsNotExist(err) {
|
||||||
l.connErr <- err
|
l.connErr <- err
|
||||||
@@ -142,58 +128,3 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
|||||||
|
|
||||||
return uapi, nil
|
return uapi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UAPIOpen(name string) (*os.File, error) {
|
|
||||||
|
|
||||||
// check if path exist
|
|
||||||
|
|
||||||
err := os.MkdirAll(socketDirectory, 0755)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// open UNIX socket
|
|
||||||
|
|
||||||
socketPath := path.Join(
|
|
||||||
socketDirectory,
|
|
||||||
fmt.Sprintf(socketName, name),
|
|
||||||
)
|
|
||||||
|
|
||||||
addr, err := net.ResolveUnixAddr("unix", socketPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldUmask := unix.Umask(0077)
|
|
||||||
listener, err := func() (*net.UnixListener, error) {
|
|
||||||
|
|
||||||
// initial connection attempt
|
|
||||||
|
|
||||||
listener, err := net.ListenUnix("unix", addr)
|
|
||||||
if err == nil {
|
|
||||||
return listener, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if socket already active
|
|
||||||
|
|
||||||
_, err = net.Dial("unix", socketPath)
|
|
||||||
if err == nil {
|
|
||||||
return nil, errors.New("unix socket in use")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup & attempt again
|
|
||||||
|
|
||||||
err = os.Remove(socketPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return net.ListenUnix("unix", addr)
|
|
||||||
}()
|
|
||||||
unix.Umask(oldUmask)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return listener.File()
|
|
||||||
}
|
|
||||||
|
|||||||
66
ipc/uapi_unix.go
Normal file
66
ipc/uapi_unix.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
//go:build linux || darwin || freebsd || openbsd
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ipc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IpcErrorIO = -int64(unix.EIO)
|
||||||
|
IpcErrorProtocol = -int64(unix.EPROTO)
|
||||||
|
IpcErrorInvalid = -int64(unix.EINVAL)
|
||||||
|
IpcErrorPortInUse = -int64(unix.EADDRINUSE)
|
||||||
|
IpcErrorUnknown = -55 // ENOANO
|
||||||
|
)
|
||||||
|
|
||||||
|
// socketDirectory is variable because it is modified by a linker
|
||||||
|
// flag in wireguard-android.
|
||||||
|
var socketDirectory = "/var/run/wireguard"
|
||||||
|
|
||||||
|
func sockPath(iface string) string {
|
||||||
|
return fmt.Sprintf("%s/%s.sock", socketDirectory, iface)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UAPIOpen(name string) (*os.File, error) {
|
||||||
|
if err := os.MkdirAll(socketDirectory, 0755); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
socketPath := sockPath(name)
|
||||||
|
addr, err := net.ResolveUnixAddr("unix", socketPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldUmask := unix.Umask(0077)
|
||||||
|
defer unix.Umask(oldUmask)
|
||||||
|
|
||||||
|
listener, err := net.ListenUnix("unix", addr)
|
||||||
|
if err == nil {
|
||||||
|
return listener.File()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test socket, if not in use cleanup and try again.
|
||||||
|
if _, err := net.Dial("unix", socketPath); err == nil {
|
||||||
|
return nil, errors.New("unix socket in use")
|
||||||
|
}
|
||||||
|
if err := os.Remove(socketPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
listener, err = net.ListenUnix("unix", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return listener.File()
|
||||||
|
}
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ipc
|
package ipc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Microsoft/go-winio"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/ipc/winpipe"
|
||||||
)
|
)
|
||||||
|
|
||||||
//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
|
||||||
const (
|
const (
|
||||||
IpcErrorIO = -int64(5)
|
IpcErrorIO = -int64(5)
|
||||||
IpcErrorProtocol = -int64(71)
|
IpcErrorProtocol = -int64(71)
|
||||||
IpcErrorInvalid = -int64(22)
|
IpcErrorInvalid = -int64(22)
|
||||||
IpcErrorPortInUse = -int64(98)
|
IpcErrorPortInUse = -int64(98)
|
||||||
|
IpcErrorUnknown = -int64(55)
|
||||||
)
|
)
|
||||||
|
|
||||||
type UAPIListener struct {
|
type UAPIListener struct {
|
||||||
@@ -46,22 +50,21 @@ func (l *UAPIListener) Addr() net.Addr {
|
|||||||
return l.listener.Addr()
|
return l.listener.Addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSystemSecurityDescriptor() string {
|
var UAPISecurityDescriptor *windows.SECURITY_DESCRIPTOR
|
||||||
//
|
|
||||||
// SDDL encoded.
|
func init() {
|
||||||
//
|
var err error
|
||||||
// (system = SECURITY_NT_AUTHORITY | SECURITY_LOCAL_SYSTEM_RID)
|
UAPISecurityDescriptor, err = windows.SecurityDescriptorFromString("O:SYD:P(A;;GA;;;SY)(A;;GA;;;BA)S:(ML;;NWNRNX;;;HI)")
|
||||||
// owner: system
|
if err != nil {
|
||||||
// grant: GENERIC_ALL to system
|
panic(err)
|
||||||
//
|
}
|
||||||
return "O:SYD:(A;;GA;;;SY)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UAPIListen(name string) (net.Listener, error) {
|
func UAPIListen(name string) (net.Listener, error) {
|
||||||
config := winio.PipeConfig{
|
config := winpipe.ListenConfig{
|
||||||
SecurityDescriptor: GetSystemSecurityDescriptor(),
|
SecurityDescriptor: UAPISecurityDescriptor,
|
||||||
}
|
}
|
||||||
listener, err := winio.ListenPipe("\\\\.\\pipe\\WireGuard\\"+name, &config)
|
listener, err := winpipe.Listen(`\\.\pipe\ProtectedPrefix\Administrators\WireGuard\`+name, &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
286
ipc/winpipe/file.go
Normal file
286
ipc/winpipe/file.go
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 Microsoft
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package winpipe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeoutChan chan struct{}
|
||||||
|
|
||||||
|
var ioInitOnce sync.Once
|
||||||
|
var ioCompletionPort windows.Handle
|
||||||
|
|
||||||
|
// ioResult contains the result of an asynchronous IO operation
|
||||||
|
type ioResult struct {
|
||||||
|
bytes uint32
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioOperation represents an outstanding asynchronous Win32 IO
|
||||||
|
type ioOperation struct {
|
||||||
|
o windows.Overlapped
|
||||||
|
ch chan ioResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func initIo() {
|
||||||
|
h, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ioCompletionPort = h
|
||||||
|
go ioCompletionProcessor(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// file implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
|
||||||
|
// It takes ownership of this handle and will close it if it is garbage collected.
|
||||||
|
type file struct {
|
||||||
|
handle windows.Handle
|
||||||
|
wg sync.WaitGroup
|
||||||
|
wgLock sync.RWMutex
|
||||||
|
closing uint32 // used as atomic boolean
|
||||||
|
socket bool
|
||||||
|
readDeadline deadlineHandler
|
||||||
|
writeDeadline deadlineHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
type deadlineHandler struct {
|
||||||
|
setLock sync.Mutex
|
||||||
|
channel timeoutChan
|
||||||
|
channelLock sync.RWMutex
|
||||||
|
timer *time.Timer
|
||||||
|
timedout uint32 // used as atomic boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeFile makes a new file from an existing file handle
|
||||||
|
func makeFile(h windows.Handle) (*file, error) {
|
||||||
|
f := &file{handle: h}
|
||||||
|
ioInitOnce.Do(initIo)
|
||||||
|
_, err := windows.CreateIoCompletionPort(h, ioCompletionPort, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = windows.SetFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.readDeadline.channel = make(timeoutChan)
|
||||||
|
f.writeDeadline.channel = make(timeoutChan)
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeHandle closes the resources associated with a Win32 handle
|
||||||
|
func (f *file) closeHandle() {
|
||||||
|
f.wgLock.Lock()
|
||||||
|
// Atomically set that we are closing, releasing the resources only once.
|
||||||
|
if atomic.SwapUint32(&f.closing, 1) == 0 {
|
||||||
|
f.wgLock.Unlock()
|
||||||
|
// cancel all IO and wait for it to complete
|
||||||
|
windows.CancelIoEx(f.handle, nil)
|
||||||
|
f.wg.Wait()
|
||||||
|
// at this point, no new IO can start
|
||||||
|
windows.Close(f.handle)
|
||||||
|
f.handle = 0
|
||||||
|
} else {
|
||||||
|
f.wgLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a file.
|
||||||
|
func (f *file) Close() error {
|
||||||
|
f.closeHandle()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareIo prepares for a new IO operation.
|
||||||
|
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
|
||||||
|
func (f *file) prepareIo() (*ioOperation, error) {
|
||||||
|
f.wgLock.RLock()
|
||||||
|
if atomic.LoadUint32(&f.closing) == 1 {
|
||||||
|
f.wgLock.RUnlock()
|
||||||
|
return nil, os.ErrClosed
|
||||||
|
}
|
||||||
|
f.wg.Add(1)
|
||||||
|
f.wgLock.RUnlock()
|
||||||
|
c := &ioOperation{}
|
||||||
|
c.ch = make(chan ioResult)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioCompletionProcessor processes completed async IOs forever
|
||||||
|
func ioCompletionProcessor(h windows.Handle) {
|
||||||
|
for {
|
||||||
|
var bytes uint32
|
||||||
|
var key uintptr
|
||||||
|
var op *ioOperation
|
||||||
|
err := windows.GetQueuedCompletionStatus(h, &bytes, &key, (**windows.Overlapped)(unsafe.Pointer(&op)), windows.INFINITE)
|
||||||
|
if op == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
op.ch <- ioResult{bytes, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncIo processes the return value from ReadFile or WriteFile, blocking until
|
||||||
|
// the operation has actually completed.
|
||||||
|
func (f *file) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
|
||||||
|
if err != windows.ERROR_IO_PENDING {
|
||||||
|
return int(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if atomic.LoadUint32(&f.closing) == 1 {
|
||||||
|
windows.CancelIoEx(f.handle, &c.o)
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout timeoutChan
|
||||||
|
if d != nil {
|
||||||
|
d.channelLock.Lock()
|
||||||
|
timeout = d.channel
|
||||||
|
d.channelLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var r ioResult
|
||||||
|
select {
|
||||||
|
case r = <-c.ch:
|
||||||
|
err = r.err
|
||||||
|
if err == windows.ERROR_OPERATION_ABORTED {
|
||||||
|
if atomic.LoadUint32(&f.closing) == 1 {
|
||||||
|
err = os.ErrClosed
|
||||||
|
}
|
||||||
|
} else if err != nil && f.socket {
|
||||||
|
// err is from Win32. Query the overlapped structure to get the winsock error.
|
||||||
|
var bytes, flags uint32
|
||||||
|
err = windows.WSAGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
windows.CancelIoEx(f.handle, &c.o)
|
||||||
|
r = <-c.ch
|
||||||
|
err = r.err
|
||||||
|
if err == windows.ERROR_OPERATION_ABORTED {
|
||||||
|
err = os.ErrDeadlineExceeded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtime.KeepAlive is needed, as c is passed via native
|
||||||
|
// code to ioCompletionProcessor, c must remain alive
|
||||||
|
// until the channel read is complete.
|
||||||
|
runtime.KeepAlive(c)
|
||||||
|
return int(r.bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from a file handle.
|
||||||
|
func (f *file) Read(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.wg.Done()
|
||||||
|
|
||||||
|
if atomic.LoadUint32(&f.readDeadline.timedout) == 1 {
|
||||||
|
return 0, os.ErrDeadlineExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.ReadFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
|
||||||
|
runtime.KeepAlive(b)
|
||||||
|
|
||||||
|
// Handle EOF conditions.
|
||||||
|
if err == nil && n == 0 && len(b) != 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else if err == windows.ERROR_BROKEN_PIPE {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to a file handle.
|
||||||
|
func (f *file) Write(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.wg.Done()
|
||||||
|
|
||||||
|
if atomic.LoadUint32(&f.writeDeadline.timedout) == 1 {
|
||||||
|
return 0, os.ErrDeadlineExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.WriteFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
|
||||||
|
runtime.KeepAlive(b)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *file) SetReadDeadline(deadline time.Time) error {
|
||||||
|
return f.readDeadline.set(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *file) SetWriteDeadline(deadline time.Time) error {
|
||||||
|
return f.writeDeadline.set(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *file) Flush() error {
|
||||||
|
return windows.FlushFileBuffers(f.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *file) Fd() uintptr {
|
||||||
|
return uintptr(f.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deadlineHandler) set(deadline time.Time) error {
|
||||||
|
d.setLock.Lock()
|
||||||
|
defer d.setLock.Unlock()
|
||||||
|
|
||||||
|
if d.timer != nil {
|
||||||
|
if !d.timer.Stop() {
|
||||||
|
<-d.channel
|
||||||
|
}
|
||||||
|
d.timer = nil
|
||||||
|
}
|
||||||
|
atomic.StoreUint32(&d.timedout, 0)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-d.channel:
|
||||||
|
d.channelLock.Lock()
|
||||||
|
d.channel = make(chan struct{})
|
||||||
|
d.channelLock.Unlock()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if deadline.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutIO := func() {
|
||||||
|
atomic.StoreUint32(&d.timedout, 1)
|
||||||
|
close(d.channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
duration := deadline.Sub(now)
|
||||||
|
if deadline.After(now) {
|
||||||
|
// Deadline is in the future, set a timer to wait
|
||||||
|
d.timer = time.AfterFunc(duration, timeoutIO)
|
||||||
|
} else {
|
||||||
|
// Deadline is in the past. Cancel all pending IO now.
|
||||||
|
timeoutIO()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
474
ipc/winpipe/winpipe.go
Normal file
474
ipc/winpipe/winpipe.go
Normal file
@@ -0,0 +1,474 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 Microsoft
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package winpipe implements a net.Conn and net.Listener around Windows named pipes.
|
||||||
|
package winpipe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pipe struct {
|
||||||
|
*file
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type messageBytePipe struct {
|
||||||
|
pipe
|
||||||
|
writeClosed bool
|
||||||
|
readEOF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeAddress string
|
||||||
|
|
||||||
|
func (f *pipe) LocalAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pipe) RemoteAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pipe) SetDeadline(t time.Time) error {
|
||||||
|
f.SetReadDeadline(t)
|
||||||
|
f.SetWriteDeadline(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite closes the write side of a message pipe in byte mode.
|
||||||
|
func (f *messageBytePipe) CloseWrite() error {
|
||||||
|
if f.writeClosed {
|
||||||
|
return io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
err := f.file.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.file.Write(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writeClosed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
||||||
|
// they are used to implement CloseWrite.
|
||||||
|
func (f *messageBytePipe) Write(b []byte) (int, error) {
|
||||||
|
if f.writeClosed {
|
||||||
|
return 0, io.ErrClosedPipe
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return f.file.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
|
||||||
|
// mode pipe will return io.EOF, as will all subsequent reads.
|
||||||
|
func (f *messageBytePipe) Read(b []byte) (int, error) {
|
||||||
|
if f.readEOF {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n, err := f.file.Read(b)
|
||||||
|
if err == io.EOF {
|
||||||
|
// If this was the result of a zero-byte read, then
|
||||||
|
// it is possible that the read was due to a zero-size
|
||||||
|
// message. Since we are simulating CloseWrite with a
|
||||||
|
// zero-byte message, ensure that all future Read calls
|
||||||
|
// also return EOF.
|
||||||
|
f.readEOF = true
|
||||||
|
} else if err == windows.ERROR_MORE_DATA {
|
||||||
|
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
|
||||||
|
// and the message still has more bytes. Treat this as a success, since
|
||||||
|
// this package presents all named pipes as byte streams.
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pipe) Handle() windows.Handle {
|
||||||
|
return f.handle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) Network() string {
|
||||||
|
return "pipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryDialPipe attempts to dial the specified pipe until cancellation or timeout.
|
||||||
|
func tryDialPipe(ctx context.Context, path *string) (windows.Handle, error) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return 0, ctx.Err()
|
||||||
|
default:
|
||||||
|
path16, err := windows.UTF16PtrFromString(*path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
h, err := windows.CreateFile(path16, windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_OVERLAPPED|windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS, 0)
|
||||||
|
if err == nil {
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
if err != windows.ERROR_PIPE_BUSY {
|
||||||
|
return h, &os.PathError{Err: err, Op: "open", Path: *path}
|
||||||
|
}
|
||||||
|
// Wait 10 msec and try again. This is a rather simplistic
|
||||||
|
// view, as we always try each 10 milliseconds.
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialConfig exposes various options for use in Dial and DialContext.
|
||||||
|
type DialConfig struct {
|
||||||
|
ExpectedOwner *windows.SID // If non-nil, the pipe is verified to be owned by this SID.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial connects to the specified named pipe by path, timing out if the connection
|
||||||
|
// takes longer than the specified duration. If timeout is nil, then we use
|
||||||
|
// a default timeout of 2 seconds.
|
||||||
|
func Dial(path string, timeout *time.Duration, config *DialConfig) (net.Conn, error) {
|
||||||
|
var absTimeout time.Time
|
||||||
|
if timeout != nil {
|
||||||
|
absTimeout = time.Now().Add(*timeout)
|
||||||
|
} else {
|
||||||
|
absTimeout = time.Now().Add(2 * time.Second)
|
||||||
|
}
|
||||||
|
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
|
||||||
|
conn, err := DialContext(ctx, path, config)
|
||||||
|
if err == context.DeadlineExceeded {
|
||||||
|
return nil, os.ErrDeadlineExceeded
|
||||||
|
}
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext attempts to connect to the specified named pipe by path
|
||||||
|
// cancellation or timeout.
|
||||||
|
func DialContext(ctx context.Context, path string, config *DialConfig) (net.Conn, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = &DialConfig{}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var h windows.Handle
|
||||||
|
h, err = tryDialPipe(ctx, &path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.ExpectedOwner != nil {
|
||||||
|
sd, err := windows.GetSecurityInfo(h, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
realOwner, _, err := sd.Owner()
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !realOwner.Equals(config.ExpectedOwner) {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, windows.ERROR_ACCESS_DENIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32
|
||||||
|
err = windows.GetNamedPipeInfo(h, &flags, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := makeFile(h)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pipe is in message mode, return a message byte pipe, which
|
||||||
|
// supports CloseWrite.
|
||||||
|
if flags&windows.PIPE_TYPE_MESSAGE != 0 {
|
||||||
|
return &messageBytePipe{
|
||||||
|
pipe: pipe{file: f, path: path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &pipe{file: f, path: path}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type acceptResponse struct {
|
||||||
|
f *file
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeListener struct {
|
||||||
|
firstHandle windows.Handle
|
||||||
|
path string
|
||||||
|
config ListenConfig
|
||||||
|
acceptCh chan (chan acceptResponse)
|
||||||
|
closeCh chan int
|
||||||
|
doneCh chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeServerPipeHandle(path string, sd *windows.SECURITY_DESCRIPTOR, c *ListenConfig, first bool) (windows.Handle, error) {
|
||||||
|
path16, err := windows.UTF16PtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var oa windows.OBJECT_ATTRIBUTES
|
||||||
|
oa.Length = uint32(unsafe.Sizeof(oa))
|
||||||
|
|
||||||
|
var ntPath windows.NTUnicodeString
|
||||||
|
if err := windows.RtlDosPathNameToNtPathName(path16, &ntPath, nil, nil); err != nil {
|
||||||
|
if ntstatus, ok := err.(windows.NTStatus); ok {
|
||||||
|
err = ntstatus.Errno()
|
||||||
|
}
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(unsafe.Pointer(ntPath.Buffer)))
|
||||||
|
oa.ObjectName = &ntPath
|
||||||
|
|
||||||
|
// The security descriptor is only needed for the first pipe.
|
||||||
|
if first {
|
||||||
|
if sd != nil {
|
||||||
|
oa.SecurityDescriptor = sd
|
||||||
|
} else {
|
||||||
|
// Construct the default named pipe security descriptor.
|
||||||
|
var acl *windows.ACL
|
||||||
|
if err := windows.RtlDefaultNpAcl(&acl); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(unsafe.Pointer(acl)))
|
||||||
|
sd, err := windows.NewSecurityDescriptor()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if err = sd.SetDACL(acl, true, false); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
oa.SecurityDescriptor = sd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS)
|
||||||
|
if c.MessageMode {
|
||||||
|
typ |= windows.FILE_PIPE_MESSAGE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
disposition := uint32(windows.FILE_OPEN)
|
||||||
|
access := uint32(windows.GENERIC_READ | windows.GENERIC_WRITE | windows.SYNCHRONIZE)
|
||||||
|
if first {
|
||||||
|
disposition = windows.FILE_CREATE
|
||||||
|
// By not asking for read or write access, the named pipe file system
|
||||||
|
// will put this pipe into an initially disconnected state, blocking
|
||||||
|
// client connections until the next call with first == false.
|
||||||
|
access = windows.SYNCHRONIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := int64(-50 * 10000) // 50ms
|
||||||
|
|
||||||
|
var (
|
||||||
|
h windows.Handle
|
||||||
|
iosb windows.IO_STATUS_BLOCK
|
||||||
|
)
|
||||||
|
err = windows.NtCreateNamedPipeFile(&h, access, &oa, &iosb, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout)
|
||||||
|
if err != nil {
|
||||||
|
if ntstatus, ok := err.(windows.NTStatus); ok {
|
||||||
|
err = ntstatus.Errno()
|
||||||
|
}
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.KeepAlive(ntPath)
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *pipeListener) makeServerPipe() (*file, error) {
|
||||||
|
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := makeFile(h)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *pipeListener) makeConnectedServerPipe() (*file, error) {
|
||||||
|
p, err := l.makeServerPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the client to connect.
|
||||||
|
ch := make(chan error)
|
||||||
|
go func(p *file) {
|
||||||
|
ch <- connectPipe(p)
|
||||||
|
}(p)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-ch:
|
||||||
|
if err != nil {
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
case <-l.closeCh:
|
||||||
|
// Abort the connect request by closing the handle.
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
err = <-ch
|
||||||
|
if err == nil || err == os.ErrClosed {
|
||||||
|
err = net.ErrClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *pipeListener) listenerRoutine() {
|
||||||
|
closed := false
|
||||||
|
for !closed {
|
||||||
|
select {
|
||||||
|
case <-l.closeCh:
|
||||||
|
closed = true
|
||||||
|
case responseCh := <-l.acceptCh:
|
||||||
|
var (
|
||||||
|
p *file
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
p, err = l.makeConnectedServerPipe()
|
||||||
|
// If the connection was immediately closed by the client, try
|
||||||
|
// again.
|
||||||
|
if err != windows.ERROR_NO_DATA {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseCh <- acceptResponse{p, err}
|
||||||
|
closed = err == net.ErrClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
windows.Close(l.firstHandle)
|
||||||
|
l.firstHandle = 0
|
||||||
|
// Notify Close and Accept callers that the handle has been closed.
|
||||||
|
close(l.doneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenConfig contains configuration for the pipe listener.
|
||||||
|
type ListenConfig struct {
|
||||||
|
// SecurityDescriptor contains a Windows security descriptor. If nil, the default from RtlDefaultNpAcl is used.
|
||||||
|
SecurityDescriptor *windows.SECURITY_DESCRIPTOR
|
||||||
|
|
||||||
|
// MessageMode determines whether the pipe is in byte or message mode. In either
|
||||||
|
// case the pipe is read in byte mode by default. The only practical difference in
|
||||||
|
// this implementation is that CloseWrite is only supported for message mode pipes;
|
||||||
|
// CloseWrite is implemented as a zero-byte write, but zero-byte writes are only
|
||||||
|
// transferred to the reader (and returned as io.EOF in this implementation)
|
||||||
|
// when the pipe is in message mode.
|
||||||
|
MessageMode bool
|
||||||
|
|
||||||
|
// InputBufferSize specifies the initial size of the input buffer, in bytes, which the OS will grow as needed.
|
||||||
|
InputBufferSize int32
|
||||||
|
|
||||||
|
// OutputBufferSize specifies the initial size of the output buffer, in bytes, which the OS will grow as needed.
|
||||||
|
OutputBufferSize int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen creates a listener on a Windows named pipe path,such as \\.\pipe\mypipe.
|
||||||
|
// The pipe must not already exist.
|
||||||
|
func Listen(path string, c *ListenConfig) (net.Listener, error) {
|
||||||
|
if c == nil {
|
||||||
|
c = &ListenConfig{}
|
||||||
|
}
|
||||||
|
h, err := makeServerPipeHandle(path, c.SecurityDescriptor, c, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l := &pipeListener{
|
||||||
|
firstHandle: h,
|
||||||
|
path: path,
|
||||||
|
config: *c,
|
||||||
|
acceptCh: make(chan (chan acceptResponse)),
|
||||||
|
closeCh: make(chan int),
|
||||||
|
doneCh: make(chan int),
|
||||||
|
}
|
||||||
|
// The first connection is swallowed on Windows 7 & 8, so synthesize it.
|
||||||
|
if maj, _, _ := windows.RtlGetNtVersionNumbers(); maj <= 8 {
|
||||||
|
path16, err := windows.UTF16PtrFromString(path)
|
||||||
|
if err == nil {
|
||||||
|
h, err = windows.CreateFile(path16, 0, 0, nil, windows.OPEN_EXISTING, windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS, 0)
|
||||||
|
if err == nil {
|
||||||
|
windows.CloseHandle(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go l.listenerRoutine()
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectPipe(p *file) error {
|
||||||
|
c, err := p.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer p.wg.Done()
|
||||||
|
|
||||||
|
err = windows.ConnectNamedPipe(p.handle, &c.o)
|
||||||
|
_, err = p.asyncIo(c, nil, 0, err)
|
||||||
|
if err != nil && err != windows.ERROR_PIPE_CONNECTED {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *pipeListener) Accept() (net.Conn, error) {
|
||||||
|
ch := make(chan acceptResponse)
|
||||||
|
select {
|
||||||
|
case l.acceptCh <- ch:
|
||||||
|
response := <-ch
|
||||||
|
err := response.err
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if l.config.MessageMode {
|
||||||
|
return &messageBytePipe{
|
||||||
|
pipe: pipe{file: response.f, path: l.path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &pipe{file: response.f, path: l.path}, nil
|
||||||
|
case <-l.doneCh:
|
||||||
|
return nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *pipeListener) Close() error {
|
||||||
|
select {
|
||||||
|
case l.closeCh <- 1:
|
||||||
|
<-l.doneCh
|
||||||
|
case <-l.doneCh:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *pipeListener) Addr() net.Addr {
|
||||||
|
return pipeAddress(l.path)
|
||||||
|
}
|
||||||
660
ipc/winpipe/winpipe_test.go
Normal file
660
ipc/winpipe/winpipe_test.go
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005 Microsoft
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package winpipe_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.zx2c4.com/wireguard/ipc/winpipe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func randomPipePath() string {
|
||||||
|
guid, err := windows.GenerateGUID()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return `\\.\PIPE\go-winpipe-test-` + guid.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPingPong(t *testing.T) {
|
||||||
|
const (
|
||||||
|
ping = 42
|
||||||
|
pong = 24
|
||||||
|
)
|
||||||
|
pipePath := randomPipePath()
|
||||||
|
listener, err := winpipe.Listen(pipePath, nil)
|
||||||
|
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 := winpipe.Dial(pipePath, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to dial pipe: %v", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
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 := winpipe.Dial(randomPipePath(), nil, nil)
|
||||||
|
if !errors.Is(err, syscall.ENOENT) {
|
||||||
|
t.Fatalf("expected ENOENT got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialListenerTimesOut(t *testing.T) {
|
||||||
|
pipePath := randomPipePath()
|
||||||
|
l, err := winpipe.Listen(pipePath, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
d := 10 * time.Millisecond
|
||||||
|
_, err = winpipe.Dial(pipePath, &d, nil)
|
||||||
|
if err != os.ErrDeadlineExceeded {
|
||||||
|
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialContextListenerTimesOut(t *testing.T) {
|
||||||
|
pipePath := randomPipePath()
|
||||||
|
l, err := winpipe.Listen(pipePath, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
d := 10 * time.Millisecond
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), d)
|
||||||
|
_, err = winpipe.DialContext(ctx, pipePath, nil)
|
||||||
|
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 := winpipe.Listen(pipePath, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ch := make(chan error)
|
||||||
|
defer l.Close()
|
||||||
|
go func(ctx context.Context, ch chan error) {
|
||||||
|
_, err := winpipe.DialContext(ctx, pipePath, nil)
|
||||||
|
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:")
|
||||||
|
c := winpipe.ListenConfig{
|
||||||
|
SecurityDescriptor: sd,
|
||||||
|
}
|
||||||
|
l, err := winpipe.Listen(pipePath, &c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
_, err = winpipe.Dial(pipePath, nil, nil)
|
||||||
|
if !errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||||
|
t.Fatalf("expected ERROR_ACCESS_DENIED, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConnection(cfg *winpipe.ListenConfig) (client net.Conn, server net.Conn, err error) {
|
||||||
|
pipePath := randomPipePath()
|
||||||
|
l, err := winpipe.Listen(pipePath, cfg)
|
||||||
|
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 := winpipe.Dial(pipePath, nil, nil)
|
||||||
|
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 := winpipe.Listen(pipePath, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
ch := make(chan int)
|
||||||
|
go server(l, ch)
|
||||||
|
|
||||||
|
c, err := winpipe.Dial(pipePath, nil, nil)
|
||||||
|
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 := winpipe.Listen(pipePath, nil)
|
||||||
|
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 := &winpipe.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 := winpipe.Listen(pipePath, nil)
|
||||||
|
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 := winpipe.Listen(pipePath, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
_, err = winpipe.Dial(pipePath, nil, nil)
|
||||||
|
if err != os.ErrDeadlineExceeded {
|
||||||
|
t.Fatalf("expected os.ErrDeadlineExceeded, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeoutPendingRead(t *testing.T) {
|
||||||
|
pipePath := randomPipePath()
|
||||||
|
l, err := winpipe.Listen(pipePath, nil)
|
||||||
|
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 := winpipe.Dial(pipePath, nil, nil)
|
||||||
|
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 := winpipe.Listen(pipePath, nil)
|
||||||
|
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 := winpipe.Dial(pipePath, nil, nil)
|
||||||
|
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) {
|
||||||
|
c := winpipe.ListenConfig{
|
||||||
|
MessageMode: true, // Use message mode so that CloseWrite() is supported
|
||||||
|
InputBufferSize: 65536, // Use 64KB buffers to improve performance
|
||||||
|
OutputBufferSize: 65536,
|
||||||
|
}
|
||||||
|
pipePath := randomPipePath()
|
||||||
|
l, err := winpipe.Listen(pipePath, &c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
listenerDone := make(chan bool)
|
||||||
|
clientDone := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
// server echo
|
||||||
|
conn, e := l.Accept()
|
||||||
|
if e != nil {
|
||||||
|
t.Fatal(e)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Millisecond) // make *sure* we don't begin to read before eof signal is sent
|
||||||
|
io.Copy(conn, conn)
|
||||||
|
conn.(CloseWriter).CloseWrite()
|
||||||
|
close(listenerDone)
|
||||||
|
}()
|
||||||
|
timeout := 1 * time.Second
|
||||||
|
client, err := winpipe.Dial(pipePath, &timeout, nil)
|
||||||
|
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 {
|
||||||
|
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 := winpipe.Listen(pipePath, nil)
|
||||||
|
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 := winpipe.Dial(pipePath, nil, nil)
|
||||||
|
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 := winpipe.Listen(pipePath, &winpipe.ListenConfig{MessageMode: true})
|
||||||
|
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 := winpipe.Dial(pipePath, nil, nil)
|
||||||
|
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 := winpipe.Dial(pipePath, nil, nil)
|
||||||
|
if err == nil {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
s, err := winpipe.Listen(pipePath, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(i, err)
|
||||||
|
} else {
|
||||||
|
s.Close()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
101
main.go
101
main.go
@@ -1,22 +1,24 @@
|
|||||||
// +build !windows
|
//go:build !windows
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
|
||||||
"golang.zx2c4.com/wireguard/ipc"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/ipc"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -31,61 +33,38 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func printUsage() {
|
func printUsage() {
|
||||||
fmt.Printf("usage:\n")
|
fmt.Printf("Usage: %s [-f/--foreground] INTERFACE-NAME\n", os.Args[0])
|
||||||
fmt.Printf("%s [-f/--foreground] INTERFACE-NAME\n", os.Args[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func warning() {
|
func warning() {
|
||||||
if os.Getenv(ENV_WG_PROCESS_FOREGROUND) == "1" {
|
switch runtime.GOOS {
|
||||||
|
case "linux", "freebsd", "openbsd":
|
||||||
|
if os.Getenv(ENV_WG_PROCESS_FOREGROUND) == "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldQuit := false
|
fmt.Fprintln(os.Stderr, "┌──────────────────────────────────────────────────────┐")
|
||||||
|
fmt.Fprintln(os.Stderr, "│ │")
|
||||||
fmt.Fprintln(os.Stderr, "WARNING WARNING WARNING WARNING WARNING WARNING WARNING")
|
fmt.Fprintln(os.Stderr, "│ Running wireguard-go is not required because this │")
|
||||||
fmt.Fprintln(os.Stderr, "W G")
|
fmt.Fprintln(os.Stderr, "│ kernel has first class support for WireGuard. For │")
|
||||||
fmt.Fprintln(os.Stderr, "W This is alpha software. It will very likely not G")
|
fmt.Fprintln(os.Stderr, "│ information on installing the kernel module, │")
|
||||||
fmt.Fprintln(os.Stderr, "W do what it is supposed to do, and things may go G")
|
fmt.Fprintln(os.Stderr, "│ please visit: │")
|
||||||
fmt.Fprintln(os.Stderr, "W horribly wrong. You have been warned. Proceed G")
|
fmt.Fprintln(os.Stderr, "│ https://www.wireguard.com/install/ │")
|
||||||
fmt.Fprintln(os.Stderr, "W at your own risk. G")
|
fmt.Fprintln(os.Stderr, "│ │")
|
||||||
if runtime.GOOS == "linux" {
|
fmt.Fprintln(os.Stderr, "└──────────────────────────────────────────────────────┘")
|
||||||
shouldQuit = os.Getenv("WG_I_PREFER_BUGGY_USERSPACE_TO_POLISHED_KMOD") != "1"
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "W G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W Furthermore, you are running this software on a G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W Linux kernel, which is probably unnecessary and G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W foolish. This is because the Linux kernel has G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W built-in first class support for WireGuard, and G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W this support is much more refined than this G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W program. For more information on installing the G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W kernel module, please visit: G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W https://www.wireguard.com/install G")
|
|
||||||
if shouldQuit {
|
|
||||||
fmt.Fprintln(os.Stderr, "W G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W If you still want to use this program, against G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W the sage advice here, please first export this G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W environment variable: G")
|
|
||||||
fmt.Fprintln(os.Stderr, "W WG_I_PREFER_BUGGY_USERSPACE_TO_POLISHED_KMOD=1 G")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Fprintln(os.Stderr, "W G")
|
|
||||||
fmt.Fprintln(os.Stderr, "WARNING WARNING WARNING WARNING WARNING WARNING WARNING")
|
|
||||||
|
|
||||||
if shouldQuit {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) == 2 && os.Args[1] == "--version" {
|
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", device.WireGuardGoVersion, runtime.GOOS, runtime.GOARCH)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
warning()
|
warning()
|
||||||
|
|
||||||
// parse arguments
|
|
||||||
|
|
||||||
var foreground bool
|
var foreground bool
|
||||||
var interfaceName string
|
var interfaceName string
|
||||||
if len(os.Args) < 2 || len(os.Args) > 3 {
|
if len(os.Args) < 2 || len(os.Args) > 3 {
|
||||||
@@ -120,21 +99,19 @@ func main() {
|
|||||||
|
|
||||||
logLevel := func() int {
|
logLevel := func() int {
|
||||||
switch os.Getenv("LOG_LEVEL") {
|
switch os.Getenv("LOG_LEVEL") {
|
||||||
case "debug":
|
case "verbose", "debug":
|
||||||
return device.LogLevelDebug
|
return device.LogLevelVerbose
|
||||||
case "info":
|
|
||||||
return device.LogLevelInfo
|
|
||||||
case "error":
|
case "error":
|
||||||
return device.LogLevelError
|
return device.LogLevelError
|
||||||
case "silent":
|
case "silent":
|
||||||
return device.LogLevelSilent
|
return device.LogLevelSilent
|
||||||
}
|
}
|
||||||
return device.LogLevelInfo
|
return device.LogLevelError
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// open TUN device (or use supplied fd)
|
// open TUN device (or use supplied fd)
|
||||||
|
|
||||||
tun, err := func() (tun.TUNDevice, error) {
|
tun, err := func() (tun.Device, error) {
|
||||||
tunFdStr := os.Getenv(ENV_WG_TUN_FD)
|
tunFdStr := os.Getenv(ENV_WG_TUN_FD)
|
||||||
if tunFdStr == "" {
|
if tunFdStr == "" {
|
||||||
return tun.CreateTUN(interfaceName, device.DefaultMTU)
|
return tun.CreateTUN(interfaceName, device.DefaultMTU)
|
||||||
@@ -168,12 +145,10 @@ func main() {
|
|||||||
fmt.Sprintf("(%s) ", interfaceName),
|
fmt.Sprintf("(%s) ", interfaceName),
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.Info.Println("Starting wireguard-go version", device.WireGuardGoVersion)
|
logger.Verbosef("Starting wireguard-go version %s", Version)
|
||||||
|
|
||||||
logger.Debug.Println("Debug log enabled")
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Println("Failed to create TUN device:", err)
|
logger.Errorf("Failed to create TUN device: %v", err)
|
||||||
os.Exit(ExitSetupFailed)
|
os.Exit(ExitSetupFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +171,7 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Println("UAPI listen error:", err)
|
logger.Errorf("UAPI listen error: %v", err)
|
||||||
os.Exit(ExitSetupFailed)
|
os.Exit(ExitSetupFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -231,7 +206,7 @@ func main() {
|
|||||||
|
|
||||||
path, err := os.Executable()
|
path, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Println("Failed to determine executable:", err)
|
logger.Errorf("Failed to determine executable: %v", err)
|
||||||
os.Exit(ExitSetupFailed)
|
os.Exit(ExitSetupFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,23 +216,23 @@ func main() {
|
|||||||
attr,
|
attr,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Println("Failed to daemonize:", err)
|
logger.Errorf("Failed to daemonize: %v", err)
|
||||||
os.Exit(ExitSetupFailed)
|
os.Exit(ExitSetupFailed)
|
||||||
}
|
}
|
||||||
process.Release()
|
process.Release()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
device := device.NewDevice(tun, logger)
|
device := device.NewDevice(tun, conn.NewDefaultBind(), logger)
|
||||||
|
|
||||||
logger.Info.Println("Device started")
|
logger.Verbosef("Device started")
|
||||||
|
|
||||||
errs := make(chan error)
|
errs := make(chan error)
|
||||||
term := make(chan os.Signal, 1)
|
term := make(chan os.Signal, 1)
|
||||||
|
|
||||||
uapi, err := ipc.UAPIListen(interfaceName, fileUAPI)
|
uapi, err := ipc.UAPIListen(interfaceName, fileUAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Println("Failed to listen on uapi socket:", err)
|
logger.Errorf("Failed to listen on uapi socket: %v", err)
|
||||||
os.Exit(ExitSetupFailed)
|
os.Exit(ExitSetupFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +247,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
logger.Info.Println("UAPI listener started")
|
logger.Verbosef("UAPI listener started")
|
||||||
|
|
||||||
// wait for program to terminate
|
// wait for program to terminate
|
||||||
|
|
||||||
@@ -290,5 +265,5 @@ func main() {
|
|||||||
uapi.Close()
|
uapi.Close()
|
||||||
device.Close()
|
device.Close()
|
||||||
|
|
||||||
logger.Info.Println("Shutting down")
|
logger.Verbosef("Shutting down")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.zx2c4.com/wireguard/device"
|
|
||||||
"golang.zx2c4.com/wireguard/ipc"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/ipc"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,31 +29,36 @@ func main() {
|
|||||||
}
|
}
|
||||||
interfaceName := os.Args[1]
|
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(
|
logger := device.NewLogger(
|
||||||
device.LogLevelDebug,
|
device.LogLevelVerbose,
|
||||||
fmt.Sprintf("(%s) ", interfaceName),
|
fmt.Sprintf("(%s) ", interfaceName),
|
||||||
)
|
)
|
||||||
logger.Info.Println("Starting wireguard-go version", WireGuardGoVersion)
|
logger.Verbosef("Starting wireguard-go version %s", Version)
|
||||||
logger.Debug.Println("Debug log enabled")
|
|
||||||
|
|
||||||
tun, err := tun.CreateTUN(interfaceName)
|
tun, err := tun.CreateTUN(interfaceName, 0)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
realInterfaceName, err2 := tun.Name()
|
realInterfaceName, err2 := tun.Name()
|
||||||
if err2 == nil {
|
if err2 == nil {
|
||||||
interfaceName = realInterfaceName
|
interfaceName = realInterfaceName
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.Error.Println("Failed to create TUN device:", err)
|
logger.Errorf("Failed to create TUN device: %v", err)
|
||||||
os.Exit(ExitSetupFailed)
|
os.Exit(ExitSetupFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
device := device.NewDevice(tun, logger)
|
device := device.NewDevice(tun, conn.NewDefaultBind(), logger)
|
||||||
device.Up()
|
err = device.Up()
|
||||||
logger.Info.Println("Device started")
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to bring up device: %v", err)
|
||||||
|
os.Exit(ExitSetupFailed)
|
||||||
|
}
|
||||||
|
logger.Verbosef("Device started")
|
||||||
|
|
||||||
uapi, err := ipc.UAPIListen(interfaceName)
|
uapi, err := ipc.UAPIListen(interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Println("Failed to listen on uapi socket:", err)
|
logger.Errorf("Failed to listen on uapi socket: %v", err)
|
||||||
os.Exit(ExitSetupFailed)
|
os.Exit(ExitSetupFailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +75,7 @@ func main() {
|
|||||||
go device.IpcHandle(conn)
|
go device.IpcHandle(conn)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
logger.Info.Println("UAPI listener started")
|
logger.Verbosef("UAPI listener started")
|
||||||
|
|
||||||
// wait for program to terminate
|
// wait for program to terminate
|
||||||
|
|
||||||
@@ -87,5 +94,5 @@ func main() {
|
|||||||
uapi.Close()
|
uapi.Close()
|
||||||
device.Close()
|
device.Close()
|
||||||
|
|
||||||
logger.Info.Println("Shutting down")
|
logger.Verbosef("Shutting down")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ratelimiter
|
package ratelimiter
|
||||||
@@ -20,21 +20,23 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RatelimiterEntry struct {
|
type RatelimiterEntry struct {
|
||||||
sync.Mutex
|
mu sync.Mutex
|
||||||
lastTime time.Time
|
lastTime time.Time
|
||||||
tokens int64
|
tokens int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ratelimiter struct {
|
type Ratelimiter struct {
|
||||||
sync.RWMutex
|
mu sync.RWMutex
|
||||||
stopReset chan struct{}
|
timeNow func() time.Time
|
||||||
|
|
||||||
|
stopReset chan struct{} // send to reset, close to stop
|
||||||
tableIPv4 map[[net.IPv4len]byte]*RatelimiterEntry
|
tableIPv4 map[[net.IPv4len]byte]*RatelimiterEntry
|
||||||
tableIPv6 map[[net.IPv6len]byte]*RatelimiterEntry
|
tableIPv6 map[[net.IPv6len]byte]*RatelimiterEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rate *Ratelimiter) Close() {
|
func (rate *Ratelimiter) Close() {
|
||||||
rate.Lock()
|
rate.mu.Lock()
|
||||||
defer rate.Unlock()
|
defer rate.mu.Unlock()
|
||||||
|
|
||||||
if rate.stopReset != nil {
|
if rate.stopReset != nil {
|
||||||
close(rate.stopReset)
|
close(rate.stopReset)
|
||||||
@@ -42,11 +44,14 @@ func (rate *Ratelimiter) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rate *Ratelimiter) Init() {
|
func (rate *Ratelimiter) Init() {
|
||||||
rate.Lock()
|
rate.mu.Lock()
|
||||||
defer rate.Unlock()
|
defer rate.mu.Unlock()
|
||||||
|
|
||||||
|
if rate.timeNow == nil {
|
||||||
|
rate.timeNow = time.Now
|
||||||
|
}
|
||||||
|
|
||||||
// stop any ongoing garbage collection routine
|
// stop any ongoing garbage collection routine
|
||||||
|
|
||||||
if rate.stopReset != nil {
|
if rate.stopReset != nil {
|
||||||
close(rate.stopReset)
|
close(rate.stopReset)
|
||||||
}
|
}
|
||||||
@@ -55,50 +60,52 @@ func (rate *Ratelimiter) Init() {
|
|||||||
rate.tableIPv4 = make(map[[net.IPv4len]byte]*RatelimiterEntry)
|
rate.tableIPv4 = make(map[[net.IPv4len]byte]*RatelimiterEntry)
|
||||||
rate.tableIPv6 = make(map[[net.IPv6len]byte]*RatelimiterEntry)
|
rate.tableIPv6 = make(map[[net.IPv6len]byte]*RatelimiterEntry)
|
||||||
|
|
||||||
// start garbage collection routine
|
stopReset := rate.stopReset // store in case Init is called again.
|
||||||
|
|
||||||
|
// Start garbage collection routine.
|
||||||
go func() {
|
go func() {
|
||||||
ticker := time.NewTicker(time.Second)
|
ticker := time.NewTicker(time.Second)
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case _, ok := <-rate.stopReset:
|
case _, ok := <-stopReset:
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
if ok {
|
if !ok {
|
||||||
ticker = time.NewTicker(time.Second)
|
|
||||||
} else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ticker = time.NewTicker(time.Second)
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
func() {
|
if rate.cleanup() {
|
||||||
rate.Lock()
|
ticker.Stop()
|
||||||
defer rate.Unlock()
|
}
|
||||||
|
|
||||||
for key, entry := range rate.tableIPv4 {
|
|
||||||
entry.Lock()
|
|
||||||
if time.Now().Sub(entry.lastTime) > garbageCollectTime {
|
|
||||||
delete(rate.tableIPv4, key)
|
|
||||||
}
|
|
||||||
entry.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, entry := range rate.tableIPv6 {
|
|
||||||
entry.Lock()
|
|
||||||
if time.Now().Sub(entry.lastTime) > garbageCollectTime {
|
|
||||||
delete(rate.tableIPv6, key)
|
|
||||||
}
|
|
||||||
entry.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rate.tableIPv4) == 0 && len(rate.tableIPv6) == 0 {
|
|
||||||
ticker.Stop()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rate *Ratelimiter) cleanup() (empty bool) {
|
||||||
|
rate.mu.Lock()
|
||||||
|
defer rate.mu.Unlock()
|
||||||
|
|
||||||
|
for key, entry := range rate.tableIPv4 {
|
||||||
|
entry.mu.Lock()
|
||||||
|
if rate.timeNow().Sub(entry.lastTime) > garbageCollectTime {
|
||||||
|
delete(rate.tableIPv4, key)
|
||||||
|
}
|
||||||
|
entry.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, entry := range rate.tableIPv6 {
|
||||||
|
entry.mu.Lock()
|
||||||
|
if rate.timeNow().Sub(entry.lastTime) > garbageCollectTime {
|
||||||
|
delete(rate.tableIPv6, key)
|
||||||
|
}
|
||||||
|
entry.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(rate.tableIPv4) == 0 && len(rate.tableIPv6) == 0
|
||||||
|
}
|
||||||
|
|
||||||
func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
||||||
var entry *RatelimiterEntry
|
var entry *RatelimiterEntry
|
||||||
var keyIPv4 [net.IPv4len]byte
|
var keyIPv4 [net.IPv4len]byte
|
||||||
@@ -109,7 +116,7 @@ func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
|||||||
IPv4 := ip.To4()
|
IPv4 := ip.To4()
|
||||||
IPv6 := ip.To16()
|
IPv6 := ip.To16()
|
||||||
|
|
||||||
rate.RLock()
|
rate.mu.RLock()
|
||||||
|
|
||||||
if IPv4 != nil {
|
if IPv4 != nil {
|
||||||
copy(keyIPv4[:], IPv4)
|
copy(keyIPv4[:], IPv4)
|
||||||
@@ -119,15 +126,15 @@ func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
|||||||
entry = rate.tableIPv6[keyIPv6]
|
entry = rate.tableIPv6[keyIPv6]
|
||||||
}
|
}
|
||||||
|
|
||||||
rate.RUnlock()
|
rate.mu.RUnlock()
|
||||||
|
|
||||||
// make new entry if not found
|
// make new entry if not found
|
||||||
|
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
entry = new(RatelimiterEntry)
|
entry = new(RatelimiterEntry)
|
||||||
entry.tokens = maxTokens - packetCost
|
entry.tokens = maxTokens - packetCost
|
||||||
entry.lastTime = time.Now()
|
entry.lastTime = rate.timeNow()
|
||||||
rate.Lock()
|
rate.mu.Lock()
|
||||||
if IPv4 != nil {
|
if IPv4 != nil {
|
||||||
rate.tableIPv4[keyIPv4] = entry
|
rate.tableIPv4[keyIPv4] = entry
|
||||||
if len(rate.tableIPv4) == 1 && len(rate.tableIPv6) == 0 {
|
if len(rate.tableIPv4) == 1 && len(rate.tableIPv6) == 0 {
|
||||||
@@ -139,14 +146,14 @@ func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
|||||||
rate.stopReset <- struct{}{}
|
rate.stopReset <- struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rate.Unlock()
|
rate.mu.Unlock()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// add tokens to entry
|
// add tokens to entry
|
||||||
|
|
||||||
entry.Lock()
|
entry.mu.Lock()
|
||||||
now := time.Now()
|
now := rate.timeNow()
|
||||||
entry.tokens += now.Sub(entry.lastTime).Nanoseconds()
|
entry.tokens += now.Sub(entry.lastTime).Nanoseconds()
|
||||||
entry.lastTime = now
|
entry.lastTime = now
|
||||||
if entry.tokens > maxTokens {
|
if entry.tokens > maxTokens {
|
||||||
@@ -157,9 +164,9 @@ func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
|||||||
|
|
||||||
if entry.tokens > packetCost {
|
if entry.tokens > packetCost {
|
||||||
entry.tokens -= packetCost
|
entry.tokens -= packetCost
|
||||||
entry.Unlock()
|
entry.mu.Unlock()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
entry.Unlock()
|
entry.mu.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package ratelimiter
|
package ratelimiter
|
||||||
@@ -11,22 +11,21 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RatelimiterResult struct {
|
type result struct {
|
||||||
allowed bool
|
allowed bool
|
||||||
text string
|
text string
|
||||||
wait time.Duration
|
wait time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRatelimiter(t *testing.T) {
|
func TestRatelimiter(t *testing.T) {
|
||||||
|
var rate Ratelimiter
|
||||||
|
var expectedResults []result
|
||||||
|
|
||||||
var ratelimiter Ratelimiter
|
nano := func(nano int64) time.Duration {
|
||||||
var expectedResults []RatelimiterResult
|
|
||||||
|
|
||||||
Nano := func(nano int64) time.Duration {
|
|
||||||
return time.Nanosecond * time.Duration(nano)
|
return time.Nanosecond * time.Duration(nano)
|
||||||
}
|
}
|
||||||
|
|
||||||
Add := func(res RatelimiterResult) {
|
add := func(res result) {
|
||||||
expectedResults = append(
|
expectedResults = append(
|
||||||
expectedResults,
|
expectedResults,
|
||||||
res,
|
res,
|
||||||
@@ -34,40 +33,40 @@ func TestRatelimiter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < packetsBurstable; i++ {
|
for i := 0; i < packetsBurstable; i++ {
|
||||||
Add(RatelimiterResult{
|
add(result{
|
||||||
allowed: true,
|
allowed: true,
|
||||||
text: "inital burst",
|
text: "initial burst",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Add(RatelimiterResult{
|
add(result{
|
||||||
allowed: false,
|
allowed: false,
|
||||||
text: "after burst",
|
text: "after burst",
|
||||||
})
|
})
|
||||||
|
|
||||||
Add(RatelimiterResult{
|
add(result{
|
||||||
allowed: true,
|
allowed: true,
|
||||||
wait: Nano(time.Second.Nanoseconds() / packetsPerSecond),
|
wait: nano(time.Second.Nanoseconds() / packetsPerSecond),
|
||||||
text: "filling tokens for single packet",
|
text: "filling tokens for single packet",
|
||||||
})
|
})
|
||||||
|
|
||||||
Add(RatelimiterResult{
|
add(result{
|
||||||
allowed: false,
|
allowed: false,
|
||||||
text: "not having refilled enough",
|
text: "not having refilled enough",
|
||||||
})
|
})
|
||||||
|
|
||||||
Add(RatelimiterResult{
|
add(result{
|
||||||
allowed: true,
|
allowed: true,
|
||||||
wait: 2 * (Nano(time.Second.Nanoseconds() / packetsPerSecond)),
|
wait: 2 * (nano(time.Second.Nanoseconds() / packetsPerSecond)),
|
||||||
text: "filling tokens for two packet burst",
|
text: "filling tokens for two packet burst",
|
||||||
})
|
})
|
||||||
|
|
||||||
Add(RatelimiterResult{
|
add(result{
|
||||||
allowed: true,
|
allowed: true,
|
||||||
text: "second packet in 2 packet burst",
|
text: "second packet in 2 packet burst",
|
||||||
})
|
})
|
||||||
|
|
||||||
Add(RatelimiterResult{
|
add(result{
|
||||||
allowed: false,
|
allowed: false,
|
||||||
text: "packet following 2 packet burst",
|
text: "packet following 2 packet burst",
|
||||||
})
|
})
|
||||||
@@ -89,14 +88,31 @@ func TestRatelimiter(t *testing.T) {
|
|||||||
net.ParseIP("3f0e:54a2:f5b4:cd19:a21d:58e1:3746:84c4"),
|
net.ParseIP("3f0e:54a2:f5b4:cd19:a21d:58e1:3746:84c4"),
|
||||||
}
|
}
|
||||||
|
|
||||||
ratelimiter.Init()
|
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 {
|
for i, res := range expectedResults {
|
||||||
time.Sleep(res.wait)
|
timeSleep(res.wait)
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
allowed := ratelimiter.Allow(ip)
|
allowed := rate.Allow(ip)
|
||||||
if allowed != res.allowed {
|
if allowed != res.allowed {
|
||||||
t.Fatal("Test failed for", ip.String(), ", on:", i, "(", res.text, ")", "expected:", res.allowed, "got:", allowed)
|
t.Fatalf("%d: %s: rate.Allow(%q)=%v, want %v", i, res.text, ip, allowed, res.allowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
101
replay/replay.go
101
replay/replay.go
@@ -1,83 +1,62 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Package replay implements an efficient anti-replay algorithm as specified in RFC 6479.
|
||||||
package replay
|
package replay
|
||||||
|
|
||||||
/* Implementation of RFC6479
|
type block uint64
|
||||||
* https://tools.ietf.org/html/rfc6479
|
|
||||||
*
|
|
||||||
* The implementation is not safe for concurrent use!
|
|
||||||
*/
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// See: https://golang.org/src/math/big/arith.go
|
blockBitLog = 6 // 1<<6 == 64 bits
|
||||||
_Wordm = ^uintptr(0)
|
blockBits = 1 << blockBitLog // must be power of 2
|
||||||
_WordLogSize = _Wordm>>8&1 + _Wordm>>16&1 + _Wordm>>32&1
|
ringBlocks = 1 << 7 // must be power of 2
|
||||||
_WordSize = 1 << _WordLogSize
|
windowSize = (ringBlocks - 1) * blockBits
|
||||||
|
blockMask = ringBlocks - 1
|
||||||
|
bitMask = blockBits - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// A Filter rejects replayed messages by checking if message counter value is
|
||||||
CounterRedundantBitsLog = _WordLogSize + 3
|
// within a sliding window of previously received messages.
|
||||||
CounterRedundantBits = _WordSize * 8
|
// The zero value for Filter is an empty filter ready to use.
|
||||||
CounterBitsTotal = 2048
|
// Filters are unsafe for concurrent use.
|
||||||
CounterWindowSize = uint64(CounterBitsTotal - CounterRedundantBits)
|
type Filter struct {
|
||||||
)
|
last uint64
|
||||||
|
ring [ringBlocks]block
|
||||||
const (
|
|
||||||
BacktrackWords = CounterBitsTotal / _WordSize
|
|
||||||
)
|
|
||||||
|
|
||||||
func minUint64(a uint64, b uint64) uint64 {
|
|
||||||
if a > b {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReplayFilter struct {
|
// Reset resets the filter to empty state.
|
||||||
counter uint64
|
func (f *Filter) Reset() {
|
||||||
backtrack [BacktrackWords]uintptr
|
f.last = 0
|
||||||
|
f.ring[0] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (filter *ReplayFilter) Init() {
|
// ValidateCounter checks if the counter should be accepted.
|
||||||
filter.counter = 0
|
// Overlimit counters (>= limit) are always rejected.
|
||||||
filter.backtrack[0] = 0
|
func (f *Filter) ValidateCounter(counter uint64, limit uint64) bool {
|
||||||
}
|
|
||||||
|
|
||||||
func (filter *ReplayFilter) ValidateCounter(counter uint64, limit uint64) bool {
|
|
||||||
if counter >= limit {
|
if counter >= limit {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
indexBlock := counter >> blockBitLog
|
||||||
indexWord := counter >> CounterRedundantBitsLog
|
if counter > f.last { // move window forward
|
||||||
|
current := f.last >> blockBitLog
|
||||||
if counter > filter.counter {
|
diff := indexBlock - current
|
||||||
|
if diff > ringBlocks {
|
||||||
// move window forward
|
diff = ringBlocks // cap diff to clear the whole ring
|
||||||
|
|
||||||
current := filter.counter >> CounterRedundantBitsLog
|
|
||||||
diff := minUint64(indexWord-current, BacktrackWords)
|
|
||||||
for i := uint64(1); i <= diff; i++ {
|
|
||||||
filter.backtrack[(current+i)%BacktrackWords] = 0
|
|
||||||
}
|
}
|
||||||
filter.counter = counter
|
for i := current + 1; i <= current+diff; i++ {
|
||||||
|
f.ring[i&blockMask] = 0
|
||||||
} else if filter.counter-counter > CounterWindowSize {
|
}
|
||||||
|
f.last = counter
|
||||||
// behind current window
|
} else if f.last-counter > windowSize { // behind current window
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
indexWord %= BacktrackWords
|
|
||||||
indexBit := counter & uint64(CounterRedundantBits-1)
|
|
||||||
|
|
||||||
// check and set bit
|
// check and set bit
|
||||||
|
indexBlock &= blockMask
|
||||||
oldValue := filter.backtrack[indexWord]
|
indexBit := counter & bitMask
|
||||||
newValue := oldValue | (1 << indexBit)
|
old := f.ring[indexBlock]
|
||||||
filter.backtrack[indexWord] = newValue
|
new := old | 1<<indexBit
|
||||||
return oldValue != newValue
|
f.ring[indexBlock] = new
|
||||||
|
return old != new
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package replay
|
package replay
|
||||||
@@ -14,22 +14,22 @@ import (
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const RejectAfterMessages = (1 << 64) - (1 << 4) - 1
|
const RejectAfterMessages = 1<<64 - 1<<13 - 1
|
||||||
|
|
||||||
func TestReplay(t *testing.T) {
|
func TestReplay(t *testing.T) {
|
||||||
var filter ReplayFilter
|
var filter Filter
|
||||||
|
|
||||||
T_LIM := CounterWindowSize + 1
|
const T_LIM = windowSize + 1
|
||||||
|
|
||||||
testNumber := 0
|
testNumber := 0
|
||||||
T := func(n uint64, v bool) {
|
T := func(n uint64, expected bool) {
|
||||||
testNumber++
|
testNumber++
|
||||||
if filter.ValidateCounter(n, RejectAfterMessages) != v {
|
if filter.ValidateCounter(n, RejectAfterMessages) != expected {
|
||||||
t.Fatal("Test", testNumber, "failed", n, v)
|
t.Fatal("Test", testNumber, "failed", n, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filter.Init()
|
filter.Reset()
|
||||||
|
|
||||||
T(0, true) /* 1 */
|
T(0, true) /* 1 */
|
||||||
T(1, true) /* 2 */
|
T(1, true) /* 2 */
|
||||||
@@ -67,53 +67,53 @@ func TestReplay(t *testing.T) {
|
|||||||
T(0, false) /* 34 */
|
T(0, false) /* 34 */
|
||||||
|
|
||||||
t.Log("Bulk test 1")
|
t.Log("Bulk test 1")
|
||||||
filter.Init()
|
filter.Reset()
|
||||||
testNumber = 0
|
testNumber = 0
|
||||||
for i := uint64(1); i <= CounterWindowSize; i++ {
|
for i := uint64(1); i <= windowSize; i++ {
|
||||||
T(i, true)
|
T(i, true)
|
||||||
}
|
}
|
||||||
T(0, true)
|
T(0, true)
|
||||||
T(0, false)
|
T(0, false)
|
||||||
|
|
||||||
t.Log("Bulk test 2")
|
t.Log("Bulk test 2")
|
||||||
filter.Init()
|
filter.Reset()
|
||||||
testNumber = 0
|
testNumber = 0
|
||||||
for i := uint64(2); i <= CounterWindowSize+1; i++ {
|
for i := uint64(2); i <= windowSize+1; i++ {
|
||||||
T(i, true)
|
T(i, true)
|
||||||
}
|
}
|
||||||
T(1, true)
|
T(1, true)
|
||||||
T(0, false)
|
T(0, false)
|
||||||
|
|
||||||
t.Log("Bulk test 3")
|
t.Log("Bulk test 3")
|
||||||
filter.Init()
|
filter.Reset()
|
||||||
testNumber = 0
|
testNumber = 0
|
||||||
for i := CounterWindowSize + 1; i > 0; i-- {
|
for i := uint64(windowSize + 1); i > 0; i-- {
|
||||||
T(i, true)
|
T(i, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Log("Bulk test 4")
|
t.Log("Bulk test 4")
|
||||||
filter.Init()
|
filter.Reset()
|
||||||
testNumber = 0
|
testNumber = 0
|
||||||
for i := CounterWindowSize + 2; i > 1; i-- {
|
for i := uint64(windowSize + 2); i > 1; i-- {
|
||||||
T(i, true)
|
T(i, true)
|
||||||
}
|
}
|
||||||
T(0, false)
|
T(0, false)
|
||||||
|
|
||||||
t.Log("Bulk test 5")
|
t.Log("Bulk test 5")
|
||||||
filter.Init()
|
filter.Reset()
|
||||||
testNumber = 0
|
testNumber = 0
|
||||||
for i := CounterWindowSize; i > 0; i-- {
|
for i := uint64(windowSize); i > 0; i-- {
|
||||||
T(i, true)
|
T(i, true)
|
||||||
}
|
}
|
||||||
T(CounterWindowSize+1, true)
|
T(windowSize+1, true)
|
||||||
T(0, false)
|
T(0, false)
|
||||||
|
|
||||||
t.Log("Bulk test 6")
|
t.Log("Bulk test 6")
|
||||||
filter.Init()
|
filter.Reset()
|
||||||
testNumber = 0
|
testNumber = 0
|
||||||
for i := CounterWindowSize; i > 0; i-- {
|
for i := uint64(windowSize); i > 0; i-- {
|
||||||
T(i, true)
|
T(i, true)
|
||||||
}
|
}
|
||||||
T(0, true)
|
T(0, true)
|
||||||
T(CounterWindowSize+1, true)
|
T(windowSize+1, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package rwcancel
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
type fdSet struct {
|
|
||||||
unix.FdSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fdset *fdSet) set(i int) {
|
|
||||||
bits := 32 << (^uint(0) >> 63)
|
|
||||||
fdset.Bits[i/bits] |= 1 << uint(i%bits)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fdset *fdSet) check(i int) bool {
|
|
||||||
bits := 32 << (^uint(0) >> 63)
|
|
||||||
return (fdset.Bits[i/bits] & (1 << uint(i%bits))) != 0
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,21 @@
|
|||||||
|
//go:build !windows
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Package rwcancel implements cancelable read/write operations on
|
||||||
|
// a file descriptor.
|
||||||
package rwcancel
|
package rwcancel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
|
||||||
|
|
||||||
func max(a, b int) int {
|
"golang.org/x/sys/unix"
|
||||||
if a > b {
|
)
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
type RWCancel struct {
|
type RWCancel struct {
|
||||||
fd int
|
fd int
|
||||||
@@ -41,47 +39,47 @@ func NewRWCancel(fd int) (*RWCancel, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RetryAfterError(err error) bool {
|
func RetryAfterError(err error) bool {
|
||||||
if pe, ok := err.(*os.PathError); ok {
|
return errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.EINTR)
|
||||||
err = pe.Err
|
|
||||||
}
|
|
||||||
if errno, ok := err.(syscall.Errno); ok {
|
|
||||||
switch errno {
|
|
||||||
case syscall.EAGAIN, syscall.EINTR:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *RWCancel) ReadyRead() bool {
|
func (rw *RWCancel) ReadyRead() bool {
|
||||||
closeFd := int(rw.closingReader.Fd())
|
closeFd := int32(rw.closingReader.Fd())
|
||||||
fdset := fdSet{}
|
|
||||||
fdset.set(rw.fd)
|
pollFds := []unix.PollFd{{Fd: int32(rw.fd), Events: unix.POLLIN}, {Fd: closeFd, Events: unix.POLLIN}}
|
||||||
fdset.set(closeFd)
|
var err error
|
||||||
err := unixSelect(max(rw.fd, closeFd)+1, &fdset.FdSet, nil, nil, nil)
|
for {
|
||||||
|
_, err = unix.Poll(pollFds, -1)
|
||||||
|
if err == nil || !RetryAfterError(err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if fdset.check(closeFd) {
|
if pollFds[1].Revents != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return fdset.check(rw.fd)
|
return pollFds[0].Revents != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *RWCancel) ReadyWrite() bool {
|
func (rw *RWCancel) ReadyWrite() bool {
|
||||||
closeFd := int(rw.closingReader.Fd())
|
closeFd := int32(rw.closingReader.Fd())
|
||||||
fdset := fdSet{}
|
pollFds := []unix.PollFd{{Fd: int32(rw.fd), Events: unix.POLLOUT}, {Fd: closeFd, Events: unix.POLLOUT}}
|
||||||
fdset.set(rw.fd)
|
var err error
|
||||||
fdset.set(closeFd)
|
for {
|
||||||
err := unixSelect(max(rw.fd, closeFd)+1, nil, &fdset.FdSet, nil, nil)
|
_, err = unix.Poll(pollFds, -1)
|
||||||
|
if err == nil || !RetryAfterError(err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if fdset.check(closeFd) {
|
|
||||||
|
if pollFds[1].Revents != 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return fdset.check(rw.fd)
|
return pollFds[0].Revents != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw *RWCancel) Read(p []byte) (n int, err error) {
|
func (rw *RWCancel) Read(p []byte) (n int, err error) {
|
||||||
@@ -91,7 +89,7 @@ func (rw *RWCancel) Read(p []byte) (n int, err error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
if !rw.ReadyRead() {
|
if !rw.ReadyRead() {
|
||||||
return 0, errors.New("fd closed")
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +101,7 @@ func (rw *RWCancel) Write(p []byte) (n int, err error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
if !rw.ReadyWrite() {
|
if !rw.ReadyWrite() {
|
||||||
return 0, errors.New("fd closed")
|
return 0, os.ErrClosed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,3 +110,8 @@ func (rw *RWCancel) Cancel() (err error) {
|
|||||||
_, err = rw.closingWriter.Write([]byte{0})
|
_, err = rw.closingWriter.Write([]byte{0})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rw *RWCancel) Close() {
|
||||||
|
rw.closingReader.Close()
|
||||||
|
rw.closingWriter.Close()
|
||||||
|
}
|
||||||
|
|||||||
8
rwcancel/rwcancel_windows.go
Normal file
8
rwcancel/rwcancel_windows.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package rwcancel
|
||||||
|
|
||||||
|
type RWCancel struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*RWCancel) Cancel() {}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
// +build !linux
|
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package rwcancel
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
func unixSelect(nfd int, r *unix.FdSet, w *unix.FdSet, e *unix.FdSet, timeout *unix.Timeval) error {
|
|
||||||
return unix.Select(nfd, r, w, e, timeout)
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package rwcancel
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
func unixSelect(nfd int, r *unix.FdSet, w *unix.FdSet, e *unix.FdSet, timeout *unix.Timeval) (err error) {
|
|
||||||
_, err = unix.Select(nfd, r, w, e, timeout)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tai64n
|
package tai64n
|
||||||
@@ -17,16 +17,23 @@ const whitenerMask = uint32(0x1000000 - 1)
|
|||||||
|
|
||||||
type Timestamp [TimestampSize]byte
|
type Timestamp [TimestampSize]byte
|
||||||
|
|
||||||
func Now() Timestamp {
|
func stamp(t time.Time) Timestamp {
|
||||||
var tai64n Timestamp
|
var tai64n Timestamp
|
||||||
now := time.Now()
|
secs := base + uint64(t.Unix())
|
||||||
secs := base + uint64(now.Unix())
|
nano := uint32(t.Nanosecond()) &^ whitenerMask
|
||||||
nano := uint32(now.Nanosecond()) &^ whitenerMask
|
|
||||||
binary.BigEndian.PutUint64(tai64n[:], secs)
|
binary.BigEndian.PutUint64(tai64n[:], secs)
|
||||||
binary.BigEndian.PutUint32(tai64n[8:], nano)
|
binary.BigEndian.PutUint32(tai64n[8:], nano)
|
||||||
return tai64n
|
return tai64n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Now() Timestamp {
|
||||||
|
return stamp(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
func (t1 Timestamp) After(t2 Timestamp) bool {
|
func (t1 Timestamp) After(t2 Timestamp) bool {
|
||||||
return bytes.Compare(t1[:], t2[:]) > 0
|
return bytes.Compare(t1[:], t2[:]) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t Timestamp) String() string {
|
||||||
|
return time.Unix(int64(binary.BigEndian.Uint64(t[:8])-base), int64(binary.BigEndian.Uint32(t[8:12]))).String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tai64n
|
package tai64n
|
||||||
@@ -10,17 +10,31 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Testing the essential property of the timestamp
|
// Test that timestamps are monotonic as required by Wireguard and that
|
||||||
* as used by WireGuard.
|
// nanosecond-level information is whitened to prevent side channel attacks.
|
||||||
*/
|
|
||||||
func TestMonotonic(t *testing.T) {
|
func TestMonotonic(t *testing.T) {
|
||||||
old := Now()
|
startTime := time.Unix(0, 123456789) // a nontrivial bit pattern
|
||||||
for i := 0; i < 10000; i++ {
|
// Whitening should reduce timestamp granularity
|
||||||
time.Sleep(time.Nanosecond)
|
// to more than 10 but fewer than 20 milliseconds.
|
||||||
next := Now()
|
tests := []struct {
|
||||||
if !next.After(old) {
|
name string
|
||||||
t.Error("TAI64N, not monotonically increasing on nano-second scale")
|
t1, t2 time.Time
|
||||||
}
|
wantAfter bool
|
||||||
old = next
|
}{
|
||||||
|
{"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)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ netns0="wg-test-$$-0"
|
|||||||
netns1="wg-test-$$-1"
|
netns1="wg-test-$$-1"
|
||||||
netns2="wg-test-$$-2"
|
netns2="wg-test-$$-2"
|
||||||
program=$1
|
program=$1
|
||||||
export LOG_LEVEL="info"
|
export LOG_LEVEL="verbose"
|
||||||
|
|
||||||
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
|
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
|
||||||
pp() { pretty "" "$*"; "$@"; }
|
pp() { pretty "" "$*"; "$@"; }
|
||||||
|
|||||||
67
tun/alignment_windows_test.go
Normal file
67
tun/alignment_windows_test.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkAlignment(t *testing.T, name string, offset uintptr) {
|
||||||
|
t.Helper()
|
||||||
|
if offset%8 != 0 {
|
||||||
|
t.Errorf("offset of %q within struct is %d bytes, which does not align to 64-bit word boundaries (missing %d bytes). Atomic operations will crash on 32-bit systems.", name, offset, 8-(offset%8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestRateJugglerAlignment checks that atomically-accessed fields are
|
||||||
|
// aligned to 64-bit boundaries, as required by the atomic package.
|
||||||
|
//
|
||||||
|
// Unfortunately, violating this rule on 32-bit platforms results in a
|
||||||
|
// hard segfault at runtime.
|
||||||
|
func TestRateJugglerAlignment(t *testing.T) {
|
||||||
|
var r rateJuggler
|
||||||
|
|
||||||
|
typ := reflect.TypeOf(&r).Elem()
|
||||||
|
t.Logf("Peer type size: %d, with fields:", typ.Size())
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := typ.Field(i)
|
||||||
|
t.Logf("\t%30s\toffset=%3v\t(type size=%3d, align=%d)",
|
||||||
|
field.Name,
|
||||||
|
field.Offset,
|
||||||
|
field.Type.Size(),
|
||||||
|
field.Type.Align(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAlignment(t, "rateJuggler.current", unsafe.Offsetof(r.current))
|
||||||
|
checkAlignment(t, "rateJuggler.nextByteCount", unsafe.Offsetof(r.nextByteCount))
|
||||||
|
checkAlignment(t, "rateJuggler.nextStartTime", unsafe.Offsetof(r.nextStartTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestNativeTunAlignment checks that atomically-accessed fields are
|
||||||
|
// aligned to 64-bit boundaries, as required by the atomic package.
|
||||||
|
//
|
||||||
|
// Unfortunately, violating this rule on 32-bit platforms results in a
|
||||||
|
// hard segfault at runtime.
|
||||||
|
func TestNativeTunAlignment(t *testing.T) {
|
||||||
|
var tun NativeTun
|
||||||
|
|
||||||
|
typ := reflect.TypeOf(&tun).Elem()
|
||||||
|
t.Logf("Peer type size: %d, with fields:", typ.Size())
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := typ.Field(i)
|
||||||
|
t.Logf("\t%30s\toffset=%3v\t(type size=%3d, align=%d)",
|
||||||
|
field.Name,
|
||||||
|
field.Offset,
|
||||||
|
field.Type.Size(),
|
||||||
|
field.Type.Align(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAlignment(t, "NativeTun.rate", unsafe.Offsetof(tun.rate))
|
||||||
|
}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package tun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"golang.zx2c4.com/wireguard/tun"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
/* Helpers for writing unit tests
|
|
||||||
*/
|
|
||||||
|
|
||||||
type DummyTUN struct {
|
|
||||||
name string
|
|
||||||
mtu int
|
|
||||||
packets chan []byte
|
|
||||||
events chan tun.TUNEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *DummyTUN) File() *os.File {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *DummyTUN) Name() (string, error) {
|
|
||||||
return tun.name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *DummyTUN) MTU() (int, error) {
|
|
||||||
return tun.mtu, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *DummyTUN) Write(d []byte, offset int) (int, error) {
|
|
||||||
tun.packets <- d[offset:]
|
|
||||||
return len(d), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *DummyTUN) Close() error {
|
|
||||||
close(tun.events)
|
|
||||||
close(tun.packets)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *DummyTUN) Events() chan tun.TUNEvent {
|
|
||||||
return tun.events
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tun *DummyTUN) Read(d []byte, offset int) (int, error) {
|
|
||||||
t, ok := <-tun.packets
|
|
||||||
if !ok {
|
|
||||||
return 0, errors.New("device closed")
|
|
||||||
}
|
|
||||||
copy(d[offset:], t)
|
|
||||||
return len(t), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateDummyTUN(name string) (tun.TUNDevice, error) {
|
|
||||||
var dummy DummyTUN
|
|
||||||
dummy.mtu = 0
|
|
||||||
dummy.packets = make(chan []byte, 100)
|
|
||||||
dummy.events = make(chan tun.TUNEvent, 10)
|
|
||||||
return &dummy, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertNil(t *testing.T, err error) {
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertEqual(t *testing.T, a []byte, b []byte) {
|
|
||||||
if bytes.Compare(a, b) != 0 {
|
|
||||||
t.Fatal(a, "!=", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func randDevice(t *testing.T) *Device {
|
|
||||||
sk, err := newPrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
tun, _ := CreateDummyTUN("dummy")
|
|
||||||
logger := NewLogger(LogLevelError, "")
|
|
||||||
device := NewDevice(tun, logger)
|
|
||||||
device.SetPrivateKey(sk)
|
|
||||||
return device
|
|
||||||
}
|
|
||||||
54
tun/netstack/examples/http_client.go
Normal file
54
tun/netstack/examples/http_client.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
tun, tnet, err := netstack.CreateNetTUN(
|
||||||
|
[]net.IP{net.ParseIP("192.168.4.29")},
|
||||||
|
[]net.IP{net.ParseIP("8.8.8.8")},
|
||||||
|
1420)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))
|
||||||
|
dev.IpcSet(`private_key=a8dac1d8a70a751f0f699fb14ba1cff7b79cf4fbd8f09f44c6e6a90d0369604f
|
||||||
|
public_key=25123c5dcd3328ff645e4f2a3fce0d754400d3887a0cb7c56f0267e20fbf3c5b
|
||||||
|
endpoint=163.172.161.0:12912
|
||||||
|
allowed_ip=0.0.0.0/0
|
||||||
|
`)
|
||||||
|
err = dev.Up()
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DialContext: tnet.DialContext,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := client.Get("https://www.zx2c4.com/ip")
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
log.Println(string(body))
|
||||||
|
}
|
||||||
50
tun/netstack/examples/http_server.go
Normal file
50
tun/netstack/examples/http_server.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//go:build ignore
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/conn"
|
||||||
|
"golang.zx2c4.com/wireguard/device"
|
||||||
|
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
tun, tnet, err := netstack.CreateNetTUN(
|
||||||
|
[]net.IP{net.ParseIP("192.168.4.29")},
|
||||||
|
[]net.IP{net.ParseIP("8.8.8.8"), net.ParseIP("8.8.4.4")},
|
||||||
|
1420,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
|
dev := device.NewDevice(tun, conn.NewDefaultBind(), device.NewLogger(device.LogLevelVerbose, ""))
|
||||||
|
dev.IpcSet(`private_key=a8dac1d8a70a751f0f699fb14ba1cff7b79cf4fbd8f09f44c6e6a90d0369604f
|
||||||
|
public_key=25123c5dcd3328ff645e4f2a3fce0d754400d3887a0cb7c56f0267e20fbf3c5b
|
||||||
|
endpoint=163.172.161.0:12912
|
||||||
|
allowed_ip=0.0.0.0/0
|
||||||
|
persistent_keepalive_interval=25
|
||||||
|
`)
|
||||||
|
dev.Up()
|
||||||
|
listener, err := tnet.ListenTCP(&net.TCPAddr{Port: 80})
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
log.Printf("> %s - %s - %s", request.RemoteAddr, request.URL.String(), request.UserAgent())
|
||||||
|
io.WriteString(writer, "Hello from userspace TCP!")
|
||||||
|
})
|
||||||
|
err = http.Serve(listener, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
11
tun/netstack/go.mod
Normal file
11
tun/netstack/go.mod
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module golang.zx2c4.com/wireguard/tun/netstack
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6
|
||||||
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect
|
||||||
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20210424170727-c9db4b7aaa22
|
||||||
|
gvisor.dev/gvisor v0.0.0-20210506004418-fbfeba3024f0
|
||||||
|
)
|
||||||
605
tun/netstack/go.sum
Normal file
605
tun/netstack/go.sum
Normal file
@@ -0,0 +1,605 @@
|
|||||||
|
bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
|
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||||
|
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||||
|
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||||
|
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||||
|
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||||
|
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||||
|
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||||
|
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||||
|
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
|
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||||
|
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
|
||||||
|
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||||
|
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||||
|
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||||
|
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||||
|
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||||
|
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||||
|
github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg=
|
||||||
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||||
|
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/cenkalti/backoff v1.1.1-0.20190506075156-2146c9339422/go.mod h1:b6Nc7NRH5C4aCISLry0tLnTjcuTEvoiqcWDdsU0sOGM=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg=
|
||||||
|
github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM=
|
||||||
|
github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
|
||||||
|
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||||
|
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
|
||||||
|
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
|
||||||
|
github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
|
github.com/containerd/containerd v1.3.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
|
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
|
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
|
||||||
|
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||||
|
github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
|
||||||
|
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
|
||||||
|
github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
|
||||||
|
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
||||||
|
github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
|
||||||
|
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||||
|
github.com/containerd/typeurl v0.0.0-20200205145503-b45ef1f1f737/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||||
|
github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
|
github.com/docker/docker v1.4.2-0.20191028175130-9e7d5ac5ea55/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
|
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||||
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
|
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||||
|
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||||
|
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||||
|
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||||
|
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||||
|
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
|
||||||
|
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
|
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
|
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20210115211752-39141e76b647/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/subcommands v1.0.2-0.20190508160503-636abe8753b8/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||||
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||||
|
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.4-0.20190131011033-7dc38fb350b1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170308212314-bb9b5e7adda9/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||||
|
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
|
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
|
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
|
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
|
github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
|
github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
|
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
|
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
|
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
|
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||||
|
github.com/vishvananda/netlink v1.0.1-0.20190930145447-2ec5bdc52b86/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||||
|
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
|
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
|
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk=
|
||||||
|
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210309040221-94ec62e08169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
|
||||||
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
|
||||||
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
|
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20210424170727-c9db4b7aaa22 h1:ytS28bw9HtZVDRMDxviC6ryCJuccw+zXhh04u2IRWJw=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20210424170727-c9db4b7aaa22/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
|
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||||
|
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||||
|
google.golang.org/grpc v1.36.0-dev.0.20210208035533-9280052d3665/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.25.1-0.20201020201750-d3470999428b/go.mod h1:hFxJC2f0epmp1elRCiEGJTKAWbwxZ2nvqZdHl3FQXCY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20210506004418-fbfeba3024f0 h1:Ny53d6x76f7EgvYe7pi8SQcIzdRM69y1ckQ8rRtT6ww=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20210506004418-fbfeba3024f0/go.mod h1:ucHEMlckp+S/YzKEpwwAyGBhAh807Wxq/8Erc6gFxCE=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
honnef.co/go/tools v0.1.1/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
||||||
|
k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs=
|
||||||
|
k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
|
||||||
|
k8s.io/apimachinery v0.16.14-rc.0/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
|
||||||
|
k8s.io/client-go v0.16.13/go.mod h1:UKvVT4cajC2iN7DCjLgT0KVY/cbY6DGdUCyRiIfws5M=
|
||||||
|
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||||
|
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||||
|
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||||
|
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20200410163147-594e756bea31/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||||
|
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||||
|
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||||
818
tun/netstack/tun.go
Normal file
818
tun/netstack/tun.go
Normal file
@@ -0,0 +1,818 @@
|
|||||||
|
/* SPDX-License-Identifier: MIT
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package netstack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type netTun struct {
|
||||||
|
stack *stack.Stack
|
||||||
|
dispatcher stack.NetworkDispatcher
|
||||||
|
events chan tun.Event
|
||||||
|
incomingPacket chan buffer.VectorisedView
|
||||||
|
mtu int
|
||||||
|
dnsServers []net.IP
|
||||||
|
hasV4, hasV6 bool
|
||||||
|
}
|
||||||
|
type endpoint netTun
|
||||||
|
type Net netTun
|
||||||
|
|
||||||
|
func (e *endpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||||
|
e.dispatcher = dispatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endpoint) IsAttached() bool {
|
||||||
|
return e.dispatcher != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endpoint) MTU() uint32 {
|
||||||
|
mtu, err := (*netTun)(e).MTU()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return uint32(mtu)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*endpoint) Capabilities() stack.LinkEndpointCapabilities {
|
||||||
|
return stack.CapabilityNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*endpoint) MaxHeaderLength() uint16 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*endpoint) LinkAddress() tcpip.LinkAddress {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*endpoint) Wait() {}
|
||||||
|
|
||||||
|
func (e *endpoint) WritePacket(_ stack.RouteInfo, _ tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) tcpip.Error {
|
||||||
|
e.incomingPacket <- buffer.NewVectorisedView(pkt.Size(), pkt.Views())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endpoint) WritePackets(stack.RouteInfo, stack.PacketBufferList, tcpip.NetworkProtocolNumber) (int, tcpip.Error) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||||
|
return header.ARPHardwareNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *endpoint) AddHeader(tcpip.LinkAddress, tcpip.LinkAddress, tcpip.NetworkProtocolNumber, *stack.PacketBuffer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateNetTUN(localAddresses, dnsServers []net.IP, mtu int) (tun.Device, *Net, error) {
|
||||||
|
opts := stack.Options{
|
||||||
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||||
|
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol},
|
||||||
|
HandleLocal: true,
|
||||||
|
}
|
||||||
|
dev := &netTun{
|
||||||
|
stack: stack.New(opts),
|
||||||
|
events: make(chan tun.Event, 10),
|
||||||
|
incomingPacket: make(chan buffer.VectorisedView),
|
||||||
|
dnsServers: dnsServers,
|
||||||
|
mtu: mtu,
|
||||||
|
}
|
||||||
|
tcpipErr := dev.stack.CreateNIC(1, (*endpoint)(dev))
|
||||||
|
if tcpipErr != nil {
|
||||||
|
return nil, nil, fmt.Errorf("CreateNIC: %v", tcpipErr)
|
||||||
|
}
|
||||||
|
for _, ip := range localAddresses {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
tcpipErr = dev.stack.AddAddress(1, ipv4.ProtocolNumber, tcpip.Address(ip4))
|
||||||
|
if tcpipErr != nil {
|
||||||
|
return nil, nil, fmt.Errorf("AddAddress(%v): %v", ip4, tcpipErr)
|
||||||
|
}
|
||||||
|
dev.hasV4 = true
|
||||||
|
} else {
|
||||||
|
tcpipErr = dev.stack.AddAddress(1, ipv6.ProtocolNumber, tcpip.Address(ip))
|
||||||
|
if tcpipErr != nil {
|
||||||
|
return nil, nil, fmt.Errorf("AddAddress(%v): %v", ip4, tcpipErr)
|
||||||
|
}
|
||||||
|
dev.hasV6 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dev.hasV4 {
|
||||||
|
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv4EmptySubnet, NIC: 1})
|
||||||
|
}
|
||||||
|
if dev.hasV6 {
|
||||||
|
dev.stack.AddRoute(tcpip.Route{Destination: header.IPv6EmptySubnet, NIC: 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.events <- tun.EventUp
|
||||||
|
return dev, (*Net)(dev), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *netTun) Name() (string, error) {
|
||||||
|
return "go", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *netTun) File() *os.File {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *netTun) Events() chan tun.Event {
|
||||||
|
return tun.events
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *netTun) Read(buf []byte, offset int) (int, error) {
|
||||||
|
view, ok := <-tun.incomingPacket
|
||||||
|
if !ok {
|
||||||
|
return 0, os.ErrClosed
|
||||||
|
}
|
||||||
|
return view.Read(buf[offset:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *netTun) Write(buf []byte, offset int) (int, error) {
|
||||||
|
packet := buf[offset:]
|
||||||
|
if len(packet) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pkb := stack.NewPacketBuffer(stack.PacketBufferOptions{Data: buffer.NewVectorisedView(len(packet), []buffer.View{buffer.NewViewFromBytes(packet)})})
|
||||||
|
switch packet[0] >> 4 {
|
||||||
|
case 4:
|
||||||
|
tun.dispatcher.DeliverNetworkPacket("", "", ipv4.ProtocolNumber, pkb)
|
||||||
|
case 6:
|
||||||
|
tun.dispatcher.DeliverNetworkPacket("", "", ipv6.ProtocolNumber, pkb)
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *netTun) Flush() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *netTun) Close() error {
|
||||||
|
tun.stack.RemoveNIC(1)
|
||||||
|
|
||||||
|
if tun.events != nil {
|
||||||
|
close(tun.events)
|
||||||
|
}
|
||||||
|
if tun.incomingPacket != nil {
|
||||||
|
close(tun.incomingPacket)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *netTun) MTU() (int, error) {
|
||||||
|
return tun.mtu, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToFullAddr(ip net.IP, port int) (tcpip.FullAddress, tcpip.NetworkProtocolNumber) {
|
||||||
|
if ip4 := ip.To4(); ip4 != nil {
|
||||||
|
return tcpip.FullAddress{
|
||||||
|
NIC: 1,
|
||||||
|
Addr: tcpip.Address(ip4),
|
||||||
|
Port: uint16(port),
|
||||||
|
}, ipv4.ProtocolNumber
|
||||||
|
} else {
|
||||||
|
return tcpip.FullAddress{
|
||||||
|
NIC: 1,
|
||||||
|
Addr: tcpip.Address(ip),
|
||||||
|
Port: uint16(port),
|
||||||
|
}, ipv6.ProtocolNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (net *Net) DialContextTCP(ctx context.Context, addr *net.TCPAddr) (*gonet.TCPConn, error) {
|
||||||
|
if addr == nil {
|
||||||
|
panic("todo: deal with auto addr semantics for nil addr")
|
||||||
|
}
|
||||||
|
fa, pn := convertToFullAddr(addr.IP, addr.Port)
|
||||||
|
return gonet.DialContextTCP(ctx, net.stack, fa, pn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (net *Net) DialTCP(addr *net.TCPAddr) (*gonet.TCPConn, error) {
|
||||||
|
if addr == nil {
|
||||||
|
panic("todo: deal with auto addr semantics for nil addr")
|
||||||
|
}
|
||||||
|
fa, pn := convertToFullAddr(addr.IP, addr.Port)
|
||||||
|
return gonet.DialTCP(net.stack, fa, pn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (net *Net) ListenTCP(addr *net.TCPAddr) (*gonet.TCPListener, error) {
|
||||||
|
if addr == nil {
|
||||||
|
panic("todo: deal with auto addr semantics for nil addr")
|
||||||
|
}
|
||||||
|
fa, pn := convertToFullAddr(addr.IP, addr.Port)
|
||||||
|
return gonet.ListenTCP(net.stack, fa, pn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (net *Net) DialUDP(laddr, raddr *net.UDPAddr) (*gonet.UDPConn, error) {
|
||||||
|
var lfa, rfa *tcpip.FullAddress
|
||||||
|
var pn tcpip.NetworkProtocolNumber
|
||||||
|
if laddr != nil {
|
||||||
|
var addr tcpip.FullAddress
|
||||||
|
addr, pn = convertToFullAddr(laddr.IP, laddr.Port)
|
||||||
|
lfa = &addr
|
||||||
|
}
|
||||||
|
if raddr != nil {
|
||||||
|
var addr tcpip.FullAddress
|
||||||
|
addr, pn = convertToFullAddr(raddr.IP, raddr.Port)
|
||||||
|
rfa = &addr
|
||||||
|
}
|
||||||
|
return gonet.DialUDP(net.stack, lfa, rfa, pn)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errNoSuchHost = errors.New("no such host")
|
||||||
|
errLameReferral = errors.New("lame referral")
|
||||||
|
errCannotUnmarshalDNSMessage = errors.New("cannot unmarshal DNS message")
|
||||||
|
errCannotMarshalDNSMessage = errors.New("cannot marshal DNS message")
|
||||||
|
errServerMisbehaving = errors.New("server misbehaving")
|
||||||
|
errInvalidDNSResponse = errors.New("invalid DNS response")
|
||||||
|
errNoAnswerFromDNSServer = errors.New("no answer from DNS server")
|
||||||
|
errServerTemporarilyMisbehaving = errors.New("server misbehaving")
|
||||||
|
errCanceled = errors.New("operation was canceled")
|
||||||
|
errTimeout = errors.New("i/o timeout")
|
||||||
|
errNumericPort = errors.New("port must be numeric")
|
||||||
|
errNoSuitableAddress = errors.New("no suitable address found")
|
||||||
|
errMissingAddress = errors.New("missing address")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (net *Net) LookupHost(host string) (addrs []string, err error) {
|
||||||
|
return net.LookupContextHost(context.Background(), host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDomainName(s string) bool {
|
||||||
|
l := len(s)
|
||||||
|
if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
last := byte('.')
|
||||||
|
nonNumeric := false
|
||||||
|
partlen := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_':
|
||||||
|
nonNumeric = true
|
||||||
|
partlen++
|
||||||
|
case '0' <= c && c <= '9':
|
||||||
|
partlen++
|
||||||
|
case c == '-':
|
||||||
|
if last == '.' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
partlen++
|
||||||
|
nonNumeric = true
|
||||||
|
case c == '.':
|
||||||
|
if last == '.' || last == '-' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if partlen > 63 || partlen == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
partlen = 0
|
||||||
|
}
|
||||||
|
last = c
|
||||||
|
}
|
||||||
|
if last == '-' || partlen > 63 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return nonNumeric
|
||||||
|
}
|
||||||
|
|
||||||
|
func randU16() uint16 {
|
||||||
|
var b [2]byte
|
||||||
|
_, err := rand.Read(b[:])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return binary.LittleEndian.Uint16(b[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRequest(q dnsmessage.Question) (id uint16, udpReq, tcpReq []byte, err error) {
|
||||||
|
id = randU16()
|
||||||
|
b := dnsmessage.NewBuilder(make([]byte, 2, 514), dnsmessage.Header{ID: id, RecursionDesired: true})
|
||||||
|
b.EnableCompression()
|
||||||
|
if err := b.StartQuestions(); err != nil {
|
||||||
|
return 0, nil, nil, err
|
||||||
|
}
|
||||||
|
if err := b.Question(q); err != nil {
|
||||||
|
return 0, nil, nil, err
|
||||||
|
}
|
||||||
|
tcpReq, err = b.Finish()
|
||||||
|
udpReq = tcpReq[2:]
|
||||||
|
l := len(tcpReq) - 2
|
||||||
|
tcpReq[0] = byte(l >> 8)
|
||||||
|
tcpReq[1] = byte(l)
|
||||||
|
return id, udpReq, tcpReq, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func equalASCIIName(x, y dnsmessage.Name) bool {
|
||||||
|
if x.Length != y.Length {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < int(x.Length); i++ {
|
||||||
|
a := x.Data[i]
|
||||||
|
b := y.Data[i]
|
||||||
|
if 'A' <= a && a <= 'Z' {
|
||||||
|
a += 0x20
|
||||||
|
}
|
||||||
|
if 'A' <= b && b <= 'Z' {
|
||||||
|
b += 0x20
|
||||||
|
}
|
||||||
|
if a != b {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkResponse(reqID uint16, reqQues dnsmessage.Question, respHdr dnsmessage.Header, respQues dnsmessage.Question) bool {
|
||||||
|
if !respHdr.Response {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if reqID != respHdr.ID {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if reqQues.Type != respQues.Type || reqQues.Class != respQues.Class || !equalASCIIName(reqQues.Name, respQues.Name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsPacketRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) {
|
||||||
|
if _, err := c.Write(b); err != nil {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||||
|
}
|
||||||
|
b = make([]byte, 512)
|
||||||
|
for {
|
||||||
|
n, err := c.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||||
|
}
|
||||||
|
var p dnsmessage.Parser
|
||||||
|
h, err := p.Start(b[:n])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
q, err := p.Question()
|
||||||
|
if err != nil || !checkResponse(id, query, h, q) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return p, h, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsStreamRoundTrip(c net.Conn, id uint16, query dnsmessage.Question, b []byte) (dnsmessage.Parser, dnsmessage.Header, error) {
|
||||||
|
if _, err := c.Write(b); err != nil {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||||
|
}
|
||||||
|
b = make([]byte, 1280)
|
||||||
|
if _, err := io.ReadFull(c, b[:2]); err != nil {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||||
|
}
|
||||||
|
l := int(b[0])<<8 | int(b[1])
|
||||||
|
if l > len(b) {
|
||||||
|
b = make([]byte, l)
|
||||||
|
}
|
||||||
|
n, err := io.ReadFull(c, b[:l])
|
||||||
|
if err != nil {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||||
|
}
|
||||||
|
var p dnsmessage.Parser
|
||||||
|
h, err := p.Start(b[:n])
|
||||||
|
if err != nil {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage
|
||||||
|
}
|
||||||
|
q, err := p.Question()
|
||||||
|
if err != nil {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotUnmarshalDNSMessage
|
||||||
|
}
|
||||||
|
if !checkResponse(id, query, h, q) {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
|
||||||
|
}
|
||||||
|
return p, h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tnet *Net) exchange(ctx context.Context, server net.IP, q dnsmessage.Question, timeout time.Duration) (dnsmessage.Parser, dnsmessage.Header, error) {
|
||||||
|
q.Class = dnsmessage.ClassINET
|
||||||
|
id, udpReq, tcpReq, err := newRequest(q)
|
||||||
|
if err != nil {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, errCannotMarshalDNSMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, useUDP := range []bool{true, false} {
|
||||||
|
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var c net.Conn
|
||||||
|
var err error
|
||||||
|
if useUDP {
|
||||||
|
c, err = tnet.DialUDP(nil, &net.UDPAddr{IP: server, Port: 53})
|
||||||
|
} else {
|
||||||
|
c, err = tnet.DialContextTCP(ctx, &net.TCPAddr{IP: server, Port: 53})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||||
|
}
|
||||||
|
if d, ok := ctx.Deadline(); ok && !d.IsZero() {
|
||||||
|
c.SetDeadline(d)
|
||||||
|
}
|
||||||
|
var p dnsmessage.Parser
|
||||||
|
var h dnsmessage.Header
|
||||||
|
if useUDP {
|
||||||
|
p, h, err = dnsPacketRoundTrip(c, id, q, udpReq)
|
||||||
|
} else {
|
||||||
|
p, h, err = dnsStreamRoundTrip(c, id, q, tcpReq)
|
||||||
|
}
|
||||||
|
c.Close()
|
||||||
|
if err != nil {
|
||||||
|
if err == context.Canceled {
|
||||||
|
err = errCanceled
|
||||||
|
} else if err == context.DeadlineExceeded {
|
||||||
|
err = errTimeout
|
||||||
|
}
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, err
|
||||||
|
}
|
||||||
|
if err := p.SkipQuestion(); err != dnsmessage.ErrSectionDone {
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
|
||||||
|
}
|
||||||
|
if h.Truncated {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return p, h, nil
|
||||||
|
}
|
||||||
|
return dnsmessage.Parser{}, dnsmessage.Header{}, errNoAnswerFromDNSServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error {
|
||||||
|
if h.RCode == dnsmessage.RCodeNameError {
|
||||||
|
return errNoSuchHost
|
||||||
|
}
|
||||||
|
_, err := p.AnswerHeader()
|
||||||
|
if err != nil && err != dnsmessage.ErrSectionDone {
|
||||||
|
return errCannotUnmarshalDNSMessage
|
||||||
|
}
|
||||||
|
if h.RCode == dnsmessage.RCodeSuccess && !h.Authoritative && !h.RecursionAvailable && err == dnsmessage.ErrSectionDone {
|
||||||
|
return errLameReferral
|
||||||
|
}
|
||||||
|
if h.RCode != dnsmessage.RCodeSuccess && h.RCode != dnsmessage.RCodeNameError {
|
||||||
|
if h.RCode == dnsmessage.RCodeServerFailure {
|
||||||
|
return errServerTemporarilyMisbehaving
|
||||||
|
}
|
||||||
|
return errServerMisbehaving
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipToAnswer(p *dnsmessage.Parser, qtype dnsmessage.Type) error {
|
||||||
|
for {
|
||||||
|
h, err := p.AnswerHeader()
|
||||||
|
if err == dnsmessage.ErrSectionDone {
|
||||||
|
return errNoSuchHost
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errCannotUnmarshalDNSMessage
|
||||||
|
}
|
||||||
|
if h.Type == qtype {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := p.SkipAnswer(); err != nil {
|
||||||
|
return errCannotUnmarshalDNSMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tnet *Net) tryOneName(ctx context.Context, name string, qtype dnsmessage.Type) (dnsmessage.Parser, string, error) {
|
||||||
|
var lastErr error
|
||||||
|
|
||||||
|
n, err := dnsmessage.NewName(name)
|
||||||
|
if err != nil {
|
||||||
|
return dnsmessage.Parser{}, "", errCannotMarshalDNSMessage
|
||||||
|
}
|
||||||
|
q := dnsmessage.Question{
|
||||||
|
Name: n,
|
||||||
|
Type: qtype,
|
||||||
|
Class: dnsmessage.ClassINET,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
for _, server := range tnet.dnsServers {
|
||||||
|
p, h, err := tnet.exchange(ctx, server, q, time.Second*5)
|
||||||
|
if err != nil {
|
||||||
|
dnsErr := &net.DNSError{
|
||||||
|
Err: err.Error(),
|
||||||
|
Name: name,
|
||||||
|
Server: server.String(),
|
||||||
|
}
|
||||||
|
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||||
|
dnsErr.IsTimeout = true
|
||||||
|
}
|
||||||
|
if _, ok := err.(*net.OpError); ok {
|
||||||
|
dnsErr.IsTemporary = true
|
||||||
|
}
|
||||||
|
lastErr = dnsErr
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkHeader(&p, h); err != nil {
|
||||||
|
dnsErr := &net.DNSError{
|
||||||
|
Err: err.Error(),
|
||||||
|
Name: name,
|
||||||
|
Server: server.String(),
|
||||||
|
}
|
||||||
|
if err == errServerTemporarilyMisbehaving {
|
||||||
|
dnsErr.IsTemporary = true
|
||||||
|
}
|
||||||
|
if err == errNoSuchHost {
|
||||||
|
dnsErr.IsNotFound = true
|
||||||
|
return p, server.String(), dnsErr
|
||||||
|
}
|
||||||
|
lastErr = dnsErr
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = skipToAnswer(&p, qtype)
|
||||||
|
if err == nil {
|
||||||
|
return p, server.String(), nil
|
||||||
|
}
|
||||||
|
lastErr = &net.DNSError{
|
||||||
|
Err: err.Error(),
|
||||||
|
Name: name,
|
||||||
|
Server: server.String(),
|
||||||
|
}
|
||||||
|
if err == errNoSuchHost {
|
||||||
|
lastErr.(*net.DNSError).IsNotFound = true
|
||||||
|
return p, server.String(), lastErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dnsmessage.Parser{}, "", lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tnet *Net) LookupContextHost(ctx context.Context, host string) ([]string, error) {
|
||||||
|
if host == "" || (!tnet.hasV6 && !tnet.hasV4) {
|
||||||
|
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
|
||||||
|
}
|
||||||
|
zlen := len(host)
|
||||||
|
if strings.IndexByte(host, ':') != -1 {
|
||||||
|
if zidx := strings.LastIndexByte(host, '%'); zidx != -1 {
|
||||||
|
zlen = zidx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ip := net.ParseIP(host[:zlen]); ip != nil {
|
||||||
|
return []string{host[:zlen]}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isDomainName(host) {
|
||||||
|
return nil, &net.DNSError{Err: errNoSuchHost.Error(), Name: host, IsNotFound: true}
|
||||||
|
}
|
||||||
|
type result struct {
|
||||||
|
p dnsmessage.Parser
|
||||||
|
server string
|
||||||
|
error
|
||||||
|
}
|
||||||
|
var addrsV4, addrsV6 []net.IP
|
||||||
|
lanes := 0
|
||||||
|
if tnet.hasV4 {
|
||||||
|
lanes++
|
||||||
|
}
|
||||||
|
if tnet.hasV6 {
|
||||||
|
lanes++
|
||||||
|
}
|
||||||
|
lane := make(chan result, lanes)
|
||||||
|
var lastErr error
|
||||||
|
if tnet.hasV4 {
|
||||||
|
go func() {
|
||||||
|
p, server, err := tnet.tryOneName(ctx, host+".", dnsmessage.TypeA)
|
||||||
|
lane <- result{p, server, err}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if tnet.hasV6 {
|
||||||
|
go func() {
|
||||||
|
p, server, err := tnet.tryOneName(ctx, host+".", dnsmessage.TypeAAAA)
|
||||||
|
lane <- result{p, server, err}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
for l := 0; l < lanes; l++ {
|
||||||
|
result := <-lane
|
||||||
|
if result.error != nil {
|
||||||
|
if lastErr == nil {
|
||||||
|
lastErr = result.error
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
h, err := result.p.AnswerHeader()
|
||||||
|
if err != nil && err != dnsmessage.ErrSectionDone {
|
||||||
|
lastErr = &net.DNSError{
|
||||||
|
Err: errCannotMarshalDNSMessage.Error(),
|
||||||
|
Name: host,
|
||||||
|
Server: result.server,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch h.Type {
|
||||||
|
case dnsmessage.TypeA:
|
||||||
|
a, err := result.p.AResource()
|
||||||
|
if err != nil {
|
||||||
|
lastErr = &net.DNSError{
|
||||||
|
Err: errCannotMarshalDNSMessage.Error(),
|
||||||
|
Name: host,
|
||||||
|
Server: result.server,
|
||||||
|
}
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
addrsV4 = append(addrsV4, net.IP(a.A[:]))
|
||||||
|
|
||||||
|
case dnsmessage.TypeAAAA:
|
||||||
|
aaaa, err := result.p.AAAAResource()
|
||||||
|
if err != nil {
|
||||||
|
lastErr = &net.DNSError{
|
||||||
|
Err: errCannotMarshalDNSMessage.Error(),
|
||||||
|
Name: host,
|
||||||
|
Server: result.server,
|
||||||
|
}
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
addrsV6 = append(addrsV6, net.IP(aaaa.AAAA[:]))
|
||||||
|
|
||||||
|
default:
|
||||||
|
if err := result.p.SkipAnswer(); err != nil {
|
||||||
|
lastErr = &net.DNSError{
|
||||||
|
Err: errCannotMarshalDNSMessage.Error(),
|
||||||
|
Name: host,
|
||||||
|
Server: result.server,
|
||||||
|
}
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We don't do RFC6724. Instead just put V6 addresess first if an IPv6 address is enabled
|
||||||
|
var addrs []net.IP
|
||||||
|
if tnet.hasV6 {
|
||||||
|
addrs = append(addrsV6, addrsV4...)
|
||||||
|
} else {
|
||||||
|
addrs = append(addrsV4, addrsV6...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrs) == 0 && lastErr != nil {
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
saddrs := make([]string, 0, len(addrs))
|
||||||
|
for _, ip := range addrs {
|
||||||
|
saddrs = append(saddrs, ip.String())
|
||||||
|
}
|
||||||
|
return saddrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) {
|
||||||
|
if deadline.IsZero() {
|
||||||
|
return deadline, nil
|
||||||
|
}
|
||||||
|
timeRemaining := deadline.Sub(now)
|
||||||
|
if timeRemaining <= 0 {
|
||||||
|
return time.Time{}, errTimeout
|
||||||
|
}
|
||||||
|
timeout := timeRemaining / time.Duration(addrsRemaining)
|
||||||
|
const saneMinimum = 2 * time.Second
|
||||||
|
if timeout < saneMinimum {
|
||||||
|
if timeRemaining < saneMinimum {
|
||||||
|
timeout = timeRemaining
|
||||||
|
} else {
|
||||||
|
timeout = saneMinimum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return now.Add(timeout), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tnet *Net) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
if ctx == nil {
|
||||||
|
panic("nil context")
|
||||||
|
}
|
||||||
|
var acceptV4, acceptV6, useUDP bool
|
||||||
|
if len(network) == 3 {
|
||||||
|
acceptV4 = true
|
||||||
|
acceptV6 = true
|
||||||
|
} else if len(network) == 4 {
|
||||||
|
acceptV4 = network[3] == '4'
|
||||||
|
acceptV6 = network[3] == '6'
|
||||||
|
}
|
||||||
|
if !acceptV4 && !acceptV6 {
|
||||||
|
return nil, &net.OpError{Op: "dial", Err: net.UnknownNetworkError(network)}
|
||||||
|
}
|
||||||
|
if network[:3] == "udp" {
|
||||||
|
useUDP = true
|
||||||
|
} else if network[:3] != "tcp" {
|
||||||
|
return nil, &net.OpError{Op: "dial", Err: net.UnknownNetworkError(network)}
|
||||||
|
}
|
||||||
|
host, sport, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &net.OpError{Op: "dial", Err: err}
|
||||||
|
}
|
||||||
|
port, err := strconv.Atoi(sport)
|
||||||
|
if err != nil || port < 0 || port > 65535 {
|
||||||
|
return nil, &net.OpError{Op: "dial", Err: errNumericPort}
|
||||||
|
}
|
||||||
|
allAddr, err := tnet.LookupContextHost(ctx, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &net.OpError{Op: "dial", Err: err}
|
||||||
|
}
|
||||||
|
var addrs []net.IP
|
||||||
|
for _, addr := range allAddr {
|
||||||
|
if strings.IndexByte(addr, ':') != -1 && acceptV6 {
|
||||||
|
addrs = append(addrs, net.ParseIP(addr))
|
||||||
|
} else if strings.IndexByte(addr, '.') != -1 && acceptV4 {
|
||||||
|
addrs = append(addrs, net.ParseIP(addr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(addrs) == 0 && len(allAddr) != 0 {
|
||||||
|
return nil, &net.OpError{Op: "dial", Err: errNoSuitableAddress}
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstErr error
|
||||||
|
for i, addr := range addrs {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err := ctx.Err()
|
||||||
|
if err == context.Canceled {
|
||||||
|
err = errCanceled
|
||||||
|
} else if err == context.DeadlineExceeded {
|
||||||
|
err = errTimeout
|
||||||
|
}
|
||||||
|
return nil, &net.OpError{Op: "dial", Err: err}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
dialCtx := ctx
|
||||||
|
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
||||||
|
partialDeadline, err := partialDeadline(time.Now(), deadline, len(addrs)-i)
|
||||||
|
if err != nil {
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = &net.OpError{Op: "dial", Err: err}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if partialDeadline.Before(deadline) {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
dialCtx, cancel = context.WithDeadline(ctx, partialDeadline)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var c net.Conn
|
||||||
|
if useUDP {
|
||||||
|
c, err = tnet.DialUDP(nil, &net.UDPAddr{IP: addr, Port: port})
|
||||||
|
} else {
|
||||||
|
c, err = tnet.DialContextTCP(dialCtx, &net.TCPAddr{IP: addr, Port: port})
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if firstErr == nil {
|
||||||
|
firstErr = &net.OpError{Op: "dial", Err: errMissingAddress}
|
||||||
|
}
|
||||||
|
return nil, firstErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tnet *Net) Dial(network, address string) (net.Conn, error) {
|
||||||
|
return tnet.DialContext(context.Background(), network, address)
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
// +build !windows
|
//go:build darwin || freebsd
|
||||||
|
|
||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|||||||
14
tun/tun.go
14
tun/tun.go
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
@@ -9,21 +9,21 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TUNEvent int
|
type Event int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TUNEventUp = 1 << iota
|
EventUp = 1 << iota
|
||||||
TUNEventDown
|
EventDown
|
||||||
TUNEventMTUUpdate
|
EventMTUUpdate
|
||||||
)
|
)
|
||||||
|
|
||||||
type TUNDevice interface {
|
type Device interface {
|
||||||
File() *os.File // returns the file descriptor of the device
|
File() *os.File // returns the file descriptor of the device
|
||||||
Read([]byte, int) (int, error) // read a packet from the device (without any additional headers)
|
Read([]byte, int) (int, error) // read a packet from the device (without any additional headers)
|
||||||
Write([]byte, int) (int, error) // writes a packet to the device (without any additional headers)
|
Write([]byte, int) (int, error) // writes a packet to the device (without any additional headers)
|
||||||
Flush() error // flush all previous writes to the device
|
Flush() error // flush all previous writes to the device
|
||||||
MTU() (int, error) // returns the MTU of the device
|
MTU() (int, error) // returns the MTU of the device
|
||||||
Name() (string, error) // fetches and returns the current name
|
Name() (string, error) // fetches and returns the current name
|
||||||
Events() chan TUNEvent // returns a constant channel of events related to the device
|
Events() chan Event // returns a constant channel of events related to the device
|
||||||
Close() error // stops the device and closes the event channel
|
Close() error // stops the device and closes the event channel
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,46 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/net/ipv6"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const utunControlName = "com.apple.net.utun_control"
|
const utunControlName = "com.apple.net.utun_control"
|
||||||
|
|
||||||
// _CTLIOCGINFO value derived from /usr/include/sys/{kern_control,ioccom}.h
|
|
||||||
const _CTLIOCGINFO = (0x40000000 | 0x80000000) | ((100 & 0x1fff) << 16) | uint32(byte('N'))<<8 | 3
|
|
||||||
|
|
||||||
// sockaddr_ctl specifeid in /usr/include/sys/kern_control.h
|
|
||||||
type sockaddrCtl struct {
|
|
||||||
scLen uint8
|
|
||||||
scFamily uint8
|
|
||||||
ssSysaddr uint16
|
|
||||||
scID uint32
|
|
||||||
scUnit uint32
|
|
||||||
scReserved [5]uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
type NativeTun struct {
|
type NativeTun struct {
|
||||||
name string
|
name string
|
||||||
tunFile *os.File
|
tunFile *os.File
|
||||||
events chan TUNEvent
|
events chan Event
|
||||||
errors chan error
|
errors chan error
|
||||||
routeSocket int
|
routeSocket int
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
var sockaddrCtlSize uintptr = 32
|
func retryInterfaceByIndex(index int) (iface *net.Interface, err error) {
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
iface, err = net.InterfaceByIndex(index)
|
||||||
|
if err != nil && errors.Is(err, syscall.ENOMEM) {
|
||||||
|
time.Sleep(time.Duration(i) * time.Second / 3)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return iface, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
||||||
var (
|
var (
|
||||||
@@ -73,7 +74,7 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
iface, err := net.InterfaceByIndex(ifindex)
|
iface, err := retryInterfaceByIndex(ifindex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.errors <- err
|
tun.errors <- err
|
||||||
return
|
return
|
||||||
@@ -82,22 +83,22 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
|||||||
// Up / Down event
|
// Up / Down event
|
||||||
up := (iface.Flags & net.FlagUp) != 0
|
up := (iface.Flags & net.FlagUp) != 0
|
||||||
if up != statusUp && up {
|
if up != statusUp && up {
|
||||||
tun.events <- TUNEventUp
|
tun.events <- EventUp
|
||||||
}
|
}
|
||||||
if up != statusUp && !up {
|
if up != statusUp && !up {
|
||||||
tun.events <- TUNEventDown
|
tun.events <- EventDown
|
||||||
}
|
}
|
||||||
statusUp = up
|
statusUp = up
|
||||||
|
|
||||||
// MTU changes
|
// MTU changes
|
||||||
if iface.MTU != statusMTU {
|
if iface.MTU != statusMTU {
|
||||||
tun.events <- TUNEventMTUUpdate
|
tun.events <- EventMTUUpdate
|
||||||
}
|
}
|
||||||
statusMTU = iface.MTU
|
statusMTU = iface.MTU
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
func CreateTUN(name string, mtu int) (Device, error) {
|
||||||
ifIndex := -1
|
ifIndex := -1
|
||||||
if name != "utun" {
|
if name != "utun" {
|
||||||
_, err := fmt.Sscanf(name, "utun%d", &ifIndex)
|
_, err := fmt.Sscanf(name, "utun%d", &ifIndex)
|
||||||
@@ -107,52 +108,32 @@ func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2)
|
fd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, 2)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ctlInfo = &struct {
|
ctlInfo := &unix.CtlInfo{}
|
||||||
ctlID uint32
|
copy(ctlInfo.Name[:], []byte(utunControlName))
|
||||||
ctlName [96]byte
|
err = unix.IoctlCtlInfo(fd, ctlInfo)
|
||||||
}{}
|
|
||||||
|
|
||||||
copy(ctlInfo.ctlName[:], []byte(utunControlName))
|
|
||||||
|
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(_CTLIOCGINFO),
|
|
||||||
uintptr(unsafe.Pointer(ctlInfo)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return nil, fmt.Errorf("_CTLIOCGINFO: %v", errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
sc := sockaddrCtl{
|
|
||||||
scLen: uint8(sockaddrCtlSize),
|
|
||||||
scFamily: unix.AF_SYSTEM,
|
|
||||||
ssSysaddr: 2,
|
|
||||||
scID: ctlInfo.ctlID,
|
|
||||||
scUnit: uint32(ifIndex) + 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
scPointer := unsafe.Pointer(&sc)
|
|
||||||
|
|
||||||
_, _, errno = unix.RawSyscall(
|
|
||||||
unix.SYS_CONNECT,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(scPointer),
|
|
||||||
uintptr(sockaddrCtlSize),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return nil, fmt.Errorf("SYS_CONNECT: %v", errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = syscall.SetNonblock(fd, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
|
return nil, fmt.Errorf("IoctlGetCtlInfo: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sc := &unix.SockaddrCtl{
|
||||||
|
ID: ctlInfo.Id,
|
||||||
|
Unit: uint32(ifIndex) + 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unix.Connect(fd, sc)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unix.SetNonblock(fd, true)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tun, err := CreateTUNFromFile(os.NewFile(uintptr(fd), ""), mtu)
|
tun, err := CreateTUNFromFile(os.NewFile(uintptr(fd), ""), mtu)
|
||||||
@@ -160,17 +141,17 @@ func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
|||||||
if err == nil && name == "utun" {
|
if err == nil && name == "utun" {
|
||||||
fname := os.Getenv("WG_TUN_NAME_FILE")
|
fname := os.Getenv("WG_TUN_NAME_FILE")
|
||||||
if fname != "" {
|
if fname != "" {
|
||||||
ioutil.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0400)
|
os.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0400)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tun, err
|
return tun, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTUNFromFile(file *os.File, mtu int) (TUNDevice, error) {
|
func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
|
||||||
tun := &NativeTun{
|
tun := &NativeTun{
|
||||||
tunFile: file,
|
tunFile: file,
|
||||||
events: make(chan TUNEvent, 10),
|
events: make(chan Event, 10),
|
||||||
errors: make(chan error, 5),
|
errors: make(chan error, 5),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,27 +193,19 @@ func CreateTUNFromFile(file *os.File, mtu int) (TUNDevice, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Name() (string, error) {
|
func (tun *NativeTun) Name() (string, error) {
|
||||||
var ifName struct {
|
var err error
|
||||||
name [16]byte
|
|
||||||
}
|
|
||||||
ifNameSize := uintptr(16)
|
|
||||||
|
|
||||||
var errno syscall.Errno
|
|
||||||
tun.operateOnFd(func(fd uintptr) {
|
tun.operateOnFd(func(fd uintptr) {
|
||||||
_, _, errno = unix.Syscall6(
|
tun.name, err = unix.GetsockoptString(
|
||||||
unix.SYS_GETSOCKOPT,
|
int(fd),
|
||||||
fd,
|
|
||||||
2, /* #define SYSPROTO_CONTROL 2 */
|
2, /* #define SYSPROTO_CONTROL 2 */
|
||||||
2, /* #define UTUN_OPT_IFNAME 2 */
|
2, /* #define UTUN_OPT_IFNAME 2 */
|
||||||
uintptr(unsafe.Pointer(&ifName)),
|
)
|
||||||
uintptr(unsafe.Pointer(&ifNameSize)), 0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if errno != 0 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("SYS_GETSOCKOPT: %v", errno)
|
return "", fmt.Errorf("GetSockoptString: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tun.name = string(ifName.name[:ifNameSize-1])
|
|
||||||
return tun.name, nil
|
return tun.name, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +213,7 @@ func (tun *NativeTun) File() *os.File {
|
|||||||
return tun.tunFile
|
return tun.tunFile
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Events() chan TUNEvent {
|
func (tun *NativeTun) Events() chan Event {
|
||||||
return tun.events
|
return tun.events
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,20 +255,21 @@ func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Flush() error {
|
func (tun *NativeTun) Flush() error {
|
||||||
//TODO: can flushing be implemented by buffering and using sendmmsg?
|
// TODO: can flushing be implemented by buffering and using sendmmsg?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Close() error {
|
func (tun *NativeTun) Close() error {
|
||||||
var err2 error
|
var err1, err2 error
|
||||||
err1 := tun.tunFile.Close()
|
tun.closeOnce.Do(func() {
|
||||||
if tun.routeSocket != -1 {
|
err1 = tun.tunFile.Close()
|
||||||
unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
|
if tun.routeSocket != -1 {
|
||||||
err2 = unix.Close(tun.routeSocket)
|
unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
|
||||||
tun.routeSocket = -1
|
err2 = unix.Close(tun.routeSocket)
|
||||||
} else if tun.events != nil {
|
} else if tun.events != nil {
|
||||||
close(tun.events)
|
close(tun.events)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
@@ -303,11 +277,6 @@ func (tun *NativeTun) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) setMTU(n int) error {
|
func (tun *NativeTun) setMTU(n int) error {
|
||||||
|
|
||||||
// open datagram socket
|
|
||||||
|
|
||||||
var fd int
|
|
||||||
|
|
||||||
fd, err := unix.Socket(
|
fd, err := unix.Socket(
|
||||||
unix.AF_INET,
|
unix.AF_INET,
|
||||||
unix.SOCK_DGRAM,
|
unix.SOCK_DGRAM,
|
||||||
@@ -320,29 +289,18 @@ func (tun *NativeTun) setMTU(n int) error {
|
|||||||
|
|
||||||
defer unix.Close(fd)
|
defer unix.Close(fd)
|
||||||
|
|
||||||
// do ioctl call
|
var ifr unix.IfreqMTU
|
||||||
|
copy(ifr.Name[:], tun.name)
|
||||||
var ifr [32]byte
|
ifr.MTU = int32(n)
|
||||||
copy(ifr[:], tun.name)
|
err = unix.IoctlSetIfreqMTU(fd, &ifr)
|
||||||
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n)
|
if err != nil {
|
||||||
_, _, errno := unix.Syscall(
|
return fmt.Errorf("failed to set MTU on %s: %w", tun.name, err)
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(unix.SIOCSIFMTU),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return fmt.Errorf("failed to set MTU on %s", tun.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) MTU() (int, error) {
|
func (tun *NativeTun) MTU() (int, error) {
|
||||||
|
|
||||||
// open datagram socket
|
|
||||||
|
|
||||||
fd, err := unix.Socket(
|
fd, err := unix.Socket(
|
||||||
unix.AF_INET,
|
unix.AF_INET,
|
||||||
unix.SOCK_DGRAM,
|
unix.SOCK_DGRAM,
|
||||||
@@ -355,19 +313,10 @@ func (tun *NativeTun) MTU() (int, error) {
|
|||||||
|
|
||||||
defer unix.Close(fd)
|
defer unix.Close(fd)
|
||||||
|
|
||||||
// do ioctl call
|
ifr, err := unix.IoctlGetIfreqMTU(fd, tun.name)
|
||||||
|
if err != nil {
|
||||||
var ifr [64]byte
|
return 0, fmt.Errorf("failed to get MTU on %s: %w", tun.name, err)
|
||||||
copy(ifr[:], tun.name)
|
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(unix.SIOCGIFMTU),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])),
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
return 0, fmt.Errorf("failed to get MTU on %s", tun.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return int(*(*int32)(unsafe.Pointer(&ifr[16]))), nil
|
return int(ifr.MTU), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +1,79 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/net/ipv6"
|
"io"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// _TUNSIFHEAD, value derived from sys/net/{if_tun,ioccom}.h
|
const (
|
||||||
// const _TUNSIFHEAD = ((0x80000000) | (((4) & ((1 << 13) - 1) ) << 16) | (uint32(byte('t')) << 8) | (96))
|
_TUNSIFHEAD = 0x80047460
|
||||||
const _TUNSIFHEAD = 0x80047460
|
_TUNSIFMODE = 0x8004745e
|
||||||
const _TUNSIFMODE = 0x8004745e
|
_TUNGIFNAME = 0x4020745d
|
||||||
const _TUNSIFPID = 0x2000745f
|
_TUNSIFPID = 0x2000745f
|
||||||
|
|
||||||
// Iface status string max len
|
_SIOCGIFINFO_IN6 = 0xc048696c
|
||||||
const _IFSTATMAX = 800
|
_SIOCSIFINFO_IN6 = 0xc048696d
|
||||||
|
_ND6_IFF_AUTO_LINKLOCAL = 0x20
|
||||||
|
_ND6_IFF_NO_DAD = 0x100
|
||||||
|
)
|
||||||
|
|
||||||
const SIZEOF_UINTPTR = 4 << (^uintptr(0) >> 32 & 1)
|
// Iface requests with just the name
|
||||||
|
type ifreqName struct {
|
||||||
|
Name [unix.IFNAMSIZ]byte
|
||||||
|
_ [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
// structure for iface requests with a pointer
|
// Iface requests with a pointer
|
||||||
type ifreq_ptr struct {
|
type ifreqPtr struct {
|
||||||
Name [unix.IFNAMSIZ]byte
|
Name [unix.IFNAMSIZ]byte
|
||||||
Data uintptr
|
Data uintptr
|
||||||
Pad0 [24 - SIZEOF_UINTPTR]byte
|
_ [16 - unsafe.Sizeof(uintptr(0))]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Structure for iface mtu get/set ioctls
|
// Iface requests with MTU
|
||||||
type ifreq_mtu struct {
|
type ifreqMtu struct {
|
||||||
Name [unix.IFNAMSIZ]byte
|
Name [unix.IFNAMSIZ]byte
|
||||||
MTU uint32
|
MTU uint32
|
||||||
Pad0 [12]byte
|
_ [12]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Structure for interface status request ioctl
|
// ND6 flag manipulation
|
||||||
type ifstat struct {
|
type nd6Req struct {
|
||||||
IfsName [unix.IFNAMSIZ]byte
|
Name [unix.IFNAMSIZ]byte
|
||||||
Ascii [_IFSTATMAX]byte
|
Linkmtu uint32
|
||||||
|
Maxmtu uint32
|
||||||
|
Basereachable uint32
|
||||||
|
Reachable uint32
|
||||||
|
Retrans uint32
|
||||||
|
Flags uint32
|
||||||
|
Recalctm int
|
||||||
|
Chlim uint8
|
||||||
|
Initialized uint8
|
||||||
|
Randomseed0 [8]byte
|
||||||
|
Randomseed1 [8]byte
|
||||||
|
Randomid [8]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type NativeTun struct {
|
type NativeTun struct {
|
||||||
name string
|
name string
|
||||||
tunFile *os.File
|
tunFile *os.File
|
||||||
events chan TUNEvent
|
events chan Event
|
||||||
errors chan error
|
errors chan error
|
||||||
routeSocket int
|
routeSocket int
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
||||||
@@ -69,7 +89,7 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
|||||||
retry:
|
retry:
|
||||||
n, err := unix.Read(tun.routeSocket, data)
|
n, err := unix.Read(tun.routeSocket, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR {
|
if errors.Is(err, syscall.EINTR) {
|
||||||
goto retry
|
goto retry
|
||||||
}
|
}
|
||||||
tun.errors <- err
|
tun.errors <- err
|
||||||
@@ -97,134 +117,49 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
|||||||
// Up / Down event
|
// Up / Down event
|
||||||
up := (iface.Flags & net.FlagUp) != 0
|
up := (iface.Flags & net.FlagUp) != 0
|
||||||
if up != statusUp && up {
|
if up != statusUp && up {
|
||||||
tun.events <- TUNEventUp
|
tun.events <- EventUp
|
||||||
}
|
}
|
||||||
if up != statusUp && !up {
|
if up != statusUp && !up {
|
||||||
tun.events <- TUNEventDown
|
tun.events <- EventDown
|
||||||
}
|
}
|
||||||
statusUp = up
|
statusUp = up
|
||||||
|
|
||||||
// MTU changes
|
// MTU changes
|
||||||
if iface.MTU != statusMTU {
|
if iface.MTU != statusMTU {
|
||||||
tun.events <- TUNEventMTUUpdate
|
tun.events <- EventMTUUpdate
|
||||||
}
|
}
|
||||||
statusMTU = iface.MTU
|
statusMTU = iface.MTU
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tunName(fd uintptr) (string, error) {
|
func tunName(fd uintptr) (string, error) {
|
||||||
//Terrible hack to make up for freebsd not having a TUNGIFNAME
|
var ifreq ifreqName
|
||||||
|
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, _TUNGIFNAME, uintptr(unsafe.Pointer(&ifreq)))
|
||||||
//First, make sure the tun pid matches this proc's pid
|
if err != 0 {
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(_TUNSIFPID),
|
|
||||||
uintptr(0),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return "", fmt.Errorf("failed to set tun device PID: %s", errno.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open iface control socket
|
|
||||||
|
|
||||||
confd, err := unix.Socket(
|
|
||||||
unix.AF_INET,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
return unix.ByteSliceToString(ifreq.Name[:]), nil
|
||||||
defer unix.Close(confd)
|
|
||||||
|
|
||||||
procPid := os.Getpid()
|
|
||||||
|
|
||||||
//Try to find interface with matching PID
|
|
||||||
for i := 1; ; i++ {
|
|
||||||
iface, _ := net.InterfaceByIndex(i)
|
|
||||||
if err != nil || iface == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Structs for getting data in and out of SIOCGIFSTATUS ioctl
|
|
||||||
var ifstatus ifstat
|
|
||||||
copy(ifstatus.IfsName[:], iface.Name)
|
|
||||||
|
|
||||||
// Make the syscall to get the status string
|
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(confd),
|
|
||||||
uintptr(unix.SIOCGIFSTATUS),
|
|
||||||
uintptr(unsafe.Pointer(&ifstatus)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
nullStr := ifstatus.Ascii[:]
|
|
||||||
i := bytes.IndexByte(nullStr, 0)
|
|
||||||
if i < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
statStr := string(nullStr[:i])
|
|
||||||
var pidNum int = 0
|
|
||||||
|
|
||||||
// Finally get the owning PID
|
|
||||||
// Format string taken from sys/net/if_tun.c
|
|
||||||
_, err := fmt.Sscanf(statStr, "\tOpened by PID %d\n", &pidNum)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if pidNum == procPid {
|
|
||||||
return iface.Name, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy a named system interface
|
// Destroy a named system interface
|
||||||
func tunDestroy(name string) error {
|
func tunDestroy(name string) error {
|
||||||
// open control socket
|
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||||
var fd int
|
|
||||||
|
|
||||||
fd, err := unix.Socket(
|
|
||||||
unix.AF_INET,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer unix.Close(fd)
|
defer unix.Close(fd)
|
||||||
|
|
||||||
// do ioctl call
|
|
||||||
|
|
||||||
var ifr [32]byte
|
var ifr [32]byte
|
||||||
copy(ifr[:], name)
|
copy(ifr[:], name)
|
||||||
_, _, errno := unix.Syscall(
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCIFDESTROY), uintptr(unsafe.Pointer(&ifr[0])))
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(unix.SIOCIFDESTROY),
|
|
||||||
uintptr(unsafe.Pointer(&ifr[0])),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return fmt.Errorf("failed to destroy interface %s: %s", name, errno.Error())
|
return fmt.Errorf("failed to destroy interface %s: %w", name, errno)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
func CreateTUN(name string, mtu int) (Device, error) {
|
||||||
if len(name) > unix.IFNAMSIZ-1 {
|
if len(name) > unix.IFNAMSIZ-1 {
|
||||||
return nil, errors.New("interface name too long")
|
return nil, errors.New("interface name too long")
|
||||||
}
|
}
|
||||||
@@ -254,65 +189,94 @@ func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
|||||||
ifheadmode := 1
|
ifheadmode := 1
|
||||||
var errno syscall.Errno
|
var errno syscall.Errno
|
||||||
tun.operateOnFd(func(fd uintptr) {
|
tun.operateOnFd(func(fd uintptr) {
|
||||||
_, _, errno = unix.Syscall(
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNSIFHEAD, uintptr(unsafe.Pointer(&ifheadmode)))
|
||||||
unix.SYS_IOCTL,
|
|
||||||
fd,
|
|
||||||
uintptr(_TUNSIFHEAD),
|
|
||||||
uintptr(unsafe.Pointer(&ifheadmode)),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return nil, fmt.Errorf("error %s", errno.Error())
|
tunFile.Close()
|
||||||
|
tunDestroy(assignedName)
|
||||||
|
return nil, fmt.Errorf("unable to put into IFHEAD mode: %w", errno)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename tun interface
|
// Get out of PTP mode.
|
||||||
|
ifflags := syscall.IFF_BROADCAST | syscall.IFF_MULTICAST
|
||||||
|
tun.operateOnFd(func(fd uintptr) {
|
||||||
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, uintptr(_TUNSIFMODE), uintptr(unsafe.Pointer(&ifflags)))
|
||||||
|
})
|
||||||
|
|
||||||
// Open control socket
|
|
||||||
confd, err := unix.Socket(
|
|
||||||
unix.AF_INET,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer unix.Close(confd)
|
|
||||||
|
|
||||||
// set up struct for iface rename
|
|
||||||
var newnp [unix.IFNAMSIZ]byte
|
|
||||||
copy(newnp[:], name)
|
|
||||||
|
|
||||||
var ifr ifreq_ptr
|
|
||||||
copy(ifr.Name[:], assignedName)
|
|
||||||
ifr.Data = uintptr(unsafe.Pointer(&newnp[0]))
|
|
||||||
|
|
||||||
//do actual ioctl to rename iface
|
|
||||||
_, _, errno = unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(confd),
|
|
||||||
uintptr(unix.SIOCSIFNAME),
|
|
||||||
uintptr(unsafe.Pointer(&ifr)),
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
tunFile.Close()
|
tunFile.Close()
|
||||||
tunDestroy(name)
|
tunDestroy(assignedName)
|
||||||
return nil, fmt.Errorf("failed to rename %s to %s: %s", assignedName, name, errno.Error())
|
return nil, fmt.Errorf("unable to put into IFF_BROADCAST mode: %w", errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable link-local v6, not just because WireGuard doesn't do that anyway, but
|
||||||
|
// also because there are serious races with attaching and detaching LLv6 addresses
|
||||||
|
// in relation to interface lifetime within the FreeBSD kernel.
|
||||||
|
confd6, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
tunFile.Close()
|
||||||
|
tunDestroy(assignedName)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer unix.Close(confd6)
|
||||||
|
var ndireq nd6Req
|
||||||
|
copy(ndireq.Name[:], assignedName)
|
||||||
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(confd6), uintptr(_SIOCGIFINFO_IN6), uintptr(unsafe.Pointer(&ndireq)))
|
||||||
|
if errno != 0 {
|
||||||
|
tunFile.Close()
|
||||||
|
tunDestroy(assignedName)
|
||||||
|
return nil, fmt.Errorf("unable to get nd6 flags for %s: %w", assignedName, errno)
|
||||||
|
}
|
||||||
|
ndireq.Flags = ndireq.Flags &^ _ND6_IFF_AUTO_LINKLOCAL
|
||||||
|
ndireq.Flags = ndireq.Flags | _ND6_IFF_NO_DAD
|
||||||
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(confd6), uintptr(_SIOCSIFINFO_IN6), uintptr(unsafe.Pointer(&ndireq)))
|
||||||
|
if errno != 0 {
|
||||||
|
tunFile.Close()
|
||||||
|
tunDestroy(assignedName)
|
||||||
|
return nil, fmt.Errorf("unable to set nd6 flags for %s: %w", assignedName, errno)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
confd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
tunFile.Close()
|
||||||
|
tunDestroy(assignedName)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer unix.Close(confd)
|
||||||
|
var newnp [unix.IFNAMSIZ]byte
|
||||||
|
copy(newnp[:], name)
|
||||||
|
var ifr ifreqPtr
|
||||||
|
copy(ifr.Name[:], assignedName)
|
||||||
|
ifr.Data = uintptr(unsafe.Pointer(&newnp[0]))
|
||||||
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, uintptr(confd), uintptr(unix.SIOCSIFNAME), uintptr(unsafe.Pointer(&ifr)))
|
||||||
|
if errno != 0 {
|
||||||
|
tunFile.Close()
|
||||||
|
tunDestroy(assignedName)
|
||||||
|
return nil, fmt.Errorf("Failed to rename %s to %s: %w", assignedName, name, errno)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateTUNFromFile(tunFile, mtu)
|
return CreateTUNFromFile(tunFile, mtu)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTUNFromFile(file *os.File, mtu int) (TUNDevice, error) {
|
func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
|
||||||
|
|
||||||
tun := &NativeTun{
|
tun := &NativeTun{
|
||||||
tunFile: file,
|
tunFile: file,
|
||||||
events: make(chan TUNEvent, 10),
|
events: make(chan Event, 10),
|
||||||
errors: make(chan error, 1),
|
errors: make(chan error, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errno syscall.Errno
|
||||||
|
tun.operateOnFd(func(fd uintptr) {
|
||||||
|
_, _, errno = unix.Syscall(unix.SYS_IOCTL, fd, _TUNSIFPID, uintptr(0))
|
||||||
|
})
|
||||||
|
if errno != 0 {
|
||||||
|
tun.tunFile.Close()
|
||||||
|
return nil, fmt.Errorf("unable to become controlling TUN process: %w", errno)
|
||||||
|
}
|
||||||
|
|
||||||
name, err := tun.Name()
|
name, err := tun.Name()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.tunFile.Close()
|
tun.tunFile.Close()
|
||||||
@@ -365,7 +329,7 @@ func (tun *NativeTun) File() *os.File {
|
|||||||
return tun.tunFile
|
return tun.tunFile
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Events() chan TUNEvent {
|
func (tun *NativeTun) Events() chan Event {
|
||||||
return tun.events
|
return tun.events
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,45 +347,46 @@ func (tun *NativeTun) Read(buff []byte, offset int) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
|
func (tun *NativeTun) Write(buf []byte, offset int) (int, error) {
|
||||||
|
if offset < 4 {
|
||||||
// reserve space for header
|
return 0, io.ErrShortBuffer
|
||||||
|
|
||||||
buff = buff[offset-4:]
|
|
||||||
|
|
||||||
// add packet information header
|
|
||||||
|
|
||||||
buff[0] = 0x00
|
|
||||||
buff[1] = 0x00
|
|
||||||
buff[2] = 0x00
|
|
||||||
|
|
||||||
if buff[4]>>4 == ipv6.Version {
|
|
||||||
buff[3] = unix.AF_INET6
|
|
||||||
} else {
|
|
||||||
buff[3] = unix.AF_INET
|
|
||||||
}
|
}
|
||||||
|
buf = buf[offset-4:]
|
||||||
// write
|
if len(buf) < 5 {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
return tun.tunFile.Write(buff)
|
}
|
||||||
|
buf[0] = 0x00
|
||||||
|
buf[1] = 0x00
|
||||||
|
buf[2] = 0x00
|
||||||
|
switch buf[4] >> 4 {
|
||||||
|
case 4:
|
||||||
|
buf[3] = unix.AF_INET
|
||||||
|
case 6:
|
||||||
|
buf[3] = unix.AF_INET6
|
||||||
|
default:
|
||||||
|
return 0, unix.EAFNOSUPPORT
|
||||||
|
}
|
||||||
|
return tun.tunFile.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Flush() error {
|
func (tun *NativeTun) Flush() error {
|
||||||
//TODO: can flushing be implemented by buffering and using sendmmsg?
|
// TODO: can flushing be implemented by buffering and using sendmmsg?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Close() error {
|
func (tun *NativeTun) Close() error {
|
||||||
var err3 error
|
var err1, err2, err3 error
|
||||||
err1 := tun.tunFile.Close()
|
tun.closeOnce.Do(func() {
|
||||||
err2 := tunDestroy(tun.name)
|
err1 = tun.tunFile.Close()
|
||||||
if tun.routeSocket != -1 {
|
err2 = tunDestroy(tun.name)
|
||||||
unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
|
if tun.routeSocket != -1 {
|
||||||
err3 = unix.Close(tun.routeSocket)
|
unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
|
||||||
tun.routeSocket = -1
|
err3 = unix.Close(tun.routeSocket)
|
||||||
} else if tun.events != nil {
|
tun.routeSocket = -1
|
||||||
close(tun.events)
|
} else if tun.events != nil {
|
||||||
}
|
close(tun.events)
|
||||||
|
}
|
||||||
|
})
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
@@ -432,70 +397,34 @@ func (tun *NativeTun) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) setMTU(n int) error {
|
func (tun *NativeTun) setMTU(n int) error {
|
||||||
// open datagram socket
|
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||||
|
|
||||||
var fd int
|
|
||||||
|
|
||||||
fd, err := unix.Socket(
|
|
||||||
unix.AF_INET,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer unix.Close(fd)
|
defer unix.Close(fd)
|
||||||
|
|
||||||
// do ioctl call
|
var ifr ifreqMtu
|
||||||
|
|
||||||
var ifr ifreq_mtu
|
|
||||||
copy(ifr.Name[:], tun.name)
|
copy(ifr.Name[:], tun.name)
|
||||||
ifr.MTU = uint32(n)
|
ifr.MTU = uint32(n)
|
||||||
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCSIFMTU), uintptr(unsafe.Pointer(&ifr)))
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(unix.SIOCSIFMTU),
|
|
||||||
uintptr(unsafe.Pointer(&ifr)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return fmt.Errorf("failed to set MTU on %s", tun.name)
|
return fmt.Errorf("failed to set MTU on %s: %w", tun.name, errno)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) MTU() (int, error) {
|
func (tun *NativeTun) MTU() (int, error) {
|
||||||
// open datagram socket
|
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
|
||||||
|
|
||||||
fd, err := unix.Socket(
|
|
||||||
unix.AF_INET,
|
|
||||||
unix.SOCK_DGRAM,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer unix.Close(fd)
|
defer unix.Close(fd)
|
||||||
|
|
||||||
// do ioctl call
|
var ifr ifreqMtu
|
||||||
var ifr ifreq_mtu
|
|
||||||
copy(ifr.Name[:], tun.name)
|
copy(ifr.Name[:], tun.name)
|
||||||
|
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(unix.SIOCGIFMTU), uintptr(unsafe.Pointer(&ifr)))
|
||||||
_, _, errno := unix.Syscall(
|
|
||||||
unix.SYS_IOCTL,
|
|
||||||
uintptr(fd),
|
|
||||||
uintptr(unix.SIOCGIFMTU),
|
|
||||||
uintptr(unsafe.Pointer(&ifr)),
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return 0, fmt.Errorf("failed to get MTU on %s", tun.name)
|
return 0, fmt.Errorf("failed to get MTU on %s: %w", tun.name, errno)
|
||||||
}
|
}
|
||||||
|
|
||||||
return int(*(*int32)(unsafe.Pointer(&ifr.MTU))), nil
|
return int(*(*int32)(unsafe.Pointer(&ifr.MTU))), nil
|
||||||
}
|
}
|
||||||
|
|||||||
224
tun/tun_linux.go
224
tun/tun_linux.go
@@ -1,6 +1,6 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
@@ -12,15 +12,16 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/net/ipv6"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"golang.zx2c4.com/wireguard/rwcancel"
|
|
||||||
"net"
|
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/rwcancel"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -30,15 +31,20 @@ const (
|
|||||||
|
|
||||||
type NativeTun struct {
|
type NativeTun struct {
|
||||||
tunFile *os.File
|
tunFile *os.File
|
||||||
index int32 // if index
|
index int32 // if index
|
||||||
name string // name of interface
|
errors chan error // async error handling
|
||||||
errors chan error // async error handling
|
events chan Event // device related events
|
||||||
events chan TUNEvent // device related events
|
nopi bool // the device was passed IFF_NO_PI
|
||||||
nopi bool // the device was pased IFF_NO_PI
|
|
||||||
netlinkSock int
|
netlinkSock int
|
||||||
netlinkCancel *rwcancel.RWCancel
|
netlinkCancel *rwcancel.RWCancel
|
||||||
hackListenerClosed sync.Mutex
|
hackListenerClosed sync.Mutex
|
||||||
statusListenersShutdown chan struct{}
|
statusListenersShutdown chan struct{}
|
||||||
|
|
||||||
|
closeOnce sync.Once
|
||||||
|
|
||||||
|
nameOnce sync.Once // guards calling initNameCache, which sets following fields
|
||||||
|
nameCache string // name of interface
|
||||||
|
nameErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) File() *os.File {
|
func (tun *NativeTun) File() *os.File {
|
||||||
@@ -50,6 +56,11 @@ func (tun *NativeTun) routineHackListener() {
|
|||||||
/* This is needed for the detection to work across network namespaces
|
/* This is needed for the detection to work across network namespaces
|
||||||
* If you are reading this and know a better method, please get in touch.
|
* If you are reading this and know a better method, please get in touch.
|
||||||
*/
|
*/
|
||||||
|
last := 0
|
||||||
|
const (
|
||||||
|
up = 1
|
||||||
|
down = 2
|
||||||
|
)
|
||||||
for {
|
for {
|
||||||
sysconn, err := tun.tunFile.SyscallConn()
|
sysconn, err := tun.tunFile.SyscallConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -63,14 +74,25 @@ func (tun *NativeTun) routineHackListener() {
|
|||||||
}
|
}
|
||||||
switch err {
|
switch err {
|
||||||
case unix.EINVAL:
|
case unix.EINVAL:
|
||||||
tun.events <- TUNEventUp
|
if last != up {
|
||||||
|
// If the tunnel is up, it reports that write() is
|
||||||
|
// allowed but we provided invalid data.
|
||||||
|
tun.events <- EventUp
|
||||||
|
last = up
|
||||||
|
}
|
||||||
case unix.EIO:
|
case unix.EIO:
|
||||||
tun.events <- TUNEventDown
|
if last != down {
|
||||||
|
// If the tunnel is down, it reports that no I/O
|
||||||
|
// is possible, without checking our provided data.
|
||||||
|
tun.events <- EventDown
|
||||||
|
last = down
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
|
// nothing
|
||||||
case <-tun.statusListenersShutdown:
|
case <-tun.statusListenersShutdown:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -84,7 +106,7 @@ func createNetlinkSocket() (int, error) {
|
|||||||
}
|
}
|
||||||
saddr := &unix.SockaddrNetlink{
|
saddr := &unix.SockaddrNetlink{
|
||||||
Family: unix.AF_NETLINK,
|
Family: unix.AF_NETLINK,
|
||||||
Groups: uint32((1 << (unix.RTNLGRP_LINK - 1)) | (1 << (unix.RTNLGRP_IPV4_IFADDR - 1)) | (1 << (unix.RTNLGRP_IPV6_IFADDR - 1))),
|
Groups: unix.RTMGRP_LINK | unix.RTMGRP_IPV4_IFADDR | unix.RTMGRP_IPV6_IFADDR,
|
||||||
}
|
}
|
||||||
err = unix.Bind(sock, saddr)
|
err = unix.Bind(sock, saddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -98,10 +120,10 @@ func (tun *NativeTun) routineNetlinkListener() {
|
|||||||
unix.Close(tun.netlinkSock)
|
unix.Close(tun.netlinkSock)
|
||||||
tun.hackListenerClosed.Lock()
|
tun.hackListenerClosed.Lock()
|
||||||
close(tun.events)
|
close(tun.events)
|
||||||
|
tun.netlinkCancel.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for msg := make([]byte, 1<<16); ; {
|
for msg := make([]byte, 1<<16); ; {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var msgn int
|
var msgn int
|
||||||
for {
|
for {
|
||||||
@@ -110,12 +132,12 @@ func (tun *NativeTun) routineNetlinkListener() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if !tun.netlinkCancel.ReadyRead() {
|
if !tun.netlinkCancel.ReadyRead() {
|
||||||
tun.errors <- fmt.Errorf("netlink socket closed: %s", err.Error())
|
tun.errors <- fmt.Errorf("netlink socket closed: %w", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tun.errors <- fmt.Errorf("failed to receive netlink message: %s", err.Error())
|
tun.errors <- fmt.Errorf("failed to receive netlink message: %w", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +147,7 @@ func (tun *NativeTun) routineNetlinkListener() {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wasEverUp := false
|
||||||
for remain := msg[:msgn]; len(remain) >= unix.SizeofNlMsghdr; {
|
for remain := msg[:msgn]; len(remain) >= unix.SizeofNlMsghdr; {
|
||||||
|
|
||||||
hdr := *(*unix.NlMsghdr)(unsafe.Pointer(&remain[0]))
|
hdr := *(*unix.NlMsghdr)(unsafe.Pointer(&remain[0]))
|
||||||
@@ -147,14 +170,20 @@ func (tun *NativeTun) routineNetlinkListener() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if info.Flags&unix.IFF_RUNNING != 0 {
|
if info.Flags&unix.IFF_RUNNING != 0 {
|
||||||
tun.events <- TUNEventUp
|
tun.events <- EventUp
|
||||||
|
wasEverUp = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.Flags&unix.IFF_RUNNING == 0 {
|
if info.Flags&unix.IFF_RUNNING == 0 {
|
||||||
tun.events <- TUNEventDown
|
// Don't emit EventDown before we've ever emitted EventUp.
|
||||||
|
// This avoids a startup race with HackListener, which
|
||||||
|
// might detect Up before we have finished reporting Down.
|
||||||
|
if wasEverUp {
|
||||||
|
tun.events <- EventDown
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tun.events <- TUNEventMTUUpdate
|
tun.events <- EventMTUUpdate
|
||||||
|
|
||||||
default:
|
default:
|
||||||
remain = remain[hdr.Len:]
|
remain = remain[hdr.Len:]
|
||||||
@@ -163,11 +192,6 @@ func (tun *NativeTun) routineNetlinkListener() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) isUp() (bool, error) {
|
|
||||||
inter, err := net.InterfaceByName(tun.name)
|
|
||||||
return inter.Flags&net.FlagUp != 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIFIndex(name string) (int32, error) {
|
func getIFIndex(name string) (int32, error) {
|
||||||
fd, err := unix.Socket(
|
fd, err := unix.Socket(
|
||||||
unix.AF_INET,
|
unix.AF_INET,
|
||||||
@@ -197,6 +221,11 @@ func getIFIndex(name string) (int32, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) setMTU(n int) error {
|
func (tun *NativeTun) setMTU(n int) error {
|
||||||
|
name, err := tun.Name()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// open datagram socket
|
// open datagram socket
|
||||||
fd, err := unix.Socket(
|
fd, err := unix.Socket(
|
||||||
unix.AF_INET,
|
unix.AF_INET,
|
||||||
@@ -211,9 +240,8 @@ func (tun *NativeTun) setMTU(n int) error {
|
|||||||
defer unix.Close(fd)
|
defer unix.Close(fd)
|
||||||
|
|
||||||
// do ioctl call
|
// do ioctl call
|
||||||
|
|
||||||
var ifr [ifReqSize]byte
|
var ifr [ifReqSize]byte
|
||||||
copy(ifr[:], tun.name)
|
copy(ifr[:], name)
|
||||||
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n)
|
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n)
|
||||||
_, _, errno := unix.Syscall(
|
_, _, errno := unix.Syscall(
|
||||||
unix.SYS_IOCTL,
|
unix.SYS_IOCTL,
|
||||||
@@ -223,13 +251,18 @@ func (tun *NativeTun) setMTU(n int) error {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return errors.New("failed to set MTU of TUN device")
|
return fmt.Errorf("failed to set MTU of TUN device: %w", errno)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) MTU() (int, error) {
|
func (tun *NativeTun) MTU() (int, error) {
|
||||||
|
name, err := tun.Name()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
// open datagram socket
|
// open datagram socket
|
||||||
fd, err := unix.Socket(
|
fd, err := unix.Socket(
|
||||||
unix.AF_INET,
|
unix.AF_INET,
|
||||||
@@ -246,7 +279,7 @@ func (tun *NativeTun) MTU() (int, error) {
|
|||||||
// do ioctl call
|
// do ioctl call
|
||||||
|
|
||||||
var ifr [ifReqSize]byte
|
var ifr [ifReqSize]byte
|
||||||
copy(ifr[:], tun.name)
|
copy(ifr[:], name)
|
||||||
_, _, errno := unix.Syscall(
|
_, _, errno := unix.Syscall(
|
||||||
unix.SYS_IOCTL,
|
unix.SYS_IOCTL,
|
||||||
uintptr(fd),
|
uintptr(fd),
|
||||||
@@ -254,13 +287,22 @@ func (tun *NativeTun) MTU() (int, error) {
|
|||||||
uintptr(unsafe.Pointer(&ifr[0])),
|
uintptr(unsafe.Pointer(&ifr[0])),
|
||||||
)
|
)
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return 0, errors.New("failed to get MTU of TUN device: " + errno.Error())
|
return 0, fmt.Errorf("failed to get MTU of TUN device: %w", errno)
|
||||||
}
|
}
|
||||||
|
|
||||||
return int(*(*int32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ]))), nil
|
return int(*(*int32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ]))), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Name() (string, error) {
|
func (tun *NativeTun) Name() (string, error) {
|
||||||
|
tun.nameOnce.Do(tun.initNameCache)
|
||||||
|
return tun.nameCache, tun.nameErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *NativeTun) initNameCache() {
|
||||||
|
tun.nameCache, tun.nameErr = tun.nameSlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tun *NativeTun) nameSlow() (string, error) {
|
||||||
sysconn, err := tun.tunFile.SyscallConn()
|
sysconn, err := tun.tunFile.SyscallConn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -276,96 +318,100 @@ func (tun *NativeTun) Name() (string, error) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New("failed to get name of TUN device: " + err.Error())
|
return "", fmt.Errorf("failed to get name of TUN device: %w", err)
|
||||||
}
|
}
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
return "", errors.New("failed to get name of TUN device: " + errno.Error())
|
return "", fmt.Errorf("failed to get name of TUN device: %w", errno)
|
||||||
}
|
}
|
||||||
nullStr := ifr[:]
|
name := ifr[:]
|
||||||
i := bytes.IndexByte(nullStr, 0)
|
if i := bytes.IndexByte(name, 0); i != -1 {
|
||||||
if i != -1 {
|
name = name[:i]
|
||||||
nullStr = nullStr[:i]
|
|
||||||
}
|
}
|
||||||
tun.name = string(nullStr)
|
return string(name), nil
|
||||||
return tun.name, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
|
func (tun *NativeTun) Write(buf []byte, offset int) (int, error) {
|
||||||
|
|
||||||
if tun.nopi {
|
if tun.nopi {
|
||||||
buff = buff[offset:]
|
buf = buf[offset:]
|
||||||
} else {
|
} else {
|
||||||
// reserve space for header
|
// reserve space for header
|
||||||
|
buf = buf[offset-4:]
|
||||||
buff = buff[offset-4:]
|
|
||||||
|
|
||||||
// add packet information header
|
// add packet information header
|
||||||
|
buf[0] = 0x00
|
||||||
buff[0] = 0x00
|
buf[1] = 0x00
|
||||||
buff[1] = 0x00
|
if buf[4]>>4 == ipv6.Version {
|
||||||
|
buf[2] = 0x86
|
||||||
if buff[4]>>4 == ipv6.Version {
|
buf[3] = 0xdd
|
||||||
buff[2] = 0x86
|
|
||||||
buff[3] = 0xdd
|
|
||||||
} else {
|
} else {
|
||||||
buff[2] = 0x08
|
buf[2] = 0x08
|
||||||
buff[3] = 0x00
|
buf[3] = 0x00
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// write
|
n, err := tun.tunFile.Write(buf)
|
||||||
|
if errors.Is(err, syscall.EBADFD) {
|
||||||
return tun.tunFile.Write(buff)
|
err = os.ErrClosed
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Flush() error {
|
func (tun *NativeTun) Flush() error {
|
||||||
//TODO: can flushing be implemented by buffering and using sendmmsg?
|
// TODO: can flushing be implemented by buffering and using sendmmsg?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Read(buff []byte, offset int) (int, error) {
|
func (tun *NativeTun) Read(buf []byte, offset int) (n int, err error) {
|
||||||
select {
|
select {
|
||||||
case err := <-tun.errors:
|
case err = <-tun.errors:
|
||||||
return 0, err
|
|
||||||
default:
|
default:
|
||||||
if tun.nopi {
|
if tun.nopi {
|
||||||
return tun.tunFile.Read(buff[offset:])
|
n, err = tun.tunFile.Read(buf[offset:])
|
||||||
} else {
|
} else {
|
||||||
buff := buff[offset-4:]
|
buff := buf[offset-4:]
|
||||||
n, err := tun.tunFile.Read(buff[:])
|
n, err = tun.tunFile.Read(buff[:])
|
||||||
if n < 4 {
|
if errors.Is(err, syscall.EBADFD) {
|
||||||
return 0, err
|
err = os.ErrClosed
|
||||||
|
}
|
||||||
|
if n < 4 {
|
||||||
|
n = 0
|
||||||
|
} else {
|
||||||
|
n -= 4
|
||||||
}
|
}
|
||||||
return n - 4, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Events() chan TUNEvent {
|
func (tun *NativeTun) Events() chan Event {
|
||||||
return tun.events
|
return tun.events
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Close() error {
|
func (tun *NativeTun) Close() error {
|
||||||
var err1 error
|
var err1, err2 error
|
||||||
if tun.statusListenersShutdown != nil {
|
tun.closeOnce.Do(func() {
|
||||||
close(tun.statusListenersShutdown)
|
if tun.statusListenersShutdown != nil {
|
||||||
if tun.netlinkCancel != nil {
|
close(tun.statusListenersShutdown)
|
||||||
err1 = tun.netlinkCancel.Cancel()
|
if tun.netlinkCancel != nil {
|
||||||
|
err1 = tun.netlinkCancel.Cancel()
|
||||||
|
}
|
||||||
|
} else if tun.events != nil {
|
||||||
|
close(tun.events)
|
||||||
}
|
}
|
||||||
} else if tun.events != nil {
|
err2 = tun.tunFile.Close()
|
||||||
close(tun.events)
|
})
|
||||||
}
|
|
||||||
err2 := tun.tunFile.Close()
|
|
||||||
|
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
return err2
|
return err2
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
func CreateTUN(name string, mtu int) (Device, error) {
|
||||||
nfd, err := unix.Open(cloneDevicePath, os.O_RDWR, 0)
|
nfd, err := unix.Open(cloneDevicePath, os.O_RDWR, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("CreateTUN(%q) failed; %s does not exist", name, cloneDevicePath)
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +419,8 @@ func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
|||||||
var flags uint16 = unix.IFF_TUN // | unix.IFF_NO_PI (disabled for TUN status hack)
|
var flags uint16 = unix.IFF_TUN // | unix.IFF_NO_PI (disabled for TUN status hack)
|
||||||
nameBytes := []byte(name)
|
nameBytes := []byte(name)
|
||||||
if len(nameBytes) >= unix.IFNAMSIZ {
|
if len(nameBytes) >= unix.IFNAMSIZ {
|
||||||
return nil, errors.New("interface name too long")
|
unix.Close(nfd)
|
||||||
|
return nil, fmt.Errorf("interface name too long: %w", unix.ENAMETOOLONG)
|
||||||
}
|
}
|
||||||
copy(ifr[:], nameBytes)
|
copy(ifr[:], nameBytes)
|
||||||
*(*uint16)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = flags
|
*(*uint16)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = flags
|
||||||
@@ -385,38 +432,39 @@ func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
|||||||
uintptr(unsafe.Pointer(&ifr[0])),
|
uintptr(unsafe.Pointer(&ifr[0])),
|
||||||
)
|
)
|
||||||
if errno != 0 {
|
if errno != 0 {
|
||||||
|
unix.Close(nfd)
|
||||||
return nil, errno
|
return nil, errno
|
||||||
}
|
}
|
||||||
|
|
||||||
err = unix.SetNonblock(nfd, true)
|
err = unix.SetNonblock(nfd, true)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(nfd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Note that the above -- open,ioctl,nonblock -- must happen prior to handing it to netpoll as below this line.
|
// Note that the above -- open,ioctl,nonblock -- must happen prior to handing it to netpoll as below this line.
|
||||||
|
|
||||||
fd := os.NewFile(uintptr(nfd), cloneDevicePath)
|
fd := os.NewFile(uintptr(nfd), cloneDevicePath)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateTUNFromFile(fd, mtu)
|
return CreateTUNFromFile(fd, mtu)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTUNFromFile(file *os.File, mtu int) (TUNDevice, error) {
|
func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
|
||||||
tun := &NativeTun{
|
tun := &NativeTun{
|
||||||
tunFile: file,
|
tunFile: file,
|
||||||
events: make(chan TUNEvent, 5),
|
events: make(chan Event, 5),
|
||||||
errors: make(chan error, 5),
|
errors: make(chan error, 5),
|
||||||
statusListenersShutdown: make(chan struct{}),
|
statusListenersShutdown: make(chan struct{}),
|
||||||
nopi: false,
|
nopi: false,
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
|
|
||||||
_, err = tun.Name()
|
name, err := tun.Name()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// start event listener
|
// start event listener
|
||||||
|
|
||||||
tun.index, err = getIFIndex(tun.name)
|
tun.index, err = getIFIndex(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -444,7 +492,7 @@ func CreateTUNFromFile(file *os.File, mtu int) (TUNDevice, error) {
|
|||||||
return tun, nil
|
return tun, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateUnmonitoredTUNFromFD(fd int) (TUNDevice, string, error) {
|
func CreateUnmonitoredTUNFromFD(fd int) (Device, string, error) {
|
||||||
err := unix.SetNonblock(fd, true)
|
err := unix.SetNonblock(fd, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
@@ -452,7 +500,7 @@ func CreateUnmonitoredTUNFromFD(fd int) (TUNDevice, string, error) {
|
|||||||
file := os.NewFile(uintptr(fd), "/dev/tun")
|
file := os.NewFile(uintptr(fd), "/dev/tun")
|
||||||
tun := &NativeTun{
|
tun := &NativeTun{
|
||||||
tunFile: file,
|
tunFile: file,
|
||||||
events: make(chan TUNEvent, 5),
|
events: make(chan Event, 5),
|
||||||
errors: make(chan error, 5),
|
errors: make(chan error, 5),
|
||||||
nopi: true,
|
nopi: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
/* SPDX-License-Identifier: MIT
|
/* SPDX-License-Identifier: MIT
|
||||||
*
|
*
|
||||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package tun
|
package tun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/net/ipv6"
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Structure for iface mtu get/set ioctls
|
// Structure for iface mtu get/set ioctls
|
||||||
@@ -28,9 +30,10 @@ const _TUNSIFMODE = 0x8004745d
|
|||||||
type NativeTun struct {
|
type NativeTun struct {
|
||||||
name string
|
name string
|
||||||
tunFile *os.File
|
tunFile *os.File
|
||||||
events chan TUNEvent
|
events chan Event
|
||||||
errors chan error
|
errors chan error
|
||||||
routeSocket int
|
routeSocket int
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
||||||
@@ -41,13 +44,41 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
|||||||
|
|
||||||
defer close(tun.events)
|
defer close(tun.events)
|
||||||
|
|
||||||
|
check := func() bool {
|
||||||
|
iface, err := net.InterfaceByIndex(tunIfindex)
|
||||||
|
if err != nil {
|
||||||
|
tun.errors <- err
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Up / Down event
|
||||||
|
up := (iface.Flags & net.FlagUp) != 0
|
||||||
|
if up != statusUp && up {
|
||||||
|
tun.events <- EventUp
|
||||||
|
}
|
||||||
|
if up != statusUp && !up {
|
||||||
|
tun.events <- EventDown
|
||||||
|
}
|
||||||
|
statusUp = up
|
||||||
|
|
||||||
|
// MTU changes
|
||||||
|
if iface.MTU != statusMTU {
|
||||||
|
tun.events <- EventMTUUpdate
|
||||||
|
}
|
||||||
|
statusMTU = iface.MTU
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if check() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
data := make([]byte, os.Getpagesize())
|
data := make([]byte, os.Getpagesize())
|
||||||
for {
|
for {
|
||||||
retry:
|
|
||||||
n, err := unix.Read(tun.routeSocket, data)
|
n, err := unix.Read(tun.routeSocket, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR {
|
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR {
|
||||||
goto retry
|
continue
|
||||||
}
|
}
|
||||||
tun.errors <- err
|
tun.errors <- err
|
||||||
return
|
return
|
||||||
@@ -64,42 +95,13 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
|||||||
if ifindex != tunIfindex {
|
if ifindex != tunIfindex {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if check() {
|
||||||
iface, err := net.InterfaceByIndex(ifindex)
|
|
||||||
if err != nil {
|
|
||||||
tun.errors <- err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Up / Down event
|
|
||||||
up := (iface.Flags & net.FlagUp) != 0
|
|
||||||
if up != statusUp && up {
|
|
||||||
tun.events <- TUNEventUp
|
|
||||||
}
|
|
||||||
if up != statusUp && !up {
|
|
||||||
tun.events <- TUNEventDown
|
|
||||||
}
|
|
||||||
statusUp = up
|
|
||||||
|
|
||||||
// MTU changes
|
|
||||||
if iface.MTU != statusMTU {
|
|
||||||
tun.events <- TUNEventMTUUpdate
|
|
||||||
}
|
|
||||||
statusMTU = iface.MTU
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorIsEBUSY(err error) bool {
|
func CreateTUN(name string, mtu int) (Device, error) {
|
||||||
if pe, ok := err.(*os.PathError); ok {
|
|
||||||
err = pe.Err
|
|
||||||
}
|
|
||||||
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EBUSY {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
|
||||||
ifIndex := -1
|
ifIndex := -1
|
||||||
if name != "tun" {
|
if name != "tun" {
|
||||||
_, err := fmt.Sscanf(name, "tun%d", &ifIndex)
|
_, err := fmt.Sscanf(name, "tun%d", &ifIndex)
|
||||||
@@ -114,9 +116,9 @@ func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
|||||||
if ifIndex != -1 {
|
if ifIndex != -1 {
|
||||||
tunfile, err = os.OpenFile(fmt.Sprintf("/dev/tun%d", ifIndex), unix.O_RDWR, 0)
|
tunfile, err = os.OpenFile(fmt.Sprintf("/dev/tun%d", ifIndex), unix.O_RDWR, 0)
|
||||||
} else {
|
} else {
|
||||||
for ifIndex = 0; ifIndex < 256; ifIndex += 1 {
|
for ifIndex = 0; ifIndex < 256; ifIndex++ {
|
||||||
tunfile, err = os.OpenFile(fmt.Sprintf("/dev/tun%d", ifIndex), unix.O_RDWR, 0)
|
tunfile, err = os.OpenFile(fmt.Sprintf("/dev/tun%d", ifIndex), unix.O_RDWR, 0)
|
||||||
if err == nil || !errorIsEBUSY(err) {
|
if err == nil || !errors.Is(err, syscall.EBUSY) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,18 +133,17 @@ func CreateTUN(name string, mtu int) (TUNDevice, error) {
|
|||||||
if err == nil && name == "tun" {
|
if err == nil && name == "tun" {
|
||||||
fname := os.Getenv("WG_TUN_NAME_FILE")
|
fname := os.Getenv("WG_TUN_NAME_FILE")
|
||||||
if fname != "" {
|
if fname != "" {
|
||||||
ioutil.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0400)
|
os.WriteFile(fname, []byte(tun.(*NativeTun).name+"\n"), 0400)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tun, err
|
return tun, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTUNFromFile(file *os.File, mtu int) (TUNDevice, error) {
|
func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
|
||||||
|
|
||||||
tun := &NativeTun{
|
tun := &NativeTun{
|
||||||
tunFile: file,
|
tunFile: file,
|
||||||
events: make(chan TUNEvent, 10),
|
events: make(chan Event, 10),
|
||||||
errors: make(chan error, 1),
|
errors: make(chan error, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,10 +173,13 @@ func CreateTUNFromFile(file *os.File, mtu int) (TUNDevice, error) {
|
|||||||
|
|
||||||
go tun.routineRouteListener(tunIfindex)
|
go tun.routineRouteListener(tunIfindex)
|
||||||
|
|
||||||
err = tun.setMTU(mtu)
|
currentMTU, err := tun.MTU()
|
||||||
if err != nil {
|
if err != nil || currentMTU != mtu {
|
||||||
tun.Close()
|
err = tun.setMTU(mtu)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
tun.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tun, nil
|
return tun, nil
|
||||||
@@ -196,7 +200,7 @@ func (tun *NativeTun) File() *os.File {
|
|||||||
return tun.tunFile
|
return tun.tunFile
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Events() chan TUNEvent {
|
func (tun *NativeTun) Events() chan Event {
|
||||||
return tun.events
|
return tun.events
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,20 +242,22 @@ func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Flush() error {
|
func (tun *NativeTun) Flush() error {
|
||||||
//TODO: can flushing be implemented by buffering and using sendmmsg?
|
// TODO: can flushing be implemented by buffering and using sendmmsg?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Close() error {
|
func (tun *NativeTun) Close() error {
|
||||||
var err2 error
|
var err1, err2 error
|
||||||
err1 := tun.tunFile.Close()
|
tun.closeOnce.Do(func() {
|
||||||
if tun.routeSocket != -1 {
|
err1 = tun.tunFile.Close()
|
||||||
unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
|
if tun.routeSocket != -1 {
|
||||||
err2 = unix.Close(tun.routeSocket)
|
unix.Shutdown(tun.routeSocket, unix.SHUT_RDWR)
|
||||||
tun.routeSocket = -1
|
err2 = unix.Close(tun.routeSocket)
|
||||||
} else if tun.events != nil {
|
tun.routeSocket = -1
|
||||||
close(tun.events)
|
} else if tun.events != nil {
|
||||||
}
|
close(tun.events)
|
||||||
|
}
|
||||||
|
})
|
||||||
if err1 != nil {
|
if err1 != nil {
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user