Compare commits
206 Commits
0.0.202001
...
0.0.202102
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1 @@
|
||||
wireguard-go
|
||||
vendor
|
||||
.gopath
|
||||
ireallywantobuildon_linux.go
|
||||
|
||||
13
Makefile
13
Makefile
@@ -10,10 +10,10 @@ MAKEFLAGS += --no-print-directory
|
||||
generate-version-and-build:
|
||||
@export GIT_CEILING_DIRECTORIES="$(realpath $(CURDIR)/..)" && \
|
||||
tag="$$(git describe --dirty 2>/dev/null)" && \
|
||||
ver="$$(printf 'package device\nconst WireGuardGoVersion = "%s"\n' "$${tag#v}")" && \
|
||||
[ "$$(cat device/version.go 2>/dev/null)" != "$$ver" ] && \
|
||||
echo "$$ver" > device/version.go && \
|
||||
git update-index --assume-unchanged device/version.go || true
|
||||
ver="$$(printf 'package main\nconst Version = "%s"\n' "$$tag")" && \
|
||||
[ "$$(cat version.go 2>/dev/null)" != "$$ver" ] && \
|
||||
echo "$$ver" > version.go && \
|
||||
git update-index --assume-unchanged version.go || true
|
||||
@$(MAKE) wireguard-go
|
||||
|
||||
wireguard-go: $(wildcard *.go) $(wildcard */*.go)
|
||||
@@ -22,7 +22,10 @@ wireguard-go: $(wildcard *.go) $(wildcard */*.go)
|
||||
install: wireguard-go
|
||||
@install -v -d "$(DESTDIR)$(BINDIR)" && install -v -m 0755 "$<" "$(DESTDIR)$(BINDIR)/wireguard-go"
|
||||
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
clean:
|
||||
rm -f wireguard-go
|
||||
|
||||
.PHONY: all clean install generate-version-and-build
|
||||
.PHONY: all clean test install generate-version-and-build
|
||||
|
||||
@@ -26,7 +26,7 @@ To run with more logging you may set the environment variable `LOG_LEVEL=debug`.
|
||||
|
||||
### 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
|
||||
|
||||
@@ -46,7 +46,7 @@ This will run on OpenBSD. It does not yet support sticky sockets. Fwmark is mapp
|
||||
|
||||
## Building
|
||||
|
||||
This requires an installation of [go](https://golang.org) ≥ 1.12.
|
||||
This requires an installation of [go](https://golang.org) ≥ 1.13.
|
||||
|
||||
```
|
||||
$ git clone https://git.zx2c4.com/wireguard-go
|
||||
@@ -56,7 +56,7 @@ $ make
|
||||
|
||||
## 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
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
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 *nativeBind) 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 *nativeBind) 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
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
/* 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 (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -18,17 +17,13 @@ const (
|
||||
sockoptIPV6_UNICAST_IF = 31
|
||||
)
|
||||
|
||||
func (device *Device) BindSocketToInterface4(interfaceIndex uint32, blackhole bool) error {
|
||||
func (bind *nativeBind) BindSocketToInterface4(interfaceIndex uint32, blackhole bool) 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]))
|
||||
|
||||
if device.net.bind == nil {
|
||||
return errors.New("Bind is not yet initialized")
|
||||
}
|
||||
|
||||
sysconn, err := device.net.bind.(*nativeBind).ipv4.SyscallConn()
|
||||
sysconn, err := bind.ipv4.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -41,12 +36,12 @@ func (device *Device) BindSocketToInterface4(interfaceIndex uint32, blackhole bo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
device.net.bind.(*nativeBind).blackhole4 = blackhole
|
||||
bind.blackhole4 = blackhole
|
||||
return nil
|
||||
}
|
||||
|
||||
func (device *Device) BindSocketToInterface6(interfaceIndex uint32, blackhole bool) error {
|
||||
sysconn, err := device.net.bind.(*nativeBind).ipv6.SyscallConn()
|
||||
func (bind *nativeBind) BindSocketToInterface6(interfaceIndex uint32, blackhole bool) error {
|
||||
sysconn, err := bind.ipv6.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,6 +54,6 @@ func (device *Device) BindSocketToInterface6(interfaceIndex uint32, blackhole bo
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
device.net.bind.(*nativeBind).blackhole6 = blackhole
|
||||
bind.blackhole6 = blackhole
|
||||
return nil
|
||||
}
|
||||
111
conn/conn.go
Normal file
111
conn/conn.go
Normal file
@@ -0,0 +1,111 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
// Package conn implements WireGuard's network connections.
|
||||
package conn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
// LastMark reports the last mark set for this Bind.
|
||||
LastMark() uint32
|
||||
|
||||
// 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
|
||||
|
||||
// ReceiveIPv6 reads an IPv6 UDP packet into b.
|
||||
//
|
||||
// It reports the number of bytes read, n,
|
||||
// the packet source address ep,
|
||||
// and any error.
|
||||
ReceiveIPv6(b []byte) (n int, ep Endpoint, err error)
|
||||
|
||||
// ReceiveIPv4 reads an IPv4 UDP packet into b.
|
||||
//
|
||||
// It reports the number of bytes read, n,
|
||||
// the packet source address ep,
|
||||
// and any error.
|
||||
ReceiveIPv4(b []byte) (n int, ep Endpoint, err error)
|
||||
|
||||
// Send writes a packet b to address ep.
|
||||
Send(b []byte, ep Endpoint) error
|
||||
|
||||
// Close closes the Bind connection.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// CreateBind creates a Bind bound to a port.
|
||||
//
|
||||
// The value actualPort reports the actual port number the Bind
|
||||
// object gets bound to.
|
||||
func CreateBind(port uint16) (b Bind, actualPort uint16, err error) {
|
||||
return createBind(port)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
/* 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 (
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ func CreateEndpoint(s string) (Endpoint, error) {
|
||||
return (*NativeEndpoint)(addr), err
|
||||
}
|
||||
|
||||
func (_ *NativeEndpoint) ClearSrc() {}
|
||||
func (*NativeEndpoint) ClearSrc() {}
|
||||
|
||||
func (e *NativeEndpoint) DstIP() net.IP {
|
||||
return (*net.UDPAddr)(e).IP
|
||||
@@ -67,16 +67,12 @@ func (e *NativeEndpoint) SrcToString() string {
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Retrieve port.
|
||||
laddr := conn.LocalAddr()
|
||||
uaddr, err := net.ResolveUDPAddr(
|
||||
laddr.Network(),
|
||||
@@ -88,31 +84,26 @@ func listenNet(network string, port int) (*net.UDPConn, int, error) {
|
||||
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) {
|
||||
func createBind(uport uint16) (Bind, uint16, error) {
|
||||
var err error
|
||||
var bind nativeBind
|
||||
var tries int
|
||||
|
||||
again:
|
||||
port := int(uport)
|
||||
|
||||
bind.ipv4, port, err = listenNet("udp4", port)
|
||||
if err != nil && extractErrno(err) != syscall.EAFNOSUPPORT {
|
||||
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
bind.ipv6, port, err = listenNet("udp6", port)
|
||||
if err != nil && extractErrno(err) != syscall.EAFNOSUPPORT {
|
||||
if uport == 0 && err != nil && errors.Is(err, syscall.EADDRINUSE) && tries < 100 {
|
||||
bind.ipv4.Close()
|
||||
tries++
|
||||
goto again
|
||||
}
|
||||
if err != nil && !errors.Is(err, syscall.EAFNOSUPPORT) {
|
||||
bind.ipv4.Close()
|
||||
bind.ipv4 = nil
|
||||
return nil, 0, err
|
||||
@@ -135,6 +126,8 @@ func (bind *nativeBind) Close() error {
|
||||
return err2
|
||||
}
|
||||
|
||||
func (bind *nativeBind) LastMark() uint32 { return 0 }
|
||||
|
||||
func (bind *nativeBind) ReceiveIPv4(buff []byte) (int, Endpoint, error) {
|
||||
if bind.ipv4 == nil {
|
||||
return 0, nil, syscall.EAFNOSUPPORT
|
||||
@@ -2,19 +2,10 @@
|
||||
|
||||
/* 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-tools/tree/contrib/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.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
package conn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -25,16 +16,11 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.zx2c4.com/wireguard/rwcancel"
|
||||
)
|
||||
|
||||
const (
|
||||
FD_ERR = -1
|
||||
)
|
||||
|
||||
type IPv4Source struct {
|
||||
src [4]byte
|
||||
ifindex int32
|
||||
Src [4]byte
|
||||
Ifindex int32
|
||||
}
|
||||
|
||||
type IPv6Source struct {
|
||||
@@ -49,6 +35,10 @@ type NativeEndpoint struct {
|
||||
isV6 bool
|
||||
}
|
||||
|
||||
func (endpoint *NativeEndpoint) Src4() *IPv4Source { return endpoint.src4() }
|
||||
func (endpoint *NativeEndpoint) Dst4() *unix.SockaddrInet4 { return endpoint.dst4() }
|
||||
func (endpoint *NativeEndpoint) IsV6() bool { return endpoint.isV6 }
|
||||
|
||||
func (endpoint *NativeEndpoint) src4() *IPv4Source {
|
||||
return (*IPv4Source)(unsafe.Pointer(&endpoint.src[0]))
|
||||
}
|
||||
@@ -68,9 +58,8 @@ func (endpoint *NativeEndpoint) dst6() *unix.SockaddrInet6 {
|
||||
type nativeBind struct {
|
||||
sock4 int
|
||||
sock6 int
|
||||
netlinkSock int
|
||||
netlinkCancel *rwcancel.RWCancel
|
||||
lastMark uint32
|
||||
closing sync.RWMutex
|
||||
}
|
||||
|
||||
var _ Endpoint = (*NativeEndpoint)(nil)
|
||||
@@ -111,59 +100,34 @@ func CreateEndpoint(s string) (Endpoint, error) {
|
||||
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) {
|
||||
func createBind(port uint16) (Bind, uint16, error) {
|
||||
var err error
|
||||
var bind nativeBind
|
||||
var newPort uint16
|
||||
var tries int
|
||||
originalPort := port
|
||||
|
||||
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 successful
|
||||
|
||||
again:
|
||||
port = originalPort
|
||||
// Attempt ipv6 bind, update port if successful.
|
||||
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 successful
|
||||
|
||||
// Attempt ipv4 bind, update port if successful.
|
||||
bind.sock4, newPort, err = create4(port)
|
||||
if err != nil {
|
||||
if originalPort == 0 && err == syscall.EADDRINUSE && tries < 100 {
|
||||
unix.Close(bind.sock6)
|
||||
tries++
|
||||
goto again
|
||||
}
|
||||
if err != syscall.EAFNOSUPPORT {
|
||||
bind.netlinkCancel.Cancel()
|
||||
unix.Close(bind.sock6)
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -171,14 +135,21 @@ func CreateBind(port uint16, device *Device) (*nativeBind, uint16, error) {
|
||||
port = newPort
|
||||
}
|
||||
|
||||
if bind.sock4 == FD_ERR && bind.sock6 == FD_ERR {
|
||||
if bind.sock4 == -1 && bind.sock6 == -1 {
|
||||
return nil, 0, errors.New("ipv4 and ipv6 not supported")
|
||||
}
|
||||
|
||||
return &bind, port, nil
|
||||
}
|
||||
|
||||
func (bind *nativeBind) LastMark() uint32 {
|
||||
return bind.lastMark
|
||||
}
|
||||
|
||||
func (bind *nativeBind) SetMark(value uint32) error {
|
||||
bind.closing.RLock()
|
||||
defer bind.closing.RUnlock()
|
||||
|
||||
if bind.sock6 != -1 {
|
||||
err := unix.SetsockoptInt(
|
||||
bind.sock6,
|
||||
@@ -209,35 +180,40 @@ func (bind *nativeBind) SetMark(value uint32) error {
|
||||
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
|
||||
var err1, err2 error
|
||||
bind.closing.RLock()
|
||||
if bind.sock6 != -1 {
|
||||
err1 = closeUnblock(bind.sock6)
|
||||
unix.Shutdown(bind.sock6, unix.SHUT_RDWR)
|
||||
}
|
||||
if bind.sock4 != -1 {
|
||||
err2 = closeUnblock(bind.sock4)
|
||||
unix.Shutdown(bind.sock4, unix.SHUT_RDWR)
|
||||
}
|
||||
err3 = bind.netlinkCancel.Cancel()
|
||||
bind.closing.RUnlock()
|
||||
bind.closing.Lock()
|
||||
if bind.sock6 != -1 {
|
||||
err1 = unix.Close(bind.sock6)
|
||||
bind.sock6 = -1
|
||||
}
|
||||
if bind.sock4 != -1 {
|
||||
err2 = unix.Close(bind.sock4)
|
||||
bind.sock4 = -1
|
||||
}
|
||||
bind.closing.Unlock()
|
||||
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
return err3
|
||||
}
|
||||
|
||||
func (bind *nativeBind) ReceiveIPv6(buff []byte) (int, Endpoint, error) {
|
||||
bind.closing.RLock()
|
||||
defer bind.closing.RUnlock()
|
||||
|
||||
var end NativeEndpoint
|
||||
if bind.sock6 == -1 {
|
||||
return 0, nil, syscall.EAFNOSUPPORT
|
||||
return 0, nil, NetErrClosed
|
||||
}
|
||||
n, err := receive6(
|
||||
bind.sock6,
|
||||
@@ -248,9 +224,12 @@ func (bind *nativeBind) ReceiveIPv6(buff []byte) (int, Endpoint, error) {
|
||||
}
|
||||
|
||||
func (bind *nativeBind) ReceiveIPv4(buff []byte) (int, Endpoint, error) {
|
||||
bind.closing.RLock()
|
||||
defer bind.closing.RUnlock()
|
||||
|
||||
var end NativeEndpoint
|
||||
if bind.sock4 == -1 {
|
||||
return 0, nil, syscall.EAFNOSUPPORT
|
||||
return 0, nil, NetErrClosed
|
||||
}
|
||||
n, err := receive4(
|
||||
bind.sock4,
|
||||
@@ -261,15 +240,18 @@ func (bind *nativeBind) ReceiveIPv4(buff []byte) (int, Endpoint, error) {
|
||||
}
|
||||
|
||||
func (bind *nativeBind) Send(buff []byte, end Endpoint) error {
|
||||
bind.closing.RLock()
|
||||
defer bind.closing.RUnlock()
|
||||
|
||||
nend := end.(*NativeEndpoint)
|
||||
if !nend.isV6 {
|
||||
if bind.sock4 == -1 {
|
||||
return syscall.EAFNOSUPPORT
|
||||
return NetErrClosed
|
||||
}
|
||||
return send4(bind.sock4, nend, buff)
|
||||
} else {
|
||||
if bind.sock6 == -1 {
|
||||
return syscall.EAFNOSUPPORT
|
||||
return NetErrClosed
|
||||
}
|
||||
return send6(bind.sock6, nend, buff)
|
||||
}
|
||||
@@ -278,10 +260,10 @@ func (bind *nativeBind) Send(buff []byte, end Endpoint) error {
|
||||
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],
|
||||
end.src4().Src[0],
|
||||
end.src4().Src[1],
|
||||
end.src4().Src[2],
|
||||
end.src4().Src[3],
|
||||
)
|
||||
} else {
|
||||
return end.src6().src[:]
|
||||
@@ -358,7 +340,7 @@ func create4(port uint16) (int, uint16, error) {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return FD_ERR, 0, err
|
||||
return -1, 0, err
|
||||
}
|
||||
|
||||
addr := unix.SockaddrInet4{
|
||||
@@ -368,15 +350,6 @@ func create4(port uint16) (int, uint16, error) {
|
||||
// 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,
|
||||
@@ -389,7 +362,7 @@ func create4(port uint16) (int, uint16, error) {
|
||||
return unix.Bind(fd, &addr)
|
||||
}(); err != nil {
|
||||
unix.Close(fd)
|
||||
return FD_ERR, 0, err
|
||||
return -1, 0, err
|
||||
}
|
||||
|
||||
sa, err := unix.Getsockname(fd)
|
||||
@@ -411,7 +384,7 @@ func create6(port uint16) (int, uint16, error) {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return FD_ERR, 0, err
|
||||
return -1, 0, err
|
||||
}
|
||||
|
||||
// set sockopts and bind
|
||||
@@ -421,16 +394,6 @@ func create6(port uint16) (int, uint16, error) {
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -453,7 +416,7 @@ func create6(port uint16) (int, uint16, error) {
|
||||
|
||||
}(); err != nil {
|
||||
unix.Close(fd)
|
||||
return FD_ERR, 0, err
|
||||
return -1, 0, err
|
||||
}
|
||||
|
||||
sa, err := unix.Getsockname(fd)
|
||||
@@ -478,8 +441,8 @@ func send4(sock int, end *NativeEndpoint, buff []byte) error {
|
||||
Len: unix.SizeofInet4Pktinfo + unix.SizeofCmsghdr,
|
||||
},
|
||||
unix.Inet4Pktinfo{
|
||||
Spec_dst: end.src4().src,
|
||||
Ifindex: end.src4().ifindex,
|
||||
Spec_dst: end.src4().Src,
|
||||
Ifindex: end.src4().Ifindex,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -573,8 +536,8 @@ func receive4(sock int, buff []byte, end *NativeEndpoint) (int, error) {
|
||||
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
|
||||
end.src4().Src = cmsg.pktinfo.Spec_dst
|
||||
end.src4().Ifindex = cmsg.pktinfo.Ifindex
|
||||
}
|
||||
|
||||
return size, nil
|
||||
@@ -611,156 +574,3 @@ func receive6(sock int, buff []byte, end *NativeEndpoint) (int, error) {
|
||||
|
||||
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: unix.RTA_MARK,
|
||||
},
|
||||
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:]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
/* 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
|
||||
|
||||
func (bind *nativeBind) SetMark(mark uint32) error {
|
||||
return nil
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
/* 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 (
|
||||
"runtime"
|
||||
13
conn/net_err_closed.go
Normal file
13
conn/net_err_closed.go
Normal file
@@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package conn
|
||||
|
||||
import _ "unsafe"
|
||||
|
||||
//TODO: replace this with net.ErrClosed for Go 1.16
|
||||
|
||||
//go:linkname NetErrClosed internal/poll.ErrNetClosing
|
||||
var NetErrClosed error
|
||||
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
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"math/bits"
|
||||
"net"
|
||||
@@ -14,15 +15,13 @@ import (
|
||||
)
|
||||
|
||||
type trieEntry struct {
|
||||
cidr uint
|
||||
child [2]*trieEntry
|
||||
bits net.IP
|
||||
peer *Peer
|
||||
|
||||
// index of "branching" bit
|
||||
|
||||
bits net.IP
|
||||
cidr uint
|
||||
bit_at_byte uint
|
||||
bit_at_shift uint
|
||||
perPeerElem *list.Element
|
||||
}
|
||||
|
||||
func isLittleEndian() bool {
|
||||
@@ -69,6 +68,17 @@ func commonBits(ip1 net.IP, ip2 net.IP) uint {
|
||||
}
|
||||
}
|
||||
|
||||
func (node *trieEntry) addToPeerEntries() {
|
||||
node.perPeerElem = node.peer.trieEntries.PushBack(node)
|
||||
}
|
||||
|
||||
func (node *trieEntry) removeFromPeerEntries() {
|
||||
if node.perPeerElem != nil {
|
||||
node.peer.trieEntries.Remove(node.perPeerElem)
|
||||
node.perPeerElem = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (node *trieEntry) removeByPeer(p *Peer) *trieEntry {
|
||||
if node == nil {
|
||||
return node
|
||||
@@ -85,6 +95,7 @@ func (node *trieEntry) removeByPeer(p *Peer) *trieEntry {
|
||||
|
||||
// remove peer & merge
|
||||
|
||||
node.removeFromPeerEntries()
|
||||
node.peer = nil
|
||||
if node.child[0] == nil {
|
||||
return node.child[1]
|
||||
@@ -96,18 +107,28 @@ func (node *trieEntry) choose(ip net.IP) byte {
|
||||
return (ip[node.bit_at_byte] >> node.bit_at_shift) & 1
|
||||
}
|
||||
|
||||
func (node *trieEntry) maskSelf() {
|
||||
mask := net.CIDRMask(int(node.cidr), len(node.bits)*8)
|
||||
for i := 0; i < len(mask); i++ {
|
||||
node.bits[i] &= mask[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (node *trieEntry) insert(ip net.IP, cidr uint, peer *Peer) *trieEntry {
|
||||
|
||||
// at leaf
|
||||
|
||||
if node == nil {
|
||||
return &trieEntry{
|
||||
node := &trieEntry{
|
||||
bits: ip,
|
||||
peer: peer,
|
||||
cidr: cidr,
|
||||
bit_at_byte: cidr / 8,
|
||||
bit_at_shift: 7 - (cidr % 8),
|
||||
}
|
||||
node.maskSelf()
|
||||
node.addToPeerEntries()
|
||||
return node
|
||||
}
|
||||
|
||||
// traverse deeper
|
||||
@@ -115,7 +136,9 @@ func (node *trieEntry) insert(ip net.IP, cidr uint, peer *Peer) *trieEntry {
|
||||
common := commonBits(node.bits, ip)
|
||||
if node.cidr <= cidr && common >= node.cidr {
|
||||
if node.cidr == cidr {
|
||||
node.removeFromPeerEntries()
|
||||
node.peer = peer
|
||||
node.addToPeerEntries()
|
||||
return node
|
||||
}
|
||||
bit := node.choose(ip)
|
||||
@@ -132,6 +155,8 @@ func (node *trieEntry) insert(ip net.IP, cidr uint, peer *Peer) *trieEntry {
|
||||
bit_at_byte: cidr / 8,
|
||||
bit_at_shift: 7 - (cidr % 8),
|
||||
}
|
||||
newNode.maskSelf()
|
||||
newNode.addToPeerEntries()
|
||||
|
||||
cidr = min(cidr, common)
|
||||
|
||||
@@ -146,12 +171,13 @@ func (node *trieEntry) insert(ip net.IP, cidr uint, peer *Peer) *trieEntry {
|
||||
// create new parent for node & newNode
|
||||
|
||||
parent := &trieEntry{
|
||||
bits: ip,
|
||||
bits: append([]byte{}, ip...),
|
||||
peer: nil,
|
||||
cidr: cidr,
|
||||
bit_at_byte: cidr / 8,
|
||||
bit_at_shift: 7 - (cidr % 8),
|
||||
}
|
||||
parent.maskSelf()
|
||||
|
||||
bit := parent.choose(ip)
|
||||
parent.child[bit] = newNode
|
||||
@@ -176,44 +202,22 @@ func (node *trieEntry) lookup(ip net.IP) *Peer {
|
||||
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 {
|
||||
IPv4 *trieEntry
|
||||
IPv6 *trieEntry
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (table *AllowedIPs) EntriesForPeer(peer *Peer) []net.IPNet {
|
||||
func (table *AllowedIPs) EntriesForPeer(peer *Peer, cb func(ip net.IP, cidr uint) bool) {
|
||||
table.mutex.RLock()
|
||||
defer table.mutex.RUnlock()
|
||||
|
||||
allowed := make([]net.IPNet, 0, 10)
|
||||
allowed = table.IPv4.entriesForPeer(peer, allowed)
|
||||
allowed = table.IPv6.entriesForPeer(peer, allowed)
|
||||
return allowed
|
||||
}
|
||||
|
||||
func (table *AllowedIPs) Reset() {
|
||||
table.mutex.Lock()
|
||||
defer table.mutex.Unlock()
|
||||
|
||||
table.IPv4 = nil
|
||||
table.IPv6 = nil
|
||||
for elem := peer.trieEntries.Front(); elem != nil; elem = elem.Next() {
|
||||
node := elem.Value.(*trieEntry)
|
||||
if !cb(node.bits, node.cidr) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (table *AllowedIPs) RemoveByPeer(peer *Peer) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -73,11 +73,11 @@ func TestTrieRandomIPv4(t *testing.T) {
|
||||
|
||||
const AddressLength = 4
|
||||
|
||||
for n := 0; n < NumberOfPeers; n += 1 {
|
||||
for n := 0; n < NumberOfPeers; n++ {
|
||||
peers = append(peers, &Peer{})
|
||||
}
|
||||
|
||||
for n := 0; n < NumberOfAddresses; n += 1 {
|
||||
for n := 0; n < NumberOfAddresses; n++ {
|
||||
var addr [AddressLength]byte
|
||||
rand.Read(addr[:])
|
||||
cidr := uint(rand.Uint32() % (AddressLength * 8))
|
||||
@@ -86,7 +86,7 @@ func TestTrieRandomIPv4(t *testing.T) {
|
||||
slow = slow.Insert(addr[:], cidr, peers[index])
|
||||
}
|
||||
|
||||
for n := 0; n < NumberOfTests; n += 1 {
|
||||
for n := 0; n < NumberOfTests; n++ {
|
||||
var addr [AddressLength]byte
|
||||
rand.Read(addr[:])
|
||||
peer1 := slow.Lookup(addr[:])
|
||||
@@ -106,11 +106,11 @@ func TestTrieRandomIPv6(t *testing.T) {
|
||||
|
||||
const AddressLength = 16
|
||||
|
||||
for n := 0; n < NumberOfPeers; n += 1 {
|
||||
for n := 0; n < NumberOfPeers; n++ {
|
||||
peers = append(peers, &Peer{})
|
||||
}
|
||||
|
||||
for n := 0; n < NumberOfAddresses; n += 1 {
|
||||
for n := 0; n < NumberOfAddresses; n++ {
|
||||
var addr [AddressLength]byte
|
||||
rand.Read(addr[:])
|
||||
cidr := uint(rand.Uint32() % (AddressLength * 8))
|
||||
@@ -119,7 +119,7 @@ func TestTrieRandomIPv6(t *testing.T) {
|
||||
slow = slow.Insert(addr[:], cidr, peers[index])
|
||||
}
|
||||
|
||||
for n := 0; n < NumberOfTests; n += 1 {
|
||||
for n := 0; n < NumberOfTests; n++ {
|
||||
var addr [AddressLength]byte
|
||||
rand.Read(addr[:])
|
||||
peer1 := slow.Lookup(addr[:])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -20,26 +20,6 @@ type testPairCommonBits struct {
|
||||
match uint
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
tests := []testPairCommonBits{
|
||||
@@ -70,11 +50,11 @@ func benchmarkTrie(peerNumber int, addressNumber int, addressLength int, b *test
|
||||
|
||||
const AddressLength = 4
|
||||
|
||||
for n := 0; n < peerNumber; n += 1 {
|
||||
for n := 0; n < peerNumber; n++ {
|
||||
peers = append(peers, &Peer{})
|
||||
}
|
||||
|
||||
for n := 0; n < addressNumber; n += 1 {
|
||||
for n := 0; n < addressNumber; n++ {
|
||||
var addr [AddressLength]byte
|
||||
rand.Read(addr[:])
|
||||
cidr := uint(rand.Uint32() % (AddressLength * 8))
|
||||
@@ -82,7 +62,7 @@ func benchmarkTrie(peerNumber int, addressNumber int, addressLength int, b *test
|
||||
trie = trie.insert(addr[:], cidr, peers[index])
|
||||
}
|
||||
|
||||
for n := 0; n < b.N; n += 1 {
|
||||
for n := 0; n < b.N; n++ {
|
||||
var addr [AddressLength]byte
|
||||
rand.Read(addr[:])
|
||||
trie.lookup(addr[:])
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
)
|
||||
|
||||
type DummyDatagram struct {
|
||||
msg []byte
|
||||
endpoint Endpoint
|
||||
world bool // better type
|
||||
endpoint conn.Endpoint
|
||||
}
|
||||
|
||||
type DummyBind struct {
|
||||
in6 chan DummyDatagram
|
||||
ou6 chan DummyDatagram
|
||||
in4 chan DummyDatagram
|
||||
ou4 chan DummyDatagram
|
||||
closed bool
|
||||
}
|
||||
|
||||
@@ -25,7 +26,7 @@ func (b *DummyBind) SetMark(v uint32) error {
|
||||
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
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
|
||||
func (b *DummyBind) ReceiveIPv4(buff []byte) (int, Endpoint, error) {
|
||||
func (b *DummyBind) ReceiveIPv4(buff []byte) (int, conn.Endpoint, error) {
|
||||
datagram, ok := <-b.in4
|
||||
if !ok {
|
||||
return 0, nil, errors.New("closed")
|
||||
@@ -50,6 +51,6 @@ func (b *DummyBind) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *DummyBind) Send(buff []byte, end Endpoint) error {
|
||||
func (b *DummyBind) Send(buff []byte, end conn.Endpoint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import "errors"
|
||||
|
||||
func (device *Device) PeekLookAtSocketFd4() (fd int, err error) {
|
||||
nb, ok := device.net.bind.(*nativeBind)
|
||||
if !ok {
|
||||
return 0, errors.New("no socket exists")
|
||||
}
|
||||
sysconn, err := nb.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) {
|
||||
nb, ok := device.net.bind.(*nativeBind)
|
||||
if !ok {
|
||||
return 0, errors.New("no socket exists")
|
||||
}
|
||||
sysconn, err := nb.ipv6.SyscallConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = sysconn.Control(func(f uintptr) {
|
||||
fd = int(f)
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
187
device/conn.go
187
device/conn.go
@@ -1,187 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
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 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
|
||||
}
|
||||
|
||||
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,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
const (
|
||||
RekeyAfterMessages = (1 << 60)
|
||||
RejectAfterMessages = (1 << 64) - (1 << 4) - 1
|
||||
RejectAfterMessages = (1 << 64) - (1 << 13) - 1
|
||||
RekeyAfterTime = time.Second * 120
|
||||
RekeyAttemptTime = time.Second * 90
|
||||
RekeyTimeout = time.Second * 5
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
459
device/device.go
459
device/device.go
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -11,35 +11,40 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/ratelimiter"
|
||||
"golang.zx2c4.com/wireguard/rwcancel"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
const (
|
||||
DeviceRoutineNumberPerCPU = 3
|
||||
DeviceRoutineNumberAdditional = 2
|
||||
)
|
||||
|
||||
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 {
|
||||
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
|
||||
// mu protects state changes.
|
||||
sync.Mutex
|
||||
changing AtomicBool
|
||||
current bool
|
||||
}
|
||||
|
||||
net struct {
|
||||
starting sync.WaitGroup
|
||||
stopping sync.WaitGroup
|
||||
sync.RWMutex
|
||||
bind Bind // bind interface
|
||||
bind conn.Bind // bind interface
|
||||
netlinkCancel *rwcancel.RWCancel
|
||||
port uint16 // listening port
|
||||
fwmark uint32 // mark value (0 = disabled)
|
||||
}
|
||||
@@ -51,153 +56,172 @@ type Device struct {
|
||||
}
|
||||
|
||||
peers struct {
|
||||
sync.RWMutex
|
||||
empty AtomicBool // empty reports whether len(keyMap) == 0
|
||||
sync.RWMutex // protects keyMap
|
||||
keyMap map[NoisePublicKey]*Peer
|
||||
}
|
||||
|
||||
// unprotected / "self-synchronising resources"
|
||||
|
||||
allowedips AllowedIPs
|
||||
indexTable IndexTable
|
||||
cookieChecker CookieChecker
|
||||
|
||||
rate struct {
|
||||
underLoadUntil atomic.Value
|
||||
underLoadUntil int64
|
||||
limiter ratelimiter.Ratelimiter
|
||||
}
|
||||
|
||||
pool struct {
|
||||
messageBufferPool *sync.Pool
|
||||
messageBufferReuseChan chan *[MaxMessageSize]byte
|
||||
inboundElementPool *sync.Pool
|
||||
inboundElementReuseChan chan *QueueInboundElement
|
||||
outboundElementPool *sync.Pool
|
||||
outboundElementReuseChan chan *QueueOutboundElement
|
||||
messageBuffers *WaitPool
|
||||
inboundElements *WaitPool
|
||||
outboundElements *WaitPool
|
||||
}
|
||||
|
||||
queue struct {
|
||||
encryption chan *QueueOutboundElement
|
||||
decryption chan *QueueInboundElement
|
||||
handshake chan QueueHandshakeElement
|
||||
}
|
||||
|
||||
signals struct {
|
||||
stop chan struct{}
|
||||
encryption *outboundQueue
|
||||
decryption *inboundQueue
|
||||
handshake *handshakeQueue
|
||||
}
|
||||
|
||||
tun struct {
|
||||
device tun.Device
|
||||
mtu int32
|
||||
}
|
||||
|
||||
ipcMutex sync.RWMutex
|
||||
closed chan struct{}
|
||||
log *Logger
|
||||
}
|
||||
|
||||
/* Converts the peer into a "zombie", which remains in the peer map,
|
||||
* but processes no packets and does not exists in the routing table.
|
||||
*
|
||||
* Must hold device.peers.Mutex
|
||||
*/
|
||||
func unsafeRemovePeer(device *Device, peer *Peer, key NoisePublicKey) {
|
||||
// deviceState represents the state of a Device.
|
||||
// There are three states: down, up, closed.
|
||||
// Transitions:
|
||||
//
|
||||
// down -----+
|
||||
// ↑↓ ↓
|
||||
// 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
|
||||
|
||||
device.allowedips.RemoveByPeer(peer)
|
||||
peer.Stop()
|
||||
|
||||
// remove from peer map
|
||||
|
||||
delete(device.peers.keyMap, key)
|
||||
device.peers.empty.Set(len(device.peers.keyMap) == 0)
|
||||
}
|
||||
|
||||
func deviceUpdateState(device *Device) {
|
||||
|
||||
// check if state already being updated (guard)
|
||||
|
||||
if device.state.changing.Swap(true) {
|
||||
return
|
||||
}
|
||||
|
||||
// compare to current state of device
|
||||
|
||||
// changeState attempts to change the device state to match want.
|
||||
func (device *Device) changeState(want deviceState) (err error) {
|
||||
device.state.Lock()
|
||||
|
||||
newIsUp := device.isUp.Get()
|
||||
|
||||
if newIsUp == device.state.current {
|
||||
device.state.changing.Set(false)
|
||||
device.state.Unlock()
|
||||
return
|
||||
defer device.state.Unlock()
|
||||
old := device.deviceState()
|
||||
if old == deviceStateClosed {
|
||||
// once closed, always closed
|
||||
device.log.Verbosef("Interface closed, ignored requested state %s", want)
|
||||
return nil
|
||||
}
|
||||
|
||||
// change state of device
|
||||
|
||||
switch newIsUp {
|
||||
case true:
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
device.log.Error.Printf("Unable to update bind: %v\n", err)
|
||||
device.isUp.Set(false)
|
||||
switch want {
|
||||
case old:
|
||||
return nil
|
||||
case deviceStateUp:
|
||||
atomic.StoreUint32(&device.state.state, uint32(deviceStateUp))
|
||||
err = device.upLocked()
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
fallthrough // up failed; bring the device all the way back down
|
||||
case deviceStateDown:
|
||||
atomic.StoreUint32(&device.state.state, uint32(deviceStateDown))
|
||||
errDown := device.downLocked()
|
||||
if err == nil {
|
||||
err = errDown
|
||||
}
|
||||
}
|
||||
device.log.Verbosef("Interface state was %s, requested %s, now %s", old, want, device.deviceState())
|
||||
return
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (device *Device) upLocked() error {
|
||||
if err := device.BindUpdate(); err != nil {
|
||||
device.log.Errorf("Unable to update bind: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
device.peers.RLock()
|
||||
for _, peer := range device.peers.keyMap {
|
||||
peer.Start()
|
||||
if peer.persistentKeepaliveInterval > 0 {
|
||||
if atomic.LoadUint32(&peer.persistentKeepaliveInterval) > 0 {
|
||||
peer.SendKeepalive()
|
||||
}
|
||||
}
|
||||
device.peers.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// downLocked attempts to bring the device down.
|
||||
// The caller must hold device.state.mu and is responsible for updating device.state.state.
|
||||
func (device *Device) downLocked() error {
|
||||
err := device.BindClose()
|
||||
if err != nil {
|
||||
device.log.Errorf("Bind close failed: %v", err)
|
||||
}
|
||||
|
||||
case false:
|
||||
device.BindClose()
|
||||
device.peers.RLock()
|
||||
for _, peer := range device.peers.keyMap {
|
||||
peer.Stop()
|
||||
}
|
||||
device.peers.RUnlock()
|
||||
}
|
||||
|
||||
// update state variables
|
||||
|
||||
device.state.current = newIsUp
|
||||
device.state.changing.Set(false)
|
||||
device.state.Unlock()
|
||||
|
||||
// check for state change in the mean time
|
||||
|
||||
deviceUpdateState(device)
|
||||
return err
|
||||
}
|
||||
|
||||
func (device *Device) Up() {
|
||||
|
||||
// closed device cannot be brought up
|
||||
|
||||
if device.isClosed.Get() {
|
||||
return
|
||||
}
|
||||
|
||||
device.isUp.Set(true)
|
||||
deviceUpdateState(device)
|
||||
func (device *Device) Up() error {
|
||||
return device.changeState(deviceStateUp)
|
||||
}
|
||||
|
||||
func (device *Device) Down() {
|
||||
device.isUp.Set(false)
|
||||
deviceUpdateState(device)
|
||||
func (device *Device) Down() error {
|
||||
return device.changeState(deviceStateDown)
|
||||
}
|
||||
|
||||
func (device *Device) IsUnderLoad() bool {
|
||||
|
||||
// check if currently under load
|
||||
|
||||
now := time.Now()
|
||||
underLoad := len(device.queue.handshake) >= UnderLoadQueueSize
|
||||
underLoad := len(device.queue.handshake.c) >= UnderLoadQueueSize
|
||||
if underLoad {
|
||||
device.rate.underLoadUntil.Store(now.Add(UnderLoadAfterTime))
|
||||
atomic.StoreInt64(&device.rate.underLoadUntil, now.Add(UnderLoadAfterTime).UnixNano())
|
||||
return true
|
||||
}
|
||||
|
||||
// check if recently under load
|
||||
|
||||
until := device.rate.underLoadUntil.Load().(time.Time)
|
||||
return until.After(now)
|
||||
return atomic.LoadInt64(&device.rate.underLoadUntil) > now.UnixNano()
|
||||
}
|
||||
|
||||
func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
||||
@@ -224,7 +248,9 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
||||
publicKey := sk.publicKey()
|
||||
for key, peer := range device.peers.keyMap {
|
||||
if peer.handshake.remoteStatic.Equals(publicKey) {
|
||||
unsafeRemovePeer(device, peer, key)
|
||||
peer.handshake.mutex.RUnlock()
|
||||
removePeerLocked(device, peer, key)
|
||||
peer.handshake.mutex.RLock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,24 +262,12 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
||||
|
||||
// do static-static DH pre-computations
|
||||
|
||||
rmKey := device.staticIdentity.privateKey.IsZero()
|
||||
|
||||
expiredPeers := make([]*Peer, 0, len(device.peers.keyMap))
|
||||
for key, peer := range device.peers.keyMap {
|
||||
for _, peer := range device.peers.keyMap {
|
||||
handshake := &peer.handshake
|
||||
|
||||
if rmKey {
|
||||
handshake.precomputedStaticStatic = [NoisePublicKeySize]byte{}
|
||||
} else {
|
||||
handshake.precomputedStaticStatic = device.staticIdentity.privateKey.sharedSecret(handshake.remoteStatic)
|
||||
}
|
||||
|
||||
if isZero(handshake.precomputedStaticStatic[:]) {
|
||||
unsafeRemovePeer(device, peer, key)
|
||||
} else {
|
||||
expiredPeers = append(expiredPeers, peer)
|
||||
}
|
||||
}
|
||||
|
||||
for _, peer := range lockedPeers {
|
||||
peer.handshake.mutex.RUnlock()
|
||||
@@ -267,39 +281,26 @@ func (device *Device) SetPrivateKey(sk NoisePrivateKey) error {
|
||||
|
||||
func NewDevice(tunDevice tun.Device, logger *Logger) *Device {
|
||||
device := new(Device)
|
||||
|
||||
device.isUp.Set(false)
|
||||
device.isClosed.Set(false)
|
||||
|
||||
device.state.state = uint32(deviceStateDown)
|
||||
device.closed = make(chan struct{})
|
||||
device.log = logger
|
||||
|
||||
device.tun.device = tunDevice
|
||||
mtu, err := device.tun.device.MTU()
|
||||
if err != nil {
|
||||
logger.Error.Println("Trouble determining MTU, assuming default:", err)
|
||||
device.log.Errorf("Trouble determining MTU, assuming default: %v", err)
|
||||
mtu = DefaultMTU
|
||||
}
|
||||
device.tun.mtu = int32(mtu)
|
||||
|
||||
device.peers.keyMap = make(map[NoisePublicKey]*Peer)
|
||||
|
||||
device.rate.limiter.Init()
|
||||
device.rate.underLoadUntil.Store(time.Time{})
|
||||
|
||||
device.indexTable.Init()
|
||||
device.allowedips.Reset()
|
||||
|
||||
device.PopulatePools()
|
||||
|
||||
// create queues
|
||||
|
||||
device.queue.handshake = make(chan QueueHandshakeElement, QueueHandshakeSize)
|
||||
device.queue.encryption = make(chan *QueueOutboundElement, QueueOutboundSize)
|
||||
device.queue.decryption = make(chan *QueueInboundElement, QueueInboundSize)
|
||||
|
||||
// prepare signals
|
||||
|
||||
device.signals.stop = make(chan struct{})
|
||||
device.queue.handshake = newHandshakeQueue()
|
||||
device.queue.encryption = newOutboundQueue()
|
||||
device.queue.decryption = newInboundQueue()
|
||||
|
||||
// prepare net
|
||||
|
||||
@@ -309,21 +310,19 @@ func NewDevice(tunDevice tun.Device, logger *Logger) *Device {
|
||||
// start workers
|
||||
|
||||
cpus := runtime.NumCPU()
|
||||
device.state.starting.Wait()
|
||||
device.state.stopping.Wait()
|
||||
device.state.stopping.Add(DeviceRoutineNumberPerCPU*cpus + DeviceRoutineNumberAdditional)
|
||||
device.state.starting.Add(DeviceRoutineNumberPerCPU*cpus + DeviceRoutineNumberAdditional)
|
||||
for i := 0; i < cpus; i += 1 {
|
||||
device.queue.encryption.wg.Add(cpus) // One for each RoutineHandshake
|
||||
for i := 0; i < cpus; i++ {
|
||||
go device.RoutineEncryption()
|
||||
go device.RoutineDecryption()
|
||||
go device.RoutineHandshake()
|
||||
}
|
||||
|
||||
device.state.stopping.Add(1) // RoutineReadFromTUN
|
||||
device.queue.encryption.wg.Add(1) // RoutineReadFromTUN
|
||||
go device.RoutineReadFromTUN()
|
||||
go device.RoutineTUNEventReader()
|
||||
|
||||
device.state.starting.Wait()
|
||||
|
||||
return device
|
||||
}
|
||||
|
||||
@@ -341,7 +340,7 @@ func (device *Device) RemovePeer(key NoisePublicKey) {
|
||||
|
||||
peer, ok := device.peers.keyMap[key]
|
||||
if ok {
|
||||
unsafeRemovePeer(device, peer, key)
|
||||
removePeerLocked(device, peer, key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,67 +349,48 @@ func (device *Device) RemoveAllPeers() {
|
||||
defer device.peers.Unlock()
|
||||
|
||||
for key, peer := range device.peers.keyMap {
|
||||
unsafeRemovePeer(device, peer, key)
|
||||
removePeerLocked(device, peer, key)
|
||||
}
|
||||
|
||||
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() {
|
||||
if device.isClosed.Swap(true) {
|
||||
return
|
||||
}
|
||||
|
||||
device.state.starting.Wait()
|
||||
|
||||
device.log.Info.Println("Device closing")
|
||||
device.state.changing.Set(true)
|
||||
device.state.Lock()
|
||||
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.BindClose()
|
||||
|
||||
device.isUp.Set(false)
|
||||
|
||||
close(device.signals.stop)
|
||||
device.downLocked()
|
||||
|
||||
// Remove peers before closing queues,
|
||||
// because peers assume that queues are active.
|
||||
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.FlushPacketQueues()
|
||||
|
||||
device.rate.limiter.Close()
|
||||
|
||||
device.state.changing.Set(false)
|
||||
device.log.Info.Println("Interface closed")
|
||||
device.log.Verbosef("Device closed")
|
||||
close(device.closed)
|
||||
}
|
||||
|
||||
func (device *Device) Wait() chan struct{} {
|
||||
return device.signals.stop
|
||||
return device.closed
|
||||
}
|
||||
|
||||
func (device *Device) SendKeepalivesToPeersWithCurrentKeypair() {
|
||||
if device.isClosed.Get() {
|
||||
if !device.isUp() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -425,3 +405,122 @@ func (device *Device) SendKeepalivesToPeersWithCurrentKeypair() {
|
||||
}
|
||||
device.peers.RUnlock()
|
||||
}
|
||||
|
||||
func unsafeCloseBind(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.bind = nil
|
||||
}
|
||||
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 := unsafeCloseBind(device); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// open new sockets
|
||||
if !device.isUp() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// bind to new port
|
||||
var err error
|
||||
netc := &device.net
|
||||
netc.bind, netc.port, err = conn.CreateBind(netc.port)
|
||||
if err != nil {
|
||||
netc.bind = nil
|
||||
netc.port = 0
|
||||
return err
|
||||
}
|
||||
netc.netlinkCancel, err = device.startRouteListener(netc.bind)
|
||||
if err != nil {
|
||||
netc.bind.Close()
|
||||
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.stopping.Add(2)
|
||||
device.queue.decryption.wg.Add(2) // each RoutineReceiveIncoming goroutine writes to device.queue.decryption
|
||||
device.queue.handshake.wg.Add(2) // each RoutineReceiveIncoming goroutine writes to device.queue.handshake
|
||||
go device.RoutineReceiveIncoming(ipv4.Version, netc.bind)
|
||||
go device.RoutineReceiveIncoming(ipv6.Version, netc.bind)
|
||||
|
||||
device.log.Verbosef("UDP bind has been updated")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (device *Device) BindClose() error {
|
||||
device.net.Lock()
|
||||
err := unsafeCloseBind(device)
|
||||
device.net.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,216 +1,316 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"golang.zx2c4.com/wireguard/tun/tuntest"
|
||||
)
|
||||
|
||||
func TestTwoDevicePing(t *testing.T) {
|
||||
// TODO(crawshaw): pick unused ports on localhost
|
||||
cfg1 := `private_key=481eb0d8113a4a5da532d2c3e9c14b53c8454b34ab109676f6b58c2245e37b58
|
||||
listen_port=53511
|
||||
replace_peers=true
|
||||
public_key=f70dbb6b1b92a1dde1c783b297016af3f572fef13b0abb16a2623d89a58e9725
|
||||
protocol_version=1
|
||||
replace_allowed_ips=true
|
||||
allowed_ip=1.0.0.2/32
|
||||
endpoint=127.0.0.1:53512`
|
||||
tun1 := NewChannelTUN()
|
||||
dev1 := NewDevice(tun1.TUN(), NewLogger(LogLevelDebug, "dev1: "))
|
||||
dev1.Up()
|
||||
defer dev1.Close()
|
||||
if err := dev1.IpcSetOperation(bufio.NewReader(strings.NewReader(cfg1))); err != nil {
|
||||
t.Fatal(err)
|
||||
// uapiCfg returns a string that contains cfg formatted use with IpcSet.
|
||||
// cfg is a series of alternating key/value strings.
|
||||
// uapiCfg exists because editors and humans like to insert
|
||||
// whitespace into configs, which can cause failures, some of which are silent.
|
||||
// For example, a leading blank newline causes the remainder
|
||||
// of the config to be silently ignored.
|
||||
func uapiCfg(cfg ...string) string {
|
||||
if len(cfg)%2 != 0 {
|
||||
panic("odd number of args to uapiReader")
|
||||
}
|
||||
|
||||
cfg2 := `private_key=98c7989b1661a0d64fd6af3502000f87716b7c4bbcf00d04fc6073aa7b539768
|
||||
listen_port=53512
|
||||
replace_peers=true
|
||||
public_key=49e80929259cebdda4f322d6d2b1a6fad819d603acd26fd5d845e7a123036427
|
||||
protocol_version=1
|
||||
replace_allowed_ips=true
|
||||
allowed_ip=1.0.0.1/32
|
||||
endpoint=127.0.0.1:53511`
|
||||
tun2 := NewChannelTUN()
|
||||
dev2 := NewDevice(tun2.TUN(), NewLogger(LogLevelDebug, "dev2: "))
|
||||
dev2.Up()
|
||||
defer dev2.Close()
|
||||
if err := dev2.IpcSetOperation(bufio.NewReader(strings.NewReader(cfg2))); err != nil {
|
||||
t.Fatal(err)
|
||||
buf := new(bytes.Buffer)
|
||||
for i, s := range cfg {
|
||||
buf.WriteString(s)
|
||||
sep := byte('\n')
|
||||
if i%2 == 0 {
|
||||
sep = '='
|
||||
}
|
||||
|
||||
t.Run("ping 1.0.0.1", func(t *testing.T) {
|
||||
msg2to1 := ping(net.ParseIP("1.0.0.1"), net.ParseIP("1.0.0.2"))
|
||||
tun2.Outbound <- msg2to1
|
||||
select {
|
||||
case msgRecv := <-tun1.Inbound:
|
||||
if !bytes.Equal(msg2to1, msgRecv) {
|
||||
t.Error("ping did not transit correctly")
|
||||
buf.WriteByte(sep)
|
||||
}
|
||||
case <-time.After(300 * time.Millisecond):
|
||||
t.Error("ping did not transit")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ping 1.0.0.2", func(t *testing.T) {
|
||||
msg1to2 := ping(net.ParseIP("1.0.0.2"), net.ParseIP("1.0.0.1"))
|
||||
tun1.Outbound <- msg1to2
|
||||
select {
|
||||
case msgRecv := <-tun2.Inbound:
|
||||
if !bytes.Equal(msg1to2, msgRecv) {
|
||||
t.Error("return ping did not transit correctly")
|
||||
}
|
||||
case <-time.After(300 * time.Millisecond):
|
||||
t.Error("return ping did not transit")
|
||||
}
|
||||
})
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func ping(dst, src net.IP) []byte {
|
||||
localPort := uint16(1337)
|
||||
seq := uint16(0)
|
||||
|
||||
payload := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(payload[0:], localPort)
|
||||
binary.BigEndian.PutUint16(payload[2:], seq)
|
||||
|
||||
return genICMPv4(payload, dst, src)
|
||||
}
|
||||
|
||||
// checksum is the "internet checksum" from https://tools.ietf.org/html/rfc1071.
|
||||
func checksum(buf []byte, initial uint16) uint16 {
|
||||
v := uint32(initial)
|
||||
for i := 0; i < len(buf)-1; i += 2 {
|
||||
v += uint32(binary.BigEndian.Uint16(buf[i:]))
|
||||
// genConfigs generates a pair of configs that connect to each other.
|
||||
// The configs use distinct, probably-usable ports.
|
||||
func genConfigs(tb testing.TB) (cfgs [2]string, endpointCfgs [2]string) {
|
||||
var key1, key2 NoisePrivateKey
|
||||
_, err := rand.Read(key1[:])
|
||||
if err != nil {
|
||||
tb.Errorf("unable to generate private key random bytes: %v", err)
|
||||
}
|
||||
if len(buf)%2 == 1 {
|
||||
v += uint32(buf[len(buf)-1]) << 8
|
||||
_, err = rand.Read(key2[:])
|
||||
if err != nil {
|
||||
tb.Errorf("unable to generate private key random bytes: %v", err)
|
||||
}
|
||||
for v > 0xffff {
|
||||
v = (v >> 16) + (v & 0xffff)
|
||||
}
|
||||
return ^uint16(v)
|
||||
}
|
||||
pub1, pub2 := key1.publicKey(), key2.publicKey()
|
||||
|
||||
func genICMPv4(payload []byte, dst, src net.IP) []byte {
|
||||
const (
|
||||
icmpv4ProtocolNumber = 1
|
||||
icmpv4Echo = 8
|
||||
icmpv4ChecksumOffset = 2
|
||||
icmpv4Size = 8
|
||||
ipv4Size = 20
|
||||
ipv4TotalLenOffset = 2
|
||||
ipv4ChecksumOffset = 10
|
||||
ttl = 65
|
||||
cfgs[0] = uapiCfg(
|
||||
"private_key", hex.EncodeToString(key1[:]),
|
||||
"listen_port", "0",
|
||||
"replace_peers", "true",
|
||||
"public_key", hex.EncodeToString(pub2[:]),
|
||||
"protocol_version", "1",
|
||||
"replace_allowed_ips", "true",
|
||||
"allowed_ip", "1.0.0.2/32",
|
||||
)
|
||||
|
||||
hdr := make([]byte, ipv4Size+icmpv4Size)
|
||||
|
||||
ip := hdr[0:ipv4Size]
|
||||
icmpv4 := hdr[ipv4Size : ipv4Size+icmpv4Size]
|
||||
|
||||
// https://tools.ietf.org/html/rfc792
|
||||
icmpv4[0] = icmpv4Echo // type
|
||||
icmpv4[1] = 0 // code
|
||||
chksum := ^checksum(icmpv4, checksum(payload, 0))
|
||||
binary.BigEndian.PutUint16(icmpv4[icmpv4ChecksumOffset:], chksum)
|
||||
|
||||
// https://tools.ietf.org/html/rfc760 section 3.1
|
||||
length := uint16(len(hdr) + len(payload))
|
||||
ip[0] = (4 << 4) | (ipv4Size / 4)
|
||||
binary.BigEndian.PutUint16(ip[ipv4TotalLenOffset:], length)
|
||||
ip[8] = ttl
|
||||
ip[9] = icmpv4ProtocolNumber
|
||||
copy(ip[12:], src.To4())
|
||||
copy(ip[16:], dst.To4())
|
||||
chksum = ^checksum(ip[:], 0)
|
||||
binary.BigEndian.PutUint16(ip[ipv4ChecksumOffset:], chksum)
|
||||
|
||||
var v []byte
|
||||
v = append(v, hdr...)
|
||||
v = append(v, payload...)
|
||||
return []byte(v)
|
||||
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
|
||||
}
|
||||
|
||||
// TODO(crawshaw): find a reusable home for this. package devicetest?
|
||||
type ChannelTUN struct {
|
||||
Inbound chan []byte // incoming packets, closed on TUN close
|
||||
Outbound chan []byte // outbound packets, blocks forever on TUN close
|
||||
// A testPair is a pair of testPeers.
|
||||
type testPair [2]testPeer
|
||||
|
||||
closed chan struct{}
|
||||
events chan tun.Event
|
||||
tun chTun
|
||||
// A testPeer is a peer used for testing.
|
||||
type testPeer struct {
|
||||
tun *tuntest.ChannelTUN
|
||||
dev *Device
|
||||
ip net.IP
|
||||
}
|
||||
|
||||
func NewChannelTUN() *ChannelTUN {
|
||||
c := &ChannelTUN{
|
||||
Inbound: make(chan []byte),
|
||||
Outbound: make(chan []byte),
|
||||
closed: make(chan struct{}),
|
||||
events: make(chan tun.Event, 1),
|
||||
type SendDirection bool
|
||||
|
||||
const (
|
||||
Ping SendDirection = true
|
||||
Pong SendDirection = false
|
||||
)
|
||||
|
||||
func (d SendDirection) String() string {
|
||||
if d == Ping {
|
||||
return "ping"
|
||||
}
|
||||
c.tun.c = c
|
||||
c.events <- tun.EventUp
|
||||
return c
|
||||
return "pong"
|
||||
}
|
||||
|
||||
func (c *ChannelTUN) TUN() tun.Device {
|
||||
return &c.tun
|
||||
}
|
||||
|
||||
type chTun struct {
|
||||
c *ChannelTUN
|
||||
}
|
||||
|
||||
func (t *chTun) File() *os.File { return nil }
|
||||
|
||||
func (t *chTun) Read(data []byte, offset int) (int, error) {
|
||||
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 <-t.c.closed:
|
||||
return 0, io.EOF // TODO(crawshaw): what is the correct error value?
|
||||
case msg := <-t.c.Outbound:
|
||||
return copy(data[offset:], msg), nil
|
||||
case msgRecv := <-p0.tun.Inbound:
|
||||
if !bytes.Equal(msg, msgRecv) {
|
||||
err = fmt.Errorf("%s did not transit correctly", ping)
|
||||
}
|
||||
}
|
||||
|
||||
// Write is called by the wireguard device to deliver a packet for routing.
|
||||
func (t *chTun) Write(data []byte, offset int) (int, error) {
|
||||
if offset == -1 {
|
||||
close(t.c.closed)
|
||||
close(t.c.events)
|
||||
return 0, io.EOF
|
||||
case <-timer.C:
|
||||
err = fmt.Errorf("%s did not transit", ping)
|
||||
case <-done:
|
||||
}
|
||||
msg := make([]byte, len(data)-offset)
|
||||
copy(msg, data[offset:])
|
||||
if err != nil {
|
||||
// The error may have occurred because the test is done.
|
||||
select {
|
||||
case <-t.c.closed:
|
||||
return 0, io.EOF // TODO(crawshaw): what is the correct error value?
|
||||
case t.c.Inbound <- msg:
|
||||
return len(data) - offset, nil
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
// Real error.
|
||||
tb.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *chTun) Flush() error { return nil }
|
||||
func (t *chTun) MTU() (int, error) { return DefaultMTU, nil }
|
||||
func (t *chTun) Name() (string, error) { return "loopbackTun1", nil }
|
||||
func (t *chTun) Events() chan tun.Event { return t.c.events }
|
||||
func (t *chTun) Close() error {
|
||||
t.Write(nil, -1)
|
||||
return nil
|
||||
// genTestPair creates a testPair.
|
||||
func genTestPair(tb testing.TB) (pair testPair) {
|
||||
cfg, endpointCfg := genConfigs(tb)
|
||||
// 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(), 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)
|
||||
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 = 20
|
||||
const otrials = 1
|
||||
|
||||
for n := 0; n < otrials; n++ {
|
||||
pair := genTestPair(t)
|
||||
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++ {
|
||||
start := time.Now()
|
||||
for {
|
||||
if err := d.Up(); err != nil {
|
||||
if errors.Is(err, syscall.EADDRINUSE) && time.Now().Sub(start) < time.Second*4 {
|
||||
// Some other test process is racing with us, so try again.
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
continue
|
||||
}
|
||||
t.Errorf("failed up bring up device: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
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)
|
||||
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 assertNil(t *testing.T, err error) {
|
||||
@@ -236,3 +336,99 @@ func randDevice(t *testing.T) *Device {
|
||||
device.SetPrivateKey(sk)
|
||||
return device
|
||||
}
|
||||
|
||||
func BenchmarkLatency(b *testing.B) {
|
||||
pair := genTestPair(b)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
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(ioutil.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
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type IndexTableEntry struct {
|
||||
@@ -25,7 +25,8 @@ type IndexTable struct {
|
||||
func randUint32() (uint32, error) {
|
||||
var integer [4]byte
|
||||
_, 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() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -8,7 +8,9 @@ package device
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.zx2c4.com/wireguard/replay"
|
||||
)
|
||||
@@ -21,10 +23,10 @@ import (
|
||||
*/
|
||||
|
||||
type Keypair struct {
|
||||
sendNonce uint64
|
||||
sendNonce uint64 // accessed atomically
|
||||
send cipher.AEAD
|
||||
receive cipher.AEAD
|
||||
replayFilter replay.ReplayFilter
|
||||
replayFilter replay.Filter
|
||||
isInitiator bool
|
||||
created time.Time
|
||||
localIndex uint32
|
||||
@@ -38,6 +40,14 @@ type Keypairs struct {
|
||||
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 {
|
||||
kp.RLock()
|
||||
defer kp.RUnlock()
|
||||
|
||||
@@ -1,59 +1,48 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"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 (
|
||||
LogLevelSilent = iota
|
||||
LogLevelError
|
||||
LogLevelInfo
|
||||
LogLevelDebug
|
||||
LogLevelVerbose
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
Debug *log.Logger
|
||||
Info *log.Logger
|
||||
Error *log.Logger
|
||||
}
|
||||
// Function for use in Logger for discarding logged lines.
|
||||
func DiscardLogf(format string, args ...interface{}) {}
|
||||
|
||||
// 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 {
|
||||
output := os.Stdout
|
||||
logger := new(Logger)
|
||||
|
||||
logErr, logInfo, logDebug := func() (io.Writer, io.Writer, io.Writer) {
|
||||
if level >= LogLevelDebug {
|
||||
return output, output, output
|
||||
logger := &Logger{DiscardLogf, DiscardLogf}
|
||||
logf := func(prefix string) func(string, ...interface{}) {
|
||||
return log.New(os.Stdout, prefix+": "+prepend, log.Ldate|log.Ltime).Printf
|
||||
}
|
||||
if level >= LogLevelInfo {
|
||||
return output, output, ioutil.Discard
|
||||
if level >= LogLevelVerbose {
|
||||
logger.Verbosef = logf("DEBUG")
|
||||
}
|
||||
if level >= LogLevelError {
|
||||
return output, ioutil.Discard, ioutil.Discard
|
||||
logger.Errorf = logf("ERROR")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
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()
|
||||
defer peer.Unlock()
|
||||
peer.disableRoaming = peer.endpoint != nil
|
||||
}
|
||||
device.peers.RUnlock()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
@@ -1,29 +1,51 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/blake2s"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/poly1305"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tai64n"
|
||||
)
|
||||
|
||||
type handshakeState int
|
||||
|
||||
// TODO(crawshaw): add commentary describing each state and the transitions
|
||||
const (
|
||||
HandshakeZeroed = iota
|
||||
HandshakeInitiationCreated
|
||||
HandshakeInitiationConsumed
|
||||
HandshakeResponseCreated
|
||||
HandshakeResponseConsumed
|
||||
handshakeZeroed = handshakeState(iota)
|
||||
handshakeInitiationCreated
|
||||
handshakeInitiationConsumed
|
||||
handshakeResponseCreated
|
||||
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 (
|
||||
NoiseConstruction = "Noise_IKpsk2_25519_ChaChaPoly_BLAKE2s"
|
||||
WGIdentifier = "WireGuard v1 zx2c4 Jason@zx2c4.com"
|
||||
@@ -95,11 +117,11 @@ type MessageCookieReply struct {
|
||||
}
|
||||
|
||||
type Handshake struct {
|
||||
state int
|
||||
state handshakeState
|
||||
mutex sync.RWMutex
|
||||
hash [blake2s.Size]byte // hash value
|
||||
chainKey [blake2s.Size]byte // chain key
|
||||
presharedKey NoiseSymmetricKey // psk
|
||||
presharedKey NoisePresharedKey // psk
|
||||
localEphemeral NoisePrivateKey // ephemeral secret key
|
||||
localIndex uint32 // used to clear hash-table
|
||||
remoteIndex uint32 // index for sending
|
||||
@@ -135,7 +157,7 @@ func (h *Handshake) Clear() {
|
||||
setZero(h.chainKey[:])
|
||||
setZero(h.hash[:])
|
||||
h.localIndex = 0
|
||||
h.state = HandshakeZeroed
|
||||
h.state = handshakeZeroed
|
||||
}
|
||||
|
||||
func (h *Handshake) mixHash(data []byte) {
|
||||
@@ -154,6 +176,7 @@ func init() {
|
||||
}
|
||||
|
||||
func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, error) {
|
||||
var errZeroECDHResult = errors.New("ECDH returned all zeros")
|
||||
|
||||
device.staticIdentity.RLock()
|
||||
defer device.staticIdentity.RUnlock()
|
||||
@@ -162,12 +185,7 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
||||
handshake.mutex.Lock()
|
||||
defer handshake.mutex.Unlock()
|
||||
|
||||
if isZero(handshake.precomputedStaticStatic[:]) {
|
||||
return nil, errors.New("static shared secret is zero")
|
||||
}
|
||||
|
||||
// create ephemeral key
|
||||
|
||||
var err error
|
||||
handshake.hash = InitialHash
|
||||
handshake.chainKey = InitialChainKey
|
||||
@@ -176,31 +194,22 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
||||
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[:])
|
||||
|
||||
msg := MessageInitiation{
|
||||
Type: MessageInitiationType,
|
||||
Ephemeral: handshake.localEphemeral.publicKey(),
|
||||
Sender: handshake.localIndex,
|
||||
}
|
||||
|
||||
handshake.mixKey(msg.Ephemeral[:])
|
||||
handshake.mixHash(msg.Ephemeral[:])
|
||||
|
||||
// encrypt static key
|
||||
|
||||
func() {
|
||||
var key [chacha20poly1305.KeySize]byte
|
||||
ss := handshake.localEphemeral.sharedSecret(handshake.remoteStatic)
|
||||
if isZero(ss[:]) {
|
||||
return nil, errZeroECDHResult
|
||||
}
|
||||
var key [chacha20poly1305.KeySize]byte
|
||||
KDF2(
|
||||
&handshake.chainKey,
|
||||
&key,
|
||||
@@ -209,26 +218,32 @@ func (device *Device) CreateMessageInitiation(peer *Peer) (*MessageInitiation, e
|
||||
)
|
||||
aead, _ := chacha20poly1305.New(key[:])
|
||||
aead.Seal(msg.Static[:0], ZeroNonce[:], device.staticIdentity.publicKey[:], handshake.hash[:])
|
||||
}()
|
||||
handshake.mixHash(msg.Static[:])
|
||||
|
||||
// encrypt timestamp
|
||||
|
||||
timestamp := tai64n.Now()
|
||||
func() {
|
||||
var key [chacha20poly1305.KeySize]byte
|
||||
if isZero(handshake.precomputedStaticStatic[:]) {
|
||||
return nil, errZeroECDHResult
|
||||
}
|
||||
KDF2(
|
||||
&handshake.chainKey,
|
||||
&key,
|
||||
handshake.chainKey[:],
|
||||
handshake.precomputedStaticStatic[:],
|
||||
)
|
||||
aead, _ := chacha20poly1305.New(key[:])
|
||||
timestamp := tai64n.Now()
|
||||
aead, _ = chacha20poly1305.New(key[:])
|
||||
aead.Seal(msg.Timestamp[:0], ZeroNonce[:], timestamp[:], handshake.hash[:])
|
||||
}()
|
||||
|
||||
// assign index
|
||||
device.indexTable.Delete(handshake.localIndex)
|
||||
msg.Sender, err = device.indexTable.NewIndexForHandshake(peer, handshake)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handshake.localIndex = msg.Sender
|
||||
|
||||
handshake.mixHash(msg.Timestamp[:])
|
||||
handshake.state = HandshakeInitiationCreated
|
||||
handshake.state = handshakeInitiationCreated
|
||||
return &msg, nil
|
||||
}
|
||||
|
||||
@@ -250,16 +265,16 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
||||
mixKey(&chainKey, &InitialChainKey, msg.Ephemeral[:])
|
||||
|
||||
// decrypt static key
|
||||
|
||||
var err error
|
||||
var peerPK NoisePublicKey
|
||||
func() {
|
||||
var key [chacha20poly1305.KeySize]byte
|
||||
ss := device.staticIdentity.privateKey.sharedSecret(msg.Ephemeral)
|
||||
if isZero(ss[:]) {
|
||||
return nil
|
||||
}
|
||||
KDF2(&chainKey, &key, chainKey[:], ss[:])
|
||||
aead, _ := chacha20poly1305.New(key[:])
|
||||
_, err = aead.Open(peerPK[:0], ZeroNonce[:], msg.Static[:], hash[:])
|
||||
}()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -273,23 +288,24 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
||||
}
|
||||
|
||||
handshake := &peer.handshake
|
||||
if isZero(handshake.precomputedStaticStatic[:]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// verify identity
|
||||
|
||||
var timestamp tai64n.Timestamp
|
||||
var key [chacha20poly1305.KeySize]byte
|
||||
|
||||
handshake.mutex.RLock()
|
||||
|
||||
if isZero(handshake.precomputedStaticStatic[:]) {
|
||||
handshake.mutex.RUnlock()
|
||||
return nil
|
||||
}
|
||||
KDF2(
|
||||
&chainKey,
|
||||
&key,
|
||||
chainKey[:],
|
||||
handshake.precomputedStaticStatic[:],
|
||||
)
|
||||
aead, _ := chacha20poly1305.New(key[:])
|
||||
aead, _ = chacha20poly1305.New(key[:])
|
||||
_, err = aead.Open(timestamp[:0], ZeroNonce[:], msg.Timestamp[:], hash[:])
|
||||
if err != nil {
|
||||
handshake.mutex.RUnlock()
|
||||
@@ -299,11 +315,15 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
||||
|
||||
// protect against replay & flood
|
||||
|
||||
var ok bool
|
||||
ok = timestamp.After(handshake.lastTimestamp)
|
||||
ok = ok && time.Since(handshake.lastInitiationConsumption) > HandshakeInitationRate
|
||||
replay := !timestamp.After(handshake.lastTimestamp)
|
||||
flood := time.Since(handshake.lastInitiationConsumption) <= HandshakeInitationRate
|
||||
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
|
||||
}
|
||||
|
||||
@@ -322,7 +342,7 @@ func (device *Device) ConsumeMessageInitiation(msg *MessageInitiation) *Peer {
|
||||
if now.After(handshake.lastInitiationConsumption) {
|
||||
handshake.lastInitiationConsumption = now
|
||||
}
|
||||
handshake.state = HandshakeInitiationConsumed
|
||||
handshake.state = handshakeInitiationConsumed
|
||||
|
||||
handshake.mutex.Unlock()
|
||||
|
||||
@@ -337,7 +357,7 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
|
||||
handshake.mutex.Lock()
|
||||
defer handshake.mutex.Unlock()
|
||||
|
||||
if handshake.state != HandshakeInitiationConsumed {
|
||||
if handshake.state != handshakeInitiationConsumed {
|
||||
return nil, errors.New("handshake initiation must be consumed first")
|
||||
}
|
||||
|
||||
@@ -393,7 +413,7 @@ func (device *Device) CreateMessageResponse(peer *Peer) (*MessageResponse, error
|
||||
handshake.mixHash(msg.Empty[:])
|
||||
}()
|
||||
|
||||
handshake.state = HandshakeResponseCreated
|
||||
handshake.state = handshakeResponseCreated
|
||||
|
||||
return &msg, nil
|
||||
}
|
||||
@@ -423,7 +443,7 @@ func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
|
||||
handshake.mutex.RLock()
|
||||
defer handshake.mutex.RUnlock()
|
||||
|
||||
if handshake.state != HandshakeInitiationCreated {
|
||||
if handshake.state != handshakeInitiationCreated {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -484,7 +504,7 @@ func (device *Device) ConsumeMessageResponse(msg *MessageResponse) *Peer {
|
||||
handshake.hash = hash
|
||||
handshake.chainKey = chainKey
|
||||
handshake.remoteIndex = msg.Sender
|
||||
handshake.state = HandshakeResponseConsumed
|
||||
handshake.state = handshakeResponseConsumed
|
||||
|
||||
handshake.mutex.Unlock()
|
||||
|
||||
@@ -509,7 +529,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
||||
var sendKey [chacha20poly1305.KeySize]byte
|
||||
var recvKey [chacha20poly1305.KeySize]byte
|
||||
|
||||
if handshake.state == HandshakeResponseConsumed {
|
||||
if handshake.state == handshakeResponseConsumed {
|
||||
KDF2(
|
||||
&sendKey,
|
||||
&recvKey,
|
||||
@@ -517,7 +537,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
||||
nil,
|
||||
)
|
||||
isInitiator = true
|
||||
} else if handshake.state == HandshakeResponseCreated {
|
||||
} else if handshake.state == handshakeResponseCreated {
|
||||
KDF2(
|
||||
&recvKey,
|
||||
&sendKey,
|
||||
@@ -526,7 +546,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
||||
)
|
||||
isInitiator = false
|
||||
} else {
|
||||
return errors.New("invalid state for keypair derivation")
|
||||
return fmt.Errorf("invalid state for keypair derivation: %v", handshake.state)
|
||||
}
|
||||
|
||||
// zero handshake
|
||||
@@ -534,7 +554,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
||||
setZero(handshake.chainKey[:])
|
||||
setZero(handshake.hash[:]) // Doesn't necessarily need to be zeroed. Could be used for something interesting down the line.
|
||||
setZero(handshake.localEphemeral[:])
|
||||
peer.handshake.state = HandshakeZeroed
|
||||
peer.handshake.state = handshakeZeroed
|
||||
|
||||
// create AEAD instances
|
||||
|
||||
@@ -546,8 +566,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
||||
setZero(recvKey[:])
|
||||
|
||||
keypair.created = time.Now()
|
||||
keypair.sendNonce = 0
|
||||
keypair.replayFilter.Init()
|
||||
keypair.replayFilter.Reset()
|
||||
keypair.isInitiator = isInitiator
|
||||
keypair.localIndex = peer.handshake.localIndex
|
||||
keypair.remoteIndex = peer.handshake.remoteIndex
|
||||
@@ -564,12 +583,12 @@ func (peer *Peer) BeginSymmetricSession() error {
|
||||
defer keypairs.Unlock()
|
||||
|
||||
previous := keypairs.previous
|
||||
next := keypairs.next
|
||||
next := keypairs.loadNext()
|
||||
current := keypairs.current
|
||||
|
||||
if isInitiator {
|
||||
if next != nil {
|
||||
keypairs.next = nil
|
||||
keypairs.storeNext(nil)
|
||||
keypairs.previous = next
|
||||
device.DeleteKeypair(current)
|
||||
} else {
|
||||
@@ -578,7 +597,7 @@ func (peer *Peer) BeginSymmetricSession() error {
|
||||
device.DeleteKeypair(previous)
|
||||
keypairs.current = keypair
|
||||
} else {
|
||||
keypairs.next = keypair
|
||||
keypairs.storeNext(keypair)
|
||||
device.DeleteKeypair(next)
|
||||
keypairs.previous = nil
|
||||
device.DeleteKeypair(previous)
|
||||
@@ -589,18 +608,19 @@ func (peer *Peer) BeginSymmetricSession() error {
|
||||
|
||||
func (peer *Peer) ReceivedWithKeypair(receivedKeypair *Keypair) bool {
|
||||
keypairs := &peer.keypairs
|
||||
if keypairs.next != receivedKeypair {
|
||||
|
||||
if keypairs.loadNext() != receivedKeypair {
|
||||
return false
|
||||
}
|
||||
keypairs.Lock()
|
||||
defer keypairs.Unlock()
|
||||
if keypairs.next != receivedKeypair {
|
||||
if keypairs.loadNext() != receivedKeypair {
|
||||
return false
|
||||
}
|
||||
old := keypairs.previous
|
||||
keypairs.previous = keypairs.current
|
||||
peer.device.DeleteKeypair(old)
|
||||
keypairs.current = keypairs.next
|
||||
keypairs.next = nil
|
||||
keypairs.current = keypairs.loadNext()
|
||||
keypairs.storeNext(nil)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -9,19 +9,18 @@ import (
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
const (
|
||||
NoisePublicKeySize = 32
|
||||
NoisePrivateKeySize = 32
|
||||
NoisePresharedKeySize = 32
|
||||
)
|
||||
|
||||
type (
|
||||
NoisePublicKey [NoisePublicKeySize]byte
|
||||
NoisePrivateKey [NoisePrivateKeySize]byte
|
||||
NoiseSymmetricKey [chacha20poly1305.KeySize]byte
|
||||
NoisePresharedKey [NoisePresharedKeySize]byte
|
||||
NoiseNonce uint64 // padded to 12-bytes
|
||||
)
|
||||
|
||||
@@ -52,18 +51,19 @@ func (key *NoisePrivateKey) FromHex(src string) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (key NoisePrivateKey) ToHex() string {
|
||||
return hex.EncodeToString(key[:])
|
||||
func (key *NoisePrivateKey) FromMaybeZeroHex(src string) (err error) {
|
||||
err = loadExactHex(key[:], src)
|
||||
if key.IsZero() {
|
||||
return
|
||||
}
|
||||
key.clamp()
|
||||
return
|
||||
}
|
||||
|
||||
func (key *NoisePublicKey) FromHex(src string) error {
|
||||
return loadExactHex(key[:], src)
|
||||
}
|
||||
|
||||
func (key NoisePublicKey) ToHex() string {
|
||||
return hex.EncodeToString(key[:])
|
||||
}
|
||||
|
||||
func (key NoisePublicKey) IsZero() bool {
|
||||
var zero NoisePublicKey
|
||||
return key.Equals(zero)
|
||||
@@ -73,10 +73,6 @@ func (key NoisePublicKey) Equals(tar NoisePublicKey) bool {
|
||||
return subtle.ConstantTimeCompare(key[:], tar[:]) == 1
|
||||
}
|
||||
|
||||
func (key *NoiseSymmetricKey) FromHex(src string) error {
|
||||
func (key *NoisePresharedKey) FromHex(src string) error {
|
||||
return loadExactHex(key[:], src)
|
||||
}
|
||||
|
||||
func (key NoiseSymmetricKey) ToHex() string {
|
||||
return hex.EncodeToString(key[:])
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -36,8 +36,14 @@ func TestNoiseHandshake(t *testing.T) {
|
||||
defer dev1.Close()
|
||||
defer dev2.Close()
|
||||
|
||||
peer1, _ := dev2.NewPeer(dev1.staticIdentity.privateKey.publicKey())
|
||||
peer2, _ := dev1.NewPeer(dev2.staticIdentity.privateKey.publicKey())
|
||||
peer1, err := dev2.NewPeer(dev1.staticIdentity.privateKey.publicKey())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
peer2, err := dev1.NewPeer(dev2.staticIdentity.privateKey.publicKey())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertEqual(
|
||||
t,
|
||||
@@ -113,7 +119,7 @@ func TestNoiseHandshake(t *testing.T) {
|
||||
t.Fatal("failed to derive keypair for peer 2", err)
|
||||
}
|
||||
|
||||
key1 := peer1.keypairs.next
|
||||
key1 := peer1.keypairs.loadNext()
|
||||
key2 := peer2.keypairs.current
|
||||
|
||||
// encrypting / decryption test
|
||||
|
||||
154
device/peer.go
154
device/peer.go
@@ -1,21 +1,20 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
PeerRoutineNumber = 3
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
)
|
||||
|
||||
type Peer struct {
|
||||
@@ -24,16 +23,22 @@ type Peer struct {
|
||||
keypairs Keypairs
|
||||
handshake Handshake
|
||||
device *Device
|
||||
endpoint Endpoint
|
||||
persistentKeepaliveInterval uint16
|
||||
endpoint conn.Endpoint
|
||||
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 {
|
||||
txBytes uint64 // bytes send to peer (endpoint)
|
||||
rxBytes uint64 // bytes received from peer
|
||||
lastHandshakeNano int64 // nano seconds since epoch
|
||||
}
|
||||
|
||||
disableRoaming bool
|
||||
|
||||
timers struct {
|
||||
retransmitHandshake *Timer
|
||||
sendKeepalive *Timer
|
||||
@@ -45,35 +50,27 @@ type Peer struct {
|
||||
sentLastMinuteHandshake AtomicBool
|
||||
}
|
||||
|
||||
signals struct {
|
||||
newKeypairArrived chan struct{}
|
||||
flushNonceQueue chan struct{}
|
||||
state struct {
|
||||
sync.Mutex // protects against concurrent Start/Stop
|
||||
}
|
||||
|
||||
queue struct {
|
||||
nonce chan *QueueOutboundElement // nonce / pre-handshake queue
|
||||
outbound chan *QueueOutboundElement // sequential ordering of work
|
||||
inbound chan *QueueInboundElement // sequential ordering of work
|
||||
packetInNonceQueueIsAwaitingKey AtomicBool
|
||||
}
|
||||
|
||||
routines struct {
|
||||
sync.Mutex // held when stopping / starting routines
|
||||
starting sync.WaitGroup // routines pending start
|
||||
stopping sync.WaitGroup // routines pending stop
|
||||
stop chan struct{} // size 0, stop all go routines in peer
|
||||
staged chan *QueueOutboundElement // staged packets before a handshake is available
|
||||
outbound *autodrainingOutboundQueue // sequential ordering of udp transmission
|
||||
inbound *autodrainingInboundQueue // sequential ordering of tun writing
|
||||
}
|
||||
|
||||
cookieGenerator CookieGenerator
|
||||
trieEntries list.List
|
||||
persistentKeepaliveInterval uint32 // accessed atomically
|
||||
}
|
||||
|
||||
func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
|
||||
if device.isClosed.Get() {
|
||||
if device.isClosed() {
|
||||
return nil, errors.New("device closed")
|
||||
}
|
||||
|
||||
// lock resources
|
||||
|
||||
device.staticIdentity.RLock()
|
||||
defer device.staticIdentity.RUnlock()
|
||||
|
||||
@@ -81,52 +78,44 @@ func (device *Device) NewPeer(pk NoisePublicKey) (*Peer, error) {
|
||||
defer device.peers.Unlock()
|
||||
|
||||
// check if over limit
|
||||
|
||||
if len(device.peers.keyMap) >= MaxPeers {
|
||||
return nil, errors.New("too many peers")
|
||||
}
|
||||
|
||||
// create peer
|
||||
|
||||
peer := new(Peer)
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
|
||||
peer.cookieGenerator.Init(pk)
|
||||
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
|
||||
|
||||
_, ok := device.peers.keyMap[pk]
|
||||
if ok {
|
||||
return nil, errors.New("adding existing peer")
|
||||
}
|
||||
|
||||
// pre-compute DH
|
||||
|
||||
handshake := &peer.handshake
|
||||
handshake.mutex.Lock()
|
||||
handshake.precomputedStaticStatic = device.staticIdentity.privateKey.sharedSecret(pk)
|
||||
ssIsZero := isZero(handshake.precomputedStaticStatic[:])
|
||||
handshake.remoteStatic = pk
|
||||
handshake.mutex.Unlock()
|
||||
|
||||
// reset endpoint
|
||||
|
||||
peer.endpoint = nil
|
||||
|
||||
// conditionally add
|
||||
|
||||
if !ssIsZero {
|
||||
// add
|
||||
device.peers.keyMap[pk] = peer
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
device.peers.empty.Set(false)
|
||||
|
||||
// start peer
|
||||
|
||||
if peer.device.isUp.Get() {
|
||||
peer.timersInit()
|
||||
if peer.device.isUp() {
|
||||
peer.Start()
|
||||
}
|
||||
|
||||
@@ -138,6 +127,11 @@ func (peer *Peer) SendBuffer(buffer []byte) error {
|
||||
defer peer.device.net.RUnlock()
|
||||
|
||||
if peer.device.net.bind == nil {
|
||||
// Packets can leak through to SendBuffer while the device is closing.
|
||||
// When that happens, drop them silently to avoid spurious errors.
|
||||
if peer.device.isClosed() {
|
||||
return nil
|
||||
}
|
||||
return errors.New("no bind")
|
||||
}
|
||||
|
||||
@@ -165,51 +159,39 @@ func (peer *Peer) String() string {
|
||||
}
|
||||
|
||||
func (peer *Peer) Start() {
|
||||
|
||||
// should never start a peer on a closed device
|
||||
|
||||
if peer.device.isClosed.Get() {
|
||||
if peer.device.isClosed() {
|
||||
return
|
||||
}
|
||||
|
||||
// prevent simultaneous start/stop operations
|
||||
|
||||
peer.routines.Lock()
|
||||
defer peer.routines.Unlock()
|
||||
peer.state.Lock()
|
||||
defer peer.state.Unlock()
|
||||
|
||||
if peer.isRunning.Get() {
|
||||
return
|
||||
}
|
||||
|
||||
device := peer.device
|
||||
device.log.Debug.Println(peer, "- Starting...")
|
||||
device.log.Verbosef("%v - Starting...", peer)
|
||||
|
||||
// reset routine state
|
||||
peer.stopping.Wait()
|
||||
peer.stopping.Add(2)
|
||||
|
||||
peer.routines.starting.Wait()
|
||||
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.mutex.Lock()
|
||||
peer.handshake.lastSentHandshake = time.Now().Add(-(RekeyTimeout + time.Second))
|
||||
peer.signals.newKeypairArrived = make(chan struct{}, 1)
|
||||
peer.signals.flushNonceQueue = make(chan struct{}, 1)
|
||||
peer.handshake.mutex.Unlock()
|
||||
|
||||
// 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.RoutineSequentialReceiver()
|
||||
|
||||
peer.routines.starting.Wait()
|
||||
peer.isRunning.Set(true)
|
||||
}
|
||||
|
||||
@@ -222,10 +204,10 @@ func (peer *Peer) ZeroAndFlushAll() {
|
||||
keypairs.Lock()
|
||||
device.DeleteKeypair(keypairs.previous)
|
||||
device.DeleteKeypair(keypairs.current)
|
||||
device.DeleteKeypair(keypairs.next)
|
||||
device.DeleteKeypair(keypairs.loadNext())
|
||||
keypairs.previous = nil
|
||||
keypairs.current = nil
|
||||
keypairs.next = nil
|
||||
keypairs.storeNext(nil)
|
||||
keypairs.Unlock()
|
||||
|
||||
// clear handshake state
|
||||
@@ -236,7 +218,7 @@ func (peer *Peer) ZeroAndFlushAll() {
|
||||
handshake.Clear()
|
||||
handshake.mutex.Unlock()
|
||||
|
||||
peer.FlushNonceQueue()
|
||||
peer.FlushStagedPackets()
|
||||
}
|
||||
|
||||
func (peer *Peer) ExpireCurrentKeypairs() {
|
||||
@@ -244,55 +226,43 @@ func (peer *Peer) ExpireCurrentKeypairs() {
|
||||
handshake.mutex.Lock()
|
||||
peer.device.indexTable.Delete(handshake.localIndex)
|
||||
handshake.Clear()
|
||||
handshake.mutex.Unlock()
|
||||
peer.handshake.lastSentHandshake = time.Now().Add(-(RekeyTimeout + time.Second))
|
||||
handshake.mutex.Unlock()
|
||||
|
||||
keypairs := &peer.keypairs
|
||||
keypairs.Lock()
|
||||
if keypairs.current != nil {
|
||||
keypairs.current.sendNonce = RejectAfterMessages
|
||||
atomic.StoreUint64(&keypairs.current.sendNonce, RejectAfterMessages)
|
||||
}
|
||||
if keypairs.next != nil {
|
||||
keypairs.next.sendNonce = RejectAfterMessages
|
||||
next := keypairs.loadNext()
|
||||
atomic.StoreUint64(&next.sendNonce, RejectAfterMessages)
|
||||
}
|
||||
keypairs.Unlock()
|
||||
}
|
||||
|
||||
func (peer *Peer) Stop() {
|
||||
|
||||
// prevent simultaneous start/stop operations
|
||||
peer.state.Lock()
|
||||
defer peer.state.Unlock()
|
||||
|
||||
if !peer.isRunning.Swap(false) {
|
||||
return
|
||||
}
|
||||
|
||||
peer.routines.starting.Wait()
|
||||
|
||||
peer.routines.Lock()
|
||||
defer peer.routines.Unlock()
|
||||
|
||||
peer.device.log.Debug.Println(peer, "- Stopping...")
|
||||
peer.device.log.Verbosef("%v - Stopping...", peer)
|
||||
|
||||
peer.timersStop()
|
||||
|
||||
// stop & wait for ongoing peer routines
|
||||
|
||||
close(peer.routines.stop)
|
||||
peer.routines.stopping.Wait()
|
||||
|
||||
// close queues
|
||||
|
||||
close(peer.queue.nonce)
|
||||
close(peer.queue.outbound)
|
||||
close(peer.queue.inbound)
|
||||
// Signal that RoutineSequentialSender and RoutineSequentialReceiver should exit.
|
||||
peer.queue.inbound.c <- nil
|
||||
peer.queue.outbound.c <- nil
|
||||
peer.stopping.Wait()
|
||||
peer.device.queue.encryption.wg.Done() // no more writes to encryption queue from us
|
||||
|
||||
peer.ZeroAndFlushAll()
|
||||
}
|
||||
|
||||
var RoamingDisabled bool
|
||||
|
||||
func (peer *Peer) SetEndpointFromPacket(endpoint Endpoint) {
|
||||
if RoamingDisabled {
|
||||
func (peer *Peer) SetEndpointFromPacket(endpoint conn.Endpoint) {
|
||||
if peer.disableRoaming {
|
||||
return
|
||||
}
|
||||
peer.Lock()
|
||||
|
||||
117
device/pools.go
117
device/pools.go
@@ -1,89 +1,84 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
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() {
|
||||
if PreallocatedBuffersPerPool == 0 {
|
||||
device.pool.messageBufferPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
device.pool.messageBuffers = NewWaitPool(PreallocatedBuffersPerPool, func() interface{} {
|
||||
return new([MaxMessageSize]byte)
|
||||
},
|
||||
}
|
||||
device.pool.inboundElementPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
})
|
||||
device.pool.inboundElements = NewWaitPool(PreallocatedBuffersPerPool, func() interface{} {
|
||||
return new(QueueInboundElement)
|
||||
},
|
||||
}
|
||||
device.pool.outboundElementPool = &sync.Pool{
|
||||
New: func() interface{} {
|
||||
})
|
||||
device.pool.outboundElements = NewWaitPool(PreallocatedBuffersPerPool, 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 {
|
||||
if PreallocatedBuffersPerPool == 0 {
|
||||
return device.pool.messageBufferPool.Get().(*[MaxMessageSize]byte)
|
||||
} else {
|
||||
return <-device.pool.messageBufferReuseChan
|
||||
}
|
||||
return device.pool.messageBuffers.Get().(*[MaxMessageSize]byte)
|
||||
}
|
||||
|
||||
func (device *Device) PutMessageBuffer(msg *[MaxMessageSize]byte) {
|
||||
if PreallocatedBuffersPerPool == 0 {
|
||||
device.pool.messageBufferPool.Put(msg)
|
||||
} else {
|
||||
device.pool.messageBufferReuseChan <- msg
|
||||
}
|
||||
device.pool.messageBuffers.Put(msg)
|
||||
}
|
||||
|
||||
func (device *Device) GetInboundElement() *QueueInboundElement {
|
||||
if PreallocatedBuffersPerPool == 0 {
|
||||
return device.pool.inboundElementPool.Get().(*QueueInboundElement)
|
||||
} else {
|
||||
return <-device.pool.inboundElementReuseChan
|
||||
}
|
||||
return device.pool.inboundElements.Get().(*QueueInboundElement)
|
||||
}
|
||||
|
||||
func (device *Device) PutInboundElement(msg *QueueInboundElement) {
|
||||
if PreallocatedBuffersPerPool == 0 {
|
||||
device.pool.inboundElementPool.Put(msg)
|
||||
} else {
|
||||
device.pool.inboundElementReuseChan <- msg
|
||||
}
|
||||
func (device *Device) PutInboundElement(elem *QueueInboundElement) {
|
||||
elem.clearPointers()
|
||||
device.pool.inboundElements.Put(elem)
|
||||
}
|
||||
|
||||
func (device *Device) GetOutboundElement() *QueueOutboundElement {
|
||||
if PreallocatedBuffersPerPool == 0 {
|
||||
return device.pool.outboundElementPool.Get().(*QueueOutboundElement)
|
||||
} else {
|
||||
return <-device.pool.outboundElementReuseChan
|
||||
}
|
||||
return device.pool.outboundElements.Get().(*QueueOutboundElement)
|
||||
}
|
||||
|
||||
func (device *Device) PutOutboundElement(msg *QueueOutboundElement) {
|
||||
if PreallocatedBuffersPerPool == 0 {
|
||||
device.pool.outboundElementPool.Put(msg)
|
||||
} else {
|
||||
device.pool.outboundElementReuseChan <- msg
|
||||
}
|
||||
func (device *Device) PutOutboundElement(elem *QueueOutboundElement) {
|
||||
elem.clearPointers()
|
||||
device.pool.outboundElements.Put(elem)
|
||||
}
|
||||
|
||||
87
device/pools_test.go
Normal file
87
device/pools_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
/* 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) {
|
||||
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
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -8,6 +8,7 @@ package device
|
||||
/* Reduce memory consumption for Android */
|
||||
|
||||
const (
|
||||
QueueStagedSize = 128
|
||||
QueueOutboundSize = 1024
|
||||
QueueInboundSize = 1024
|
||||
QueueHandshakeSize = 1024
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
const (
|
||||
QueueStagedSize = 128
|
||||
QueueOutboundSize = 1024
|
||||
QueueInboundSize = 1024
|
||||
QueueHandshakeSize = 1024
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -10,6 +10,7 @@ package device
|
||||
/* Fit within memory limits for iOS's Network Extension API, which has stricter requirements */
|
||||
|
||||
const (
|
||||
QueueStagedSize = 128
|
||||
QueueOutboundSize = 1024
|
||||
QueueInboundSize = 1024
|
||||
QueueHandshakeSize = 1024
|
||||
|
||||
10
device/race_disabled_test.go
Normal file
10
device/race_disabled_test.go
Normal file
@@ -0,0 +1,10 @@
|
||||
//+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 @@
|
||||
//+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
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -8,8 +8,8 @@ package device
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -17,57 +17,35 @@ import (
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
)
|
||||
|
||||
type QueueHandshakeElement struct {
|
||||
msgType uint32
|
||||
packet []byte
|
||||
endpoint Endpoint
|
||||
endpoint conn.Endpoint
|
||||
buffer *[MaxMessageSize]byte
|
||||
}
|
||||
|
||||
type QueueInboundElement struct {
|
||||
dropped int32
|
||||
sync.Mutex
|
||||
buffer *[MaxMessageSize]byte
|
||||
packet []byte
|
||||
counter uint64
|
||||
keypair *Keypair
|
||||
endpoint Endpoint
|
||||
endpoint conn.Endpoint
|
||||
}
|
||||
|
||||
func (elem *QueueInboundElement) Drop() {
|
||||
atomic.StoreInt32(&elem.dropped, AtomicTrue)
|
||||
}
|
||||
|
||||
func (elem *QueueInboundElement) IsDropped() bool {
|
||||
return atomic.LoadInt32(&elem.dropped) == AtomicTrue
|
||||
}
|
||||
|
||||
func (device *Device) addToInboundAndDecryptionQueues(inboundQueue chan *QueueInboundElement, decryptionQueue chan *QueueInboundElement, element *QueueInboundElement) bool {
|
||||
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
|
||||
}
|
||||
// clearPointers clears elem fields that contain pointers.
|
||||
// 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) clearPointers() {
|
||||
elem.buffer = nil
|
||||
elem.packet = nil
|
||||
elem.keypair = nil
|
||||
elem.endpoint = nil
|
||||
}
|
||||
|
||||
/* Called when a new authenticated message has been received
|
||||
@@ -90,16 +68,15 @@ func (peer *Peer) keepKeyFreshReceiving() {
|
||||
* Every time the bind is updated a new routine is started for
|
||||
* IPv4 and IPv6 (separately)
|
||||
*/
|
||||
func (device *Device) RoutineReceiveIncoming(IP int, bind Bind) {
|
||||
|
||||
logDebug := device.log.Debug
|
||||
func (device *Device) RoutineReceiveIncoming(IP int, bind conn.Bind) {
|
||||
defer func() {
|
||||
logDebug.Println("Routine: receive incoming IPv" + strconv.Itoa(IP) + " - stopped")
|
||||
device.log.Verbosef("Routine: receive incoming IPv%d - stopped", IP)
|
||||
device.queue.decryption.wg.Done()
|
||||
device.queue.handshake.wg.Done()
|
||||
device.net.stopping.Done()
|
||||
}()
|
||||
|
||||
logDebug.Println("Routine: receive incoming IPv" + strconv.Itoa(IP) + " - started")
|
||||
device.net.starting.Done()
|
||||
device.log.Verbosef("Routine: receive incoming IPv%d - started", IP)
|
||||
|
||||
// receive datagrams until conn is closed
|
||||
|
||||
@@ -108,13 +85,11 @@ func (device *Device) RoutineReceiveIncoming(IP int, bind Bind) {
|
||||
var (
|
||||
err error
|
||||
size int
|
||||
endpoint Endpoint
|
||||
endpoint conn.Endpoint
|
||||
deathSpiral int
|
||||
)
|
||||
|
||||
for {
|
||||
|
||||
// read next datagram
|
||||
|
||||
switch IP {
|
||||
case ipv4.Version:
|
||||
size, endpoint, err = bind.ReceiveIPv4(buffer[:])
|
||||
@@ -126,8 +101,18 @@ func (device *Device) RoutineReceiveIncoming(IP int, bind Bind) {
|
||||
|
||||
if err != nil {
|
||||
device.PutMessageBuffer(buffer)
|
||||
if errors.Is(err, conn.NetErrClosed) {
|
||||
return
|
||||
}
|
||||
device.log.Errorf("Failed to receive packet: %v", err)
|
||||
if deathSpiral < 10 {
|
||||
deathSpiral++
|
||||
time.Sleep(time.Second / 3)
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
deathSpiral = 0
|
||||
|
||||
if size < MinMessageSize {
|
||||
continue
|
||||
@@ -175,20 +160,19 @@ func (device *Device) RoutineReceiveIncoming(IP int, bind Bind) {
|
||||
elem.packet = packet
|
||||
elem.buffer = buffer
|
||||
elem.keypair = keypair
|
||||
elem.dropped = AtomicFalse
|
||||
elem.endpoint = endpoint
|
||||
elem.counter = 0
|
||||
elem.Mutex = sync.Mutex{}
|
||||
elem.Lock()
|
||||
|
||||
// add to decryption queues
|
||||
|
||||
if peer.isRunning.Get() {
|
||||
if device.addToInboundAndDecryptionQueues(peer.queue.inbound, device.queue.decryption, elem) {
|
||||
peer.queue.inbound.c <- elem
|
||||
device.queue.decryption.c <- elem
|
||||
buffer = device.GetMessageBuffer()
|
||||
} else {
|
||||
device.PutInboundElement(elem)
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
|
||||
// otherwise it is a fixed size & handshake related packet
|
||||
@@ -203,75 +187,40 @@ func (device *Device) RoutineReceiveIncoming(IP int, bind Bind) {
|
||||
okay = len(packet) == MessageCookieReplySize
|
||||
|
||||
default:
|
||||
logDebug.Println("Received message with unknown type")
|
||||
device.log.Verbosef("Received message with unknown type")
|
||||
}
|
||||
|
||||
if okay {
|
||||
if (device.addToHandshakeQueue(
|
||||
device.queue.handshake,
|
||||
QueueHandshakeElement{
|
||||
select {
|
||||
case device.queue.handshake.c <- QueueHandshakeElement{
|
||||
msgType: msgType,
|
||||
buffer: buffer,
|
||||
packet: packet,
|
||||
endpoint: endpoint,
|
||||
},
|
||||
)) {
|
||||
}:
|
||||
buffer = device.GetMessageBuffer()
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (device *Device) RoutineDecryption() {
|
||||
|
||||
var nonce [chacha20poly1305.NonceSize]byte
|
||||
|
||||
logDebug := device.log.Debug
|
||||
defer func() {
|
||||
logDebug.Println("Routine: decryption worker - stopped")
|
||||
device.state.stopping.Done()
|
||||
}()
|
||||
logDebug.Println("Routine: decryption worker - started")
|
||||
device.state.starting.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-device.signals.stop:
|
||||
return
|
||||
|
||||
case elem, ok := <-device.queue.decryption:
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// check if dropped
|
||||
|
||||
if elem.IsDropped() {
|
||||
continue
|
||||
}
|
||||
defer device.log.Verbosef("Routine: decryption worker - stopped")
|
||||
device.log.Verbosef("Routine: decryption worker - started")
|
||||
|
||||
for elem := range device.queue.decryption.c {
|
||||
// split message into fields
|
||||
|
||||
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)
|
||||
// copy counter to nonce
|
||||
binary.LittleEndian.PutUint64(nonce[0x4:0xc], elem.counter)
|
||||
elem.packet, err = elem.keypair.receive.Open(
|
||||
content[:0],
|
||||
nonce[:],
|
||||
@@ -279,51 +228,22 @@ func (device *Device) RoutineDecryption() {
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
elem.Drop()
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
elem.packet = nil
|
||||
}
|
||||
elem.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handles incoming packets related to handshake
|
||||
*/
|
||||
func (device *Device) RoutineHandshake() {
|
||||
|
||||
logInfo := device.log.Info
|
||||
logError := device.log.Error
|
||||
logDebug := device.log.Debug
|
||||
|
||||
var elem QueueHandshakeElement
|
||||
var ok bool
|
||||
|
||||
defer func() {
|
||||
logDebug.Println("Routine: handshake worker - stopped")
|
||||
device.state.stopping.Done()
|
||||
if elem.buffer != nil {
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
}
|
||||
device.log.Verbosef("Routine: handshake worker - stopped")
|
||||
device.queue.encryption.wg.Done()
|
||||
}()
|
||||
device.log.Verbosef("Routine: handshake worker - started")
|
||||
|
||||
logDebug.Println("Routine: handshake worker - started")
|
||||
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
|
||||
}
|
||||
for elem := range device.queue.handshake.c {
|
||||
|
||||
// handle cookie fields and ratelimiting
|
||||
|
||||
@@ -337,8 +257,8 @@ func (device *Device) RoutineHandshake() {
|
||||
reader := bytes.NewReader(elem.packet)
|
||||
err := binary.Read(reader, binary.LittleEndian, &reply)
|
||||
if err != nil {
|
||||
logDebug.Println("Failed to decode cookie reply")
|
||||
return
|
||||
device.log.Verbosef("Failed to decode cookie reply")
|
||||
goto skip
|
||||
}
|
||||
|
||||
// lookup peer from index
|
||||
@@ -346,27 +266,27 @@ func (device *Device) RoutineHandshake() {
|
||||
entry := device.indexTable.Lookup(reply.Receiver)
|
||||
|
||||
if entry.peer == nil {
|
||||
continue
|
||||
goto skip
|
||||
}
|
||||
|
||||
// consume reply
|
||||
|
||||
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) {
|
||||
logDebug.Println("Could not decrypt invalid cookie response")
|
||||
device.log.Verbosef("Could not decrypt invalid cookie response")
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
goto skip
|
||||
|
||||
case MessageInitiationType, MessageResponseType:
|
||||
|
||||
// check mac fields and maybe ratelimit
|
||||
|
||||
if !device.cookieChecker.CheckMAC1(elem.packet) {
|
||||
logDebug.Println("Received packet with invalid mac1")
|
||||
continue
|
||||
device.log.Verbosef("Received packet with invalid mac1")
|
||||
goto skip
|
||||
}
|
||||
|
||||
// endpoints destination address is the source of the datagram
|
||||
@@ -377,19 +297,19 @@ func (device *Device) RoutineHandshake() {
|
||||
|
||||
if !device.cookieChecker.CheckMAC2(elem.packet, elem.endpoint.DstToBytes()) {
|
||||
device.SendHandshakeCookie(&elem)
|
||||
continue
|
||||
goto skip
|
||||
}
|
||||
|
||||
// check ratelimiter
|
||||
|
||||
if !device.rate.limiter.Allow(elem.endpoint.DstIP()) {
|
||||
continue
|
||||
goto skip
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
logError.Println("Invalid packet ended up in the handshake queue")
|
||||
continue
|
||||
device.log.Errorf("Invalid packet ended up in the handshake queue")
|
||||
goto skip
|
||||
}
|
||||
|
||||
// handle handshake initiation/response content
|
||||
@@ -403,19 +323,16 @@ func (device *Device) RoutineHandshake() {
|
||||
reader := bytes.NewReader(elem.packet)
|
||||
err := binary.Read(reader, binary.LittleEndian, &msg)
|
||||
if err != nil {
|
||||
logError.Println("Failed to decode initiation message")
|
||||
continue
|
||||
device.log.Errorf("Failed to decode initiation message")
|
||||
goto skip
|
||||
}
|
||||
|
||||
// consume initiation
|
||||
|
||||
peer := device.ConsumeMessageInitiation(&msg)
|
||||
if peer == nil {
|
||||
logInfo.Println(
|
||||
"Received invalid initiation message from",
|
||||
elem.endpoint.DstToString(),
|
||||
)
|
||||
continue
|
||||
device.log.Verbosef("Received invalid initiation message from %s", elem.endpoint.DstToString())
|
||||
goto skip
|
||||
}
|
||||
|
||||
// update timers
|
||||
@@ -426,7 +343,7 @@ func (device *Device) RoutineHandshake() {
|
||||
// update 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()
|
||||
@@ -439,25 +356,22 @@ func (device *Device) RoutineHandshake() {
|
||||
reader := bytes.NewReader(elem.packet)
|
||||
err := binary.Read(reader, binary.LittleEndian, &msg)
|
||||
if err != nil {
|
||||
logError.Println("Failed to decode response message")
|
||||
continue
|
||||
device.log.Errorf("Failed to decode response message")
|
||||
goto skip
|
||||
}
|
||||
|
||||
// consume response
|
||||
|
||||
peer := device.ConsumeMessageResponse(&msg)
|
||||
if peer == nil {
|
||||
logInfo.Println(
|
||||
"Received invalid response message from",
|
||||
elem.endpoint.DstToString(),
|
||||
)
|
||||
continue
|
||||
device.log.Verbosef("Received invalid response message from %s", elem.endpoint.DstToString())
|
||||
goto skip
|
||||
}
|
||||
|
||||
// update 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
|
||||
@@ -470,88 +384,46 @@ func (device *Device) RoutineHandshake() {
|
||||
err = peer.BeginSymmetricSession()
|
||||
|
||||
if err != nil {
|
||||
logError.Println(peer, "- Failed to derive keypair:", err)
|
||||
continue
|
||||
device.log.Errorf("%v - Failed to derive keypair: %v", peer, err)
|
||||
goto skip
|
||||
}
|
||||
|
||||
peer.timersSessionDerived()
|
||||
peer.timersHandshakeComplete()
|
||||
peer.SendKeepalive()
|
||||
select {
|
||||
case peer.signals.newKeypairArrived <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
skip:
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
func (peer *Peer) RoutineSequentialReceiver() {
|
||||
|
||||
device := peer.device
|
||||
logInfo := device.log.Info
|
||||
logError := device.log.Error
|
||||
logDebug := device.log.Debug
|
||||
|
||||
var elem *QueueInboundElement
|
||||
|
||||
defer func() {
|
||||
logDebug.Println(peer, "- Routine: sequential receiver - stopped")
|
||||
peer.routines.stopping.Done()
|
||||
if elem != nil {
|
||||
if !elem.IsDropped() {
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
}
|
||||
device.PutInboundElement(elem)
|
||||
}
|
||||
device.log.Verbosef("%v - Routine: sequential receiver - stopped", peer)
|
||||
peer.stopping.Done()
|
||||
}()
|
||||
device.log.Verbosef("%v - Routine: sequential receiver - started", peer)
|
||||
|
||||
logDebug.Println(peer, "- Routine: sequential receiver - started")
|
||||
|
||||
peer.routines.starting.Done()
|
||||
|
||||
for {
|
||||
if elem != nil {
|
||||
if !elem.IsDropped() {
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
}
|
||||
device.PutInboundElement(elem)
|
||||
elem = nil
|
||||
}
|
||||
|
||||
var elemOk bool
|
||||
select {
|
||||
case <-peer.routines.stop:
|
||||
return
|
||||
case elem, elemOk = <-peer.queue.inbound:
|
||||
if !elemOk {
|
||||
for elem := range peer.queue.inbound.c {
|
||||
if elem == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// wait for decryption
|
||||
|
||||
var err error
|
||||
elem.Lock()
|
||||
|
||||
if elem.IsDropped() {
|
||||
continue
|
||||
if elem.packet == nil {
|
||||
// decryption failed
|
||||
goto skip
|
||||
}
|
||||
|
||||
// check for replay
|
||||
|
||||
if !elem.keypair.replayFilter.ValidateCounter(elem.counter, RejectAfterMessages) {
|
||||
continue
|
||||
goto skip
|
||||
}
|
||||
|
||||
// update endpoint
|
||||
peer.SetEndpointFromPacket(elem.endpoint)
|
||||
|
||||
// check if using new keypair
|
||||
if peer.ReceivedWithKeypair(elem.keypair) {
|
||||
peer.timersHandshakeComplete()
|
||||
select {
|
||||
case peer.signals.newKeypairArrived <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
peer.SendStagedPackets()
|
||||
}
|
||||
|
||||
peer.keepKeyFreshReceiving()
|
||||
@@ -559,89 +431,63 @@ func (peer *Peer) RoutineSequentialReceiver() {
|
||||
peer.timersAnyAuthenticatedPacketReceived()
|
||||
atomic.AddUint64(&peer.stats.rxBytes, uint64(len(elem.packet)+MinMessageSize))
|
||||
|
||||
// check for keepalive
|
||||
|
||||
if len(elem.packet) == 0 {
|
||||
logDebug.Println(peer, "- Receiving keepalive packet")
|
||||
continue
|
||||
device.log.Verbosef("%v - Receiving keepalive packet", peer)
|
||||
goto skip
|
||||
}
|
||||
peer.timersDataReceived()
|
||||
|
||||
// verify source and strip padding
|
||||
|
||||
switch elem.packet[0] >> 4 {
|
||||
case ipv4.Version:
|
||||
|
||||
// strip padding
|
||||
|
||||
if len(elem.packet) < ipv4.HeaderLen {
|
||||
continue
|
||||
goto skip
|
||||
}
|
||||
|
||||
field := elem.packet[IPv4offsetTotalLength : IPv4offsetTotalLength+2]
|
||||
length := binary.BigEndian.Uint16(field)
|
||||
if int(length) > len(elem.packet) || int(length) < ipv4.HeaderLen {
|
||||
continue
|
||||
goto skip
|
||||
}
|
||||
|
||||
elem.packet = elem.packet[:length]
|
||||
|
||||
// verify IPv4 source
|
||||
|
||||
src := elem.packet[IPv4offsetSrc : IPv4offsetSrc+net.IPv4len]
|
||||
if device.allowedips.LookupIPv4(src) != peer {
|
||||
logInfo.Println(
|
||||
"IPv4 packet with disallowed source address from",
|
||||
peer,
|
||||
)
|
||||
continue
|
||||
device.log.Verbosef("IPv4 packet with disallowed source address from %v", peer)
|
||||
goto skip
|
||||
}
|
||||
|
||||
case ipv6.Version:
|
||||
|
||||
// strip padding
|
||||
|
||||
if len(elem.packet) < ipv6.HeaderLen {
|
||||
continue
|
||||
goto skip
|
||||
}
|
||||
|
||||
field := elem.packet[IPv6offsetPayloadLength : IPv6offsetPayloadLength+2]
|
||||
length := binary.BigEndian.Uint16(field)
|
||||
length += ipv6.HeaderLen
|
||||
if int(length) > len(elem.packet) {
|
||||
continue
|
||||
goto skip
|
||||
}
|
||||
|
||||
elem.packet = elem.packet[:length]
|
||||
|
||||
// verify IPv6 source
|
||||
|
||||
src := elem.packet[IPv6offsetSrc : IPv6offsetSrc+net.IPv6len]
|
||||
if device.allowedips.LookupIPv6(src) != peer {
|
||||
logInfo.Println(
|
||||
"IPv6 packet with disallowed source address from",
|
||||
peer,
|
||||
)
|
||||
continue
|
||||
device.log.Verbosef("IPv6 packet with disallowed source address from %v", peer)
|
||||
goto skip
|
||||
}
|
||||
|
||||
default:
|
||||
logInfo.Println("Packet with invalid IP version from", peer)
|
||||
continue
|
||||
device.log.Verbosef("Packet with invalid IP version from %v", peer)
|
||||
goto skip
|
||||
}
|
||||
|
||||
// write to tun device
|
||||
|
||||
offset := MessageTransportOffsetContent
|
||||
_, err := device.tun.device.Write(elem.buffer[:offset+len(elem.packet)], offset)
|
||||
if len(peer.queue.inbound) == 0 {
|
||||
_, err = device.tun.device.Write(elem.buffer[:MessageTransportOffsetContent+len(elem.packet)], MessageTransportOffsetContent)
|
||||
if err != nil && !device.isClosed() {
|
||||
device.log.Errorf("Failed to write packet to TUN device: %v", err)
|
||||
}
|
||||
if len(peer.queue.inbound.c) == 0 {
|
||||
err = device.tun.device.Flush()
|
||||
if err != nil {
|
||||
peer.device.log.Error.Printf("Unable to flush packets: %v", err)
|
||||
peer.device.log.Errorf("Unable to flush packets: %v", err)
|
||||
}
|
||||
}
|
||||
if err != nil && !device.isClosed.Get() {
|
||||
logError.Println("Failed to write packet to TUN device:", err)
|
||||
}
|
||||
skip:
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
device.PutInboundElement(elem)
|
||||
}
|
||||
}
|
||||
|
||||
389
device/send.go
389
device/send.go
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
@@ -43,7 +43,6 @@ import (
|
||||
*/
|
||||
|
||||
type QueueOutboundElement struct {
|
||||
dropped int32
|
||||
sync.Mutex
|
||||
buffer *[MaxMessageSize]byte // slice holding the packet data
|
||||
packet []byte // slice of "buffer" (always!)
|
||||
@@ -54,73 +53,38 @@ type QueueOutboundElement struct {
|
||||
|
||||
func (device *Device) NewOutboundElement() *QueueOutboundElement {
|
||||
elem := device.GetOutboundElement()
|
||||
elem.dropped = AtomicFalse
|
||||
elem.buffer = device.GetMessageBuffer()
|
||||
elem.Mutex = sync.Mutex{}
|
||||
elem.nonce = 0
|
||||
elem.keypair = nil
|
||||
elem.peer = nil
|
||||
// keypair and peer were cleared (if necessary) by clearPointers.
|
||||
return elem
|
||||
}
|
||||
|
||||
func (elem *QueueOutboundElement) Drop() {
|
||||
atomic.StoreInt32(&elem.dropped, AtomicTrue)
|
||||
}
|
||||
|
||||
func (elem *QueueOutboundElement) IsDropped() bool {
|
||||
return atomic.LoadInt32(&elem.dropped) == AtomicTrue
|
||||
}
|
||||
|
||||
func addToNonceQueue(queue chan *QueueOutboundElement, element *QueueOutboundElement, device *Device) {
|
||||
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)
|
||||
}
|
||||
// clearPointers clears elem fields that contain pointers.
|
||||
// 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) clearPointers() {
|
||||
elem.buffer = nil
|
||||
elem.packet = nil
|
||||
elem.keypair = nil
|
||||
elem.peer = nil
|
||||
}
|
||||
|
||||
/* Queues a keepalive if no packets are queued for peer
|
||||
*/
|
||||
func (peer *Peer) SendKeepalive() bool {
|
||||
if len(peer.queue.nonce) != 0 || peer.queue.packetInNonceQueueIsAwaitingKey.Get() || !peer.isRunning.Get() {
|
||||
return false
|
||||
}
|
||||
func (peer *Peer) SendKeepalive() {
|
||||
if len(peer.queue.staged) == 0 && peer.isRunning.Get() {
|
||||
elem := peer.device.NewOutboundElement()
|
||||
elem.packet = nil
|
||||
select {
|
||||
case peer.queue.nonce <- elem:
|
||||
peer.device.log.Debug.Println(peer, "- Sending keepalive packet")
|
||||
return true
|
||||
case peer.queue.staged <- elem:
|
||||
peer.device.log.Verbosef("%v - Sending keepalive packet", peer)
|
||||
default:
|
||||
peer.device.PutMessageBuffer(elem.buffer)
|
||||
peer.device.PutOutboundElement(elem)
|
||||
return false
|
||||
}
|
||||
}
|
||||
peer.SendStagedPackets()
|
||||
}
|
||||
|
||||
func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||
@@ -143,11 +107,11 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||
peer.handshake.lastSentHandshake = time.Now()
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -162,7 +126,7 @@ func (peer *Peer) SendHandshakeInitiation(isRetry bool) error {
|
||||
|
||||
err = peer.SendBuffer(packet)
|
||||
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()
|
||||
|
||||
@@ -174,11 +138,11 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||
peer.handshake.lastSentHandshake = time.Now()
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -190,7 +154,7 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||
|
||||
err = peer.BeginSymmetricSession()
|
||||
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
|
||||
}
|
||||
|
||||
@@ -200,19 +164,18 @@ func (peer *Peer) SendHandshakeResponse() error {
|
||||
|
||||
err = peer.SendBuffer(packet)
|
||||
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
|
||||
}
|
||||
|
||||
func (device *Device) SendHandshakeCookie(initiatingElem *QueueHandshakeElement) error {
|
||||
|
||||
device.log.Debug.Println("Sending cookie response for denied handshake message for", initiatingElem.endpoint.DstToString())
|
||||
device.log.Verbosef("Sending cookie response for denied handshake message for %v", initiatingElem.endpoint.DstToString())
|
||||
|
||||
sender := binary.LittleEndian.Uint32(initiatingElem.packet[4:8])
|
||||
reply, err := device.cookieChecker.CreateReply(initiatingElem.packet, sender, initiatingElem.endpoint.DstToBytes())
|
||||
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
|
||||
}
|
||||
|
||||
@@ -235,22 +198,18 @@ func (peer *Peer) keepKeyFreshSending() {
|
||||
}
|
||||
|
||||
/* Reads packets from the TUN and inserts
|
||||
* into nonce queue for peer
|
||||
* into staged queue for peer
|
||||
*
|
||||
* Obs. Single instance per TUN device
|
||||
*/
|
||||
func (device *Device) RoutineReadFromTUN() {
|
||||
|
||||
logDebug := device.log.Debug
|
||||
logError := device.log.Error
|
||||
|
||||
defer func() {
|
||||
logDebug.Println("Routine: TUN reader - stopped")
|
||||
device.log.Verbosef("Routine: TUN reader - stopped")
|
||||
device.state.stopping.Done()
|
||||
device.queue.encryption.wg.Done()
|
||||
}()
|
||||
|
||||
logDebug.Println("Routine: TUN reader - started")
|
||||
device.state.starting.Done()
|
||||
device.log.Verbosef("Routine: TUN reader - started")
|
||||
|
||||
var elem *QueueOutboundElement
|
||||
|
||||
@@ -267,8 +226,8 @@ func (device *Device) RoutineReadFromTUN() {
|
||||
size, err := device.tun.device.Read(elem.buffer[:], offset)
|
||||
|
||||
if err != nil {
|
||||
if !device.isClosed.Get() {
|
||||
logError.Println("Failed to read packet from TUN device:", err)
|
||||
if !device.isClosed() {
|
||||
device.log.Errorf("Failed to read packet from TUN device: %v", err)
|
||||
device.Close()
|
||||
}
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
@@ -301,207 +260,117 @@ func (device *Device) RoutineReadFromTUN() {
|
||||
peer = device.allowedips.LookupIPv6(dst)
|
||||
|
||||
default:
|
||||
logDebug.Println("Received packet with unknown IP version")
|
||||
device.log.Verbosef("Received packet with unknown IP version")
|
||||
}
|
||||
|
||||
if peer == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// insert into nonce/pre-handshake queue
|
||||
|
||||
if peer.isRunning.Get() {
|
||||
if peer.queue.packetInNonceQueueIsAwaitingKey.Get() {
|
||||
peer.SendHandshakeInitiation(false)
|
||||
}
|
||||
addToNonceQueue(peer.queue.nonce, elem, device)
|
||||
peer.StagePacket(elem)
|
||||
elem = nil
|
||||
peer.SendStagedPackets()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (peer *Peer) FlushNonceQueue() {
|
||||
func (peer *Peer) StagePacket(elem *QueueOutboundElement) {
|
||||
for {
|
||||
select {
|
||||
case peer.signals.flushNonceQueue <- struct{}{}:
|
||||
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.
|
||||
* Then assigns nonces to packets sequentially
|
||||
* and creates "work" structs for workers
|
||||
*
|
||||
* 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() {
|
||||
flush()
|
||||
logDebug.Println(peer, "- Routine: nonce worker - stopped")
|
||||
peer.queue.packetInNonceQueueIsAwaitingKey.Set(false)
|
||||
peer.routines.stopping.Done()
|
||||
}()
|
||||
|
||||
peer.routines.starting.Done()
|
||||
logDebug.Println(peer, "- Routine: nonce worker - started")
|
||||
|
||||
for {
|
||||
NextPacket:
|
||||
peer.queue.packetInNonceQueueIsAwaitingKey.Set(false)
|
||||
|
||||
select {
|
||||
case <-peer.routines.stop:
|
||||
return
|
||||
|
||||
case <-peer.signals.flushNonceQueue:
|
||||
flush()
|
||||
goto NextPacket
|
||||
|
||||
case elem, ok := <-peer.queue.nonce:
|
||||
|
||||
if !ok {
|
||||
func (peer *Peer) SendStagedPackets() {
|
||||
top:
|
||||
if len(peer.queue.staged) == 0 || !peer.device.isUp() {
|
||||
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.Since(keypair.created) < RejectAfterTime {
|
||||
break
|
||||
}
|
||||
}
|
||||
peer.queue.packetInNonceQueueIsAwaitingKey.Set(true)
|
||||
|
||||
// no suitable key pair, request for new handshake
|
||||
|
||||
select {
|
||||
case <-peer.signals.newKeypairArrived:
|
||||
default:
|
||||
}
|
||||
|
||||
keypair := peer.keypairs.Current()
|
||||
if keypair == nil || atomic.LoadUint64(&keypair.sendNonce) >= RejectAfterMessages || time.Since(keypair.created) >= RejectAfterTime {
|
||||
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
|
||||
|
||||
for {
|
||||
select {
|
||||
case elem := <-peer.queue.staged:
|
||||
elem.peer = peer
|
||||
elem.nonce = atomic.AddUint64(&keypair.sendNonce, 1) - 1
|
||||
|
||||
// double check in case of race condition added by future code
|
||||
|
||||
if elem.nonce >= RejectAfterMessages {
|
||||
atomic.StoreUint64(&keypair.sendNonce, RejectAfterMessages)
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
device.PutOutboundElement(elem)
|
||||
goto NextPacket
|
||||
peer.StagePacket(elem) // XXX: Out of order, but we can't front-load go chans
|
||||
goto top
|
||||
}
|
||||
|
||||
elem.keypair = keypair
|
||||
elem.dropped = AtomicFalse
|
||||
elem.Lock()
|
||||
|
||||
// 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
|
||||
* and marks them for sequential consumption (by releasing the mutex)
|
||||
*
|
||||
* Obs. One instance per core
|
||||
*/
|
||||
func (device *Device) RoutineEncryption() {
|
||||
|
||||
var paddingZeros [PaddingMultiple]byte
|
||||
var nonce [chacha20poly1305.NonceSize]byte
|
||||
|
||||
logDebug := device.log.Debug
|
||||
|
||||
defer func() {
|
||||
for {
|
||||
select {
|
||||
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")
|
||||
device.state.starting.Done()
|
||||
|
||||
for {
|
||||
|
||||
// fetch next element
|
||||
|
||||
select {
|
||||
case <-device.signals.stop:
|
||||
return
|
||||
|
||||
case elem, ok := <-device.queue.encryption:
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// check if dropped
|
||||
|
||||
if elem.IsDropped() {
|
||||
continue
|
||||
}
|
||||
defer device.log.Verbosef("Routine: encryption worker - stopped")
|
||||
device.log.Verbosef("Routine: encryption worker - started")
|
||||
|
||||
for elem := range device.queue.encryption.c {
|
||||
// populate header fields
|
||||
|
||||
header := elem.buffer[:MessageTransportHeaderSize]
|
||||
|
||||
fieldType := header[0:4]
|
||||
@@ -513,16 +382,8 @@ func (device *Device) RoutineEncryption() {
|
||||
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)
|
||||
}
|
||||
paddingSize := calculatePaddingSize(len(elem.packet), int(atomic.LoadInt32(&device.tun.mtu)))
|
||||
elem.packet = append(elem.packet, paddingZeros[:paddingSize]...)
|
||||
|
||||
// encrypt content and release to consumer
|
||||
|
||||
@@ -535,7 +396,6 @@ func (device *Device) RoutineEncryption() {
|
||||
)
|
||||
elem.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Sequentially reads packets from queue and sends to endpoint
|
||||
@@ -544,50 +404,26 @@ func (device *Device) RoutineEncryption() {
|
||||
* The routine terminates then the outbound queue is closed.
|
||||
*/
|
||||
func (peer *Peer) RoutineSequentialSender() {
|
||||
|
||||
device := peer.device
|
||||
|
||||
logDebug := device.log.Debug
|
||||
logError := device.log.Error
|
||||
|
||||
defer func() {
|
||||
for {
|
||||
select {
|
||||
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()
|
||||
defer device.log.Verbosef("%v - Routine: sequential sender - stopped", peer)
|
||||
peer.stopping.Done()
|
||||
}()
|
||||
device.log.Verbosef("%v - Routine: sequential sender - started", peer)
|
||||
|
||||
logDebug.Println(peer, "- Routine: sequential sender - started")
|
||||
|
||||
peer.routines.starting.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
|
||||
case <-peer.routines.stop:
|
||||
return
|
||||
|
||||
case elem, ok := <-peer.queue.outbound:
|
||||
|
||||
if !ok {
|
||||
for elem := range peer.queue.outbound.c {
|
||||
if elem == nil {
|
||||
return
|
||||
}
|
||||
|
||||
elem.Lock()
|
||||
if elem.IsDropped() {
|
||||
if !peer.isRunning.Get() {
|
||||
// peer has been stopped; return re-usable elems to the shared pool.
|
||||
// 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.
|
||||
// TODO(josharian): rework peer shutdown order to ensure
|
||||
// that we never accidentally keep timers alive longer than necessary.
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
device.PutOutboundElement(elem)
|
||||
continue
|
||||
}
|
||||
@@ -604,11 +440,10 @@ func (peer *Peer) RoutineSequentialSender() {
|
||||
device.PutMessageBuffer(elem.buffer)
|
||||
device.PutOutboundElement(elem)
|
||||
if err != nil {
|
||||
logError.Println(peer, "- Failed to send data packet", err)
|
||||
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 @@
|
||||
// +build !linux android
|
||||
|
||||
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
|
||||
}
|
||||
218
device/sticky_linux.go
Normal file
218
device/sticky_linux.go
Normal file
@@ -0,0 +1,218 @@
|
||||
// +build !android
|
||||
|
||||
/* 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) {
|
||||
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.NativeEndpoint).Src4().Ifindex) == ifidx {
|
||||
pePtr.peer.Unlock()
|
||||
break
|
||||
}
|
||||
pePtr.peer.endpoint.(*conn.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.RUnlock()
|
||||
continue
|
||||
}
|
||||
nativeEP, _ := peer.endpoint.(*conn.NativeEndpoint)
|
||||
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,
|
||||
},
|
||||
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 := 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
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@@ -14,10 +14,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
/* This Timer structure and related functions should roughly copy the interface of
|
||||
* the Linux kernel's struct timer_list.
|
||||
*/
|
||||
|
||||
// A Timer manages time-based aspects of the WireGuard protocol.
|
||||
// Timer roughly copies the interface of the Linux kernel's struct timer_list.
|
||||
type Timer struct {
|
||||
*time.Timer
|
||||
modifyingLock sync.RWMutex
|
||||
@@ -29,18 +27,17 @@ func (peer *Peer) NewTimer(expirationFunction func(*Peer)) *Timer {
|
||||
timer := &Timer{}
|
||||
timer.Timer = time.AfterFunc(time.Hour, func() {
|
||||
timer.runningLock.Lock()
|
||||
defer timer.runningLock.Unlock()
|
||||
|
||||
timer.modifyingLock.Lock()
|
||||
if !timer.isPending {
|
||||
timer.modifyingLock.Unlock()
|
||||
timer.runningLock.Unlock()
|
||||
return
|
||||
}
|
||||
timer.isPending = false
|
||||
timer.modifyingLock.Unlock()
|
||||
|
||||
expirationFunction(peer)
|
||||
timer.runningLock.Unlock()
|
||||
})
|
||||
timer.Stop()
|
||||
return timer
|
||||
@@ -74,12 +71,12 @@ func (timer *Timer) IsPending() 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() && !peer.device.peers.empty.Get()
|
||||
}
|
||||
|
||||
func expiredRetransmitHandshake(peer *Peer) {
|
||||
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() {
|
||||
peer.timers.sendKeepalive.Del()
|
||||
@@ -88,7 +85,7 @@ func expiredRetransmitHandshake(peer *Peer) {
|
||||
/* We drop all packets without a keypair and don't try again,
|
||||
* 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
|
||||
* of a partial exchange.
|
||||
@@ -98,7 +95,7 @@ func expiredRetransmitHandshake(peer *Peer) {
|
||||
}
|
||||
} else {
|
||||
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. */
|
||||
peer.Lock()
|
||||
@@ -122,7 +119,7 @@ func expiredSendKeepalive(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. */
|
||||
peer.Lock()
|
||||
if peer.endpoint != nil {
|
||||
@@ -134,12 +131,12 @@ func expiredNewHandshake(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()
|
||||
}
|
||||
|
||||
func expiredPersistentKeepalive(peer *Peer) {
|
||||
if peer.persistentKeepaliveInterval > 0 {
|
||||
if atomic.LoadUint32(&peer.persistentKeepaliveInterval) > 0 {
|
||||
peer.SendKeepalive()
|
||||
}
|
||||
}
|
||||
@@ -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. */
|
||||
func (peer *Peer) timersAnyAuthenticatedPacketTraversal() {
|
||||
if peer.persistentKeepaliveInterval > 0 && peer.timersActive() {
|
||||
peer.timers.persistentKeepalive.Mod(time.Duration(peer.persistentKeepaliveInterval) * time.Second)
|
||||
keepalive := atomic.LoadUint32(&peer.persistentKeepaliveInterval)
|
||||
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.zeroKeyMaterial = peer.NewTimer(expiredZeroKeyMaterial)
|
||||
peer.timers.persistentKeepalive = peer.NewTimer(expiredPersistentKeepalive)
|
||||
}
|
||||
|
||||
func (peer *Peer) timersStart() {
|
||||
atomic.StoreUint32(&peer.timers.handshakeAttempts, 0)
|
||||
peer.timers.sentLastMinuteHandshake.Set(false)
|
||||
peer.timers.needAnotherKeepalive.Set(false)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
@@ -14,43 +15,40 @@ import (
|
||||
const DefaultMTU = 1420
|
||||
|
||||
func (device *Device) RoutineTUNEventReader() {
|
||||
setUp := false
|
||||
logDebug := device.log.Debug
|
||||
logInfo := device.log.Info
|
||||
logError := device.log.Error
|
||||
|
||||
logDebug.Println("Routine: event worker - started")
|
||||
device.state.starting.Done()
|
||||
device.log.Verbosef("Routine: event worker - started")
|
||||
|
||||
for event := range device.tun.device.Events() {
|
||||
if event&tun.EventMTUUpdate != 0 {
|
||||
mtu, err := device.tun.device.MTU()
|
||||
old := atomic.LoadInt32(&device.tun.mtu)
|
||||
if err != nil {
|
||||
logError.Println("Failed to load updated MTU of device:", err)
|
||||
} else if int(old) != mtu {
|
||||
if mtu+MessageTransportSize > MaxMessageSize {
|
||||
logInfo.Println("MTU updated:", mtu, "(too large)")
|
||||
} else {
|
||||
logInfo.Println("MTU updated:", mtu)
|
||||
device.log.Errorf("Failed to load updated MTU of device: %v", err)
|
||||
continue
|
||||
}
|
||||
atomic.StoreInt32(&device.tun.mtu, int32(mtu))
|
||||
if mtu < 0 {
|
||||
device.log.Errorf("MTU not updated to negative value: %v", mtu)
|
||||
continue
|
||||
}
|
||||
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.EventUp != 0 && !setUp {
|
||||
logInfo.Println("Interface set up")
|
||||
setUp = true
|
||||
if event&tun.EventUp != 0 {
|
||||
device.log.Verbosef("Interface up requested")
|
||||
device.Up()
|
||||
}
|
||||
|
||||
if event&tun.EventDown != 0 && setUp {
|
||||
logInfo.Println("Interface set down")
|
||||
setUp = false
|
||||
if event&tun.EventDown != 0 {
|
||||
device.log.Verbosef("Interface down requested")
|
||||
device.Down()
|
||||
}
|
||||
}
|
||||
|
||||
logDebug.Println("Routine: event worker - stopped")
|
||||
device.state.stopping.Done()
|
||||
device.log.Verbosef("Routine: event worker - stopped")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
415
device/uapi.go
415
device/uapi.go
@@ -1,39 +1,75 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package device
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
)
|
||||
|
||||
type IPCError struct {
|
||||
int64
|
||||
code int64 // error code
|
||||
err error // underlying/wrapped error
|
||||
}
|
||||
|
||||
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 {
|
||||
return s.int64
|
||||
return s.code
|
||||
}
|
||||
|
||||
func (device *Device) IpcGetOperation(socket *bufio.Writer) *IPCError {
|
||||
lines := make([]string, 0, 100)
|
||||
send := func(line string) {
|
||||
lines = append(lines, line)
|
||||
func ipcErrorf(code int64, msg string, args ...interface{}) *IPCError {
|
||||
return &IPCError{code: code, err: fmt.Errorf(msg, args...)}
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -52,15 +88,15 @@ func (device *Device) IpcGetOperation(socket *bufio.Writer) *IPCError {
|
||||
// serialize device related values
|
||||
|
||||
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 {
|
||||
send(fmt.Sprintf("listen_port=%d", device.net.port))
|
||||
sendf("listen_port=%d", device.net.port)
|
||||
}
|
||||
|
||||
if device.net.fwmark != 0 {
|
||||
send(fmt.Sprintf("fwmark=%d", device.net.fwmark))
|
||||
sendf("fwmark=%d", device.net.fwmark)
|
||||
}
|
||||
|
||||
// serialize each peer state
|
||||
@@ -69,336 +105,308 @@ func (device *Device) IpcGetOperation(socket *bufio.Writer) *IPCError {
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
|
||||
send("public_key=" + peer.handshake.remoteStatic.ToHex())
|
||||
send("preshared_key=" + peer.handshake.presharedKey.ToHex())
|
||||
send("protocol_version=1")
|
||||
keyf("public_key", (*[32]byte)(&peer.handshake.remoteStatic))
|
||||
keyf("preshared_key", (*[32]byte)(&peer.handshake.presharedKey))
|
||||
sendf("protocol_version=1")
|
||||
if peer.endpoint != nil {
|
||||
send("endpoint=" + peer.endpoint.DstToString())
|
||||
sendf("endpoint=%s", peer.endpoint.DstToString())
|
||||
}
|
||||
|
||||
nano := atomic.LoadInt64(&peer.stats.lastHandshakeNano)
|
||||
secs := nano / time.Second.Nanoseconds()
|
||||
nano %= time.Second.Nanoseconds()
|
||||
|
||||
send(fmt.Sprintf("last_handshake_time_sec=%d", secs))
|
||||
send(fmt.Sprintf("last_handshake_time_nsec=%d", nano))
|
||||
send(fmt.Sprintf("tx_bytes=%d", atomic.LoadUint64(&peer.stats.txBytes)))
|
||||
send(fmt.Sprintf("rx_bytes=%d", atomic.LoadUint64(&peer.stats.rxBytes)))
|
||||
send(fmt.Sprintf("persistent_keepalive_interval=%d", peer.persistentKeepaliveInterval))
|
||||
|
||||
for _, ip := range device.allowedips.EntriesForPeer(peer) {
|
||||
send("allowed_ip=" + ip.String())
|
||||
}
|
||||
sendf("last_handshake_time_sec=%d", secs)
|
||||
sendf("last_handshake_time_nsec=%d", nano)
|
||||
sendf("tx_bytes=%d", atomic.LoadUint64(&peer.stats.txBytes))
|
||||
sendf("rx_bytes=%d", atomic.LoadUint64(&peer.stats.rxBytes))
|
||||
sendf("persistent_keepalive_interval=%d", atomic.LoadUint32(&peer.persistentKeepaliveInterval))
|
||||
|
||||
device.allowedips.EntriesForPeer(peer, func(ip net.IP, cidr uint) bool {
|
||||
sendf("allowed_ip=%s/%d", ip.String(), cidr)
|
||||
return true
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// send lines (does not require resource locks)
|
||||
|
||||
for _, line := range lines {
|
||||
_, err := socket.WriteString(line + "\n")
|
||||
if err != nil {
|
||||
return &IPCError{ipc.IpcErrorIO}
|
||||
}
|
||||
if _, err := w.Write(buf.Bytes()); err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorIO, "failed to write output: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (device *Device) IpcSetOperation(socket *bufio.Reader) *IPCError {
|
||||
scanner := bufio.NewScanner(socket)
|
||||
logError := device.log.Error
|
||||
logDebug := device.log.Debug
|
||||
// IpcSetOperation implements the WireGuard configuration protocol "set" operation.
|
||||
// See https://www.wireguard.com/xplatform/#configuration-protocol for details.
|
||||
func (device *Device) IpcSetOperation(r io.Reader) (err error) {
|
||||
device.ipcMutex.Lock()
|
||||
defer device.ipcMutex.Unlock()
|
||||
|
||||
var peer *Peer
|
||||
defer func() {
|
||||
if err != nil {
|
||||
device.log.Errorf("%v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
dummy := false
|
||||
createdNewPeer := false
|
||||
peer := new(ipcSetPeer)
|
||||
deviceConfig := true
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
|
||||
// parse line
|
||||
|
||||
line := scanner.Text()
|
||||
if line == "" {
|
||||
// Blank line means terminate operation.
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(line, "=")
|
||||
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]
|
||||
value := parts[1]
|
||||
|
||||
/* device configuration */
|
||||
|
||||
if key == "public_key" {
|
||||
if deviceConfig {
|
||||
deviceConfig = false
|
||||
}
|
||||
peer.handlePostConfig()
|
||||
// Load/create the peer we are now configuring.
|
||||
err := device.handlePublicKeyLine(peer, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
if deviceConfig {
|
||||
err = device.handleDeviceLine(key, value)
|
||||
} else {
|
||||
err = device.handlePeerLine(peer, key, value)
|
||||
}
|
||||
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.FromHex(value)
|
||||
err := sk.FromMaybeZeroHex(value)
|
||||
if err != nil {
|
||||
logError.Println("Failed to set private_key:", err)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set private_key: %w", err)
|
||||
}
|
||||
logDebug.Println("UAPI: Updating private key")
|
||||
device.log.Verbosef("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}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to parse listen_port: %w", err)
|
||||
}
|
||||
|
||||
// update port and rebind
|
||||
|
||||
logDebug.Println("UAPI: Updating listen port")
|
||||
device.log.Verbosef("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}
|
||||
return ipcErrorf(ipc.IpcErrorPortInUse, "failed to set listen_port: %w", err)
|
||||
}
|
||||
|
||||
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}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "invalid fwmark: %w", err)
|
||||
}
|
||||
|
||||
logDebug.Println("UAPI: Updating fwmark")
|
||||
|
||||
if err := device.BindSetMark(uint32(fwmark)); err != nil {
|
||||
logError.Println("Failed to update fwmark:", err)
|
||||
return &IPCError{ipc.IpcErrorPortInUse}
|
||||
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 "public_key":
|
||||
// switch to peer configuration
|
||||
logDebug.Println("UAPI: Transition to peer configuration")
|
||||
deviceConfig = false
|
||||
|
||||
case "replace_peers":
|
||||
if value != "true" {
|
||||
logError.Println("Failed to set replace_peers, invalid value:", value)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set replace_peers, invalid value: %v", value)
|
||||
}
|
||||
logDebug.Println("UAPI: Removing all peers")
|
||||
device.log.Verbosef("UAPI: Removing all peers")
|
||||
device.RemoveAllPeers()
|
||||
|
||||
default:
|
||||
logError.Println("Invalid UAPI device key:", key)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "invalid UAPI device key: %v", key)
|
||||
}
|
||||
|
||||
/* peer configuration */
|
||||
return nil
|
||||
}
|
||||
|
||||
if !deviceConfig {
|
||||
// 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
|
||||
}
|
||||
|
||||
switch key {
|
||||
func (peer *ipcSetPeer) handlePostConfig() {
|
||||
if peer.Peer != nil && !peer.dummy && peer.Peer.device.isUp() {
|
||||
peer.SendStagedPackets()
|
||||
}
|
||||
}
|
||||
|
||||
case "public_key":
|
||||
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 {
|
||||
logError.Println("Failed to get peer by public key:", err)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to get peer by public key: %w", err)
|
||||
}
|
||||
|
||||
// ignore peer with public key of device
|
||||
|
||||
// Ignore peer with the same public key as this device.
|
||||
device.staticIdentity.RLock()
|
||||
dummy = device.staticIdentity.publicKey.Equals(publicKey)
|
||||
peer.dummy = device.staticIdentity.publicKey.Equals(publicKey)
|
||||
device.staticIdentity.RUnlock()
|
||||
|
||||
if dummy {
|
||||
peer = &Peer{}
|
||||
if peer.dummy {
|
||||
peer.Peer = &Peer{}
|
||||
} else {
|
||||
peer = device.LookupPeer(publicKey)
|
||||
peer.Peer = device.LookupPeer(publicKey)
|
||||
}
|
||||
|
||||
createdNewPeer = peer == nil
|
||||
if createdNewPeer {
|
||||
peer, err = device.NewPeer(publicKey)
|
||||
peer.created = peer.Peer == nil
|
||||
if peer.created {
|
||||
peer.Peer, err = device.NewPeer(publicKey)
|
||||
if err != nil {
|
||||
logError.Println("Failed to create new peer:", err)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
}
|
||||
if peer == nil {
|
||||
dummy = true
|
||||
peer = &Peer{}
|
||||
} else {
|
||||
logDebug.Println(peer, "- UAPI: Created")
|
||||
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" {
|
||||
logError.Println("Failed to set update only, invalid value:", value)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set update only, invalid value: %v", value)
|
||||
}
|
||||
if createdNewPeer && !dummy {
|
||||
if peer.created && !peer.dummy {
|
||||
device.RemovePeer(peer.handshake.remoteStatic)
|
||||
peer = &Peer{}
|
||||
dummy = true
|
||||
peer.Peer = &Peer{}
|
||||
peer.dummy = true
|
||||
}
|
||||
|
||||
case "remove":
|
||||
|
||||
// remove currently selected peer from device
|
||||
|
||||
if value != "true" {
|
||||
logError.Println("Failed to set remove, invalid value:", value)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set remove, invalid value: %v", value)
|
||||
}
|
||||
if !dummy {
|
||||
logDebug.Println(peer, "- UAPI: Removing")
|
||||
if !peer.dummy {
|
||||
device.log.Verbosef("%v - UAPI: Removing", peer.Peer)
|
||||
device.RemovePeer(peer.handshake.remoteStatic)
|
||||
}
|
||||
peer = &Peer{}
|
||||
dummy = true
|
||||
peer.Peer = &Peer{}
|
||||
peer.dummy = true
|
||||
|
||||
case "preshared_key":
|
||||
|
||||
// update PSK
|
||||
|
||||
logDebug.Println(peer, "- UAPI: Updating 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 {
|
||||
logError.Println("Failed to set preshared key:", err)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set preshared key: %w", err)
|
||||
}
|
||||
|
||||
case "endpoint":
|
||||
|
||||
// set endpoint destination
|
||||
|
||||
logDebug.Println(peer, "- UAPI: Updating endpoint")
|
||||
|
||||
err := func() error {
|
||||
device.log.Verbosef("%v - UAPI: Updating endpoint", peer.Peer)
|
||||
endpoint, err := conn.CreateEndpoint(value)
|
||||
if err != nil {
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set endpoint %v: %w", value, err)
|
||||
}
|
||||
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")
|
||||
device.log.Verbosef("%v - UAPI: Updating persistent keepalive interval", peer.Peer)
|
||||
|
||||
secs, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil {
|
||||
logError.Println("Failed to set persistent keepalive interval:", err)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set persistent keepalive interval: %w", err)
|
||||
}
|
||||
|
||||
old := peer.persistentKeepaliveInterval
|
||||
peer.persistentKeepaliveInterval = uint16(secs)
|
||||
|
||||
// send immediate keepalive if we're turning it on and before it wasn't on
|
||||
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 {
|
||||
logError.Println("Failed to get tun device status:", err)
|
||||
return &IPCError{ipc.IpcErrorIO}
|
||||
return ipcErrorf(ipc.IpcErrorIO, "failed to get tun device status: %w", err)
|
||||
}
|
||||
if device.isUp.Get() && !dummy {
|
||||
if device.isUp() && !peer.dummy {
|
||||
peer.SendKeepalive()
|
||||
}
|
||||
}
|
||||
|
||||
case "replace_allowed_ips":
|
||||
|
||||
logDebug.Println(peer, "- UAPI: Removing all allowedips")
|
||||
|
||||
device.log.Verbosef("%v - UAPI: Removing all allowedips", peer.Peer)
|
||||
if value != "true" {
|
||||
logError.Println("Failed to replace allowedips, invalid value:", value)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to replace allowedips, invalid value: %v", value)
|
||||
}
|
||||
|
||||
if dummy {
|
||||
continue
|
||||
if peer.dummy {
|
||||
return nil
|
||||
}
|
||||
|
||||
device.allowedips.RemoveByPeer(peer)
|
||||
device.allowedips.RemoveByPeer(peer.Peer)
|
||||
|
||||
case "allowed_ip":
|
||||
|
||||
logDebug.Println(peer, "- UAPI: Adding allowedip")
|
||||
device.log.Verbosef("%v - UAPI: Adding allowedip", peer.Peer)
|
||||
|
||||
_, network, err := net.ParseCIDR(value)
|
||||
if err != nil {
|
||||
logError.Println("Failed to set allowed ip:", err)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "failed to set allowed ip: %w", err)
|
||||
}
|
||||
|
||||
if dummy {
|
||||
continue
|
||||
if peer.dummy {
|
||||
return nil
|
||||
}
|
||||
|
||||
ones, _ := network.Mask.Size()
|
||||
device.allowedips.Insert(network.IP, uint(ones), peer)
|
||||
device.allowedips.Insert(network.IP, uint(ones), peer.Peer)
|
||||
|
||||
case "protocol_version":
|
||||
|
||||
if value != "1" {
|
||||
logError.Println("Invalid protocol version:", value)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
return ipcErrorf(ipc.IpcErrorInvalid, "invalid protocol version: %v", value)
|
||||
}
|
||||
|
||||
default:
|
||||
logError.Println("Invalid UAPI peer key:", key)
|
||||
return &IPCError{ipc.IpcErrorInvalid}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
|
||||
// create buffered read/writer
|
||||
|
||||
defer socket.Close()
|
||||
|
||||
buffered := func(s io.ReadWriter) *bufio.ReadWriter {
|
||||
@@ -407,35 +415,44 @@ func (device *Device) IpcHandle(socket net.Conn) {
|
||||
return bufio.NewReadWriter(reader, writer)
|
||||
}(socket)
|
||||
|
||||
defer buffered.Flush()
|
||||
|
||||
for {
|
||||
op, err := buffered.ReadString('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// handle operation
|
||||
|
||||
var status *IPCError
|
||||
|
||||
switch op {
|
||||
case "set=1\n":
|
||||
status = device.IpcSetOperation(buffered.Reader)
|
||||
|
||||
err = device.IpcSetOperation(buffered.Reader)
|
||||
case "get=1\n":
|
||||
status = device.IpcGetOperation(buffered.Writer)
|
||||
|
||||
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.Error.Println("Invalid UAPI operation:", op)
|
||||
device.log.Errorf("invalid UAPI operation: %v", op)
|
||||
return
|
||||
}
|
||||
|
||||
// write status
|
||||
|
||||
var status *IPCError
|
||||
if err != nil && !errors.As(err, &status) {
|
||||
// shouldn't happen
|
||||
status = ipcErrorf(ipc.IpcErrorUnknown, "other UAPI error: %w", err)
|
||||
}
|
||||
if status != nil {
|
||||
device.log.Error.Println(status)
|
||||
device.log.Errorf("%v", status)
|
||||
fmt.Fprintf(buffered, "errno=%d\n\n", status.ErrorCode())
|
||||
} else {
|
||||
fmt.Fprintf(buffered, "errno=0\n\n")
|
||||
}
|
||||
buffered.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package device
|
||||
|
||||
const WireGuardGoVersion = "0.0.20200121"
|
||||
9
go.mod
9
go.mod
@@ -1,10 +1,9 @@
|
||||
module golang.zx2c4.com/wireguard
|
||||
|
||||
go 1.12
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823
|
||||
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c
|
||||
golang.org/x/text v0.3.2
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001
|
||||
)
|
||||
|
||||
21
go.sum
21
go.sum
@@ -1,14 +1,17 @@
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823 h1:Ypyv6BNJh07T1pUSrehkLemqPKXhus2MkfktJ91kRh4=
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c h1:6Zx7DRlKXf79yfxuQ/7GqV3w2y7aDsk6bGg0MzF5RVU=
|
||||
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos=
|
||||
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
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/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
@@ -2,32 +2,20 @@
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ipc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var socketDirectory = "/var/run/wireguard"
|
||||
|
||||
const (
|
||||
IpcErrorIO = -int64(unix.EIO)
|
||||
IpcErrorProtocol = -int64(unix.EPROTO)
|
||||
IpcErrorInvalid = -int64(unix.EINVAL)
|
||||
IpcErrorPortInUse = -int64(unix.EADDRINUSE)
|
||||
socketName = "%s.sock"
|
||||
)
|
||||
|
||||
type UAPIListener struct {
|
||||
listener net.Listener // unix socket listener
|
||||
connNew chan net.Conn
|
||||
@@ -84,10 +72,7 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
||||
unixListener.SetUnlinkOnClose(true)
|
||||
}
|
||||
|
||||
socketPath := path.Join(
|
||||
socketDirectory,
|
||||
fmt.Sprintf(socketName, name),
|
||||
)
|
||||
socketPath := sockPath(name)
|
||||
|
||||
// watch for deletion of socket
|
||||
|
||||
@@ -146,58 +131,3 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
||||
|
||||
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,31 +1,18 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ipc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.zx2c4.com/wireguard/rwcancel"
|
||||
)
|
||||
|
||||
var socketDirectory = "/var/run/wireguard"
|
||||
|
||||
const (
|
||||
IpcErrorIO = -int64(unix.EIO)
|
||||
IpcErrorProtocol = -int64(unix.EPROTO)
|
||||
IpcErrorInvalid = -int64(unix.EINVAL)
|
||||
IpcErrorPortInUse = -int64(unix.EADDRINUSE)
|
||||
socketName = "%s.sock"
|
||||
)
|
||||
|
||||
type UAPIListener struct {
|
||||
listener net.Listener // unix socket listener
|
||||
connNew chan net.Conn
|
||||
@@ -84,10 +71,7 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
||||
|
||||
// watch for deletion of socket
|
||||
|
||||
socketPath := path.Join(
|
||||
socketDirectory,
|
||||
fmt.Sprintf(socketName, name),
|
||||
)
|
||||
socketPath := sockPath(name)
|
||||
|
||||
uapi.inotifyFd, err = unix.InotifyInit()
|
||||
if err != nil {
|
||||
@@ -115,6 +99,7 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
||||
go func(l *UAPIListener) {
|
||||
var buff [0]byte
|
||||
for {
|
||||
defer uapi.inotifyRWCancel.Close()
|
||||
// start with lstat to avoid race condition
|
||||
if _, err := os.Lstat(socketPath); os.IsNotExist(err) {
|
||||
l.connErr <- err
|
||||
@@ -143,58 +128,3 @@ func UAPIListen(name string, file *os.File) (net.Listener, error) {
|
||||
|
||||
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 @@
|
||||
// +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,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ipc
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
IpcErrorProtocol = -int64(71)
|
||||
IpcErrorInvalid = -int64(22)
|
||||
IpcErrorPortInUse = -int64(98)
|
||||
IpcErrorUnknown = -int64(55)
|
||||
)
|
||||
|
||||
type UAPIListener struct {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2005 Microsoft
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
package winpipe
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2005 Microsoft
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winpipe
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2005 Microsoft
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package winpipe
|
||||
|
||||
54
main.go
54
main.go
@@ -2,7 +2,7 @@
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
@@ -41,23 +41,21 @@ func warning() {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "WARNING WARNING WARNING WARNING WARNING WARNING WARNING")
|
||||
fmt.Fprintln(os.Stderr, "W G")
|
||||
fmt.Fprintln(os.Stderr, "W You are running this software on a Linux kernel, G")
|
||||
fmt.Fprintln(os.Stderr, "W which is probably unnecessary and misguided. This G")
|
||||
fmt.Fprintln(os.Stderr, "W is because the Linux kernel has built-in first G")
|
||||
fmt.Fprintln(os.Stderr, "W class support for WireGuard, and this support is G")
|
||||
fmt.Fprintln(os.Stderr, "W much more refined than this slower userspace G")
|
||||
fmt.Fprintln(os.Stderr, "W implementation. For more information on G")
|
||||
fmt.Fprintln(os.Stderr, "W installing the kernel module, please visit: G")
|
||||
fmt.Fprintln(os.Stderr, "W https://www.wireguard.com/install G")
|
||||
fmt.Fprintln(os.Stderr, "W G")
|
||||
fmt.Fprintln(os.Stderr, "WARNING WARNING WARNING WARNING WARNING WARNING WARNING")
|
||||
fmt.Fprintln(os.Stderr, "┌───────────────────────────────────────────────────┐")
|
||||
fmt.Fprintln(os.Stderr, "│ │")
|
||||
fmt.Fprintln(os.Stderr, "│ Running this software on Linux is unnecessary, │")
|
||||
fmt.Fprintln(os.Stderr, "│ because the Linux kernel has built-in first │")
|
||||
fmt.Fprintln(os.Stderr, "│ class support for WireGuard, which will be │")
|
||||
fmt.Fprintln(os.Stderr, "│ faster, slicker, and better integrated. For │")
|
||||
fmt.Fprintln(os.Stderr, "│ information on installing the kernel module, │")
|
||||
fmt.Fprintln(os.Stderr, "│ please visit: <https://wireguard.com/install>. │")
|
||||
fmt.Fprintln(os.Stderr, "│ │")
|
||||
fmt.Fprintln(os.Stderr, "└───────────────────────────────────────────────────┘")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 2 && os.Args[1] == "--version" {
|
||||
fmt.Printf("wireguard-go v%s\n\nUserspace WireGuard daemon for %s-%s.\nInformation available at https://www.wireguard.com.\nCopyright (C) Jason A. Donenfeld <Jason@zx2c4.com>.\n", 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
|
||||
}
|
||||
|
||||
@@ -97,16 +95,14 @@ func main() {
|
||||
|
||||
logLevel := func() int {
|
||||
switch os.Getenv("LOG_LEVEL") {
|
||||
case "debug":
|
||||
return device.LogLevelDebug
|
||||
case "info":
|
||||
return device.LogLevelInfo
|
||||
case "verbose", "debug":
|
||||
return device.LogLevelVerbose
|
||||
case "error":
|
||||
return device.LogLevelError
|
||||
case "silent":
|
||||
return device.LogLevelSilent
|
||||
}
|
||||
return device.LogLevelInfo
|
||||
return device.LogLevelError
|
||||
}()
|
||||
|
||||
// open TUN device (or use supplied fd)
|
||||
@@ -145,12 +141,10 @@ func main() {
|
||||
fmt.Sprintf("(%s) ", interfaceName),
|
||||
)
|
||||
|
||||
logger.Info.Println("Starting wireguard-go version", device.WireGuardGoVersion)
|
||||
|
||||
logger.Debug.Println("Debug log enabled")
|
||||
logger.Verbosef("Starting wireguard-go version %s", Version)
|
||||
|
||||
if err != nil {
|
||||
logger.Error.Println("Failed to create TUN device:", err)
|
||||
logger.Errorf("Failed to create TUN device: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
|
||||
@@ -173,7 +167,7 @@ func main() {
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
logger.Error.Println("UAPI listen error:", err)
|
||||
logger.Errorf("UAPI listen error: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
return
|
||||
}
|
||||
@@ -208,7 +202,7 @@ func main() {
|
||||
|
||||
path, err := os.Executable()
|
||||
if err != nil {
|
||||
logger.Error.Println("Failed to determine executable:", err)
|
||||
logger.Errorf("Failed to determine executable: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
|
||||
@@ -218,7 +212,7 @@ func main() {
|
||||
attr,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error.Println("Failed to daemonize:", err)
|
||||
logger.Errorf("Failed to daemonize: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
process.Release()
|
||||
@@ -227,14 +221,14 @@ func main() {
|
||||
|
||||
device := device.NewDevice(tun, logger)
|
||||
|
||||
logger.Info.Println("Device started")
|
||||
logger.Verbosef("Device started")
|
||||
|
||||
errs := make(chan error)
|
||||
term := make(chan os.Signal, 1)
|
||||
|
||||
uapi, err := ipc.UAPIListen(interfaceName, fileUAPI)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -249,7 +243,7 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
logger.Info.Println("UAPI listener started")
|
||||
logger.Verbosef("UAPI listener started")
|
||||
|
||||
// wait for program to terminate
|
||||
|
||||
@@ -267,5 +261,5 @@ func main() {
|
||||
uapi.Close()
|
||||
device.Close()
|
||||
|
||||
logger.Info.Println("Shutting down")
|
||||
logger.Verbosef("Shutting down")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
@@ -31,11 +31,10 @@ func main() {
|
||||
fmt.Fprintln(os.Stderr, "Warning: this is a test program for Windows, mainly used for debugging this Go package. For a real WireGuard for Windows client, the repo you want is <https://git.zx2c4.com/wireguard-windows/>, which includes this code as a module.")
|
||||
|
||||
logger := device.NewLogger(
|
||||
device.LogLevelDebug,
|
||||
device.LogLevelVerbose,
|
||||
fmt.Sprintf("(%s) ", interfaceName),
|
||||
)
|
||||
logger.Info.Println("Starting wireguard-go version", device.WireGuardGoVersion)
|
||||
logger.Debug.Println("Debug log enabled")
|
||||
logger.Verbosef("Starting wireguard-go version %s", Version)
|
||||
|
||||
tun, err := tun.CreateTUN(interfaceName, 0)
|
||||
if err == nil {
|
||||
@@ -44,17 +43,21 @@ func main() {
|
||||
interfaceName = realInterfaceName
|
||||
}
|
||||
} else {
|
||||
logger.Error.Println("Failed to create TUN device:", err)
|
||||
logger.Errorf("Failed to create TUN device: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
|
||||
device := device.NewDevice(tun, logger)
|
||||
device.Up()
|
||||
logger.Info.Println("Device started")
|
||||
err = device.Up()
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to bring up device: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
logger.Verbosef("Device started")
|
||||
|
||||
uapi, err := ipc.UAPIListen(interfaceName)
|
||||
if err != nil {
|
||||
logger.Error.Println("Failed to listen on uapi socket:", err)
|
||||
logger.Errorf("Failed to listen on uapi socket: %v", err)
|
||||
os.Exit(ExitSetupFailed)
|
||||
}
|
||||
|
||||
@@ -71,7 +74,7 @@ func main() {
|
||||
go device.IpcHandle(conn)
|
||||
}
|
||||
}()
|
||||
logger.Info.Println("UAPI listener started")
|
||||
logger.Verbosef("UAPI listener started")
|
||||
|
||||
// wait for program to terminate
|
||||
|
||||
@@ -90,5 +93,5 @@ func main() {
|
||||
uapi.Close()
|
||||
device.Close()
|
||||
|
||||
logger.Info.Println("Shutting down")
|
||||
logger.Verbosef("Shutting down")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ratelimiter
|
||||
@@ -20,21 +20,23 @@ const (
|
||||
)
|
||||
|
||||
type RatelimiterEntry struct {
|
||||
sync.Mutex
|
||||
mu sync.Mutex
|
||||
lastTime time.Time
|
||||
tokens int64
|
||||
}
|
||||
|
||||
type Ratelimiter struct {
|
||||
sync.RWMutex
|
||||
stopReset chan struct{}
|
||||
mu sync.RWMutex
|
||||
timeNow func() time.Time
|
||||
|
||||
stopReset chan struct{} // send to reset, close to stop
|
||||
tableIPv4 map[[net.IPv4len]byte]*RatelimiterEntry
|
||||
tableIPv6 map[[net.IPv6len]byte]*RatelimiterEntry
|
||||
}
|
||||
|
||||
func (rate *Ratelimiter) Close() {
|
||||
rate.Lock()
|
||||
defer rate.Unlock()
|
||||
rate.mu.Lock()
|
||||
defer rate.mu.Unlock()
|
||||
|
||||
if rate.stopReset != nil {
|
||||
close(rate.stopReset)
|
||||
@@ -42,11 +44,14 @@ func (rate *Ratelimiter) Close() {
|
||||
}
|
||||
|
||||
func (rate *Ratelimiter) Init() {
|
||||
rate.Lock()
|
||||
defer rate.Unlock()
|
||||
rate.mu.Lock()
|
||||
defer rate.mu.Unlock()
|
||||
|
||||
if rate.timeNow == nil {
|
||||
rate.timeNow = time.Now
|
||||
}
|
||||
|
||||
// stop any ongoing garbage collection routine
|
||||
|
||||
if rate.stopReset != nil {
|
||||
close(rate.stopReset)
|
||||
}
|
||||
@@ -55,48 +60,50 @@ func (rate *Ratelimiter) Init() {
|
||||
rate.tableIPv4 = make(map[[net.IPv4len]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() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-rate.stopReset:
|
||||
case _, ok := <-stopReset:
|
||||
ticker.Stop()
|
||||
if ok {
|
||||
ticker = time.NewTicker(time.Second)
|
||||
} else {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ticker = time.NewTicker(time.Second)
|
||||
case <-ticker.C:
|
||||
func() {
|
||||
rate.Lock()
|
||||
defer rate.Unlock()
|
||||
if rate.cleanup() {
|
||||
ticker.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (rate *Ratelimiter) cleanup() (empty bool) {
|
||||
rate.mu.Lock()
|
||||
defer rate.mu.Unlock()
|
||||
|
||||
for key, entry := range rate.tableIPv4 {
|
||||
entry.Lock()
|
||||
if time.Since(entry.lastTime) > garbageCollectTime {
|
||||
entry.mu.Lock()
|
||||
if rate.timeNow().Sub(entry.lastTime) > garbageCollectTime {
|
||||
delete(rate.tableIPv4, key)
|
||||
}
|
||||
entry.Unlock()
|
||||
entry.mu.Unlock()
|
||||
}
|
||||
|
||||
for key, entry := range rate.tableIPv6 {
|
||||
entry.Lock()
|
||||
if time.Since(entry.lastTime) > garbageCollectTime {
|
||||
entry.mu.Lock()
|
||||
if rate.timeNow().Sub(entry.lastTime) > garbageCollectTime {
|
||||
delete(rate.tableIPv6, key)
|
||||
}
|
||||
entry.Unlock()
|
||||
entry.mu.Unlock()
|
||||
}
|
||||
|
||||
if len(rate.tableIPv4) == 0 && len(rate.tableIPv6) == 0 {
|
||||
ticker.Stop()
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return len(rate.tableIPv4) == 0 && len(rate.tableIPv6) == 0
|
||||
}
|
||||
|
||||
func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
||||
@@ -109,7 +116,7 @@ func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
||||
IPv4 := ip.To4()
|
||||
IPv6 := ip.To16()
|
||||
|
||||
rate.RLock()
|
||||
rate.mu.RLock()
|
||||
|
||||
if IPv4 != nil {
|
||||
copy(keyIPv4[:], IPv4)
|
||||
@@ -119,15 +126,15 @@ func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
||||
entry = rate.tableIPv6[keyIPv6]
|
||||
}
|
||||
|
||||
rate.RUnlock()
|
||||
rate.mu.RUnlock()
|
||||
|
||||
// make new entry if not found
|
||||
|
||||
if entry == nil {
|
||||
entry = new(RatelimiterEntry)
|
||||
entry.tokens = maxTokens - packetCost
|
||||
entry.lastTime = time.Now()
|
||||
rate.Lock()
|
||||
entry.lastTime = rate.timeNow()
|
||||
rate.mu.Lock()
|
||||
if IPv4 != nil {
|
||||
rate.tableIPv4[keyIPv4] = entry
|
||||
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.Unlock()
|
||||
rate.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
// add tokens to entry
|
||||
|
||||
entry.Lock()
|
||||
now := time.Now()
|
||||
entry.mu.Lock()
|
||||
now := rate.timeNow()
|
||||
entry.tokens += now.Sub(entry.lastTime).Nanoseconds()
|
||||
entry.lastTime = now
|
||||
if entry.tokens > maxTokens {
|
||||
@@ -157,9 +164,9 @@ func (rate *Ratelimiter) Allow(ip net.IP) bool {
|
||||
|
||||
if entry.tokens > packetCost {
|
||||
entry.tokens -= packetCost
|
||||
entry.Unlock()
|
||||
entry.mu.Unlock()
|
||||
return true
|
||||
}
|
||||
entry.Unlock()
|
||||
entry.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package ratelimiter
|
||||
@@ -11,22 +11,21 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type RatelimiterResult struct {
|
||||
type result struct {
|
||||
allowed bool
|
||||
text string
|
||||
wait time.Duration
|
||||
}
|
||||
|
||||
func TestRatelimiter(t *testing.T) {
|
||||
var rate Ratelimiter
|
||||
var expectedResults []result
|
||||
|
||||
var ratelimiter Ratelimiter
|
||||
var expectedResults []RatelimiterResult
|
||||
|
||||
Nano := func(nano int64) time.Duration {
|
||||
nano := func(nano int64) time.Duration {
|
||||
return time.Nanosecond * time.Duration(nano)
|
||||
}
|
||||
|
||||
Add := func(res RatelimiterResult) {
|
||||
add := func(res result) {
|
||||
expectedResults = append(
|
||||
expectedResults,
|
||||
res,
|
||||
@@ -34,40 +33,40 @@ func TestRatelimiter(t *testing.T) {
|
||||
}
|
||||
|
||||
for i := 0; i < packetsBurstable; i++ {
|
||||
Add(RatelimiterResult{
|
||||
add(result{
|
||||
allowed: true,
|
||||
text: "initial burst",
|
||||
})
|
||||
}
|
||||
|
||||
Add(RatelimiterResult{
|
||||
add(result{
|
||||
allowed: false,
|
||||
text: "after burst",
|
||||
})
|
||||
|
||||
Add(RatelimiterResult{
|
||||
add(result{
|
||||
allowed: true,
|
||||
wait: Nano(time.Second.Nanoseconds() / packetsPerSecond),
|
||||
wait: nano(time.Second.Nanoseconds() / packetsPerSecond),
|
||||
text: "filling tokens for single packet",
|
||||
})
|
||||
|
||||
Add(RatelimiterResult{
|
||||
add(result{
|
||||
allowed: false,
|
||||
text: "not having refilled enough",
|
||||
})
|
||||
|
||||
Add(RatelimiterResult{
|
||||
add(result{
|
||||
allowed: true,
|
||||
wait: 2 * (Nano(time.Second.Nanoseconds() / packetsPerSecond)),
|
||||
wait: 2 * (nano(time.Second.Nanoseconds() / packetsPerSecond)),
|
||||
text: "filling tokens for two packet burst",
|
||||
})
|
||||
|
||||
Add(RatelimiterResult{
|
||||
add(result{
|
||||
allowed: true,
|
||||
text: "second packet in 2 packet burst",
|
||||
})
|
||||
|
||||
Add(RatelimiterResult{
|
||||
add(result{
|
||||
allowed: false,
|
||||
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"),
|
||||
}
|
||||
|
||||
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 {
|
||||
time.Sleep(res.wait)
|
||||
timeSleep(res.wait)
|
||||
for _, ip := range ips {
|
||||
allowed := ratelimiter.Allow(ip)
|
||||
allowed := rate.Allow(ip)
|
||||
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
|
||||
*
|
||||
* 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
|
||||
|
||||
/* Implementation of RFC6479
|
||||
* https://tools.ietf.org/html/rfc6479
|
||||
*
|
||||
* The implementation is not safe for concurrent use!
|
||||
*/
|
||||
type block uint64
|
||||
|
||||
const (
|
||||
// See: https://golang.org/src/math/big/arith.go
|
||||
_Wordm = ^uintptr(0)
|
||||
_WordLogSize = _Wordm>>8&1 + _Wordm>>16&1 + _Wordm>>32&1
|
||||
_WordSize = 1 << _WordLogSize
|
||||
blockBitLog = 6 // 1<<6 == 64 bits
|
||||
blockBits = 1 << blockBitLog // must be power of 2
|
||||
ringBlocks = 1 << 7 // must be power of 2
|
||||
windowSize = (ringBlocks - 1) * blockBits
|
||||
blockMask = ringBlocks - 1
|
||||
bitMask = blockBits - 1
|
||||
)
|
||||
|
||||
const (
|
||||
CounterRedundantBitsLog = _WordLogSize + 3
|
||||
CounterRedundantBits = _WordSize * 8
|
||||
CounterBitsTotal = 2048
|
||||
CounterWindowSize = uint64(CounterBitsTotal - CounterRedundantBits)
|
||||
)
|
||||
|
||||
const (
|
||||
BacktrackWords = CounterBitsTotal / _WordSize
|
||||
)
|
||||
|
||||
func minUint64(a uint64, b uint64) uint64 {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
// A Filter rejects replayed messages by checking if message counter value is
|
||||
// within a sliding window of previously received messages.
|
||||
// The zero value for Filter is an empty filter ready to use.
|
||||
// Filters are unsafe for concurrent use.
|
||||
type Filter struct {
|
||||
last uint64
|
||||
ring [ringBlocks]block
|
||||
}
|
||||
|
||||
type ReplayFilter struct {
|
||||
counter uint64
|
||||
backtrack [BacktrackWords]uintptr
|
||||
// Reset resets the filter to empty state.
|
||||
func (f *Filter) Reset() {
|
||||
f.last = 0
|
||||
f.ring[0] = 0
|
||||
}
|
||||
|
||||
func (filter *ReplayFilter) Init() {
|
||||
filter.counter = 0
|
||||
filter.backtrack[0] = 0
|
||||
}
|
||||
|
||||
func (filter *ReplayFilter) ValidateCounter(counter uint64, limit uint64) bool {
|
||||
// ValidateCounter checks if the counter should be accepted.
|
||||
// Overlimit counters (>= limit) are always rejected.
|
||||
func (f *Filter) ValidateCounter(counter uint64, limit uint64) bool {
|
||||
if counter >= limit {
|
||||
return false
|
||||
}
|
||||
|
||||
indexWord := counter >> CounterRedundantBitsLog
|
||||
|
||||
if counter > filter.counter {
|
||||
|
||||
// move window forward
|
||||
|
||||
current := filter.counter >> CounterRedundantBitsLog
|
||||
diff := minUint64(indexWord-current, BacktrackWords)
|
||||
for i := uint64(1); i <= diff; i++ {
|
||||
filter.backtrack[(current+i)%BacktrackWords] = 0
|
||||
indexBlock := counter >> blockBitLog
|
||||
if counter > f.last { // move window forward
|
||||
current := f.last >> blockBitLog
|
||||
diff := indexBlock - current
|
||||
if diff > ringBlocks {
|
||||
diff = ringBlocks // cap diff to clear the whole ring
|
||||
}
|
||||
filter.counter = counter
|
||||
|
||||
} else if filter.counter-counter > CounterWindowSize {
|
||||
|
||||
// behind current window
|
||||
|
||||
for i := current + 1; i <= current+diff; i++ {
|
||||
f.ring[i&blockMask] = 0
|
||||
}
|
||||
f.last = counter
|
||||
} else if f.last-counter > windowSize { // behind current window
|
||||
return false
|
||||
}
|
||||
|
||||
indexWord %= BacktrackWords
|
||||
indexBit := counter & uint64(CounterRedundantBits-1)
|
||||
|
||||
// check and set bit
|
||||
|
||||
oldValue := filter.backtrack[indexWord]
|
||||
newValue := oldValue | (1 << indexBit)
|
||||
filter.backtrack[indexWord] = newValue
|
||||
return oldValue != newValue
|
||||
indexBlock &= blockMask
|
||||
indexBit := counter & bitMask
|
||||
old := f.ring[indexBlock]
|
||||
new := old | 1<<indexBit
|
||||
f.ring[indexBlock] = new
|
||||
return old != new
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
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) {
|
||||
var filter ReplayFilter
|
||||
var filter Filter
|
||||
|
||||
T_LIM := CounterWindowSize + 1
|
||||
const T_LIM = windowSize + 1
|
||||
|
||||
testNumber := 0
|
||||
T := func(n uint64, v bool) {
|
||||
T := func(n uint64, expected bool) {
|
||||
testNumber++
|
||||
if filter.ValidateCounter(n, RejectAfterMessages) != v {
|
||||
t.Fatal("Test", testNumber, "failed", n, v)
|
||||
if filter.ValidateCounter(n, RejectAfterMessages) != expected {
|
||||
t.Fatal("Test", testNumber, "failed", n, expected)
|
||||
}
|
||||
}
|
||||
|
||||
filter.Init()
|
||||
filter.Reset()
|
||||
|
||||
T(0, true) /* 1 */
|
||||
T(1, true) /* 2 */
|
||||
@@ -67,53 +67,53 @@ func TestReplay(t *testing.T) {
|
||||
T(0, false) /* 34 */
|
||||
|
||||
t.Log("Bulk test 1")
|
||||
filter.Init()
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := uint64(1); i <= CounterWindowSize; i++ {
|
||||
for i := uint64(1); i <= windowSize; i++ {
|
||||
T(i, true)
|
||||
}
|
||||
T(0, true)
|
||||
T(0, false)
|
||||
|
||||
t.Log("Bulk test 2")
|
||||
filter.Init()
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := uint64(2); i <= CounterWindowSize+1; i++ {
|
||||
for i := uint64(2); i <= windowSize+1; i++ {
|
||||
T(i, true)
|
||||
}
|
||||
T(1, true)
|
||||
T(0, false)
|
||||
|
||||
t.Log("Bulk test 3")
|
||||
filter.Init()
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := CounterWindowSize + 1; i > 0; i-- {
|
||||
for i := uint64(windowSize + 1); i > 0; i-- {
|
||||
T(i, true)
|
||||
}
|
||||
|
||||
t.Log("Bulk test 4")
|
||||
filter.Init()
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := CounterWindowSize + 2; i > 1; i-- {
|
||||
for i := uint64(windowSize + 2); i > 1; i-- {
|
||||
T(i, true)
|
||||
}
|
||||
T(0, false)
|
||||
|
||||
t.Log("Bulk test 5")
|
||||
filter.Init()
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := CounterWindowSize; i > 0; i-- {
|
||||
for i := uint64(windowSize); i > 0; i-- {
|
||||
T(i, true)
|
||||
}
|
||||
T(CounterWindowSize+1, true)
|
||||
T(windowSize+1, true)
|
||||
T(0, false)
|
||||
|
||||
t.Log("Bulk test 6")
|
||||
filter.Init()
|
||||
filter.Reset()
|
||||
testNumber = 0
|
||||
for i := CounterWindowSize; i > 0; i-- {
|
||||
for i := uint64(windowSize); i > 0; i-- {
|
||||
T(i, true)
|
||||
}
|
||||
T(0, true)
|
||||
T(CounterWindowSize+1, true)
|
||||
T(windowSize+1, true)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// +build !windows
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package rwcancel
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// +build !windows
|
||||
|
||||
/* 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
|
||||
|
||||
import (
|
||||
@@ -42,17 +46,7 @@ func NewRWCancel(fd int) (*RWCancel, error) {
|
||||
}
|
||||
|
||||
func RetryAfterError(err error) bool {
|
||||
if pe, ok := err.(*os.PathError); ok {
|
||||
err = pe.Err
|
||||
}
|
||||
if errno, ok := err.(syscall.Errno); ok {
|
||||
switch errno {
|
||||
case syscall.EAGAIN, syscall.EINTR:
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
return errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.EINTR)
|
||||
}
|
||||
|
||||
func (rw *RWCancel) ReadyRead() bool {
|
||||
@@ -125,3 +119,8 @@ func (rw *RWCancel) Cancel() (err error) {
|
||||
_, err = rw.closingWriter.Write([]byte{0})
|
||||
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,8 +1,8 @@
|
||||
// +build !linux
|
||||
// +build !linux,!windows
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package rwcancel
|
||||
@@ -10,5 +10,6 @@ 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)
|
||||
_, err := unix.Select(nfd, r, w, e, timeout)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package rwcancel
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tai64n
|
||||
@@ -17,16 +17,23 @@ const whitenerMask = uint32(0x1000000 - 1)
|
||||
|
||||
type Timestamp [TimestampSize]byte
|
||||
|
||||
func Now() Timestamp {
|
||||
func stamp(t time.Time) Timestamp {
|
||||
var tai64n Timestamp
|
||||
now := time.Now()
|
||||
secs := base + uint64(now.Unix())
|
||||
nano := uint32(now.Nanosecond()) &^ whitenerMask
|
||||
secs := base + uint64(t.Unix())
|
||||
nano := uint32(t.Nanosecond()) &^ whitenerMask
|
||||
binary.BigEndian.PutUint64(tai64n[:], secs)
|
||||
binary.BigEndian.PutUint32(tai64n[8:], nano)
|
||||
return tai64n
|
||||
}
|
||||
|
||||
func Now() Timestamp {
|
||||
return stamp(time.Now())
|
||||
}
|
||||
|
||||
func (t1 Timestamp) After(t2 Timestamp) bool {
|
||||
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
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tai64n
|
||||
@@ -10,21 +10,31 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
/* Testing the essential property of the timestamp
|
||||
* as used by WireGuard.
|
||||
*/
|
||||
// Test that timestamps are monotonic as required by Wireguard and that
|
||||
// nanosecond-level information is whitened to prevent side channel attacks.
|
||||
func TestMonotonic(t *testing.T) {
|
||||
old := Now()
|
||||
for i := 0; i < 50; i++ {
|
||||
next := Now()
|
||||
if next.After(old) {
|
||||
t.Error("Whitening insufficient")
|
||||
startTime := time.Unix(0, 123456789) // a nontrivial bit pattern
|
||||
// Whitening should reduce timestamp granularity
|
||||
// to more than 10 but fewer than 20 milliseconds.
|
||||
tests := []struct {
|
||||
name string
|
||||
t1, t2 time.Time
|
||||
wantAfter bool
|
||||
}{
|
||||
{"after_10_ns", startTime, startTime.Add(10 * time.Nanosecond), false},
|
||||
{"after_10_us", startTime, startTime.Add(10 * time.Microsecond), false},
|
||||
{"after_1_ms", startTime, startTime.Add(time.Millisecond), false},
|
||||
{"after_10_ms", startTime, startTime.Add(10 * time.Millisecond), false},
|
||||
{"after_20_ms", startTime, startTime.Add(20 * time.Millisecond), true},
|
||||
}
|
||||
time.Sleep(time.Duration(whitenerMask)/time.Nanosecond + 1)
|
||||
next = Now()
|
||||
if !next.After(old) {
|
||||
t.Error("Not monotonically increasing on whitened nano-second scale")
|
||||
|
||||
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)
|
||||
}
|
||||
old = next
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ netns0="wg-test-$$-0"
|
||||
netns1="wg-test-$$-1"
|
||||
netns2="wg-test-$$-2"
|
||||
program=$1
|
||||
export LOG_LEVEL="info"
|
||||
export LOG_LEVEL="verbose"
|
||||
|
||||
pretty() { echo -e "\x1b[32m\x1b[1m[+] ${1:+NS$1: }${2}\x1b[0m" >&3; }
|
||||
pp() { pretty "" "$*"; "$@"; }
|
||||
|
||||
53
tun/netstack/examples/http_client.go
Normal file
53
tun/netstack/examples/http_client.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// +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/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, &device.Logger{log.Default(), log.Default(), log.Default()})
|
||||
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))
|
||||
}
|
||||
48
tun/netstack/examples/http_server.go
Normal file
48
tun/netstack/examples/http_server.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// +build ignore
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
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, &device.Logger{log.Default(), log.Default(), log.Default()})
|
||||
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)
|
||||
}
|
||||
}
|
||||
9
tun/netstack/go.mod
Normal file
9
tun/netstack/go.mod
Normal file
@@ -0,0 +1,9 @@
|
||||
module golang.zx2c4.com/wireguard/tun/netstack
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
|
||||
golang.zx2c4.com/wireguard v0.0.20201118
|
||||
gvisor.dev/gvisor v0.0.0-20210109011639-2fb7a49fea98
|
||||
)
|
||||
391
tun/netstack/go.sum
Normal file
391
tun/netstack/go.sum
Normal file
@@ -0,0 +1,391 @@
|
||||
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.52.1-0.20200122224058-0482b626c726/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
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.15-0.20200908182639-5b44b70ab3ab/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||
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/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE=
|
||||
github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE=
|
||||
github.com/containerd/containerd v1.3.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||
github.com/containerd/continuity v0.0.0-20200928162600-f2cc35102c2a/go.mod h1:W0qIOTD7mp2He++YVq+kgfXezRYqzP1uDuMVH1bITDY=
|
||||
github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
|
||||
github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g=
|
||||
github.com/containerd/ttrpc v0.0.0-20200121165050-0be804eadb15/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y=
|
||||
github.com/containerd/typeurl v0.0.0-20200205145503-b45ef1f1f737/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg=
|
||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
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/dpjacques/clockwork v0.1.1-0.20200827220843-c1f524b839be/go.mod h1:D8mP2A8vVT2GkXqPorSBmhnshhkFBYgzhA90KmJt25Y=
|
||||
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/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/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/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 v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.6.1-0.20180915234121-886344bea079/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/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/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.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/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.5.0/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.3-0.20201020212313-ab46b8bd0abd/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github/v28 v28.1.2-0.20191108005307-e555eab49ce8/go.mod h1:g82e6OHbJ0WYrYeOrid1MMfHAtqjxBz+N74tfAt9KrQ=
|
||||
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/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/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/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.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
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/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/konsorten/go-windows-terminal-sequences v1.0.3/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 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.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-0.20181111125026-1722abf79c2f/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-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.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
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/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-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
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.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-20201117144127-c1f2f97bffc9/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-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
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-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
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.3.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-20191004110552-13f9640d40b9/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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
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-20200625203802-6e8e738ad208/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-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-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-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-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7 h1:s330+6z/Ko3J0o6rvOcwXe5nzs7UT9tLKHoOXYn6uE0=
|
||||
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/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/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 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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-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-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201021000207-d49c4edd7d96/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
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.20201118 h1:QL8y2C7uO8T6z1GY+UX/hSeWiYEBurQkXjOTRFtCvXU=
|
||||
golang.zx2c4.com/wireguard v0.0.20201118/go.mod h1:Dz+cq5bnrai9EpgYj4GDof/+qaGzbRWbeaAOs1bUYa0=
|
||||
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.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
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/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-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
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.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.0/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
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.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-20210109011639-2fb7a49fea98 h1:qDiV0V69AVoFfU6AiE1UgpLUorGJrIxSM/P4tgkF8oc=
|
||||
gvisor.dev/gvisor v0.0.0-20210109011639-2fb7a49fea98/go.mod h1:5DEMKRjYDiM24fvDUWPjBpABm9ROMcv/kEcox3fHtm0=
|
||||
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=
|
||||
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=
|
||||
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.Route, _ *stack.GSO, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) *tcpip.Error {
|
||||
e.incomingPacket <- buffer.NewVectorisedView(pkt.Size(), pkt.Views())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *endpoint) WritePackets(*stack.Route, *stack.GSO, stack.PacketBufferList, tcpip.NetworkProtocolNumber) (int, *tcpip.Error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (*endpoint) ARPHardwareType() header.ARPHardwareType {
|
||||
return header.ARPHardwareNone
|
||||
}
|
||||
|
||||
func (e *endpoint) AddHeader(local, remote tcpip.LinkAddress, protocol tcpip.NetworkProtocolNumber, pkt *stack.PacketBuffer) {
|
||||
}
|
||||
|
||||
func CreateNetTUN(localAddresses []net.IP, 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)
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -20,19 +21,6 @@ import (
|
||||
|
||||
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 {
|
||||
name string
|
||||
tunFile *os.File
|
||||
@@ -41,19 +29,13 @@ type NativeTun struct {
|
||||
routeSocket int
|
||||
}
|
||||
|
||||
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 {
|
||||
if opErr, ok := err.(*net.OpError); ok {
|
||||
if syscallErr, ok := opErr.Err.(*os.SyscallError); ok && syscallErr.Err == syscall.ENOMEM {
|
||||
if err != nil && errors.Is(err, syscall.ENOMEM) {
|
||||
time.Sleep(time.Duration(i) * time.Second / 3)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return iface, err
|
||||
}
|
||||
return nil, err
|
||||
@@ -130,43 +112,21 @@ func CreateTUN(name string, mtu int) (Device, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ctlInfo = &struct {
|
||||
ctlID uint32
|
||||
ctlName [96]byte
|
||||
}{}
|
||||
|
||||
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)
|
||||
ctlInfo := &unix.CtlInfo{}
|
||||
copy(ctlInfo.Name[:], []byte(utunControlName))
|
||||
err = unix.IoctlCtlInfo(fd, ctlInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("IoctlGetCtlInfo: %w", err)
|
||||
}
|
||||
|
||||
sc := sockaddrCtl{
|
||||
scLen: uint8(sockaddrCtlSize),
|
||||
scFamily: unix.AF_SYSTEM,
|
||||
ssSysaddr: 2,
|
||||
scID: ctlInfo.ctlID,
|
||||
scUnit: uint32(ifIndex) + 1,
|
||||
sc := &unix.SockaddrCtl{
|
||||
ID: ctlInfo.Id,
|
||||
Unit: 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 = unix.Connect(fd, sc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = syscall.SetNonblock(fd, true)
|
||||
@@ -230,27 +190,19 @@ func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Name() (string, error) {
|
||||
var ifName struct {
|
||||
name [16]byte
|
||||
}
|
||||
ifNameSize := uintptr(16)
|
||||
|
||||
var errno syscall.Errno
|
||||
var err error
|
||||
tun.operateOnFd(func(fd uintptr) {
|
||||
_, _, errno = unix.Syscall6(
|
||||
unix.SYS_GETSOCKOPT,
|
||||
fd,
|
||||
tun.name, err = unix.GetsockoptString(
|
||||
int(fd),
|
||||
2, /* #define SYSPROTO_CONTROL 2 */
|
||||
2, /* #define UTUN_OPT_IFNAME 2 */
|
||||
uintptr(unsafe.Pointer(&ifName)),
|
||||
uintptr(unsafe.Pointer(&ifNameSize)), 0)
|
||||
)
|
||||
})
|
||||
|
||||
if errno != 0 {
|
||||
return "", fmt.Errorf("SYS_GETSOCKOPT: %v", errno)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("GetSockoptString: %w", err)
|
||||
}
|
||||
|
||||
tun.name = string(ifName.name[:ifNameSize-1])
|
||||
return tun.name, nil
|
||||
}
|
||||
|
||||
@@ -320,11 +272,6 @@ func (tun *NativeTun) Close() error {
|
||||
}
|
||||
|
||||
func (tun *NativeTun) setMTU(n int) error {
|
||||
|
||||
// open datagram socket
|
||||
|
||||
var fd int
|
||||
|
||||
fd, err := unix.Socket(
|
||||
unix.AF_INET,
|
||||
unix.SOCK_DGRAM,
|
||||
@@ -337,29 +284,18 @@ func (tun *NativeTun) setMTU(n int) error {
|
||||
|
||||
defer unix.Close(fd)
|
||||
|
||||
// do ioctl call
|
||||
|
||||
var ifr [32]byte
|
||||
copy(ifr[:], tun.name)
|
||||
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n)
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
uintptr(unix.SIOCSIFMTU),
|
||||
uintptr(unsafe.Pointer(&ifr[0])),
|
||||
)
|
||||
|
||||
if errno != 0 {
|
||||
return fmt.Errorf("failed to set MTU on %s", tun.name)
|
||||
var ifr unix.IfreqMTU
|
||||
copy(ifr.Name[:], tun.name)
|
||||
ifr.MTU = int32(n)
|
||||
err = unix.IoctlSetIfreqMTU(fd, &ifr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set MTU on %s: %w", tun.name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tun *NativeTun) MTU() (int, error) {
|
||||
|
||||
// open datagram socket
|
||||
|
||||
fd, err := unix.Socket(
|
||||
unix.AF_INET,
|
||||
unix.SOCK_DGRAM,
|
||||
@@ -372,19 +308,10 @@ func (tun *NativeTun) MTU() (int, error) {
|
||||
|
||||
defer unix.Close(fd)
|
||||
|
||||
// do ioctl call
|
||||
|
||||
var ifr [64]byte
|
||||
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)
|
||||
ifr, err := unix.IoctlGetIfreqMTU(fd, tun.name)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get MTU on %s: %w", tun.name, err)
|
||||
}
|
||||
|
||||
return int(*(*int32)(unsafe.Pointer(&ifr[16]))), nil
|
||||
return int(ifr.MTU), nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
@@ -287,7 +287,7 @@ func CreateTUN(name string, mtu int) (Device, error) {
|
||||
if errno != 0 {
|
||||
tunFile.Close()
|
||||
tunDestroy(assignedName)
|
||||
return nil, fmt.Errorf("Unable to put into IFHEAD mode: %v", errno)
|
||||
return nil, fmt.Errorf("Unable to put into IFHEAD mode: %w", errno)
|
||||
}
|
||||
|
||||
// Open control sockets
|
||||
@@ -328,7 +328,7 @@ func CreateTUN(name string, mtu int) (Device, error) {
|
||||
if errno != 0 {
|
||||
tunFile.Close()
|
||||
tunDestroy(assignedName)
|
||||
return nil, fmt.Errorf("Unable to get nd6 flags for %s: %v", assignedName, errno)
|
||||
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
|
||||
@@ -341,7 +341,7 @@ func CreateTUN(name string, mtu int) (Device, error) {
|
||||
if errno != 0 {
|
||||
tunFile.Close()
|
||||
tunDestroy(assignedName)
|
||||
return nil, fmt.Errorf("Unable to set nd6 flags for %s: %v", assignedName, errno)
|
||||
return nil, fmt.Errorf("Unable to set nd6 flags for %s: %w", assignedName, errno)
|
||||
}
|
||||
|
||||
// Rename the interface
|
||||
@@ -359,7 +359,7 @@ func CreateTUN(name string, mtu int) (Device, error) {
|
||||
if errno != 0 {
|
||||
tunFile.Close()
|
||||
tunDestroy(assignedName)
|
||||
return nil, fmt.Errorf("Failed to rename %s to %s: %v", assignedName, name, errno)
|
||||
return nil, fmt.Errorf("Failed to rename %s to %s: %w", assignedName, name, errno)
|
||||
}
|
||||
|
||||
return CreateTUNFromFile(tunFile, mtu)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
@@ -10,9 +10,7 @@ package tun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"syscall"
|
||||
@@ -21,6 +19,7 @@ import (
|
||||
|
||||
"golang.org/x/net/ipv6"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"golang.zx2c4.com/wireguard/rwcancel"
|
||||
)
|
||||
|
||||
@@ -32,7 +31,6 @@ const (
|
||||
type NativeTun struct {
|
||||
tunFile *os.File
|
||||
index int32 // if index
|
||||
name string // name of interface
|
||||
errors chan error // async error handling
|
||||
events chan Event // device related events
|
||||
nopi bool // the device was passed IFF_NO_PI
|
||||
@@ -40,6 +38,10 @@ type NativeTun struct {
|
||||
netlinkCancel *rwcancel.RWCancel
|
||||
hackListenerClosed sync.Mutex
|
||||
statusListenersShutdown chan struct{}
|
||||
|
||||
nameOnce sync.Once // guards calling initNameCache, which sets following fields
|
||||
nameCache string // name of interface
|
||||
nameErr error
|
||||
}
|
||||
|
||||
func (tun *NativeTun) File() *os.File {
|
||||
@@ -64,14 +66,19 @@ func (tun *NativeTun) routineHackListener() {
|
||||
}
|
||||
switch err {
|
||||
case unix.EINVAL:
|
||||
// If the tunnel is up, it reports that write() is
|
||||
// allowed but we provided invalid data.
|
||||
tun.events <- EventUp
|
||||
case unix.EIO:
|
||||
// If the tunnel is down, it reports that no I/O
|
||||
// is possible, without checking our provided data.
|
||||
tun.events <- EventDown
|
||||
default:
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-time.After(time.Second):
|
||||
// nothing
|
||||
case <-tun.statusListenersShutdown:
|
||||
return
|
||||
}
|
||||
@@ -85,7 +92,7 @@ func createNetlinkSocket() (int, error) {
|
||||
}
|
||||
saddr := &unix.SockaddrNetlink{
|
||||
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)
|
||||
if err != nil {
|
||||
@@ -99,10 +106,10 @@ func (tun *NativeTun) routineNetlinkListener() {
|
||||
unix.Close(tun.netlinkSock)
|
||||
tun.hackListenerClosed.Lock()
|
||||
close(tun.events)
|
||||
tun.netlinkCancel.Close()
|
||||
}()
|
||||
|
||||
for msg := make([]byte, 1<<16); ; {
|
||||
|
||||
var err error
|
||||
var msgn int
|
||||
for {
|
||||
@@ -111,12 +118,12 @@ func (tun *NativeTun) routineNetlinkListener() {
|
||||
break
|
||||
}
|
||||
if !tun.netlinkCancel.ReadyRead() {
|
||||
tun.errors <- fmt.Errorf("netlink socket closed: %s", err.Error())
|
||||
tun.errors <- fmt.Errorf("netlink socket closed: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -126,6 +133,7 @@ func (tun *NativeTun) routineNetlinkListener() {
|
||||
default:
|
||||
}
|
||||
|
||||
wasEverUp := false
|
||||
for remain := msg[:msgn]; len(remain) >= unix.SizeofNlMsghdr; {
|
||||
|
||||
hdr := *(*unix.NlMsghdr)(unsafe.Pointer(&remain[0]))
|
||||
@@ -149,11 +157,17 @@ func (tun *NativeTun) routineNetlinkListener() {
|
||||
|
||||
if info.Flags&unix.IFF_RUNNING != 0 {
|
||||
tun.events <- EventUp
|
||||
wasEverUp = true
|
||||
}
|
||||
|
||||
if info.Flags&unix.IFF_RUNNING == 0 {
|
||||
// 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 <- EventMTUUpdate
|
||||
|
||||
@@ -164,11 +178,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) {
|
||||
fd, err := unix.Socket(
|
||||
unix.AF_INET,
|
||||
@@ -198,6 +207,11 @@ func getIFIndex(name string) (int32, error) {
|
||||
}
|
||||
|
||||
func (tun *NativeTun) setMTU(n int) error {
|
||||
name, err := tun.Name()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// open datagram socket
|
||||
fd, err := unix.Socket(
|
||||
unix.AF_INET,
|
||||
@@ -212,9 +226,8 @@ func (tun *NativeTun) setMTU(n int) error {
|
||||
defer unix.Close(fd)
|
||||
|
||||
// do ioctl call
|
||||
|
||||
var ifr [ifReqSize]byte
|
||||
copy(ifr[:], tun.name)
|
||||
copy(ifr[:], name)
|
||||
*(*uint32)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = uint32(n)
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
@@ -224,13 +237,18 @@ func (tun *NativeTun) setMTU(n int) error {
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (tun *NativeTun) MTU() (int, error) {
|
||||
name, err := tun.Name()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// open datagram socket
|
||||
fd, err := unix.Socket(
|
||||
unix.AF_INET,
|
||||
@@ -247,7 +265,7 @@ func (tun *NativeTun) MTU() (int, error) {
|
||||
// do ioctl call
|
||||
|
||||
var ifr [ifReqSize]byte
|
||||
copy(ifr[:], tun.name)
|
||||
copy(ifr[:], name)
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
@@ -255,13 +273,22 @@ func (tun *NativeTun) MTU() (int, error) {
|
||||
uintptr(unsafe.Pointer(&ifr[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
|
||||
}
|
||||
|
||||
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()
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -277,18 +304,16 @@ func (tun *NativeTun) Name() (string, error) {
|
||||
)
|
||||
})
|
||||
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 {
|
||||
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[:]
|
||||
i := bytes.IndexByte(nullStr, 0)
|
||||
if i != -1 {
|
||||
nullStr = nullStr[:i]
|
||||
name := ifr[:]
|
||||
if i := bytes.IndexByte(name, 0); i != -1 {
|
||||
name = name[:i]
|
||||
}
|
||||
tun.name = string(nullStr)
|
||||
return tun.name, nil
|
||||
return string(name), nil
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
|
||||
@@ -367,6 +392,9 @@ func (tun *NativeTun) Close() error {
|
||||
func CreateTUN(name string, mtu int) (Device, error) {
|
||||
nfd, err := unix.Open(cloneDevicePath, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("CreateTUN(%q) failed; %s does not exist", name, cloneDevicePath)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -374,7 +402,7 @@ func CreateTUN(name string, mtu int) (Device, error) {
|
||||
var flags uint16 = unix.IFF_TUN // | unix.IFF_NO_PI (disabled for TUN status hack)
|
||||
nameBytes := []byte(name)
|
||||
if len(nameBytes) >= unix.IFNAMSIZ {
|
||||
return nil, errors.New("interface name too long")
|
||||
return nil, fmt.Errorf("interface name too long: %w", unix.ENAMETOOLONG)
|
||||
}
|
||||
copy(ifr[:], nameBytes)
|
||||
*(*uint16)(unsafe.Pointer(&ifr[unix.IFNAMSIZ])) = flags
|
||||
@@ -408,16 +436,15 @@ func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
|
||||
statusListenersShutdown: make(chan struct{}),
|
||||
nopi: false,
|
||||
}
|
||||
var err error
|
||||
|
||||
_, err = tun.Name()
|
||||
name, err := tun.Name()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// start event listener
|
||||
|
||||
tun.index, err = getIFIndex(tun.name)
|
||||
tun.index, err = getIFIndex(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -99,16 +100,6 @@ func (tun *NativeTun) routineRouteListener(tunIfindex int) {
|
||||
}
|
||||
}
|
||||
|
||||
func errorIsEBUSY(err error) bool {
|
||||
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) (Device, error) {
|
||||
ifIndex := -1
|
||||
if name != "tun" {
|
||||
@@ -124,9 +115,9 @@ func CreateTUN(name string, mtu int) (Device, error) {
|
||||
if ifIndex != -1 {
|
||||
tunfile, err = os.OpenFile(fmt.Sprintf("/dev/tun%d", ifIndex), unix.O_RDWR, 0)
|
||||
} 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)
|
||||
if err == nil || !errorIsEBUSY(err) {
|
||||
if err == nil || !errors.Is(err, syscall.EBUSY) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
* Copyright (C) 2018-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tun
|
||||
@@ -8,11 +8,11 @@ package tun
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
_ "unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
@@ -33,18 +33,19 @@ type rateJuggler struct {
|
||||
}
|
||||
|
||||
type NativeTun struct {
|
||||
wt *wintun.Interface
|
||||
wt *wintun.Adapter
|
||||
handle windows.Handle
|
||||
close bool
|
||||
events chan Event
|
||||
errors chan error
|
||||
forcedMTU int
|
||||
rate rateJuggler
|
||||
rings *wintun.RingDescriptor
|
||||
writeLock sync.Mutex
|
||||
session wintun.Session
|
||||
readWait windows.Handle
|
||||
}
|
||||
|
||||
const WintunPool = wintun.Pool("WireGuard")
|
||||
var WintunPool, _ = wintun.MakePool("WireGuard")
|
||||
var WintunStaticRequestedGUID *windows.GUID
|
||||
|
||||
//go:linkname procyield runtime.procyield
|
||||
func procyield(cycles uint32)
|
||||
@@ -57,7 +58,7 @@ func nanotime() int64
|
||||
// interface with the same name exist, it is reused.
|
||||
//
|
||||
func CreateTUN(ifname string, mtu int) (Device, error) {
|
||||
return CreateTUNWithRequestedGUID(ifname, nil, mtu)
|
||||
return CreateTUNWithRequestedGUID(ifname, WintunStaticRequestedGUID, mtu)
|
||||
}
|
||||
|
||||
//
|
||||
@@ -66,20 +67,23 @@ func CreateTUN(ifname string, mtu int) (Device, error) {
|
||||
//
|
||||
func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu int) (Device, error) {
|
||||
var err error
|
||||
var wt *wintun.Interface
|
||||
var wt *wintun.Adapter
|
||||
|
||||
// Does an interface with this name already exist?
|
||||
wt, err = WintunPool.GetInterface(ifname)
|
||||
wt, err = WintunPool.OpenAdapter(ifname)
|
||||
if err == nil {
|
||||
// If so, we delete it, in case it has weird residual configuration.
|
||||
_, err = wt.DeleteInterface()
|
||||
_, err = wt.Delete(true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error deleting already existing interface: %v", err)
|
||||
return nil, fmt.Errorf("Error deleting already existing interface: %w", err)
|
||||
}
|
||||
}
|
||||
wt, _, err = WintunPool.CreateInterface(ifname, requestedGUID)
|
||||
wt, rebootRequired, err := WintunPool.CreateAdapter(ifname, requestedGUID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error creating interface: %v", err)
|
||||
return nil, fmt.Errorf("Error creating interface: %w", err)
|
||||
}
|
||||
if rebootRequired {
|
||||
log.Println("Windows indicated a reboot is required.")
|
||||
}
|
||||
|
||||
forcedMTU := 1420
|
||||
@@ -95,17 +99,13 @@ func CreateTUNWithRequestedGUID(ifname string, requestedGUID *windows.GUID, mtu
|
||||
forcedMTU: forcedMTU,
|
||||
}
|
||||
|
||||
tun.rings, err = wintun.NewRingDescriptor()
|
||||
tun.session, err = wt.StartSession(0x800000) // Ring capacity, 8 MiB
|
||||
if err != nil {
|
||||
tun.Close()
|
||||
return nil, fmt.Errorf("Error creating events: %v", err)
|
||||
}
|
||||
|
||||
tun.handle, err = tun.wt.Register(tun.rings)
|
||||
if err != nil {
|
||||
tun.Close()
|
||||
return nil, fmt.Errorf("Error registering rings: %v", err)
|
||||
tun.wt.Delete(false)
|
||||
close(tun.events)
|
||||
return nil, fmt.Errorf("Error starting session: %w", err)
|
||||
}
|
||||
tun.readWait = tun.session.ReadWaitEvent()
|
||||
return tun, nil
|
||||
}
|
||||
|
||||
@@ -123,16 +123,10 @@ func (tun *NativeTun) Events() chan Event {
|
||||
|
||||
func (tun *NativeTun) Close() error {
|
||||
tun.close = true
|
||||
if tun.rings.Send.TailMoved != 0 {
|
||||
windows.SetEvent(tun.rings.Send.TailMoved) // wake the reader if it's sleeping
|
||||
}
|
||||
if tun.handle != windows.InvalidHandle {
|
||||
windows.CloseHandle(tun.handle)
|
||||
}
|
||||
tun.rings.Close()
|
||||
tun.session.End()
|
||||
var err error
|
||||
if tun.wt != nil {
|
||||
_, err = tun.wt.DeleteInterface()
|
||||
_, err = tun.wt.Delete(false)
|
||||
}
|
||||
close(tun.events)
|
||||
return err
|
||||
@@ -156,56 +150,34 @@ retry:
|
||||
return 0, err
|
||||
default:
|
||||
}
|
||||
if tun.close {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
buffHead := atomic.LoadUint32(&tun.rings.Send.Ring.Head)
|
||||
if buffHead >= wintun.PacketCapacity {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
start := nanotime()
|
||||
shouldSpin := atomic.LoadUint64(&tun.rate.current) >= spinloopRateThreshold && uint64(start-atomic.LoadInt64(&tun.rate.nextStartTime)) <= rateMeasurementGranularity*2
|
||||
var buffTail uint32
|
||||
for {
|
||||
buffTail = atomic.LoadUint32(&tun.rings.Send.Ring.Tail)
|
||||
if buffHead != buffTail {
|
||||
break
|
||||
}
|
||||
if tun.close {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
packet, err := tun.session.ReceivePacket()
|
||||
switch err {
|
||||
case nil:
|
||||
packetSize := len(packet)
|
||||
copy(buff[offset:], packet)
|
||||
tun.session.ReleaseReceivePacket(packet)
|
||||
tun.rate.update(uint64(packetSize))
|
||||
return packetSize, nil
|
||||
case windows.ERROR_NO_MORE_ITEMS:
|
||||
if !shouldSpin || uint64(nanotime()-start) >= spinloopDuration {
|
||||
windows.WaitForSingleObject(tun.rings.Send.TailMoved, windows.INFINITE)
|
||||
windows.WaitForSingleObject(tun.readWait, windows.INFINITE)
|
||||
goto retry
|
||||
}
|
||||
procyield(1)
|
||||
}
|
||||
if buffTail >= wintun.PacketCapacity {
|
||||
continue
|
||||
case windows.ERROR_HANDLE_EOF:
|
||||
return 0, os.ErrClosed
|
||||
case windows.ERROR_INVALID_DATA:
|
||||
return 0, errors.New("Send ring corrupt")
|
||||
}
|
||||
|
||||
buffContent := tun.rings.Send.Ring.Wrap(buffTail - buffHead)
|
||||
if buffContent < uint32(unsafe.Sizeof(wintun.PacketHeader{})) {
|
||||
return 0, errors.New("incomplete packet header in send ring")
|
||||
return 0, fmt.Errorf("Read failed: %w", err)
|
||||
}
|
||||
|
||||
packet := (*wintun.Packet)(unsafe.Pointer(&tun.rings.Send.Ring.Data[buffHead]))
|
||||
if packet.Size > wintun.PacketSizeMax {
|
||||
return 0, errors.New("packet too big in send ring")
|
||||
}
|
||||
|
||||
alignedPacketSize := wintun.PacketAlign(uint32(unsafe.Sizeof(wintun.PacketHeader{})) + packet.Size)
|
||||
if alignedPacketSize > buffContent {
|
||||
return 0, errors.New("incomplete packet in send ring")
|
||||
}
|
||||
|
||||
copy(buff[offset:], packet.Data[:packet.Size])
|
||||
buffHead = tun.rings.Send.Ring.Wrap(buffHead + alignedPacketSize)
|
||||
atomic.StoreUint32(&tun.rings.Send.Ring.Head, buffHead)
|
||||
tun.rate.update(uint64(packet.Size))
|
||||
return int(packet.Size), nil
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Flush() error {
|
||||
@@ -217,36 +189,22 @@ func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
packetSize := uint32(len(buff) - offset)
|
||||
packetSize := len(buff) - offset
|
||||
tun.rate.update(uint64(packetSize))
|
||||
alignedPacketSize := wintun.PacketAlign(uint32(unsafe.Sizeof(wintun.PacketHeader{})) + packetSize)
|
||||
|
||||
tun.writeLock.Lock()
|
||||
defer tun.writeLock.Unlock()
|
||||
|
||||
buffHead := atomic.LoadUint32(&tun.rings.Receive.Ring.Head)
|
||||
if buffHead >= wintun.PacketCapacity {
|
||||
return 0, os.ErrClosed
|
||||
packet, err := tun.session.AllocateSendPacket(packetSize)
|
||||
if err == nil {
|
||||
copy(packet, buff[offset:])
|
||||
tun.session.SendPacket(packet)
|
||||
return packetSize, nil
|
||||
}
|
||||
|
||||
buffTail := atomic.LoadUint32(&tun.rings.Receive.Ring.Tail)
|
||||
if buffTail >= wintun.PacketCapacity {
|
||||
switch err {
|
||||
case windows.ERROR_HANDLE_EOF:
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
buffSpace := tun.rings.Receive.Ring.Wrap(buffHead - buffTail - wintun.PacketAlignment)
|
||||
if alignedPacketSize > buffSpace {
|
||||
case windows.ERROR_BUFFER_OVERFLOW:
|
||||
return 0, nil // Dropping when ring is full.
|
||||
}
|
||||
|
||||
packet := (*wintun.Packet)(unsafe.Pointer(&tun.rings.Receive.Ring.Data[buffTail]))
|
||||
packet.Size = packetSize
|
||||
copy(packet.Data[:packetSize], buff[offset:])
|
||||
atomic.StoreUint32(&tun.rings.Receive.Ring.Tail, tun.rings.Receive.Ring.Wrap(buffTail+alignedPacketSize))
|
||||
if atomic.LoadInt32(&tun.rings.Receive.Ring.Alertable) != 0 {
|
||||
windows.SetEvent(tun.rings.Receive.TailMoved)
|
||||
}
|
||||
return int(packetSize), nil
|
||||
return 0, fmt.Errorf("Write failed: %w", err)
|
||||
}
|
||||
|
||||
// LUID returns Windows interface instance ID.
|
||||
@@ -254,9 +212,9 @@ func (tun *NativeTun) LUID() uint64 {
|
||||
return tun.wt.LUID()
|
||||
}
|
||||
|
||||
// Version returns the version of the Wintun driver and NDIS system currently loaded.
|
||||
func (tun *NativeTun) Version() (driverVersion string, ndisVersion string, err error) {
|
||||
return tun.wt.Version()
|
||||
// RunningVersion returns the running version of the Wintun driver.
|
||||
func (tun *NativeTun) RunningVersion() (version uint32, err error) {
|
||||
return wintun.RunningVersion()
|
||||
}
|
||||
|
||||
func (rate *rateJuggler) update(packetLen uint64) {
|
||||
|
||||
149
tun/tuntest/tuntest.go
Normal file
149
tun/tuntest/tuntest.go
Normal file
@@ -0,0 +1,149 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package tuntest
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
)
|
||||
|
||||
func Ping(dst, src net.IP) []byte {
|
||||
localPort := uint16(1337)
|
||||
seq := uint16(0)
|
||||
|
||||
payload := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(payload[0:], localPort)
|
||||
binary.BigEndian.PutUint16(payload[2:], seq)
|
||||
|
||||
return genICMPv4(payload, dst, src)
|
||||
}
|
||||
|
||||
// Checksum is the "internet checksum" from https://tools.ietf.org/html/rfc1071.
|
||||
func checksum(buf []byte, initial uint16) uint16 {
|
||||
v := uint32(initial)
|
||||
for i := 0; i < len(buf)-1; i += 2 {
|
||||
v += uint32(binary.BigEndian.Uint16(buf[i:]))
|
||||
}
|
||||
if len(buf)%2 == 1 {
|
||||
v += uint32(buf[len(buf)-1]) << 8
|
||||
}
|
||||
for v > 0xffff {
|
||||
v = (v >> 16) + (v & 0xffff)
|
||||
}
|
||||
return ^uint16(v)
|
||||
}
|
||||
|
||||
func genICMPv4(payload []byte, dst, src net.IP) []byte {
|
||||
const (
|
||||
icmpv4ProtocolNumber = 1
|
||||
icmpv4Echo = 8
|
||||
icmpv4ChecksumOffset = 2
|
||||
icmpv4Size = 8
|
||||
ipv4Size = 20
|
||||
ipv4TotalLenOffset = 2
|
||||
ipv4ChecksumOffset = 10
|
||||
ttl = 65
|
||||
headerSize = ipv4Size + icmpv4Size
|
||||
)
|
||||
|
||||
pkt := make([]byte, headerSize+len(payload))
|
||||
|
||||
ip := pkt[0:ipv4Size]
|
||||
icmpv4 := pkt[ipv4Size : ipv4Size+icmpv4Size]
|
||||
|
||||
// https://tools.ietf.org/html/rfc792
|
||||
icmpv4[0] = icmpv4Echo // type
|
||||
icmpv4[1] = 0 // code
|
||||
chksum := ^checksum(icmpv4, checksum(payload, 0))
|
||||
binary.BigEndian.PutUint16(icmpv4[icmpv4ChecksumOffset:], chksum)
|
||||
|
||||
// https://tools.ietf.org/html/rfc760 section 3.1
|
||||
length := uint16(len(pkt))
|
||||
ip[0] = (4 << 4) | (ipv4Size / 4)
|
||||
binary.BigEndian.PutUint16(ip[ipv4TotalLenOffset:], length)
|
||||
ip[8] = ttl
|
||||
ip[9] = icmpv4ProtocolNumber
|
||||
copy(ip[12:], src.To4())
|
||||
copy(ip[16:], dst.To4())
|
||||
chksum = ^checksum(ip[:], 0)
|
||||
binary.BigEndian.PutUint16(ip[ipv4ChecksumOffset:], chksum)
|
||||
|
||||
copy(pkt[headerSize:], payload)
|
||||
return pkt
|
||||
}
|
||||
|
||||
// TODO(crawshaw): find a reusable home for this. package devicetest?
|
||||
type ChannelTUN struct {
|
||||
Inbound chan []byte // incoming packets, closed on TUN close
|
||||
Outbound chan []byte // outbound packets, blocks forever on TUN close
|
||||
|
||||
closed chan struct{}
|
||||
events chan tun.Event
|
||||
tun chTun
|
||||
}
|
||||
|
||||
func NewChannelTUN() *ChannelTUN {
|
||||
c := &ChannelTUN{
|
||||
Inbound: make(chan []byte),
|
||||
Outbound: make(chan []byte),
|
||||
closed: make(chan struct{}),
|
||||
events: make(chan tun.Event, 1),
|
||||
}
|
||||
c.tun.c = c
|
||||
c.events <- tun.EventUp
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *ChannelTUN) TUN() tun.Device {
|
||||
return &c.tun
|
||||
}
|
||||
|
||||
type chTun struct {
|
||||
c *ChannelTUN
|
||||
}
|
||||
|
||||
func (t *chTun) File() *os.File { return nil }
|
||||
|
||||
func (t *chTun) Read(data []byte, offset int) (int, error) {
|
||||
select {
|
||||
case <-t.c.closed:
|
||||
return 0, io.EOF // TODO(crawshaw): what is the correct error value?
|
||||
case msg := <-t.c.Outbound:
|
||||
return copy(data[offset:], msg), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Write is called by the wireguard device to deliver a packet for routing.
|
||||
func (t *chTun) Write(data []byte, offset int) (int, error) {
|
||||
if offset == -1 {
|
||||
close(t.c.closed)
|
||||
close(t.c.events)
|
||||
return 0, io.EOF
|
||||
}
|
||||
msg := make([]byte, len(data)-offset)
|
||||
copy(msg, data[offset:])
|
||||
select {
|
||||
case <-t.c.closed:
|
||||
return 0, io.EOF // TODO(crawshaw): what is the correct error value?
|
||||
case t.c.Inbound <- msg:
|
||||
return len(data) - offset, nil
|
||||
}
|
||||
}
|
||||
|
||||
const DefaultMTU = 1420
|
||||
|
||||
func (t *chTun) Flush() error { return nil }
|
||||
func (t *chTun) MTU() (int, error) { return DefaultMTU, nil }
|
||||
func (t *chTun) Name() (string, error) { return "loopbackTun1", nil }
|
||||
func (t *chTun) Events() chan tun.Event { return t.c.events }
|
||||
func (t *chTun) Close() error {
|
||||
t.Write(nil, -1)
|
||||
return nil
|
||||
}
|
||||
54
tun/wintun/dll_fromfile_windows.go
Normal file
54
tun/wintun/dll_fromfile_windows.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// +build !load_wintun_from_rsrc
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wintun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type lazyDLL struct {
|
||||
Name string
|
||||
mu sync.Mutex
|
||||
module windows.Handle
|
||||
onLoad func(d *lazyDLL)
|
||||
}
|
||||
|
||||
func (d *lazyDLL) Load() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
|
||||
return nil
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.module != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200
|
||||
LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
|
||||
)
|
||||
module, err := windows.LoadLibraryEx(d.Name, 0, LOAD_LIBRARY_SEARCH_APPLICATION_DIR|LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to load library: %w", err)
|
||||
}
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
|
||||
if d.onLoad != nil {
|
||||
d.onLoad(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) nameToAddr() (uintptr, error) {
|
||||
return windows.GetProcAddress(p.dll.module, p.Name)
|
||||
}
|
||||
62
tun/wintun/dll_fromrsrc_windows.go
Normal file
62
tun/wintun/dll_fromrsrc_windows.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// +build load_wintun_from_rsrc
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wintun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun/wintun/memmod"
|
||||
"golang.zx2c4.com/wireguard/tun/wintun/resource"
|
||||
)
|
||||
|
||||
type lazyDLL struct {
|
||||
Name string
|
||||
mu sync.Mutex
|
||||
module *memmod.Module
|
||||
onLoad func(d *lazyDLL)
|
||||
}
|
||||
|
||||
func (d *lazyDLL) Load() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
|
||||
return nil
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.module != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
const ourModule windows.Handle = 0
|
||||
resInfo, err := resource.FindByName(ourModule, d.Name, resource.RT_RCDATA)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find \"%v\" RCDATA resource: %w", d.Name, err)
|
||||
}
|
||||
data, err := resource.Load(ourModule, resInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to load resource: %w", err)
|
||||
}
|
||||
module, err := memmod.LoadLibrary(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to load library: %w", err)
|
||||
}
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
|
||||
if d.onLoad != nil {
|
||||
d.onLoad(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) nameToAddr() (uintptr, error) {
|
||||
return p.dll.module.ProcAddressByName(p.Name)
|
||||
}
|
||||
59
tun/wintun/dll_windows.go
Normal file
59
tun/wintun/dll_windows.go
Normal file
@@ -0,0 +1,59 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package wintun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
|
||||
return &lazyDLL{Name: name, onLoad: onLoad}
|
||||
}
|
||||
|
||||
func (d *lazyDLL) NewProc(name string) *lazyProc {
|
||||
return &lazyProc{dll: d, Name: name}
|
||||
}
|
||||
|
||||
type lazyProc struct {
|
||||
Name string
|
||||
mu sync.Mutex
|
||||
dll *lazyDLL
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
func (p *lazyProc) Find() error {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
|
||||
return nil
|
||||
}
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.addr != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.dll.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading %v DLL: %w", p.dll.Name, err)
|
||||
}
|
||||
addr, err := p.nameToAddr()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting %v address: %w", p.Name, err)
|
||||
}
|
||||
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *lazyProc) Addr() uintptr {
|
||||
err := p.Find()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p.addr
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package iphlpapi
|
||||
|
||||
import "golang.org/x/sys/windows"
|
||||
|
||||
//sys convertInterfaceLUIDToGUID(interfaceLUID *uint64, interfaceGUID *windows.GUID) (ret error) = iphlpapi.ConvertInterfaceLuidToGuid
|
||||
//sys convertInterfaceAliasToLUID(interfaceAlias *uint16, interfaceLUID *uint64) (ret error) = iphlpapi.ConvertInterfaceAliasToLuid
|
||||
|
||||
func InterfaceGUIDFromAlias(alias string) (*windows.GUID, error) {
|
||||
var luid uint64
|
||||
var guid windows.GUID
|
||||
err := convertInterfaceAliasToLUID(windows.StringToUTF16Ptr(alias), &luid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = convertInterfaceLUIDToGUID(&luid, &guid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &guid, nil
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package iphlpapi
|
||||
|
||||
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go conversion_windows.go
|
||||
@@ -1,60 +0,0 @@
|
||||
// Code generated by 'go generate'; DO NOT EDIT.
|
||||
|
||||
package iphlpapi
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const (
|
||||
errnoERROR_IO_PENDING = 997
|
||||
)
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error {
|
||||
switch e {
|
||||
case 0:
|
||||
return nil
|
||||
case errnoERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
}
|
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e
|
||||
}
|
||||
|
||||
var (
|
||||
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
|
||||
procConvertInterfaceLuidToGuid = modiphlpapi.NewProc("ConvertInterfaceLuidToGuid")
|
||||
procConvertInterfaceAliasToLuid = modiphlpapi.NewProc("ConvertInterfaceAliasToLuid")
|
||||
)
|
||||
|
||||
func convertInterfaceLUIDToGUID(interfaceLUID *uint64, interfaceGUID *windows.GUID) (ret error) {
|
||||
r0, _, _ := syscall.Syscall(procConvertInterfaceLuidToGuid.Addr(), 2, uintptr(unsafe.Pointer(interfaceLUID)), uintptr(unsafe.Pointer(interfaceGUID)), 0)
|
||||
if r0 != 0 {
|
||||
ret = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func convertInterfaceAliasToLUID(interfaceAlias *uint16, interfaceLUID *uint64) (ret error) {
|
||||
r0, _, _ := syscall.Syscall(procConvertInterfaceAliasToLuid.Addr(), 2, uintptr(unsafe.Pointer(interfaceAlias)), uintptr(unsafe.Pointer(interfaceLUID)), 0)
|
||||
if r0 != 0 {
|
||||
ret = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
620
tun/wintun/memmod/memmod_windows.go
Normal file
620
tun/wintun/memmod/memmod_windows.go
Normal file
@@ -0,0 +1,620 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package memmod
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type addressList struct {
|
||||
next *addressList
|
||||
address uintptr
|
||||
}
|
||||
|
||||
func (head *addressList) free() {
|
||||
for node := head; node != nil; node = node.next {
|
||||
windows.VirtualFree(node.address, 0, windows.MEM_RELEASE)
|
||||
}
|
||||
}
|
||||
|
||||
type Module struct {
|
||||
headers *IMAGE_NT_HEADERS
|
||||
codeBase uintptr
|
||||
modules []windows.Handle
|
||||
initialized bool
|
||||
isDLL bool
|
||||
isRelocated bool
|
||||
nameExports map[string]uint16
|
||||
entry uintptr
|
||||
blockedMemory *addressList
|
||||
}
|
||||
|
||||
func (module *Module) headerDirectory(idx int) *IMAGE_DATA_DIRECTORY {
|
||||
return &module.headers.OptionalHeader.DataDirectory[idx]
|
||||
}
|
||||
|
||||
func (module *Module) copySections(address uintptr, size uintptr, old_headers *IMAGE_NT_HEADERS) error {
|
||||
sections := module.headers.Sections()
|
||||
for i := range sections {
|
||||
if sections[i].SizeOfRawData == 0 {
|
||||
// Section doesn't contain data in the dll itself, but may define uninitialized data.
|
||||
sectionSize := old_headers.OptionalHeader.SectionAlignment
|
||||
if sectionSize == 0 {
|
||||
continue
|
||||
}
|
||||
dest, err := windows.VirtualAlloc(module.codeBase+uintptr(sections[i].VirtualAddress),
|
||||
uintptr(sectionSize),
|
||||
windows.MEM_COMMIT,
|
||||
windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error allocating section: %w", err)
|
||||
}
|
||||
|
||||
// Always use position from file to support alignments smaller than page size (allocation above will align to page size).
|
||||
dest = module.codeBase + uintptr(sections[i].VirtualAddress)
|
||||
// NOTE: On 64bit systems we truncate to 32bit here but expand again later when "PhysicalAddress" is used.
|
||||
sections[i].SetPhysicalAddress((uint32)(dest & 0xffffffff))
|
||||
var dst []byte
|
||||
unsafeSlice(unsafe.Pointer(&dst), a2p(dest), int(sectionSize))
|
||||
for j := range dst {
|
||||
dst[j] = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if size < uintptr(sections[i].PointerToRawData+sections[i].SizeOfRawData) {
|
||||
return errors.New("Incomplete section")
|
||||
}
|
||||
|
||||
// Commit memory block and copy data from dll.
|
||||
dest, err := windows.VirtualAlloc(module.codeBase+uintptr(sections[i].VirtualAddress),
|
||||
uintptr(sections[i].SizeOfRawData),
|
||||
windows.MEM_COMMIT,
|
||||
windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error allocating memory block: %w", err)
|
||||
}
|
||||
|
||||
// Always use position from file to support alignments smaller than page size (allocation above will align to page size).
|
||||
memcpy(
|
||||
module.codeBase+uintptr(sections[i].VirtualAddress),
|
||||
address+uintptr(sections[i].PointerToRawData),
|
||||
uintptr(sections[i].SizeOfRawData))
|
||||
// NOTE: On 64bit systems we truncate to 32bit here but expand again later when "PhysicalAddress" is used.
|
||||
sections[i].SetPhysicalAddress((uint32)(dest & 0xffffffff))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *Module) realSectionSize(section *IMAGE_SECTION_HEADER) uintptr {
|
||||
size := section.SizeOfRawData
|
||||
if size != 0 {
|
||||
return uintptr(size)
|
||||
}
|
||||
if (section.Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) != 0 {
|
||||
return uintptr(module.headers.OptionalHeader.SizeOfInitializedData)
|
||||
}
|
||||
if (section.Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) != 0 {
|
||||
return uintptr(module.headers.OptionalHeader.SizeOfUninitializedData)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type sectionFinalizeData struct {
|
||||
address uintptr
|
||||
alignedAddress uintptr
|
||||
size uintptr
|
||||
characteristics uint32
|
||||
last bool
|
||||
}
|
||||
|
||||
func (module *Module) finalizeSection(sectionData *sectionFinalizeData) error {
|
||||
if sectionData.size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if (sectionData.characteristics & IMAGE_SCN_MEM_DISCARDABLE) != 0 {
|
||||
// Section is not needed any more and can safely be freed.
|
||||
if sectionData.address == sectionData.alignedAddress &&
|
||||
(sectionData.last ||
|
||||
(sectionData.size%uintptr(module.headers.OptionalHeader.SectionAlignment)) == 0) {
|
||||
// Only allowed to decommit whole pages.
|
||||
windows.VirtualFree(sectionData.address, sectionData.size, windows.MEM_DECOMMIT)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// determine protection flags based on characteristics
|
||||
var ProtectionFlags = [8]uint32{
|
||||
windows.PAGE_NOACCESS, // not writeable, not readable, not executable
|
||||
windows.PAGE_EXECUTE, // not writeable, not readable, executable
|
||||
windows.PAGE_READONLY, // not writeable, readable, not executable
|
||||
windows.PAGE_EXECUTE_READ, // not writeable, readable, executable
|
||||
windows.PAGE_WRITECOPY, // writeable, not readable, not executable
|
||||
windows.PAGE_EXECUTE_WRITECOPY, // writeable, not readable, executable
|
||||
windows.PAGE_READWRITE, // writeable, readable, not executable
|
||||
windows.PAGE_EXECUTE_READWRITE, // writeable, readable, executable
|
||||
}
|
||||
protect := ProtectionFlags[sectionData.characteristics>>29]
|
||||
if (sectionData.characteristics & IMAGE_SCN_MEM_NOT_CACHED) != 0 {
|
||||
protect |= windows.PAGE_NOCACHE
|
||||
}
|
||||
|
||||
// Change memory access flags.
|
||||
var oldProtect uint32
|
||||
err := windows.VirtualProtect(sectionData.address, sectionData.size, protect, &oldProtect)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error protecting memory page: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *Module) finalizeSections() error {
|
||||
sections := module.headers.Sections()
|
||||
imageOffset := module.headers.OptionalHeader.imageOffset()
|
||||
sectionData := sectionFinalizeData{}
|
||||
sectionData.address = uintptr(sections[0].PhysicalAddress()) | imageOffset
|
||||
sectionData.alignedAddress = alignDown(sectionData.address, uintptr(module.headers.OptionalHeader.SectionAlignment))
|
||||
sectionData.size = module.realSectionSize(§ions[0])
|
||||
sectionData.characteristics = sections[0].Characteristics
|
||||
|
||||
// Loop through all sections and change access flags.
|
||||
for i := uint16(1); i < module.headers.FileHeader.NumberOfSections; i++ {
|
||||
sectionAddress := uintptr(sections[i].PhysicalAddress()) | imageOffset
|
||||
alignedAddress := alignDown(sectionAddress, uintptr(module.headers.OptionalHeader.SectionAlignment))
|
||||
sectionSize := module.realSectionSize(§ions[i])
|
||||
// Combine access flags of all sections that share a page.
|
||||
// TODO: We currently share flags of a trailing large section with the page of a first small section. This should be optimized.
|
||||
if sectionData.alignedAddress == alignedAddress || sectionData.address+sectionData.size > alignedAddress {
|
||||
// Section shares page with previous.
|
||||
if (sections[i].Characteristics&IMAGE_SCN_MEM_DISCARDABLE) == 0 || (sectionData.characteristics&IMAGE_SCN_MEM_DISCARDABLE) == 0 {
|
||||
sectionData.characteristics = (sectionData.characteristics | sections[i].Characteristics) &^ IMAGE_SCN_MEM_DISCARDABLE
|
||||
} else {
|
||||
sectionData.characteristics |= sections[i].Characteristics
|
||||
}
|
||||
sectionData.size = sectionAddress + sectionSize - sectionData.address
|
||||
continue
|
||||
}
|
||||
|
||||
err := module.finalizeSection(§ionData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error finalizing section: %w", err)
|
||||
}
|
||||
sectionData.address = sectionAddress
|
||||
sectionData.alignedAddress = alignedAddress
|
||||
sectionData.size = sectionSize
|
||||
sectionData.characteristics = sections[i].Characteristics
|
||||
}
|
||||
sectionData.last = true
|
||||
err := module.finalizeSection(§ionData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error finalizing section: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *Module) executeTLS() {
|
||||
directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_TLS)
|
||||
if directory.VirtualAddress == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
tls := (*IMAGE_TLS_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
|
||||
callback := tls.AddressOfCallbacks
|
||||
if callback != 0 {
|
||||
for {
|
||||
f := *(*uintptr)(a2p(callback))
|
||||
if f == 0 {
|
||||
break
|
||||
}
|
||||
syscall.Syscall(f, 3, module.codeBase, uintptr(DLL_PROCESS_ATTACH), uintptr(0))
|
||||
callback += unsafe.Sizeof(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (module *Module) performBaseRelocation(delta uintptr) (relocated bool, err error) {
|
||||
directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_BASERELOC)
|
||||
if directory.Size == 0 {
|
||||
return delta == 0, nil
|
||||
}
|
||||
|
||||
relocationHdr := (*IMAGE_BASE_RELOCATION)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
|
||||
for relocationHdr.VirtualAddress > 0 {
|
||||
dest := module.codeBase + uintptr(relocationHdr.VirtualAddress)
|
||||
|
||||
var relInfos []uint16
|
||||
unsafeSlice(
|
||||
unsafe.Pointer(&relInfos),
|
||||
a2p(uintptr(unsafe.Pointer(relocationHdr))+unsafe.Sizeof(*relocationHdr)),
|
||||
int((uintptr(relocationHdr.SizeOfBlock)-unsafe.Sizeof(*relocationHdr))/unsafe.Sizeof(relInfos[0])))
|
||||
for _, relInfo := range relInfos {
|
||||
// The upper 4 bits define the type of relocation.
|
||||
relType := relInfo >> 12
|
||||
// The lower 12 bits define the offset.
|
||||
relOffset := uintptr(relInfo & 0xfff)
|
||||
|
||||
switch relType {
|
||||
case IMAGE_REL_BASED_ABSOLUTE:
|
||||
// Skip relocation.
|
||||
|
||||
case IMAGE_REL_BASED_LOW:
|
||||
*(*uint16)(a2p(dest + relOffset)) += uint16(delta & 0xffff)
|
||||
break
|
||||
|
||||
case IMAGE_REL_BASED_HIGH:
|
||||
*(*uint16)(a2p(dest + relOffset)) += uint16(uint32(delta) >> 16)
|
||||
break
|
||||
|
||||
case IMAGE_REL_BASED_HIGHLOW:
|
||||
*(*uint32)(a2p(dest + relOffset)) += uint32(delta)
|
||||
|
||||
case IMAGE_REL_BASED_DIR64:
|
||||
*(*uint64)(a2p(dest + relOffset)) += uint64(delta)
|
||||
|
||||
case IMAGE_REL_BASED_THUMB_MOV32:
|
||||
inst := *(*uint32)(a2p(dest + relOffset))
|
||||
imm16 := ((inst << 1) & 0x0800) + ((inst << 12) & 0xf000) +
|
||||
((inst >> 20) & 0x0700) + ((inst >> 16) & 0x00ff)
|
||||
if (inst & 0x8000fbf0) != 0x0000f240 {
|
||||
return false, fmt.Errorf("Wrong Thumb2 instruction %08x, expected MOVW", inst)
|
||||
}
|
||||
imm16 += uint32(delta) & 0xffff
|
||||
hiDelta := (uint32(delta&0xffff0000) >> 16) + ((imm16 & 0xffff0000) >> 16)
|
||||
*(*uint32)(a2p(dest + relOffset)) = (inst & 0x8f00fbf0) + ((imm16 >> 1) & 0x0400) +
|
||||
((imm16 >> 12) & 0x000f) +
|
||||
((imm16 << 20) & 0x70000000) +
|
||||
((imm16 << 16) & 0xff0000)
|
||||
if hiDelta != 0 {
|
||||
inst = *(*uint32)(a2p(dest + relOffset + 4))
|
||||
imm16 = ((inst << 1) & 0x0800) + ((inst << 12) & 0xf000) +
|
||||
((inst >> 20) & 0x0700) + ((inst >> 16) & 0x00ff)
|
||||
if (inst & 0x8000fbf0) != 0x0000f2c0 {
|
||||
return false, fmt.Errorf("Wrong Thumb2 instruction %08x, expected MOVT", inst)
|
||||
}
|
||||
imm16 += hiDelta
|
||||
if imm16 > 0xffff {
|
||||
return false, fmt.Errorf("Resulting immediate value won't fit: %08x", imm16)
|
||||
}
|
||||
*(*uint32)(a2p(dest + relOffset + 4)) = (inst & 0x8f00fbf0) +
|
||||
((imm16 >> 1) & 0x0400) +
|
||||
((imm16 >> 12) & 0x000f) +
|
||||
((imm16 << 20) & 0x70000000) +
|
||||
((imm16 << 16) & 0xff0000)
|
||||
}
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("Unsupported relocation: %v", relType)
|
||||
}
|
||||
}
|
||||
|
||||
// Advance to next relocation block.
|
||||
relocationHdr = (*IMAGE_BASE_RELOCATION)(a2p(uintptr(unsafe.Pointer(relocationHdr)) + uintptr(relocationHdr.SizeOfBlock)))
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (module *Module) buildImportTable() error {
|
||||
directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_IMPORT)
|
||||
if directory.Size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
module.modules = make([]windows.Handle, 0, 16)
|
||||
importDesc := (*IMAGE_IMPORT_DESCRIPTOR)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
|
||||
for !isBadReadPtr(uintptr(unsafe.Pointer(importDesc)), unsafe.Sizeof(*importDesc)) && importDesc.Name != 0 {
|
||||
handle, err := windows.LoadLibraryEx(windows.BytePtrToString((*byte)(a2p(module.codeBase+uintptr(importDesc.Name)))), 0, windows.LOAD_LIBRARY_SEARCH_SYSTEM32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error loading module: %w", err)
|
||||
}
|
||||
var thunkRef, funcRef *uintptr
|
||||
if importDesc.OriginalFirstThunk() != 0 {
|
||||
thunkRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.OriginalFirstThunk())))
|
||||
funcRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk)))
|
||||
} else {
|
||||
// No hint table.
|
||||
thunkRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk)))
|
||||
funcRef = (*uintptr)(a2p(module.codeBase + uintptr(importDesc.FirstThunk)))
|
||||
}
|
||||
for *thunkRef != 0 {
|
||||
if IMAGE_SNAP_BY_ORDINAL(*thunkRef) {
|
||||
*funcRef, err = windows.GetProcAddressByOrdinal(handle, IMAGE_ORDINAL(*thunkRef))
|
||||
} else {
|
||||
thunkData := (*IMAGE_IMPORT_BY_NAME)(a2p(module.codeBase + *thunkRef))
|
||||
*funcRef, err = windows.GetProcAddress(handle, windows.BytePtrToString(&thunkData.Name[0]))
|
||||
}
|
||||
if err != nil {
|
||||
windows.FreeLibrary(handle)
|
||||
return fmt.Errorf("Error getting function address: %w", err)
|
||||
}
|
||||
thunkRef = (*uintptr)(a2p(uintptr(unsafe.Pointer(thunkRef)) + unsafe.Sizeof(*thunkRef)))
|
||||
funcRef = (*uintptr)(a2p(uintptr(unsafe.Pointer(funcRef)) + unsafe.Sizeof(*funcRef)))
|
||||
}
|
||||
module.modules = append(module.modules, handle)
|
||||
importDesc = (*IMAGE_IMPORT_DESCRIPTOR)(a2p(uintptr(unsafe.Pointer(importDesc)) + unsafe.Sizeof(*importDesc)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *Module) buildNameExports() error {
|
||||
directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT)
|
||||
if directory.Size == 0 {
|
||||
return errors.New("No export table found")
|
||||
}
|
||||
exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
|
||||
if exports.NumberOfNames == 0 || exports.NumberOfFunctions == 0 {
|
||||
return errors.New("No functions exported")
|
||||
}
|
||||
if exports.NumberOfNames == 0 {
|
||||
return errors.New("No functions exported by name")
|
||||
}
|
||||
var nameRefs []uint32
|
||||
unsafeSlice(unsafe.Pointer(&nameRefs), a2p(module.codeBase+uintptr(exports.AddressOfNames)), int(exports.NumberOfNames))
|
||||
var ordinals []uint16
|
||||
unsafeSlice(unsafe.Pointer(&ordinals), a2p(module.codeBase+uintptr(exports.AddressOfNameOrdinals)), int(exports.NumberOfNames))
|
||||
module.nameExports = make(map[string]uint16)
|
||||
for i := range nameRefs {
|
||||
nameArray := windows.BytePtrToString((*byte)(a2p(module.codeBase + uintptr(nameRefs[i]))))
|
||||
module.nameExports[nameArray] = ordinals[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadLibrary loads module image to memory.
|
||||
func LoadLibrary(data []byte) (module *Module, err error) {
|
||||
addr := uintptr(unsafe.Pointer(&data[0]))
|
||||
size := uintptr(len(data))
|
||||
if size < unsafe.Sizeof(IMAGE_DOS_HEADER{}) {
|
||||
return nil, errors.New("Incomplete IMAGE_DOS_HEADER")
|
||||
}
|
||||
dosHeader := (*IMAGE_DOS_HEADER)(a2p(addr))
|
||||
if dosHeader.E_magic != IMAGE_DOS_SIGNATURE {
|
||||
return nil, fmt.Errorf("Not an MS-DOS binary (provided: %x, expected: %x)", dosHeader.E_magic, IMAGE_DOS_SIGNATURE)
|
||||
}
|
||||
if (size < uintptr(dosHeader.E_lfanew)+unsafe.Sizeof(IMAGE_NT_HEADERS{})) {
|
||||
return nil, errors.New("Incomplete IMAGE_NT_HEADERS")
|
||||
}
|
||||
oldHeader := (*IMAGE_NT_HEADERS)(a2p(addr + uintptr(dosHeader.E_lfanew)))
|
||||
if oldHeader.Signature != IMAGE_NT_SIGNATURE {
|
||||
return nil, fmt.Errorf("Not an NT binary (provided: %x, expected: %x)", oldHeader.Signature, IMAGE_NT_SIGNATURE)
|
||||
}
|
||||
if oldHeader.FileHeader.Machine != imageFileProcess {
|
||||
return nil, fmt.Errorf("Foreign platform (provided: %x, expected: %x)", oldHeader.FileHeader.Machine, imageFileProcess)
|
||||
}
|
||||
if (oldHeader.OptionalHeader.SectionAlignment & 1) != 0 {
|
||||
return nil, errors.New("Unaligned section")
|
||||
}
|
||||
lastSectionEnd := uintptr(0)
|
||||
sections := oldHeader.Sections()
|
||||
optionalSectionSize := oldHeader.OptionalHeader.SectionAlignment
|
||||
for i := range sections {
|
||||
var endOfSection uintptr
|
||||
if sections[i].SizeOfRawData == 0 {
|
||||
// Section without data in the DLL
|
||||
endOfSection = uintptr(sections[i].VirtualAddress) + uintptr(optionalSectionSize)
|
||||
} else {
|
||||
endOfSection = uintptr(sections[i].VirtualAddress) + uintptr(sections[i].SizeOfRawData)
|
||||
}
|
||||
if endOfSection > lastSectionEnd {
|
||||
lastSectionEnd = endOfSection
|
||||
}
|
||||
}
|
||||
alignedImageSize := alignUp(uintptr(oldHeader.OptionalHeader.SizeOfImage), uintptr(oldHeader.OptionalHeader.SectionAlignment))
|
||||
if alignedImageSize != alignUp(lastSectionEnd, uintptr(oldHeader.OptionalHeader.SectionAlignment)) {
|
||||
return nil, errors.New("Section is not page-aligned")
|
||||
}
|
||||
|
||||
module = &Module{isDLL: (oldHeader.FileHeader.Characteristics & IMAGE_FILE_DLL) != 0}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
module.Free()
|
||||
module = nil
|
||||
}
|
||||
}()
|
||||
|
||||
// Reserve memory for image of library.
|
||||
// TODO: Is it correct to commit the complete memory region at once? Calling DllEntry raises an exception if we don't.
|
||||
module.codeBase, err = windows.VirtualAlloc(oldHeader.OptionalHeader.ImageBase,
|
||||
alignedImageSize,
|
||||
windows.MEM_RESERVE|windows.MEM_COMMIT,
|
||||
windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
// Try to allocate memory at arbitrary position.
|
||||
module.codeBase, err = windows.VirtualAlloc(0,
|
||||
alignedImageSize,
|
||||
windows.MEM_RESERVE|windows.MEM_COMMIT,
|
||||
windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error allocating code: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
err = module.check4GBBoundaries(alignedImageSize)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reallocating code: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if size < uintptr(oldHeader.OptionalHeader.SizeOfHeaders) {
|
||||
err = errors.New("Incomplete headers")
|
||||
return
|
||||
}
|
||||
// Commit memory for headers.
|
||||
headers, err := windows.VirtualAlloc(module.codeBase,
|
||||
uintptr(oldHeader.OptionalHeader.SizeOfHeaders),
|
||||
windows.MEM_COMMIT,
|
||||
windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error allocating headers: %w", err)
|
||||
return
|
||||
}
|
||||
// Copy PE header to code.
|
||||
memcpy(headers, addr, uintptr(oldHeader.OptionalHeader.SizeOfHeaders))
|
||||
module.headers = (*IMAGE_NT_HEADERS)(a2p(headers + uintptr(dosHeader.E_lfanew)))
|
||||
|
||||
// Update position.
|
||||
module.headers.OptionalHeader.ImageBase = module.codeBase
|
||||
|
||||
// Copy sections from DLL file block to new memory location.
|
||||
err = module.copySections(addr, size, oldHeader)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error copying sections: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Adjust base address of imported data.
|
||||
locationDelta := module.headers.OptionalHeader.ImageBase - oldHeader.OptionalHeader.ImageBase
|
||||
if locationDelta != 0 {
|
||||
module.isRelocated, err = module.performBaseRelocation(locationDelta)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error relocating module: %w", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
module.isRelocated = true
|
||||
}
|
||||
|
||||
// Load required dlls and adjust function table of imports.
|
||||
err = module.buildImportTable()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error building import table: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Mark memory pages depending on section headers and release sections that are marked as "discardable".
|
||||
err = module.finalizeSections()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error finalizing sections: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
// TLS callbacks are executed BEFORE the main loading.
|
||||
module.executeTLS()
|
||||
|
||||
// Get entry point of loaded module.
|
||||
if module.headers.OptionalHeader.AddressOfEntryPoint != 0 {
|
||||
module.entry = module.codeBase + uintptr(module.headers.OptionalHeader.AddressOfEntryPoint)
|
||||
if module.isDLL {
|
||||
// Notify library about attaching to process.
|
||||
r0, _, _ := syscall.Syscall(module.entry, 3, module.codeBase, uintptr(DLL_PROCESS_ATTACH), 0)
|
||||
successful := r0 != 0
|
||||
if !successful {
|
||||
err = windows.ERROR_DLL_INIT_FAILED
|
||||
return
|
||||
}
|
||||
module.initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
module.buildNameExports()
|
||||
return
|
||||
}
|
||||
|
||||
// Free releases module resources and unloads it.
|
||||
func (module *Module) Free() {
|
||||
if module.initialized {
|
||||
// Notify library about detaching from process.
|
||||
syscall.Syscall(module.entry, 3, module.codeBase, uintptr(DLL_PROCESS_DETACH), 0)
|
||||
module.initialized = false
|
||||
}
|
||||
if module.modules != nil {
|
||||
// Free previously opened libraries.
|
||||
for _, handle := range module.modules {
|
||||
windows.FreeLibrary(handle)
|
||||
}
|
||||
module.modules = nil
|
||||
}
|
||||
if module.codeBase != 0 {
|
||||
windows.VirtualFree(module.codeBase, 0, windows.MEM_RELEASE)
|
||||
module.codeBase = 0
|
||||
}
|
||||
if module.blockedMemory != nil {
|
||||
module.blockedMemory.free()
|
||||
module.blockedMemory = nil
|
||||
}
|
||||
}
|
||||
|
||||
// ProcAddressByName returns function address by exported name.
|
||||
func (module *Module) ProcAddressByName(name string) (uintptr, error) {
|
||||
directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT)
|
||||
if directory.Size == 0 {
|
||||
return 0, errors.New("No export table found")
|
||||
}
|
||||
exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
|
||||
if module.nameExports == nil {
|
||||
return 0, errors.New("No functions exported by name")
|
||||
}
|
||||
if idx, ok := module.nameExports[name]; ok {
|
||||
if uint32(idx) > exports.NumberOfFunctions {
|
||||
return 0, errors.New("Ordinal number too high")
|
||||
}
|
||||
// AddressOfFunctions contains the RVAs to the "real" functions.
|
||||
return module.codeBase + uintptr(*(*uint32)(a2p(module.codeBase + uintptr(exports.AddressOfFunctions) + uintptr(idx)*4))), nil
|
||||
}
|
||||
return 0, errors.New("Function not found by name")
|
||||
}
|
||||
|
||||
// ProcAddressByOrdinal returns function address by exported ordinal.
|
||||
func (module *Module) ProcAddressByOrdinal(ordinal uint16) (uintptr, error) {
|
||||
directory := module.headerDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT)
|
||||
if directory.Size == 0 {
|
||||
return 0, errors.New("No export table found")
|
||||
}
|
||||
exports := (*IMAGE_EXPORT_DIRECTORY)(a2p(module.codeBase + uintptr(directory.VirtualAddress)))
|
||||
if uint32(ordinal) < exports.Base {
|
||||
return 0, errors.New("Ordinal number too low")
|
||||
}
|
||||
idx := ordinal - uint16(exports.Base)
|
||||
if uint32(idx) > exports.NumberOfFunctions {
|
||||
return 0, errors.New("Ordinal number too high")
|
||||
}
|
||||
// AddressOfFunctions contains the RVAs to the "real" functions.
|
||||
return module.codeBase + uintptr(*(*uint32)(a2p(module.codeBase + uintptr(exports.AddressOfFunctions) + uintptr(idx)*4))), nil
|
||||
}
|
||||
|
||||
func alignDown(value, alignment uintptr) uintptr {
|
||||
return value & ^(alignment - 1)
|
||||
}
|
||||
|
||||
func alignUp(value, alignment uintptr) uintptr {
|
||||
return (value + alignment - 1) & ^(alignment - 1)
|
||||
}
|
||||
|
||||
func a2p(addr uintptr) unsafe.Pointer {
|
||||
return unsafe.Pointer(addr)
|
||||
}
|
||||
|
||||
func memcpy(dst, src, size uintptr) {
|
||||
var d, s []byte
|
||||
unsafeSlice(unsafe.Pointer(&d), a2p(dst), int(size))
|
||||
unsafeSlice(unsafe.Pointer(&s), a2p(src), int(size))
|
||||
copy(d, s)
|
||||
}
|
||||
|
||||
// unsafeSlice updates the slice slicePtr to be a slice
|
||||
// referencing the provided data with its length & capacity set to
|
||||
// lenCap.
|
||||
//
|
||||
// TODO: when Go 1.16 or Go 1.17 is the minimum supported version,
|
||||
// update callers to use unsafe.Slice instead of this.
|
||||
func unsafeSlice(slicePtr, data unsafe.Pointer, lenCap int) {
|
||||
type sliceHeader struct {
|
||||
Data unsafe.Pointer
|
||||
Len int
|
||||
Cap int
|
||||
}
|
||||
h := (*sliceHeader)(slicePtr)
|
||||
h.Data = data
|
||||
h.Len = lenCap
|
||||
h.Cap = lenCap
|
||||
}
|
||||
16
tun/wintun/memmod/memmod_windows_32.go
Normal file
16
tun/wintun/memmod/memmod_windows_32.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// +build windows,386 windows,arm
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package memmod
|
||||
|
||||
func (opthdr *IMAGE_OPTIONAL_HEADER) imageOffset() uintptr {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (module *Module) check4GBBoundaries(alignedImageSize uintptr) (err error) {
|
||||
return
|
||||
}
|
||||
8
tun/wintun/memmod/memmod_windows_386.go
Normal file
8
tun/wintun/memmod/memmod_windows_386.go
Normal file
@@ -0,0 +1,8 @@
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package memmod
|
||||
|
||||
const imageFileProcess = IMAGE_FILE_MACHINE_I386
|
||||
36
tun/wintun/memmod/memmod_windows_64.go
Normal file
36
tun/wintun/memmod/memmod_windows_64.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// +build windows,amd64 windows,arm64
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
|
||||
*/
|
||||
|
||||
package memmod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func (opthdr *IMAGE_OPTIONAL_HEADER) imageOffset() uintptr {
|
||||
return uintptr(opthdr.ImageBase & 0xffffffff00000000)
|
||||
}
|
||||
|
||||
func (module *Module) check4GBBoundaries(alignedImageSize uintptr) (err error) {
|
||||
for (module.codeBase >> 32) < ((module.codeBase + alignedImageSize) >> 32) {
|
||||
node := &addressList{
|
||||
next: module.blockedMemory,
|
||||
address: module.codeBase,
|
||||
}
|
||||
module.blockedMemory = node
|
||||
module.codeBase, err = windows.VirtualAlloc(0,
|
||||
alignedImageSize,
|
||||
windows.MEM_RESERVE|windows.MEM_COMMIT,
|
||||
windows.PAGE_READWRITE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error allocating memory block: %w", err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user