migrate to golang.zx2c4.com/wireguard/wgctrl (#239)

* migrate to golang.zx2c4.com/wireguard/wgctrl

This commit introduces the usage of wgctrl.
It avoids the usage of exec calls of the wg command
and parsing the output of `wg show`.

Signed-off-by: leonnicolas <leonloechner@gmx.de>

* vendor wgctrl

Signed-off-by: leonnicolas <leonloechner@gmx.de>

* apply suggestions from code review

Remove wireguard.Enpoint struct and use net.UDPAddr for the resolved
endpoint and addr string (dnsanme:port) if a DN was supplied.

Signed-off-by: leonnicolas <leonloechner@gmx.de>

* pkg/*: use wireguard.Enpoint

This commit introduces the wireguard.Enpoint struct.
It encapsulates a DN name with port and a net.UPDAddr.
The fields are private and only accessible over exported Methods
to avoid accidental modification.

Also iptables.GetProtocol is improved to avoid ipv4 rules being applied
by `ip6tables`.

Signed-off-by: leonnicolas <leonloechner@gmx.de>

* pkg/wireguard/conf_test.go: add tests for Endpoint

Signed-off-by: leonnicolas <leonloechner@gmx.de>

* cmd/kg/main.go: validate port range

Signed-off-by: leonnicolas <leonloechner@gmx.de>

* add suggestions from review

Signed-off-by: leonnicolas <leonloechner@gmx.de>

* pkg/mesh/mesh.go: use Equal func

Implement an Equal func for Enpoint and use it instead of comparing
strings.

Signed-off-by: leonnicolas <leonloechner@gmx.de>

* cmd/kgctl/main.go: check port range

Signed-off-by: leonnicolas <leonloechner@gmx.de>

* vendor

Signed-off-by: leonnicolas <leonloechner@gmx.de>
This commit is contained in:
leonnicolas
2022-01-30 17:38:45 +01:00
committed by GitHub
parent 797133f272
commit 6a696e03e7
299 changed files with 26275 additions and 10252 deletions

17
vendor/golang.zx2c4.com/wireguard/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,17 @@
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

286
vendor/golang.zx2c4.com/wireguard/ipc/namedpipe/file.go generated vendored Normal file
View File

@@ -0,0 +1,286 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Copyright 2015 Microsoft
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows
// +build windows
package namedpipe
import (
"io"
"os"
"runtime"
"sync"
"sync/atomic"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
type timeoutChan chan struct{}
var ioInitOnce sync.Once
var ioCompletionPort windows.Handle
// ioResult contains the result of an asynchronous IO operation
type ioResult struct {
bytes uint32
err error
}
// ioOperation represents an outstanding asynchronous Win32 IO
type ioOperation struct {
o windows.Overlapped
ch chan ioResult
}
func initIo() {
h, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
if err != nil {
panic(err)
}
ioCompletionPort = h
go ioCompletionProcessor(h)
}
// file implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
// It takes ownership of this handle and will close it if it is garbage collected.
type file struct {
handle windows.Handle
wg sync.WaitGroup
wgLock sync.RWMutex
closing uint32 // used as atomic boolean
socket bool
readDeadline deadlineHandler
writeDeadline deadlineHandler
}
type deadlineHandler struct {
setLock sync.Mutex
channel timeoutChan
channelLock sync.RWMutex
timer *time.Timer
timedout uint32 // used as atomic boolean
}
// makeFile makes a new file from an existing file handle
func makeFile(h windows.Handle) (*file, error) {
f := &file{handle: h}
ioInitOnce.Do(initIo)
_, err := windows.CreateIoCompletionPort(h, ioCompletionPort, 0, 0)
if err != nil {
return nil, err
}
err = windows.SetFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE)
if err != nil {
return nil, err
}
f.readDeadline.channel = make(timeoutChan)
f.writeDeadline.channel = make(timeoutChan)
return f, nil
}
// closeHandle closes the resources associated with a Win32 handle
func (f *file) closeHandle() {
f.wgLock.Lock()
// Atomically set that we are closing, releasing the resources only once.
if atomic.SwapUint32(&f.closing, 1) == 0 {
f.wgLock.Unlock()
// cancel all IO and wait for it to complete
windows.CancelIoEx(f.handle, nil)
f.wg.Wait()
// at this point, no new IO can start
windows.Close(f.handle)
f.handle = 0
} else {
f.wgLock.Unlock()
}
}
// Close closes a file.
func (f *file) Close() error {
f.closeHandle()
return nil
}
// prepareIo prepares for a new IO operation.
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *file) prepareIo() (*ioOperation, error) {
f.wgLock.RLock()
if atomic.LoadUint32(&f.closing) == 1 {
f.wgLock.RUnlock()
return nil, os.ErrClosed
}
f.wg.Add(1)
f.wgLock.RUnlock()
c := &ioOperation{}
c.ch = make(chan ioResult)
return c, nil
}
// ioCompletionProcessor processes completed async IOs forever
func ioCompletionProcessor(h windows.Handle) {
for {
var bytes uint32
var key uintptr
var op *ioOperation
err := windows.GetQueuedCompletionStatus(h, &bytes, &key, (**windows.Overlapped)(unsafe.Pointer(&op)), windows.INFINITE)
if op == nil {
panic(err)
}
op.ch <- ioResult{bytes, err}
}
}
// asyncIo processes the return value from ReadFile or WriteFile, blocking until
// the operation has actually completed.
func (f *file) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
if err != windows.ERROR_IO_PENDING {
return int(bytes), err
}
if atomic.LoadUint32(&f.closing) == 1 {
windows.CancelIoEx(f.handle, &c.o)
}
var timeout timeoutChan
if d != nil {
d.channelLock.Lock()
timeout = d.channel
d.channelLock.Unlock()
}
var r ioResult
select {
case r = <-c.ch:
err = r.err
if err == windows.ERROR_OPERATION_ABORTED {
if atomic.LoadUint32(&f.closing) == 1 {
err = os.ErrClosed
}
} else if err != nil && f.socket {
// err is from Win32. Query the overlapped structure to get the winsock error.
var bytes, flags uint32
err = windows.WSAGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
}
case <-timeout:
windows.CancelIoEx(f.handle, &c.o)
r = <-c.ch
err = r.err
if err == windows.ERROR_OPERATION_ABORTED {
err = os.ErrDeadlineExceeded
}
}
// runtime.KeepAlive is needed, as c is passed via native
// code to ioCompletionProcessor, c must remain alive
// until the channel read is complete.
runtime.KeepAlive(c)
return int(r.bytes), err
}
// Read reads from a file handle.
func (f *file) Read(b []byte) (int, error) {
c, err := f.prepareIo()
if err != nil {
return 0, err
}
defer f.wg.Done()
if atomic.LoadUint32(&f.readDeadline.timedout) == 1 {
return 0, os.ErrDeadlineExceeded
}
var bytes uint32
err = windows.ReadFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
runtime.KeepAlive(b)
// Handle EOF conditions.
if err == nil && n == 0 && len(b) != 0 {
return 0, io.EOF
} else if err == windows.ERROR_BROKEN_PIPE {
return 0, io.EOF
} else {
return n, err
}
}
// Write writes to a file handle.
func (f *file) Write(b []byte) (int, error) {
c, err := f.prepareIo()
if err != nil {
return 0, err
}
defer f.wg.Done()
if atomic.LoadUint32(&f.writeDeadline.timedout) == 1 {
return 0, os.ErrDeadlineExceeded
}
var bytes uint32
err = windows.WriteFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
runtime.KeepAlive(b)
return n, err
}
func (f *file) SetReadDeadline(deadline time.Time) error {
return f.readDeadline.set(deadline)
}
func (f *file) SetWriteDeadline(deadline time.Time) error {
return f.writeDeadline.set(deadline)
}
func (f *file) Flush() error {
return windows.FlushFileBuffers(f.handle)
}
func (f *file) Fd() uintptr {
return uintptr(f.handle)
}
func (d *deadlineHandler) set(deadline time.Time) error {
d.setLock.Lock()
defer d.setLock.Unlock()
if d.timer != nil {
if !d.timer.Stop() {
<-d.channel
}
d.timer = nil
}
atomic.StoreUint32(&d.timedout, 0)
select {
case <-d.channel:
d.channelLock.Lock()
d.channel = make(chan struct{})
d.channelLock.Unlock()
default:
}
if deadline.IsZero() {
return nil
}
timeoutIO := func() {
atomic.StoreUint32(&d.timedout, 1)
close(d.channel)
}
now := time.Now()
duration := deadline.Sub(now)
if deadline.After(now) {
// Deadline is in the future, set a timer to wait
d.timer = time.AfterFunc(duration, timeoutIO)
} else {
// Deadline is in the past. Cancel all pending IO now.
timeoutIO()
}
return nil
}

View File

@@ -0,0 +1,486 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Copyright 2015 Microsoft
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build windows
// +build windows
// Package namedpipe implements a net.Conn and net.Listener around Windows named pipes.
package namedpipe
import (
"context"
"io"
"net"
"os"
"runtime"
"sync/atomic"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
type pipe struct {
*file
path string
}
type messageBytePipe struct {
pipe
writeClosed int32
readEOF bool
}
type pipeAddress string
func (f *pipe) LocalAddr() net.Addr {
return pipeAddress(f.path)
}
func (f *pipe) RemoteAddr() net.Addr {
return pipeAddress(f.path)
}
func (f *pipe) SetDeadline(t time.Time) error {
f.SetReadDeadline(t)
f.SetWriteDeadline(t)
return nil
}
// CloseWrite closes the write side of a message pipe in byte mode.
func (f *messageBytePipe) CloseWrite() error {
if !atomic.CompareAndSwapInt32(&f.writeClosed, 0, 1) {
return io.ErrClosedPipe
}
err := f.file.Flush()
if err != nil {
atomic.StoreInt32(&f.writeClosed, 0)
return err
}
_, err = f.file.Write(nil)
if err != nil {
atomic.StoreInt32(&f.writeClosed, 0)
return err
}
return nil
}
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
// they are used to implement CloseWrite.
func (f *messageBytePipe) Write(b []byte) (int, error) {
if atomic.LoadInt32(&f.writeClosed) != 0 {
return 0, io.ErrClosedPipe
}
if len(b) == 0 {
return 0, nil
}
return f.file.Write(b)
}
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
// mode pipe will return io.EOF, as will all subsequent reads.
func (f *messageBytePipe) Read(b []byte) (int, error) {
if f.readEOF {
return 0, io.EOF
}
n, err := f.file.Read(b)
if err == io.EOF {
// If this was the result of a zero-byte read, then
// it is possible that the read was due to a zero-size
// message. Since we are simulating CloseWrite with a
// zero-byte message, ensure that all future Read calls
// also return EOF.
f.readEOF = true
} else if err == windows.ERROR_MORE_DATA {
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
// and the message still has more bytes. Treat this as a success, since
// this package presents all named pipes as byte streams.
err = nil
}
return n, err
}
func (f *pipe) Handle() windows.Handle {
return f.handle
}
func (s pipeAddress) Network() string {
return "pipe"
}
func (s pipeAddress) String() string {
return string(s)
}
// tryDialPipe attempts to dial the specified pipe until cancellation or timeout.
func tryDialPipe(ctx context.Context, path *string) (windows.Handle, error) {
for {
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
path16, err := windows.UTF16PtrFromString(*path)
if err != nil {
return 0, err
}
h, err := windows.CreateFile(path16, windows.GENERIC_READ|windows.GENERIC_WRITE, 0, nil, windows.OPEN_EXISTING, windows.FILE_FLAG_OVERLAPPED|windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS, 0)
if err == nil {
return h, nil
}
if err != windows.ERROR_PIPE_BUSY {
return h, &os.PathError{Err: err, Op: "open", Path: *path}
}
// Wait 10 msec and try again. This is a rather simplistic
// view, as we always try each 10 milliseconds.
time.Sleep(10 * time.Millisecond)
}
}
}
// DialConfig exposes various options for use in Dial and DialContext.
type DialConfig struct {
ExpectedOwner *windows.SID // If non-nil, the pipe is verified to be owned by this SID.
}
// DialTimeout connects to the specified named pipe by path, timing out if the
// connection takes longer than the specified duration. If timeout is zero, then
// we use a default timeout of 2 seconds.
func (config *DialConfig) DialTimeout(path string, timeout time.Duration) (net.Conn, error) {
if timeout == 0 {
timeout = time.Second * 2
}
absTimeout := time.Now().Add(timeout)
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
conn, err := config.DialContext(ctx, path)
if err == context.DeadlineExceeded {
return nil, os.ErrDeadlineExceeded
}
return conn, err
}
// DialContext attempts to connect to the specified named pipe by path.
func (config *DialConfig) DialContext(ctx context.Context, path string) (net.Conn, error) {
var err error
var h windows.Handle
h, err = tryDialPipe(ctx, &path)
if err != nil {
return nil, err
}
if config.ExpectedOwner != nil {
sd, err := windows.GetSecurityInfo(h, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION)
if err != nil {
windows.Close(h)
return nil, err
}
realOwner, _, err := sd.Owner()
if err != nil {
windows.Close(h)
return nil, err
}
if !realOwner.Equals(config.ExpectedOwner) {
windows.Close(h)
return nil, windows.ERROR_ACCESS_DENIED
}
}
var flags uint32
err = windows.GetNamedPipeInfo(h, &flags, nil, nil, nil)
if err != nil {
windows.Close(h)
return nil, err
}
f, err := makeFile(h)
if err != nil {
windows.Close(h)
return nil, err
}
// If the pipe is in message mode, return a message byte pipe, which
// supports CloseWrite.
if flags&windows.PIPE_TYPE_MESSAGE != 0 {
return &messageBytePipe{
pipe: pipe{file: f, path: path},
}, nil
}
return &pipe{file: f, path: path}, nil
}
var defaultDialer DialConfig
// DialTimeout calls DialConfig.DialTimeout using an empty configuration.
func DialTimeout(path string, timeout time.Duration) (net.Conn, error) {
return defaultDialer.DialTimeout(path, timeout)
}
// DialContext calls DialConfig.DialContext using an empty configuration.
func DialContext(ctx context.Context, path string) (net.Conn, error) {
return defaultDialer.DialContext(ctx, path)
}
type acceptResponse struct {
f *file
err error
}
type pipeListener struct {
firstHandle windows.Handle
path string
config ListenConfig
acceptCh chan chan acceptResponse
closeCh chan int
doneCh chan int
}
func makeServerPipeHandle(path string, sd *windows.SECURITY_DESCRIPTOR, c *ListenConfig, isFirstPipe bool) (windows.Handle, error) {
path16, err := windows.UTF16PtrFromString(path)
if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
var oa windows.OBJECT_ATTRIBUTES
oa.Length = uint32(unsafe.Sizeof(oa))
var ntPath windows.NTUnicodeString
if err := windows.RtlDosPathNameToNtPathName(path16, &ntPath, nil, nil); err != nil {
if ntstatus, ok := err.(windows.NTStatus); ok {
err = ntstatus.Errno()
}
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
defer windows.LocalFree(windows.Handle(unsafe.Pointer(ntPath.Buffer)))
oa.ObjectName = &ntPath
// The security descriptor is only needed for the first pipe.
if isFirstPipe {
if sd != nil {
oa.SecurityDescriptor = sd
} else {
// Construct the default named pipe security descriptor.
var acl *windows.ACL
if err := windows.RtlDefaultNpAcl(&acl); err != nil {
return 0, err
}
defer windows.LocalFree(windows.Handle(unsafe.Pointer(acl)))
sd, err = windows.NewSecurityDescriptor()
if err != nil {
return 0, err
}
if err = sd.SetDACL(acl, true, false); err != nil {
return 0, err
}
oa.SecurityDescriptor = sd
}
}
typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS)
if c.MessageMode {
typ |= windows.FILE_PIPE_MESSAGE_TYPE
}
disposition := uint32(windows.FILE_OPEN)
access := uint32(windows.GENERIC_READ | windows.GENERIC_WRITE | windows.SYNCHRONIZE)
if isFirstPipe {
disposition = windows.FILE_CREATE
// By not asking for read or write access, the named pipe file system
// will put this pipe into an initially disconnected state, blocking
// client connections until the next call with isFirstPipe == false.
access = windows.SYNCHRONIZE
}
timeout := int64(-50 * 10000) // 50ms
var (
h windows.Handle
iosb windows.IO_STATUS_BLOCK
)
err = windows.NtCreateNamedPipeFile(&h, access, &oa, &iosb, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout)
if err != nil {
if ntstatus, ok := err.(windows.NTStatus); ok {
err = ntstatus.Errno()
}
return 0, &os.PathError{Op: "open", Path: path, Err: err}
}
runtime.KeepAlive(ntPath)
return h, nil
}
func (l *pipeListener) makeServerPipe() (*file, error) {
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
if err != nil {
return nil, err
}
f, err := makeFile(h)
if err != nil {
windows.Close(h)
return nil, err
}
return f, nil
}
func (l *pipeListener) makeConnectedServerPipe() (*file, error) {
p, err := l.makeServerPipe()
if err != nil {
return nil, err
}
// Wait for the client to connect.
ch := make(chan error)
go func(p *file) {
ch <- connectPipe(p)
}(p)
select {
case err = <-ch:
if err != nil {
p.Close()
p = nil
}
case <-l.closeCh:
// Abort the connect request by closing the handle.
p.Close()
p = nil
err = <-ch
if err == nil || err == os.ErrClosed {
err = net.ErrClosed
}
}
return p, err
}
func (l *pipeListener) listenerRoutine() {
closed := false
for !closed {
select {
case <-l.closeCh:
closed = true
case responseCh := <-l.acceptCh:
var (
p *file
err error
)
for {
p, err = l.makeConnectedServerPipe()
// If the connection was immediately closed by the client, try
// again.
if err != windows.ERROR_NO_DATA {
break
}
}
responseCh <- acceptResponse{p, err}
closed = err == net.ErrClosed
}
}
windows.Close(l.firstHandle)
l.firstHandle = 0
// Notify Close and Accept callers that the handle has been closed.
close(l.doneCh)
}
// ListenConfig contains configuration for the pipe listener.
type ListenConfig struct {
// SecurityDescriptor contains a Windows security descriptor. If nil, the default from RtlDefaultNpAcl is used.
SecurityDescriptor *windows.SECURITY_DESCRIPTOR
// MessageMode determines whether the pipe is in byte or message mode. In either
// case the pipe is read in byte mode by default. The only practical difference in
// this implementation is that CloseWrite is only supported for message mode pipes;
// CloseWrite is implemented as a zero-byte write, but zero-byte writes are only
// transferred to the reader (and returned as io.EOF in this implementation)
// when the pipe is in message mode.
MessageMode bool
// InputBufferSize specifies the initial size of the input buffer, in bytes, which the OS will grow as needed.
InputBufferSize int32
// OutputBufferSize specifies the initial size of the output buffer, in bytes, which the OS will grow as needed.
OutputBufferSize int32
}
// Listen creates a listener on a Windows named pipe path,such as \\.\pipe\mypipe.
// The pipe must not already exist.
func (c *ListenConfig) Listen(path string) (net.Listener, error) {
h, err := makeServerPipeHandle(path, c.SecurityDescriptor, c, true)
if err != nil {
return nil, err
}
l := &pipeListener{
firstHandle: h,
path: path,
config: *c,
acceptCh: make(chan chan acceptResponse),
closeCh: make(chan int),
doneCh: make(chan int),
}
// The first connection is swallowed on Windows 7 & 8, so synthesize it.
if maj, min, _ := windows.RtlGetNtVersionNumbers(); maj < 6 || (maj == 6 && min < 4) {
path16, err := windows.UTF16PtrFromString(path)
if err == nil {
h, err = windows.CreateFile(path16, 0, 0, nil, windows.OPEN_EXISTING, windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS, 0)
if err == nil {
windows.CloseHandle(h)
}
}
}
go l.listenerRoutine()
return l, nil
}
var defaultListener ListenConfig
// Listen calls ListenConfig.Listen using an empty configuration.
func Listen(path string) (net.Listener, error) {
return defaultListener.Listen(path)
}
func connectPipe(p *file) error {
c, err := p.prepareIo()
if err != nil {
return err
}
defer p.wg.Done()
err = windows.ConnectNamedPipe(p.handle, &c.o)
_, err = p.asyncIo(c, nil, 0, err)
if err != nil && err != windows.ERROR_PIPE_CONNECTED {
return err
}
return nil
}
func (l *pipeListener) Accept() (net.Conn, error) {
ch := make(chan acceptResponse)
select {
case l.acceptCh <- ch:
response := <-ch
err := response.err
if err != nil {
return nil, err
}
if l.config.MessageMode {
return &messageBytePipe{
pipe: pipe{file: response.f, path: l.path},
}, nil
}
return &pipe{file: response.f, path: l.path}, nil
case <-l.doneCh:
return nil, net.ErrClosed
}
}
func (l *pipeListener) Close() error {
select {
case l.closeCh <- 1:
<-l.doneCh
case <-l.doneCh:
}
return nil
}
func (l *pipeListener) Addr() net.Addr {
return pipeAddress(l.path)
}

35
vendor/golang.zx2c4.com/wireguard/wgctrl/.cibuild.sh generated vendored Normal file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -e
set -x
# !! This script is meant for use in CI build use only !!
KERNEL=$(uname -s)
# Use doas in place of sudo for OpenBSD.
SUDO="sudo"
if [ "${KERNEL}" == "OpenBSD" ]; then
SUDO="doas"
fi
if [ "${KERNEL}" == "Linux" ]; then
# Configure a WireGuard interface.
sudo ip link add wg0 type wireguard
sudo ip link set up wg0
fi
# Set up wireguard-go on all OSes.
git clone git://git.zx2c4.com/wireguard-go
cd wireguard-go
if [ "${KERNEL}" == "Linux" ]; then
# Bypass Linux compilation restriction.
make
else
# Build directly to avoid Makefile.
go build -o wireguard-go
fi
${SUDO} mv ./wireguard-go /usr/local/bin/wireguard-go
cd ..
${SUDO} rm -rf ./wireguard-go

2
vendor/golang.zx2c4.com/wireguard/wgctrl/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
cmd/wgctrl/wgctrl
*.test

View File

@@ -0,0 +1,23 @@
Contributing
============
The `wgctrl` project makes use of the [GitHub Flow](https://guides.github.com/introduction/flow/)
for contributions.
If you'd like to contribute to the project, please
[open an issue](https://github.com/WireGuard/wgctrl-go/issues/new) or find an
[existing issue](https://github.com/WireGuard/wgctrl-go/issues) that you'd like
to take on. This ensures that efforts are not duplicated, and that a new feature
aligns with the focus of the rest of the repository.
Once your suggestion has been submitted and discussed, please be sure that your
code meets the following criteria:
- code is completely `gofmt`'d
- new features or codepaths have appropriate test coverage
- `go test ./...` passes
- `go vet ./...` passes
- `staticcheck ./...` passes
- `golint ./...` returns no warnings, including documentation comment warnings
Finally, submit a pull request for review!

9
vendor/golang.zx2c4.com/wireguard/wgctrl/LICENSE.md generated vendored Normal file
View File

@@ -0,0 +1,9 @@
# MIT License
Copyright (C) 2018-2019 Matt Layher
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

33
vendor/golang.zx2c4.com/wireguard/wgctrl/README.md generated vendored Normal file
View File

@@ -0,0 +1,33 @@
# wgctrl [![Test Status](https://github.com/WireGuard/wgctrl-go/workflows/Linux%20Test/badge.svg)](https://github.com/WireGuard/wgctrl-go/actions) [![Go Reference](https://pkg.go.dev/badge/golang.zx2c4.com/wireguard/wgctrl.svg)](https://pkg.go.dev/golang.zx2c4.com/wireguard/wgctrl) [![Go Report Card](https://goreportcard.com/badge/golang.zx2c4.com/wireguard/wgctrl)](https://goreportcard.com/report/golang.zx2c4.com/wireguard/wgctrl)
Package `wgctrl` enables control of WireGuard devices on multiple platforms.
For more information on WireGuard, please see <https://www.wireguard.com/>.
MIT Licensed.
```text
go get golang.zx2c4.com/wireguard/wgctrl
```
## Overview
`wgctrl` can control multiple types of WireGuard devices, including:
- Linux kernel module devices, via generic netlink
- userspace devices (e.g. wireguard-go), via the userspace configuration protocol
- both UNIX-like and Windows operating systems are supported
- **Experimental:** OpenBSD kernel module devices (read-only), via ioctl interface
- See <https://git.zx2c4.com/wireguard-openbsd/about/> for details.
As new operating systems add support for in-kernel WireGuard implementations,
this package should also be extended to support those native implementations.
If you are aware of any efforts on this front, please
[file an issue](https://github.com/WireGuard/wgctrl-go/issues/new).
This package implements WireGuard configuration protocol operations, enabling
the configuration of existing WireGuard devices. Operations such as creating
WireGuard devices, or applying IP addresses to those devices, are out of scope
for this package.

101
vendor/golang.zx2c4.com/wireguard/wgctrl/client.go generated vendored Normal file
View File

@@ -0,0 +1,101 @@
package wgctrl
import (
"errors"
"os"
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// Expose an identical interface to the underlying packages.
var _ wginternal.Client = &Client{}
// A Client provides access to WireGuard device information.
type Client struct {
// Seamlessly use different wginternal.Client implementations to provide an
// interface similar to wg(8).
cs []wginternal.Client
}
// New creates a new Client.
func New() (*Client, error) {
cs, err := newClients()
if err != nil {
return nil, err
}
return &Client{
cs: cs,
}, nil
}
// Close releases resources used by a Client.
func (c *Client) Close() error {
for _, wgc := range c.cs {
if err := wgc.Close(); err != nil {
return err
}
}
return nil
}
// Devices retrieves all WireGuard devices on this system.
func (c *Client) Devices() ([]*wgtypes.Device, error) {
var out []*wgtypes.Device
for _, wgc := range c.cs {
devs, err := wgc.Devices()
if err != nil {
return nil, err
}
out = append(out, devs...)
}
return out, nil
}
// Device retrieves a WireGuard device by its interface name.
//
// If the device specified by name does not exist or is not a WireGuard device,
// an error is returned which can be checked using `errors.Is(err, os.ErrNotExist)`.
func (c *Client) Device(name string) (*wgtypes.Device, error) {
for _, wgc := range c.cs {
d, err := wgc.Device(name)
switch {
case err == nil:
return d, nil
case errors.Is(err, os.ErrNotExist):
continue
default:
return nil, err
}
}
return nil, os.ErrNotExist
}
// ConfigureDevice configures a WireGuard device by its interface name.
//
// Because the zero value of some Go types may be significant to WireGuard for
// Config fields, only fields which are not nil will be applied when
// configuring a device.
//
// If the device specified by name does not exist or is not a WireGuard device,
// an error is returned which can be checked using `errors.Is(err, os.ErrNotExist)`.
func (c *Client) ConfigureDevice(name string, cfg wgtypes.Config) error {
for _, wgc := range c.cs {
err := wgc.ConfigureDevice(name, cfg)
switch {
case err == nil:
return nil
case errors.Is(err, os.ErrNotExist):
continue
default:
return err
}
}
return os.ErrNotExist
}

29
vendor/golang.zx2c4.com/wireguard/wgctrl/doc.go generated vendored Normal file
View File

@@ -0,0 +1,29 @@
// Package wgctrl enables control of WireGuard devices on multiple platforms.
//
// For more information on WireGuard, please see https://www.wireguard.com/.
//
// go get golang.zx2c4.com/wireguard/wgctrl
//
//
// Overview
//
// wgctrl can control multiple types of WireGuard devices, including:
//
// - Linux kernel module devices, via generic netlink
// - userspace devices (e.g. wireguard-go), via the userspace configuration protocol
// - both UNIX-like and Windows operating systems are supported
// - **Experimental:** OpenBSD kernel module devices, via ioctl interface
// See <https://git.zx2c4.com/wireguard-openbsd/about/> for details. Specify
// environment variable WGCTRL_OPENBSD_KERNEL=1 to enable this interface.
//
// As new operating systems add support for in-kernel WireGuard implementations,
// this package should also be extended to support those native implementations.
//
// If you are aware of any efforts on this front, please file an issue:
// https://github.com/WireGuard/wgctrl-go/issues/new.
//
// This package implements WireGuard configuration protocol operations, enabling
// the configuration of existing WireGuard devices. Operations such as creating
// WireGuard devices, or applying IP addresses to those devices, are out of scope
// for this package.
package wgctrl // import "golang.zx2c4.com/wireguard/wgctrl"

View File

@@ -0,0 +1,21 @@
package wginternal
import (
"errors"
"io"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// ErrReadOnly indicates that the driver backing a device is read-only. It is
// a sentinel value used in integration tests.
// TODO(mdlayher): consider exposing in API.
var ErrReadOnly = errors.New("driver is read-only")
// A Client is a type which can control a WireGuard device.
type Client interface {
io.Closer
Devices() ([]*wgtypes.Device, error)
Device(name string) (*wgtypes.Device, error)
ConfigureDevice(name string, cfg wgtypes.Config) error
}

View File

@@ -0,0 +1,5 @@
// Package wginternal contains shared internal types for wgctrl.
//
// This package is internal-only and not meant for end users to consume.
// Please use package wgctrl (an abstraction over this package) instead.
package wginternal

View File

@@ -0,0 +1,265 @@
//go:build linux
// +build linux
package wglinux
import (
"errors"
"fmt"
"os"
"syscall"
"github.com/mdlayher/genetlink"
"github.com/mdlayher/netlink"
"github.com/mdlayher/netlink/nlenc"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/internal/wglinux/internal/wgh"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var _ wginternal.Client = &Client{}
// A Client provides access to Linux WireGuard netlink information.
type Client struct {
c *genetlink.Conn
family genetlink.Family
interfaces func() ([]string, error)
}
// New creates a new Client and returns whether or not the generic netlink
// interface is available.
func New() (*Client, bool, error) {
c, err := genetlink.Dial(nil)
if err != nil {
return nil, false, err
}
return initClient(c)
}
// initClient is the internal Client constructor used in some tests.
func initClient(c *genetlink.Conn) (*Client, bool, error) {
f, err := c.GetFamily(wgh.GenlName)
if err != nil {
_ = c.Close()
if errors.Is(err, os.ErrNotExist) {
// The generic netlink interface is not available.
return nil, false, nil
}
return nil, false, err
}
return &Client{
c: c,
family: f,
// By default, gather only WireGuard interfaces using rtnetlink.
interfaces: rtnlInterfaces,
}, true, nil
}
// Close implements wginternal.Client.
func (c *Client) Close() error {
return c.c.Close()
}
// Devices implements wginternal.Client.
func (c *Client) Devices() ([]*wgtypes.Device, error) {
// By default, rtnetlink is used to fetch a list of all interfaces and then
// filter that list to only find WireGuard interfaces.
//
// The remainder of this function assumes that any returned device from this
// function is a valid WireGuard device.
ifis, err := c.interfaces()
if err != nil {
return nil, err
}
ds := make([]*wgtypes.Device, 0, len(ifis))
for _, ifi := range ifis {
d, err := c.Device(ifi)
if err != nil {
return nil, err
}
ds = append(ds, d)
}
return ds, nil
}
// Device implements wginternal.Client.
func (c *Client) Device(name string) (*wgtypes.Device, error) {
// Don't bother querying netlink with empty input.
if name == "" {
return nil, os.ErrNotExist
}
// Fetching a device by interface index is possible as well, but we only
// support fetching by name as it seems to be more convenient in general.
b, err := netlink.MarshalAttributes([]netlink.Attribute{{
Type: wgh.DeviceAIfname,
Data: nlenc.Bytes(name),
}})
if err != nil {
return nil, err
}
msgs, err := c.execute(wgh.CmdGetDevice, netlink.Request|netlink.Dump, b)
if err != nil {
return nil, err
}
return parseDevice(msgs)
}
// ConfigureDevice implements wginternal.Client.
func (c *Client) ConfigureDevice(name string, cfg wgtypes.Config) error {
// Large configurations are split into batches for use with netlink.
for _, b := range buildBatches(cfg) {
attrs, err := configAttrs(name, b)
if err != nil {
return err
}
// Request acknowledgement of our request from netlink, even though the
// output messages are unused. The netlink package checks and trims the
// status code value.
if _, err := c.execute(wgh.CmdSetDevice, netlink.Request|netlink.Acknowledge, attrs); err != nil {
return err
}
}
return nil
}
// execute executes a single WireGuard netlink request with the specified command,
// header flags, and attribute arguments.
func (c *Client) execute(command uint8, flags netlink.HeaderFlags, attrb []byte) ([]genetlink.Message, error) {
msg := genetlink.Message{
Header: genetlink.Header{
Command: command,
Version: wgh.GenlVersion,
},
Data: attrb,
}
msgs, err := c.c.Execute(msg, c.family.ID, flags)
if err == nil {
return msgs, nil
}
// We don't want to expose netlink errors directly to callers so unpack to
// something more generic.
oerr, ok := err.(*netlink.OpError)
if !ok {
// Expect all errors to conform to netlink.OpError.
return nil, fmt.Errorf("wglinux: netlink operation returned non-netlink error (please file a bug: https://golang.zx2c4.com/wireguard/wgctrl): %v", err)
}
switch oerr.Err {
// Convert "no such device" and "not a wireguard device" to an error
// compatible with os.ErrNotExist for easy checking.
case unix.ENODEV, unix.ENOTSUP:
return nil, os.ErrNotExist
default:
// Expose the inner error directly (such as EPERM).
return nil, oerr.Err
}
}
// rtnlInterfaces uses rtnetlink to fetch a list of WireGuard interfaces.
func rtnlInterfaces() ([]string, error) {
// Use the stdlib's rtnetlink helpers to get ahold of a table of all
// interfaces, so we can begin filtering it down to just WireGuard devices.
tab, err := syscall.NetlinkRIB(unix.RTM_GETLINK, unix.AF_UNSPEC)
if err != nil {
return nil, fmt.Errorf("wglinux: failed to get list of interfaces from rtnetlink: %v", err)
}
msgs, err := syscall.ParseNetlinkMessage(tab)
if err != nil {
return nil, fmt.Errorf("wglinux: failed to parse rtnetlink messages: %v", err)
}
return parseRTNLInterfaces(msgs)
}
// parseRTNLInterfaces unpacks rtnetlink messages and returns WireGuard
// interface names.
func parseRTNLInterfaces(msgs []syscall.NetlinkMessage) ([]string, error) {
var ifis []string
for _, m := range msgs {
// Only deal with link messages, and they must have an ifinfomsg
// structure appear before the attributes.
if m.Header.Type != unix.RTM_NEWLINK {
continue
}
if len(m.Data) < unix.SizeofIfInfomsg {
return nil, fmt.Errorf("wglinux: rtnetlink message is too short for ifinfomsg: %d", len(m.Data))
}
ad, err := netlink.NewAttributeDecoder(m.Data[syscall.SizeofIfInfomsg:])
if err != nil {
return nil, err
}
// Determine the interface's name and if it's a WireGuard device.
var (
ifi string
isWG bool
)
for ad.Next() {
switch ad.Type() {
case unix.IFLA_IFNAME:
ifi = ad.String()
case unix.IFLA_LINKINFO:
ad.Do(isWGKind(&isWG))
}
}
if err := ad.Err(); err != nil {
return nil, err
}
if isWG {
// Found one; append it to the list.
ifis = append(ifis, ifi)
}
}
return ifis, nil
}
// wgKind is the IFLA_INFO_KIND value for WireGuard devices.
const wgKind = "wireguard"
// isWGKind parses netlink attributes to determine if a link is a WireGuard
// device, then populates ok with the result.
func isWGKind(ok *bool) func(b []byte) error {
return func(b []byte) error {
ad, err := netlink.NewAttributeDecoder(b)
if err != nil {
return err
}
for ad.Next() {
if ad.Type() != unix.IFLA_INFO_KIND {
continue
}
if ad.String() == wgKind {
*ok = true
return nil
}
}
return ad.Err()
}
}

View File

@@ -0,0 +1,294 @@
//go:build linux
// +build linux
package wglinux
import (
"encoding/binary"
"fmt"
"net"
"unsafe"
"github.com/mdlayher/netlink"
"github.com/mdlayher/netlink/nlenc"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/wgctrl/internal/wglinux/internal/wgh"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// configAttrs creates the required encoded netlink attributes to configure
// the device specified by name using the non-nil fields in cfg.
func configAttrs(name string, cfg wgtypes.Config) ([]byte, error) {
ae := netlink.NewAttributeEncoder()
ae.String(wgh.DeviceAIfname, name)
if cfg.PrivateKey != nil {
ae.Bytes(wgh.DeviceAPrivateKey, (*cfg.PrivateKey)[:])
}
if cfg.ListenPort != nil {
ae.Uint16(wgh.DeviceAListenPort, uint16(*cfg.ListenPort))
}
if cfg.FirewallMark != nil {
ae.Uint32(wgh.DeviceAFwmark, uint32(*cfg.FirewallMark))
}
if cfg.ReplacePeers {
ae.Uint32(wgh.DeviceAFlags, wgh.DeviceFReplacePeers)
}
// Only apply peer attributes if necessary.
if len(cfg.Peers) > 0 {
ae.Nested(wgh.DeviceAPeers, func(nae *netlink.AttributeEncoder) error {
// Netlink arrays use type as an array index.
for i, p := range cfg.Peers {
nae.Nested(uint16(i), encodePeer(p))
}
return nil
})
}
return ae.Encode()
}
// ipBatchChunk is a tunable allowed IP batch limit per peer.
//
// Because we don't necessarily know how much space a given peer will occupy,
// we play it safe and use a reasonably small value. Note that this constant
// is used both in this package and tests, so be aware when making changes.
const ipBatchChunk = 256
// peerBatchChunk specifies the number of peers that can appear in a
// configuration before we start splitting it into chunks.
const peerBatchChunk = 32
// shouldBatch determines if a configuration is sufficiently complex that it
// should be split into batches.
func shouldBatch(cfg wgtypes.Config) bool {
if len(cfg.Peers) > peerBatchChunk {
return true
}
var ips int
for _, p := range cfg.Peers {
ips += len(p.AllowedIPs)
}
return ips > ipBatchChunk
}
// buildBatches produces a batch of configs from a single config, if needed.
func buildBatches(cfg wgtypes.Config) []wgtypes.Config {
// Is this a small configuration; no need to batch?
if !shouldBatch(cfg) {
return []wgtypes.Config{cfg}
}
// Use most fields of cfg for our "base" configuration, and only differ
// peers in each batch.
base := cfg
base.Peers = nil
// Track the known peers so that peer IPs are not replaced if a single
// peer has its allowed IPs split into multiple batches.
knownPeers := make(map[wgtypes.Key]struct{})
batches := make([]wgtypes.Config, 0)
for _, p := range cfg.Peers {
batch := base
// Iterate until no more allowed IPs.
var done bool
for !done {
var tmp []net.IPNet
if len(p.AllowedIPs) < ipBatchChunk {
// IPs all fit within a batch; we are done.
tmp = make([]net.IPNet, len(p.AllowedIPs))
copy(tmp, p.AllowedIPs)
done = true
} else {
// IPs are larger than a single batch, copy a batch out and
// advance the cursor.
tmp = make([]net.IPNet, ipBatchChunk)
copy(tmp, p.AllowedIPs[:ipBatchChunk])
p.AllowedIPs = p.AllowedIPs[ipBatchChunk:]
if len(p.AllowedIPs) == 0 {
// IPs ended on a batch boundary; no more IPs left so end
// iteration after this loop.
done = true
}
}
pcfg := wgtypes.PeerConfig{
// PublicKey denotes the peer and must be present.
PublicKey: p.PublicKey,
// Apply the update only flag to every chunk to ensure
// consistency between batches when the kernel module processes
// them.
UpdateOnly: p.UpdateOnly,
// It'd be a bit weird to have a remove peer message with many
// IPs, but just in case, add this to every peer's message.
Remove: p.Remove,
// The IPs for this chunk.
AllowedIPs: tmp,
}
// Only pass certain fields on the first occurrence of a peer, so
// that subsequent IPs won't be wiped out and space isn't wasted.
if _, ok := knownPeers[p.PublicKey]; !ok {
knownPeers[p.PublicKey] = struct{}{}
pcfg.PresharedKey = p.PresharedKey
pcfg.Endpoint = p.Endpoint
pcfg.PersistentKeepaliveInterval = p.PersistentKeepaliveInterval
// Important: do not move or appending peers won't work.
pcfg.ReplaceAllowedIPs = p.ReplaceAllowedIPs
}
// Add a peer configuration to this batch and keep going.
batch.Peers = []wgtypes.PeerConfig{pcfg}
batches = append(batches, batch)
}
}
// Do not allow peer replacement beyond the first message in a batch,
// so we don't overwrite our previous batch work.
for i := range batches {
if i > 0 {
batches[i].ReplacePeers = false
}
}
return batches
}
// encodePeer returns a function to encode PeerConfig nested attributes.
func encodePeer(p wgtypes.PeerConfig) func(ae *netlink.AttributeEncoder) error {
return func(ae *netlink.AttributeEncoder) error {
ae.Bytes(wgh.PeerAPublicKey, p.PublicKey[:])
// Flags are stored in a single attribute.
var flags uint32
if p.Remove {
flags |= wgh.PeerFRemoveMe
}
if p.ReplaceAllowedIPs {
flags |= wgh.PeerFReplaceAllowedips
}
if p.UpdateOnly {
flags |= wgh.PeerFUpdateOnly
}
if flags != 0 {
ae.Uint32(wgh.PeerAFlags, flags)
}
if p.PresharedKey != nil {
ae.Bytes(wgh.PeerAPresharedKey, (*p.PresharedKey)[:])
}
if p.Endpoint != nil {
ae.Do(wgh.PeerAEndpoint, encodeSockaddr(*p.Endpoint))
}
if p.PersistentKeepaliveInterval != nil {
ae.Uint16(wgh.PeerAPersistentKeepaliveInterval, uint16(p.PersistentKeepaliveInterval.Seconds()))
}
// Only apply allowed IPs if necessary.
if len(p.AllowedIPs) > 0 {
ae.Nested(wgh.PeerAAllowedips, encodeAllowedIPs(p.AllowedIPs))
}
return nil
}
}
// encodeSockaddr returns a function which encodes a net.UDPAddr as raw
// sockaddr_in or sockaddr_in6 bytes.
func encodeSockaddr(endpoint net.UDPAddr) func() ([]byte, error) {
return func() ([]byte, error) {
if !isValidIP(endpoint.IP) {
return nil, fmt.Errorf("wglinux: invalid endpoint IP: %s", endpoint.IP.String())
}
// Is this an IPv6 address?
if isIPv6(endpoint.IP) {
var addr [16]byte
copy(addr[:], endpoint.IP.To16())
sa := unix.RawSockaddrInet6{
Family: unix.AF_INET6,
Port: sockaddrPort(endpoint.Port),
Addr: addr,
}
return (*(*[unix.SizeofSockaddrInet6]byte)(unsafe.Pointer(&sa)))[:], nil
}
// IPv4 address handling.
var addr [4]byte
copy(addr[:], endpoint.IP.To4())
sa := unix.RawSockaddrInet4{
Family: unix.AF_INET,
Port: sockaddrPort(endpoint.Port),
Addr: addr,
}
return (*(*[unix.SizeofSockaddrInet4]byte)(unsafe.Pointer(&sa)))[:], nil
}
}
// encodeAllowedIPs returns a function to encode allowed IP nested attributes.
func encodeAllowedIPs(ipns []net.IPNet) func(ae *netlink.AttributeEncoder) error {
return func(ae *netlink.AttributeEncoder) error {
for i, ipn := range ipns {
if !isValidIP(ipn.IP) {
return fmt.Errorf("wglinux: invalid allowed IP: %s", ipn.IP.String())
}
family := uint16(unix.AF_INET6)
if !isIPv6(ipn.IP) {
// Make sure address is 4 bytes if IPv4.
family = unix.AF_INET
ipn.IP = ipn.IP.To4()
}
// Netlink arrays use type as an array index.
ae.Nested(uint16(i), func(nae *netlink.AttributeEncoder) error {
nae.Uint16(wgh.AllowedipAFamily, family)
nae.Bytes(wgh.AllowedipAIpaddr, ipn.IP)
ones, _ := ipn.Mask.Size()
nae.Uint8(wgh.AllowedipACidrMask, uint8(ones))
return nil
})
}
return nil
}
}
// isValidIP determines if IP is a valid IPv4 or IPv6 address.
func isValidIP(ip net.IP) bool {
return ip.To16() != nil
}
// isIPv6 determines if IP is a valid IPv6 address.
func isIPv6(ip net.IP) bool {
return isValidIP(ip) && ip.To4() == nil
}
// sockaddrPort interprets port as a big endian uint16 for use passing sockaddr
// structures to the kernel.
func sockaddrPort(port int) uint16 {
return binary.BigEndian.Uint16(nlenc.Uint16Bytes(uint16(port)))
}

View File

@@ -0,0 +1,6 @@
// Package wglinux provides internal access to Linux's WireGuard generic
// netlink interface.
//
// This package is internal-only and not meant for end users to consume.
// Please use package wgctrl (an abstraction over this package) instead.
package wglinux

View File

@@ -0,0 +1,99 @@
// WARNING: This file has automatically been generated on Tue, 04 May 2021 18:36:46 EDT.
// Code generated by https://git.io/c-for-go. DO NOT EDIT.
package wgh
const (
// GenlName as defined in wgh/wireguard.h:134
GenlName = "wireguard"
// GenlVersion as defined in wgh/wireguard.h:135
GenlVersion = 1
// KeyLen as defined in wgh/wireguard.h:137
KeyLen = 32
// CmdMax as defined in wgh/wireguard.h:144
CmdMax = (__CmdMax - 1)
// DeviceAMax as defined in wgh/wireguard.h:162
DeviceAMax = (_DeviceALast - 1)
// PeerAMax as defined in wgh/wireguard.h:185
PeerAMax = (_PeerALast - 1)
// AllowedipAMax as defined in wgh/wireguard.h:194
AllowedipAMax = (_AllowedipALast - 1)
)
// wgCmd as declared in wgh/wireguard.h:139
type wgCmd int32
// wgCmd enumeration from wgh/wireguard.h:139
const (
CmdGetDevice = iota
CmdSetDevice = 1
__CmdMax = 2
)
// wgdeviceFlag as declared in wgh/wireguard.h:146
type wgdeviceFlag int32
// wgdeviceFlag enumeration from wgh/wireguard.h:146
const (
DeviceFReplacePeers = uint32(1) << 0
_DeviceFAll = DeviceFReplacePeers
)
// wgdeviceAttribute as declared in wgh/wireguard.h:150
type wgdeviceAttribute int32
// wgdeviceAttribute enumeration from wgh/wireguard.h:150
const (
DeviceAUnspec = iota
DeviceAIfindex = 1
DeviceAIfname = 2
DeviceAPrivateKey = 3
DeviceAPublicKey = 4
DeviceAFlags = 5
DeviceAListenPort = 6
DeviceAFwmark = 7
DeviceAPeers = 8
_DeviceALast = 9
)
// wgpeerFlag as declared in wgh/wireguard.h:164
type wgpeerFlag int32
// wgpeerFlag enumeration from wgh/wireguard.h:164
const (
PeerFRemoveMe = uint32(1) << 0
PeerFReplaceAllowedips = uint32(1) << 1
PeerFUpdateOnly = uint32(1) << 2
_PeerFAll = PeerFRemoveMe | PeerFReplaceAllowedips | PeerFUpdateOnly
)
// wgpeerAttribute as declared in wgh/wireguard.h:171
type wgpeerAttribute int32
// wgpeerAttribute enumeration from wgh/wireguard.h:171
const (
PeerAUnspec = iota
PeerAPublicKey = 1
PeerAPresharedKey = 2
PeerAFlags = 3
PeerAEndpoint = 4
PeerAPersistentKeepaliveInterval = 5
PeerALastHandshakeTime = 6
PeerARxBytes = 7
PeerATxBytes = 8
PeerAAllowedips = 9
PeerAProtocolVersion = 10
_PeerALast = 11
)
// wgallowedipAttribute as declared in wgh/wireguard.h:187
type wgallowedipAttribute int32
// wgallowedipAttribute enumeration from wgh/wireguard.h:187
const (
AllowedipAUnspec = iota
AllowedipAFamily = 1
AllowedipAIpaddr = 2
AllowedipACidrMask = 3
_AllowedipALast = 4
)

View File

@@ -0,0 +1,12 @@
// Package wgh is an auto-generated package which contains constants and
// types used to access WireGuard information using generic netlink.
package wgh
// Pull the latest wireguard.h from GitHub for code generation.
//go:generate wget https://raw.githubusercontent.com/torvalds/linux/master/include/uapi/linux/wireguard.h
// Generate Go source from C constants.
//go:generate c-for-go -out ../ -nocgo wgh.yml
// Clean up build artifacts.
//go:generate rm -rf wireguard.h _obj/

View File

@@ -0,0 +1,22 @@
---
GENERATOR:
PackageName: wgh
PARSER:
IncludePaths: [/usr/include]
SourcesPaths: [wireguard.h]
TRANSLATOR:
ConstRules:
defines: expand
enum: expand
Rules:
const:
- {transform: lower}
- {action: accept, from: "(?i)wg_"}
- {action: replace, from: "(?i)wg_", to: _}
- {action: accept, from: "(?i)wg"}
- {action: replace, from: "(?i)wg", to: }
- {transform: export}
post-global:
- {load: snakecase}

View File

@@ -0,0 +1,304 @@
//go:build linux
// +build linux
package wglinux
import (
"fmt"
"net"
"time"
"unsafe"
"github.com/mdlayher/genetlink"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/wgctrl/internal/wglinux/internal/wgh"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// parseDevice parses a Device from a slice of generic netlink messages,
// automatically merging peer lists from subsequent messages into the Device
// from the first message.
func parseDevice(msgs []genetlink.Message) (*wgtypes.Device, error) {
var first wgtypes.Device
knownPeers := make(map[wgtypes.Key]int)
for i, m := range msgs {
d, err := parseDeviceLoop(m)
if err != nil {
return nil, err
}
if i == 0 {
// First message contains our target device.
first = *d
// Gather the known peers so that we can merge
// them later if needed
for i := range first.Peers {
knownPeers[first.Peers[i].PublicKey] = i
}
continue
}
// Any subsequent messages have their peer contents merged into the
// first "target" message.
mergeDevices(&first, d, knownPeers)
}
return &first, nil
}
// parseDeviceLoop parses a Device from a single generic netlink message.
func parseDeviceLoop(m genetlink.Message) (*wgtypes.Device, error) {
ad, err := netlink.NewAttributeDecoder(m.Data)
if err != nil {
return nil, err
}
d := wgtypes.Device{Type: wgtypes.LinuxKernel}
for ad.Next() {
switch ad.Type() {
case wgh.DeviceAIfindex:
// Ignored; interface index isn't exposed at all in the userspace
// configuration protocol, and name is more friendly anyway.
case wgh.DeviceAIfname:
d.Name = ad.String()
case wgh.DeviceAPrivateKey:
ad.Do(parseKey(&d.PrivateKey))
case wgh.DeviceAPublicKey:
ad.Do(parseKey(&d.PublicKey))
case wgh.DeviceAListenPort:
d.ListenPort = int(ad.Uint16())
case wgh.DeviceAFwmark:
d.FirewallMark = int(ad.Uint32())
case wgh.DeviceAPeers:
// Netlink array of peers.
//
// Errors while parsing are propagated up to top-level ad.Err check.
ad.Nested(func(nad *netlink.AttributeDecoder) error {
// Initialize to the number of peers in this decoder and begin
// handling nested Peer attributes.
d.Peers = make([]wgtypes.Peer, 0, nad.Len())
for nad.Next() {
nad.Nested(func(nnad *netlink.AttributeDecoder) error {
d.Peers = append(d.Peers, parsePeer(nnad))
return nil
})
}
return nil
})
}
}
if err := ad.Err(); err != nil {
return nil, err
}
return &d, nil
}
// parseAllowedIPs parses a wgtypes.Peer from a netlink attribute payload.
func parsePeer(ad *netlink.AttributeDecoder) wgtypes.Peer {
var p wgtypes.Peer
for ad.Next() {
switch ad.Type() {
case wgh.PeerAPublicKey:
ad.Do(parseKey(&p.PublicKey))
case wgh.PeerAPresharedKey:
ad.Do(parseKey(&p.PresharedKey))
case wgh.PeerAEndpoint:
p.Endpoint = &net.UDPAddr{}
ad.Do(parseSockaddr(p.Endpoint))
case wgh.PeerAPersistentKeepaliveInterval:
p.PersistentKeepaliveInterval = time.Duration(ad.Uint16()) * time.Second
case wgh.PeerALastHandshakeTime:
ad.Do(parseTimespec(&p.LastHandshakeTime))
case wgh.PeerARxBytes:
p.ReceiveBytes = int64(ad.Uint64())
case wgh.PeerATxBytes:
p.TransmitBytes = int64(ad.Uint64())
case wgh.PeerAAllowedips:
ad.Nested(parseAllowedIPs(&p.AllowedIPs))
case wgh.PeerAProtocolVersion:
p.ProtocolVersion = int(ad.Uint32())
}
}
return p
}
// parseAllowedIPs parses a slice of net.IPNet from a netlink attribute payload.
func parseAllowedIPs(ipns *[]net.IPNet) func(ad *netlink.AttributeDecoder) error {
return func(ad *netlink.AttributeDecoder) error {
// Initialize to the number of allowed IPs and begin iterating through
// the netlink array to decode each one.
*ipns = make([]net.IPNet, 0, ad.Len())
for ad.Next() {
// Allowed IP nested attributes.
ad.Nested(func(nad *netlink.AttributeDecoder) error {
var (
ipn net.IPNet
mask int
family int
)
for nad.Next() {
switch nad.Type() {
case wgh.AllowedipAIpaddr:
nad.Do(parseAddr(&ipn.IP))
case wgh.AllowedipACidrMask:
mask = int(nad.Uint8())
case wgh.AllowedipAFamily:
family = int(nad.Uint16())
}
}
if err := nad.Err(); err != nil {
return err
}
// The address family determines the correct number of bits in
// the mask.
switch family {
case unix.AF_INET:
ipn.Mask = net.CIDRMask(mask, 32)
case unix.AF_INET6:
ipn.Mask = net.CIDRMask(mask, 128)
}
*ipns = append(*ipns, ipn)
return nil
})
}
return nil
}
}
// parseKey parses a wgtypes.Key from a byte slice.
func parseKey(key *wgtypes.Key) func(b []byte) error {
return func(b []byte) error {
k, err := wgtypes.NewKey(b)
if err != nil {
return err
}
*key = k
return nil
}
}
// parseAddr parses a net.IP from raw in_addr or in6_addr struct bytes.
func parseAddr(ip *net.IP) func(b []byte) error {
return func(b []byte) error {
switch len(b) {
case net.IPv4len, net.IPv6len:
// Okay to convert directly to net.IP; memory layout is identical.
*ip = make(net.IP, len(b))
copy(*ip, b)
return nil
default:
return fmt.Errorf("wglinux: unexpected IP address size: %d", len(b))
}
}
}
// parseSockaddr parses a *net.UDPAddr from raw sockaddr_in or sockaddr_in6 bytes.
func parseSockaddr(endpoint *net.UDPAddr) func(b []byte) error {
return func(b []byte) error {
switch len(b) {
case unix.SizeofSockaddrInet4:
// IPv4 address parsing.
sa := *(*unix.RawSockaddrInet4)(unsafe.Pointer(&b[0]))
*endpoint = net.UDPAddr{
IP: net.IP(sa.Addr[:]).To4(),
Port: int(sockaddrPort(int(sa.Port))),
}
return nil
case unix.SizeofSockaddrInet6:
// IPv6 address parsing.
sa := *(*unix.RawSockaddrInet6)(unsafe.Pointer(&b[0]))
*endpoint = net.UDPAddr{
IP: net.IP(sa.Addr[:]),
Port: int(sockaddrPort(int(sa.Port))),
}
return nil
default:
return fmt.Errorf("wglinux: unexpected sockaddr size: %d", len(b))
}
}
}
// timespec32 is a unix.Timespec with 32-bit integers.
type timespec32 struct {
Sec int32
Nsec int32
}
// timespec64 is a unix.Timespec with 64-bit integers.
type timespec64 struct {
Sec int64
Nsec int64
}
const (
sizeofTimespec32 = int(unsafe.Sizeof(timespec32{}))
sizeofTimespec64 = int(unsafe.Sizeof(timespec64{}))
)
// parseTimespec parses a time.Time from raw timespec bytes.
func parseTimespec(t *time.Time) func(b []byte) error {
return func(b []byte) error {
// It would appear that WireGuard can return a __kernel_timespec which
// uses 64-bit integers, even on 32-bit platforms. Clarification of this
// behavior is being sought in:
// https://lists.zx2c4.com/pipermail/wireguard/2019-April/004088.html.
//
// In the mean time, be liberal and accept 32-bit and 64-bit variants.
var sec, nsec int64
switch len(b) {
case sizeofTimespec32:
ts := *(*timespec32)(unsafe.Pointer(&b[0]))
sec = int64(ts.Sec)
nsec = int64(ts.Nsec)
case sizeofTimespec64:
ts := *(*timespec64)(unsafe.Pointer(&b[0]))
sec = ts.Sec
nsec = ts.Nsec
default:
return fmt.Errorf("wglinux: unexpected timespec size: %d bytes, expected 8 or 16 bytes", len(b))
}
// Only set fields if UNIX timestamp value is greater than 0, so the
// caller will see a zero-value time.Time otherwise.
if sec > 0 || nsec > 0 {
*t = time.Unix(sec, nsec)
}
return nil
}
}
// mergeDevices merges Peer information from d into target. mergeDevices is
// used to deal with multiple incoming netlink messages for the same device.
func mergeDevices(target, d *wgtypes.Device, knownPeers map[wgtypes.Key]int) {
for i := range d.Peers {
// Peer is already known, append to it's allowed IP networks
if peerIndex, ok := knownPeers[d.Peers[i].PublicKey]; ok {
target.Peers[peerIndex].AllowedIPs = append(target.Peers[peerIndex].AllowedIPs, d.Peers[i].AllowedIPs...)
} else { // New peer, add it to the target peers.
target.Peers = append(target.Peers, d.Peers[i])
knownPeers[d.Peers[i].PublicKey] = len(target.Peers) - 1
}
}
}

View File

@@ -0,0 +1,373 @@
//go:build openbsd
// +build openbsd
package wgopenbsd
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"os"
"runtime"
"time"
"unsafe"
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/internal/wgopenbsd/internal/wgh"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var (
// ifGroupWG is the WireGuard interface group name passed to the kernel.
ifGroupWG = [16]byte{0: 'w', 1: 'g'}
)
var _ wginternal.Client = &Client{}
// A Client provides access to OpenBSD WireGuard ioctl information.
type Client struct {
// Hooks which use system calls by default, but can also be swapped out
// during tests.
close func() error
ioctlIfgroupreq func(ifg *wgh.Ifgroupreq) error
ioctlWGDataIO func(data *wgh.WGDataIO) error
}
// New creates a new Client and returns whether or not the ioctl interface
// is available.
func New() (*Client, bool, error) {
// The OpenBSD ioctl interface operates on a generic AF_INET socket.
fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, 0)
if err != nil {
return nil, false, err
}
// TODO(mdlayher): find a call to invoke here to probe for availability.
// c.Devices won't work because it returns a "not found" error when the
// kernel WireGuard implementation is available but the interface group
// has no members.
// By default, use system call implementations for all hook functions.
return &Client{
close: func() error { return unix.Close(fd) },
ioctlIfgroupreq: ioctlIfgroupreq(fd),
ioctlWGDataIO: ioctlWGDataIO(fd),
}, true, nil
}
// Close implements wginternal.Client.
func (c *Client) Close() error {
return c.close()
}
// Devices implements wginternal.Client.
func (c *Client) Devices() ([]*wgtypes.Device, error) {
ifg := wgh.Ifgroupreq{
// Query for devices in the "wg" group.
Name: ifGroupWG,
}
// Determine how many device names we must allocate memory for.
if err := c.ioctlIfgroupreq(&ifg); err != nil {
return nil, err
}
// ifg.Len is size in bytes; allocate enough memory for the correct number
// of wgh.Ifgreq and then store a pointer to the memory where the data
// should be written (ifgrs) in ifg.Groups.
//
// From a thread in golang-nuts, this pattern is valid:
// "It would be OK to pass a pointer to a struct to ioctl if the struct
// contains a pointer to other Go memory, but the struct field must have
// pointer type."
// See: https://groups.google.com/forum/#!topic/golang-nuts/FfasFTZvU_o.
ifgrs := make([]wgh.Ifgreq, ifg.Len/wgh.SizeofIfgreq)
ifg.Groups = &ifgrs[0]
// Now actually fetch the device names.
if err := c.ioctlIfgroupreq(&ifg); err != nil {
return nil, err
}
// Keep this alive until we're done doing the ioctl dance.
runtime.KeepAlive(&ifg)
devices := make([]*wgtypes.Device, 0, len(ifgrs))
for _, ifgr := range ifgrs {
// Remove any trailing NULL bytes from the interface names.
d, err := c.Device(string(bytes.TrimRight(ifgr.Ifgrqu[:], "\x00")))
if err != nil {
return nil, err
}
devices = append(devices, d)
}
return devices, nil
}
// Device implements wginternal.Client.
func (c *Client) Device(name string) (*wgtypes.Device, error) {
dname, err := deviceName(name)
if err != nil {
return nil, err
}
// First, specify the name of the device and determine how much memory
// must be allocated in order to store the WGInterfaceIO structure and
// any trailing WGPeerIO/WGAIPIOs.
data := wgh.WGDataIO{Name: dname}
// TODO: consider preallocating some memory to avoid a second system call
// if it proves to be a concern.
var mem []byte
for {
if err := c.ioctlWGDataIO(&data); err != nil {
// ioctl functions always return a wrapped unix.Errno value.
// Conform to the wgctrl contract by unwrapping some values:
// ENXIO: "no such device": (no such WireGuard device)
// ENOTTY: "inappropriate ioctl for device" (device is not a
// WireGuard device)
switch err.(*os.SyscallError).Err {
case unix.ENXIO, unix.ENOTTY:
return nil, os.ErrNotExist
default:
return nil, err
}
}
if len(mem) >= int(data.Size) {
// Allocated enough memory!
break
}
// Ensure we don't unsafe cast into uninitialized memory. We need at very
// least a single WGInterfaceIO with no peers.
if data.Size < wgh.SizeofWGInterfaceIO {
return nil, fmt.Errorf("wgopenbsd: kernel returned unexpected number of bytes for WGInterfaceIO: %d", data.Size)
}
// Allocate the appropriate amount of memory and point the kernel at
// the first byte of our slice's backing array. When the loop continues,
// we will check if we've allocated enough memory.
mem = make([]byte, data.Size)
data.Interface = (*wgh.WGInterfaceIO)(unsafe.Pointer(&mem[0]))
}
return parseDevice(name, data.Interface)
}
// parseDevice unpacks a Device from ifio, along with its associated peers
// and their allowed IPs.
func parseDevice(name string, ifio *wgh.WGInterfaceIO) (*wgtypes.Device, error) {
d := &wgtypes.Device{
Name: name,
Type: wgtypes.OpenBSDKernel,
}
// The kernel populates ifio.Flags to indicate which fields are present.
if ifio.Flags&wgh.WG_INTERFACE_HAS_PRIVATE != 0 {
d.PrivateKey = wgtypes.Key(ifio.Private)
}
if ifio.Flags&wgh.WG_INTERFACE_HAS_PUBLIC != 0 {
d.PublicKey = wgtypes.Key(ifio.Public)
}
if ifio.Flags&wgh.WG_INTERFACE_HAS_PORT != 0 {
d.ListenPort = int(ifio.Port)
}
if ifio.Flags&wgh.WG_INTERFACE_HAS_RTABLE != 0 {
d.FirewallMark = int(ifio.Rtable)
}
d.Peers = make([]wgtypes.Peer, 0, ifio.Peers_count)
// If there were no peers, exit early so we do not advance the pointer
// beyond the end of the WGInterfaceIO structure.
if ifio.Peers_count == 0 {
return d, nil
}
// Set our pointer to the beginning of the first peer's location in memory.
peer := (*wgh.WGPeerIO)(unsafe.Pointer(
uintptr(unsafe.Pointer(ifio)) + wgh.SizeofWGInterfaceIO,
))
for i := 0; i < int(ifio.Peers_count); i++ {
p := parsePeer(peer)
// Same idea, we know how many allowed IPs we need to account for, so
// reserve the space and advance the pointer through each WGAIP structure.
p.AllowedIPs = make([]net.IPNet, 0, peer.Aips_count)
for j := uintptr(0); j < uintptr(peer.Aips_count); j++ {
aip := (*wgh.WGAIPIO)(unsafe.Pointer(
uintptr(unsafe.Pointer(peer)) + wgh.SizeofWGPeerIO + j*wgh.SizeofWGAIPIO,
))
p.AllowedIPs = append(p.AllowedIPs, parseAllowedIP(aip))
}
// Prepare for the next iteration.
d.Peers = append(d.Peers, p)
peer = (*wgh.WGPeerIO)(unsafe.Pointer(
uintptr(unsafe.Pointer(peer)) + wgh.SizeofWGPeerIO +
uintptr(peer.Aips_count)*wgh.SizeofWGAIPIO,
))
}
return d, nil
}
// ConfigureDevice implements wginternal.Client.
func (c *Client) ConfigureDevice(name string, cfg wgtypes.Config) error {
// Currently read-only: we must determine if a device belongs to this driver,
// and if it does, return a sentinel so integration tests that configure a
// device can be skipped.
if _, err := c.Device(name); err != nil {
return err
}
return wginternal.ErrReadOnly
}
// deviceName converts an interface name string to the format required to pass
// with wgh.WGGetServ.
func deviceName(name string) ([16]byte, error) {
var out [unix.IFNAMSIZ]byte
if len(name) > unix.IFNAMSIZ {
return out, fmt.Errorf("wgopenbsd: interface name %q too long", name)
}
copy(out[:], name)
return out, nil
}
// parsePeer unpacks a wgtypes.Peer from a WGPeerIO structure.
func parsePeer(pio *wgh.WGPeerIO) wgtypes.Peer {
p := wgtypes.Peer{
ReceiveBytes: int64(pio.Rxbytes),
TransmitBytes: int64(pio.Txbytes),
ProtocolVersion: int(pio.Protocol_version),
}
// Only set last handshake if a non-zero timespec was provided, matching
// the time.Time.IsZero() behavior of internal/wglinux.
if pio.Last_handshake.Sec > 0 && pio.Last_handshake.Nsec > 0 {
p.LastHandshakeTime = time.Unix(
pio.Last_handshake.Sec,
// Conversion required for GOARCH=386.
int64(pio.Last_handshake.Nsec),
)
}
if pio.Flags&wgh.WG_PEER_HAS_PUBLIC != 0 {
p.PublicKey = wgtypes.Key(pio.Public)
}
if pio.Flags&wgh.WG_PEER_HAS_PSK != 0 {
p.PresharedKey = wgtypes.Key(pio.Psk)
}
if pio.Flags&wgh.WG_PEER_HAS_PKA != 0 {
p.PersistentKeepaliveInterval = time.Duration(pio.Pka) * time.Second
}
if pio.Flags&wgh.WG_PEER_HAS_ENDPOINT != 0 {
p.Endpoint = parseEndpoint(pio.Endpoint)
}
return p
}
// parseAllowedIP unpacks a net.IPNet from a WGAIP structure.
func parseAllowedIP(aip *wgh.WGAIPIO) net.IPNet {
switch aip.Af {
case unix.AF_INET:
return net.IPNet{
IP: net.IP(aip.Addr[:net.IPv4len]),
Mask: net.CIDRMask(int(aip.Cidr), 32),
}
case unix.AF_INET6:
return net.IPNet{
IP: net.IP(aip.Addr[:]),
Mask: net.CIDRMask(int(aip.Cidr), 128),
}
default:
panicf("wgopenbsd: invalid address family for allowed IP: %+v", aip)
return net.IPNet{}
}
}
// parseEndpoint parses a peer endpoint from a wgh.WGIP structure.
func parseEndpoint(ep [28]byte) *net.UDPAddr {
// sockaddr* structures have family at index 1.
switch ep[1] {
case unix.AF_INET:
sa := *(*unix.RawSockaddrInet4)(unsafe.Pointer(&ep[0]))
ep := &net.UDPAddr{
IP: make(net.IP, net.IPv4len),
Port: bePort(sa.Port),
}
copy(ep.IP, sa.Addr[:])
return ep
case unix.AF_INET6:
sa := *(*unix.RawSockaddrInet6)(unsafe.Pointer(&ep[0]))
// TODO(mdlayher): IPv6 zone?
ep := &net.UDPAddr{
IP: make(net.IP, net.IPv6len),
Port: bePort(sa.Port),
}
copy(ep.IP, sa.Addr[:])
return ep
default:
// No endpoint configured.
return nil
}
}
// bePort interprets a port integer stored in native endianness as a big
// endian value. This is necessary for proper endpoint port handling on
// little endian machines.
func bePort(port uint16) int {
b := *(*[2]byte)(unsafe.Pointer(&port))
return int(binary.BigEndian.Uint16(b[:]))
}
// ioctlIfgroupreq returns a function which performs the appropriate ioctl on
// fd to retrieve members of an interface group.
func ioctlIfgroupreq(fd int) func(*wgh.Ifgroupreq) error {
return func(ifg *wgh.Ifgroupreq) error {
return ioctl(fd, wgh.SIOCGIFGMEMB, unsafe.Pointer(ifg))
}
}
// ioctlWGDataIO returns a function which performs the appropriate ioctl on
// fd to issue a WireGuard data I/O.
func ioctlWGDataIO(fd int) func(*wgh.WGDataIO) error {
return func(data *wgh.WGDataIO) error {
return ioctl(fd, wgh.SIOCGWG, unsafe.Pointer(data))
}
}
// ioctl is a raw wrapper for the ioctl system call.
func ioctl(fd int, req uint, arg unsafe.Pointer) error {
_, _, errno := unix.Syscall(unix.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
if errno != 0 {
return os.NewSyscallError("ioctl", errno)
}
return nil
}
func panicf(format string, a ...interface{}) {
panic(fmt.Sprintf(format, a...))
}

View File

@@ -0,0 +1,6 @@
// Package wgopenbsd provides internal access to OpenBSD's WireGuard
// ioctl interface.
//
// This package is internal-only and not meant for end users to consume.
// Please use package wgctrl (an abstraction over this package) instead.
package wgopenbsd

View File

@@ -0,0 +1,84 @@
//go:build openbsd && 386
// +build openbsd,386
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs defs.go
package wgh
const (
SIOCGIFGMEMB = 0xc024698a
SizeofIfgreq = 0x10
)
type Ifgroupreq struct {
Name [16]byte
Len uint32
Pad1 [0]byte
Groups *Ifgreq
Pad2 [12]byte
}
type Ifgreq struct {
Ifgrqu [16]byte
}
type Timespec struct {
Sec int64
Nsec int32
}
type WGAIPIO struct {
Af uint8
Cidr int32
Addr [16]byte
}
type WGDataIO struct {
Name [16]byte
Size uint32
Interface *WGInterfaceIO
}
type WGInterfaceIO struct {
Flags uint8
Port uint16
Rtable int32
Public [32]byte
Private [32]byte
Peers_count uint32
}
type WGPeerIO struct {
Flags int32
Protocol_version int32
Public [32]byte
Psk [32]byte
Pka uint16
Pad_cgo_0 [2]byte
Endpoint [28]byte
Txbytes uint64
Rxbytes uint64
Last_handshake Timespec
Aips_count uint32
}
const (
SIOCGWG = 0xc01869d3
WG_INTERFACE_HAS_PUBLIC = 0x1
WG_INTERFACE_HAS_PRIVATE = 0x2
WG_INTERFACE_HAS_PORT = 0x4
WG_INTERFACE_HAS_RTABLE = 0x8
WG_INTERFACE_REPLACE_PEERS = 0x10
WG_PEER_HAS_PUBLIC = 0x1
WG_PEER_HAS_PSK = 0x2
WG_PEER_HAS_PKA = 0x4
WG_PEER_HAS_ENDPOINT = 0x8
SizeofWGAIPIO = 0x18
SizeofWGInterfaceIO = 0x4c
SizeofWGPeerIO = 0x88
)

View File

@@ -0,0 +1,84 @@
//go:build openbsd && amd64
// +build openbsd,amd64
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs defs.go
package wgh
const (
SIOCGIFGMEMB = 0xc028698a
SizeofIfgreq = 0x10
)
type Ifgroupreq struct {
Name [16]byte
Len uint32
Pad1 [4]byte
Groups *Ifgreq
Pad2 [8]byte
}
type Ifgreq struct {
Ifgrqu [16]byte
}
type Timespec struct {
Sec int64
Nsec int64
}
type WGAIPIO struct {
Af uint8
Cidr int32
Addr [16]byte
}
type WGDataIO struct {
Name [16]byte
Size uint64
Interface *WGInterfaceIO
}
type WGInterfaceIO struct {
Flags uint8
Port uint16
Rtable int32
Public [32]byte
Private [32]byte
Peers_count uint64
}
type WGPeerIO struct {
Flags int32
Protocol_version int32
Public [32]byte
Psk [32]byte
Pka uint16
Pad_cgo_0 [2]byte
Endpoint [28]byte
Txbytes uint64
Rxbytes uint64
Last_handshake Timespec
Aips_count uint64
}
const (
SIOCGWG = 0xc02069d3
WG_INTERFACE_HAS_PUBLIC = 0x1
WG_INTERFACE_HAS_PRIVATE = 0x2
WG_INTERFACE_HAS_PORT = 0x4
WG_INTERFACE_HAS_RTABLE = 0x8
WG_INTERFACE_REPLACE_PEERS = 0x10
WG_PEER_HAS_PUBLIC = 0x1
WG_PEER_HAS_PSK = 0x2
WG_PEER_HAS_PKA = 0x4
WG_PEER_HAS_ENDPOINT = 0x8
SizeofWGAIPIO = 0x18
SizeofWGInterfaceIO = 0x50
SizeofWGPeerIO = 0x90
)

View File

@@ -0,0 +1,3 @@
// Package wgh is an auto-generated package which contains constants and
// types used to access WireGuard information using ioctl calls.
package wgh

View File

@@ -0,0 +1,25 @@
#/bin/sh
set -x
# Fix up generated code.
gofix()
{
IN=$1
OUT=$2
# Change types that are a nuisance to deal with in Go, use byte for
# consistency, and produce gofmt'd output.
sed 's/]u*int8/]byte/g' $1 | gofmt -s > $2
}
echo -e "//+build openbsd,amd64\n" > /tmp/wgamd64.go
GOARCH=amd64 go tool cgo -godefs defs.go >> /tmp/wgamd64.go
echo -e "//+build openbsd,386\n" > /tmp/wg386.go
GOARCH=386 go tool cgo -godefs defs.go >> /tmp/wg386.go
gofix /tmp/wgamd64.go defs_openbsd_amd64.go
gofix /tmp/wg386.go defs_openbsd_386.go
rm -rf _obj/ /tmp/wg*.go

View File

@@ -0,0 +1,99 @@
package wguser
import (
"fmt"
"net"
"os"
"path/filepath"
"strings"
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var _ wginternal.Client = &Client{}
// A Client provides access to userspace WireGuard device information.
type Client struct {
dial func(device string) (net.Conn, error)
find func() ([]string, error)
}
// New creates a new Client.
func New() (*Client, error) {
return &Client{
// Operating system-specific functions which can identify and connect
// to userspace WireGuard devices. These functions can also be
// overridden for tests.
dial: dial,
find: find,
}, nil
}
// Close implements wginternal.Client.
func (c *Client) Close() error { return nil }
// Devices implements wginternal.Client.
func (c *Client) Devices() ([]*wgtypes.Device, error) {
devices, err := c.find()
if err != nil {
return nil, err
}
var wgds []*wgtypes.Device
for _, d := range devices {
wgd, err := c.getDevice(d)
if err != nil {
return nil, err
}
wgds = append(wgds, wgd)
}
return wgds, nil
}
// Device implements wginternal.Client.
func (c *Client) Device(name string) (*wgtypes.Device, error) {
devices, err := c.find()
if err != nil {
return nil, err
}
for _, d := range devices {
if name != deviceName(d) {
continue
}
return c.getDevice(d)
}
return nil, os.ErrNotExist
}
// ConfigureDevice implements wginternal.Client.
func (c *Client) ConfigureDevice(name string, cfg wgtypes.Config) error {
devices, err := c.find()
if err != nil {
return err
}
for _, d := range devices {
if name != deviceName(d) {
continue
}
return c.configureDevice(d, cfg)
}
return os.ErrNotExist
}
// deviceName infers a device name from an absolute file path with extension.
func deviceName(sock string) string {
return strings.TrimSuffix(filepath.Base(sock), filepath.Ext(sock))
}
func panicf(format string, a ...interface{}) {
panic(fmt.Sprintf(format, a...))
}

View File

@@ -0,0 +1,106 @@
package wguser
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"strings"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// configureDevice configures a device specified by its path.
func (c *Client) configureDevice(device string, cfg wgtypes.Config) error {
conn, err := c.dial(device)
if err != nil {
return err
}
defer conn.Close()
// Start with set command.
var buf bytes.Buffer
buf.WriteString("set=1\n")
// Add any necessary configuration from cfg, then finish with an empty line.
writeConfig(&buf, cfg)
buf.WriteString("\n")
// Apply configuration for the device and then check the error number.
if _, err := io.Copy(conn, &buf); err != nil {
return err
}
res := make([]byte, 32)
n, err := conn.Read(res)
if err != nil {
return err
}
// errno=0 indicates success, anything else returns an error number that
// matches definitions from errno.h.
str := strings.TrimSpace(string(res[:n]))
if str != "errno=0" {
// TODO(mdlayher): return actual errno on Linux?
return os.NewSyscallError("read", fmt.Errorf("wguser: %s", str))
}
return nil
}
// writeConfig writes textual configuration to w as specified by cfg.
func writeConfig(w io.Writer, cfg wgtypes.Config) {
if cfg.PrivateKey != nil {
fmt.Fprintf(w, "private_key=%s\n", hexKey(*cfg.PrivateKey))
}
if cfg.ListenPort != nil {
fmt.Fprintf(w, "listen_port=%d\n", *cfg.ListenPort)
}
if cfg.FirewallMark != nil {
fmt.Fprintf(w, "fwmark=%d\n", *cfg.FirewallMark)
}
if cfg.ReplacePeers {
fmt.Fprintln(w, "replace_peers=true")
}
for _, p := range cfg.Peers {
fmt.Fprintf(w, "public_key=%s\n", hexKey(p.PublicKey))
if p.Remove {
fmt.Fprintln(w, "remove=true")
}
if p.UpdateOnly {
fmt.Fprintln(w, "update_only=true")
}
if p.PresharedKey != nil {
fmt.Fprintf(w, "preshared_key=%s\n", hexKey(*p.PresharedKey))
}
if p.Endpoint != nil {
fmt.Fprintf(w, "endpoint=%s\n", p.Endpoint.String())
}
if p.PersistentKeepaliveInterval != nil {
fmt.Fprintf(w, "persistent_keepalive_interval=%d\n", int(p.PersistentKeepaliveInterval.Seconds()))
}
if p.ReplaceAllowedIPs {
fmt.Fprintln(w, "replace_allowed_ips=true")
}
for _, ip := range p.AllowedIPs {
fmt.Fprintf(w, "allowed_ip=%s\n", ip.String())
}
}
}
// hexKey encodes a wgtypes.Key into a hexadecimal string.
func hexKey(k wgtypes.Key) string {
return hex.EncodeToString(k[:])
}

View File

@@ -0,0 +1,51 @@
//go:build !windows
// +build !windows
package wguser
import (
"errors"
"io/ioutil"
"net"
"os"
"path/filepath"
)
// dial is the default implementation of Client.dial.
func dial(device string) (net.Conn, error) {
return net.Dial("unix", device)
}
// find is the default implementation of Client.find.
func find() ([]string, error) {
return findUNIXSockets([]string{
// It seems that /var/run is a common location between Linux and the
// BSDs, even though it's a symlink on Linux.
"/var/run/wireguard",
})
}
// findUNIXSockets looks for UNIX socket files in the specified directories.
func findUNIXSockets(dirs []string) ([]string, error) {
var socks []string
for _, d := range dirs {
files, err := ioutil.ReadDir(d)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
continue
}
return nil, err
}
for _, f := range files {
if f.Mode()&os.ModeSocket == 0 {
continue
}
socks = append(socks, filepath.Join(d, f.Name()))
}
}
return socks, nil
}

View File

@@ -0,0 +1,82 @@
//go:build windows
// +build windows
package wguser
import (
"net"
"strings"
"time"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/ipc/namedpipe"
)
// Expected prefixes when dealing with named pipes.
const (
pipePrefix = `\\.\pipe\`
wgPrefix = `ProtectedPrefix\Administrators\WireGuard\`
)
// dial is the default implementation of Client.dial.
func dial(device string) (net.Conn, error) {
localSystem, err := windows.CreateWellKnownSid(windows.WinLocalSystemSid)
if err != nil {
return nil, err
}
return (&namedpipe.DialConfig{
ExpectedOwner: localSystem,
}).DialTimeout(device, time.Duration(0))
}
// find is the default implementation of Client.find.
func find() ([]string, error) {
return findNamedPipes(wgPrefix)
}
// findNamedPipes looks for Windows named pipes that match the specified
// search string prefix.
func findNamedPipes(search string) ([]string, error) {
var (
pipes []string
data windows.Win32finddata
)
// Thanks @zx2c4 for the tips on the appropriate Windows APIs here:
// https://א.cc/dHGpnhxX/c.
h, err := windows.FindFirstFile(
// Append * to find all named pipes.
windows.StringToUTF16Ptr(pipePrefix+"*"),
&data,
)
if err != nil {
return nil, err
}
// FindClose is used to close file search handles instead of the typical
// CloseHandle used elsewhere, see:
// https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-findclose.
defer windows.FindClose(h)
// Check the first file's name for a match, but also keep searching for
// WireGuard named pipes until no more files can be iterated.
for {
name := windows.UTF16ToString(data.FileName[:])
if strings.HasPrefix(name, search) {
// Concatenate strings directly as filepath.Join appears to break the
// named pipe prefix convention.
pipes = append(pipes, pipePrefix+name)
}
if err := windows.FindNextFile(h, &data); err != nil {
if err == windows.ERROR_NO_MORE_FILES {
break
}
return nil, err
}
}
return pipes, nil
}

View File

@@ -0,0 +1,6 @@
// Package wguser provides internal access to the userspace WireGuard
// configuration protocol interface.
//
// This package is internal-only and not meant for end users to consume.
// Please use package wgctrl (an abstraction over this package) instead.
package wguser

View File

@@ -0,0 +1,258 @@
package wguser
import (
"bufio"
"bytes"
"encoding/hex"
"fmt"
"io"
"net"
"os"
"strconv"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// The WireGuard userspace configuration protocol is described here:
// https://www.wireguard.com/xplatform/#cross-platform-userspace-implementation.
// getDevice gathers device information from a device specified by its path
// and returns a Device.
func (c *Client) getDevice(device string) (*wgtypes.Device, error) {
conn, err := c.dial(device)
if err != nil {
return nil, err
}
defer conn.Close()
// Get information about this device.
if _, err := io.WriteString(conn, "get=1\n\n"); err != nil {
return nil, err
}
// Parse the device from the incoming data stream.
d, err := parseDevice(conn)
if err != nil {
return nil, err
}
// TODO(mdlayher): populate interface index too?
d.Name = deviceName(device)
d.Type = wgtypes.Userspace
return d, nil
}
// parseDevice parses a Device and its Peers from an io.Reader.
func parseDevice(r io.Reader) (*wgtypes.Device, error) {
var dp deviceParser
s := bufio.NewScanner(r)
for s.Scan() {
b := s.Bytes()
if len(b) == 0 {
// Empty line, done parsing.
break
}
// All data is in key=value format.
kvs := bytes.Split(b, []byte("="))
if len(kvs) != 2 {
return nil, fmt.Errorf("wguser: invalid key=value pair: %q", string(b))
}
dp.Parse(string(kvs[0]), string(kvs[1]))
}
if err := s.Err(); err != nil {
return nil, err
}
return dp.Device()
}
// A deviceParser accumulates information about a Device and its Peers.
type deviceParser struct {
d wgtypes.Device
err error
parsePeers bool
peers int
hsSec, hsNano int
}
// Device returns a Device or any errors that were encountered while parsing
// a Device.
func (dp *deviceParser) Device() (*wgtypes.Device, error) {
if dp.err != nil {
return nil, dp.err
}
// Compute remaining fields of the Device now that all parsing is done.
dp.d.PublicKey = dp.d.PrivateKey.PublicKey()
return &dp.d, nil
}
// Parse parses a single key/value pair into fields of a Device.
func (dp *deviceParser) Parse(key, value string) {
switch key {
case "errno":
// 0 indicates success, anything else returns an error number that matches
// definitions from errno.h.
if errno := dp.parseInt(value); errno != 0 {
// TODO(mdlayher): return actual errno on Linux?
dp.err = os.NewSyscallError("read", fmt.Errorf("wguser: errno=%d", errno))
return
}
case "public_key":
// We've either found the first peer or the next peer. Stop parsing
// Device fields and start parsing Peer fields, including the public
// key indicated here.
dp.parsePeers = true
dp.peers++
dp.d.Peers = append(dp.d.Peers, wgtypes.Peer{
PublicKey: dp.parseKey(value),
})
return
}
// Are we parsing peer fields?
if dp.parsePeers {
dp.peerParse(key, value)
return
}
// Device field parsing.
switch key {
case "private_key":
dp.d.PrivateKey = dp.parseKey(value)
case "listen_port":
dp.d.ListenPort = dp.parseInt(value)
case "fwmark":
dp.d.FirewallMark = dp.parseInt(value)
}
}
// curPeer returns the current Peer being parsed so its fields can be populated.
func (dp *deviceParser) curPeer() *wgtypes.Peer {
return &dp.d.Peers[dp.peers-1]
}
// peerParse parses a key/value field into the current Peer.
func (dp *deviceParser) peerParse(key, value string) {
p := dp.curPeer()
switch key {
case "preshared_key":
p.PresharedKey = dp.parseKey(value)
case "endpoint":
p.Endpoint = dp.parseAddr(value)
case "last_handshake_time_sec":
dp.hsSec = dp.parseInt(value)
case "last_handshake_time_nsec":
dp.hsNano = dp.parseInt(value)
// Assume that we've seen both seconds and nanoseconds and populate this
// field now. However, if both fields were set to 0, assume we have never
// had a successful handshake with this peer, and return a zero-value
// time.Time to our callers.
if dp.hsSec > 0 && dp.hsNano > 0 {
p.LastHandshakeTime = time.Unix(int64(dp.hsSec), int64(dp.hsNano))
}
case "tx_bytes":
p.TransmitBytes = dp.parseInt64(value)
case "rx_bytes":
p.ReceiveBytes = dp.parseInt64(value)
case "persistent_keepalive_interval":
p.PersistentKeepaliveInterval = time.Duration(dp.parseInt(value)) * time.Second
case "allowed_ip":
cidr := dp.parseCIDR(value)
if cidr != nil {
p.AllowedIPs = append(p.AllowedIPs, *cidr)
}
case "protocol_version":
p.ProtocolVersion = dp.parseInt(value)
}
}
// parseKey parses a Key from a hex string.
func (dp *deviceParser) parseKey(s string) wgtypes.Key {
if dp.err != nil {
return wgtypes.Key{}
}
b, err := hex.DecodeString(s)
if err != nil {
dp.err = err
return wgtypes.Key{}
}
key, err := wgtypes.NewKey(b)
if err != nil {
dp.err = err
return wgtypes.Key{}
}
return key
}
// parseInt parses an integer from a string.
func (dp *deviceParser) parseInt(s string) int {
if dp.err != nil {
return 0
}
v, err := strconv.Atoi(s)
if err != nil {
dp.err = err
return 0
}
return v
}
// parseInt64 parses an int64 from a string.
func (dp *deviceParser) parseInt64(s string) int64 {
if dp.err != nil {
return 0
}
v, err := strconv.ParseInt(s, 10, 64)
if err != nil {
dp.err = err
return 0
}
return v
}
// parseAddr parses a UDP address from a string.
func (dp *deviceParser) parseAddr(s string) *net.UDPAddr {
if dp.err != nil {
return nil
}
addr, err := net.ResolveUDPAddr("udp", s)
if err != nil {
dp.err = err
return nil
}
return addr
}
// parseInt parses an address CIDR from a string.
func (dp *deviceParser) parseCIDR(s string) *net.IPNet {
if dp.err != nil {
return nil
}
_, cidr, err := net.ParseCIDR(s)
if err != nil {
dp.err = err
return nil
}
return cidr
}

View File

@@ -0,0 +1,295 @@
package wgwindows
import (
"net"
"os"
"time"
"unsafe"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/internal/wgwindows/internal/ioctl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
var _ wginternal.Client = &Client{}
// A Client provides access to WireGuardNT ioctl information.
type Client struct {
cachedAdapters map[string]string
lastLenGuess uint32
}
var (
deviceClassNetGUID = windows.GUID{0x4d36e972, 0xe325, 0x11ce, [8]byte{0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18}}
deviceInterfaceNetGUID = windows.GUID{0xcac88484, 0x7515, 0x4c03, [8]byte{0x82, 0xe6, 0x71, 0xa8, 0x7a, 0xba, 0xc3, 0x61}}
devpkeyWgName = windows.DEVPROPKEY{
FmtID: windows.DEVPROPGUID{0x65726957, 0x7547, 0x7261, [8]byte{0x64, 0x4e, 0x61, 0x6d, 0x65, 0x4b, 0x65, 0x79}},
PID: windows.DEVPROPID_FIRST_USABLE + 1,
}
)
var enumerator = `SWD\WireGuard`
func init() {
if maj, min, _ := windows.RtlGetNtVersionNumbers(); (maj == 6 && min <= 1) || maj < 6 {
enumerator = `ROOT\WIREGUARD`
}
}
func (c *Client) refreshInstanceIdCache() error {
cachedAdapters := make(map[string]string, 5)
devInfo, err := windows.SetupDiGetClassDevsEx(&deviceClassNetGUID, enumerator, 0, windows.DIGCF_PRESENT, 0, "")
if err != nil {
return err
}
defer windows.SetupDiDestroyDeviceInfoList(devInfo)
for i := 0; ; i++ {
devInfoData, err := windows.SetupDiEnumDeviceInfo(devInfo, i)
if err != nil {
if err == windows.ERROR_NO_MORE_ITEMS {
break
}
continue
}
prop, err := windows.SetupDiGetDeviceProperty(devInfo, devInfoData, &devpkeyWgName)
if err != nil {
continue
}
adapterName, ok := prop.(string)
if !ok {
continue
}
var status, problemCode uint32
ret := windows.CM_Get_DevNode_Status(&status, &problemCode, devInfoData.DevInst, 0)
if ret != windows.CR_SUCCESS || (status&windows.DN_DRIVER_LOADED|windows.DN_STARTED) != windows.DN_DRIVER_LOADED|windows.DN_STARTED {
continue
}
instanceId, err := windows.SetupDiGetDeviceInstanceId(devInfo, devInfoData)
if err != nil {
continue
}
cachedAdapters[adapterName] = instanceId
}
c.cachedAdapters = cachedAdapters
return nil
}
func (c *Client) interfaceHandle(name string) (windows.Handle, error) {
instanceId, ok := c.cachedAdapters[name]
if !ok {
err := c.refreshInstanceIdCache()
if err != nil {
return 0, err
}
instanceId, ok = c.cachedAdapters[name]
if !ok {
return 0, os.ErrNotExist
}
}
interfaces, err := windows.CM_Get_Device_Interface_List(instanceId, &deviceInterfaceNetGUID, windows.CM_GET_DEVICE_INTERFACE_LIST_PRESENT)
if err != nil {
return 0, err
}
interface16, err := windows.UTF16PtrFromString(interfaces[0])
if err != nil {
return 0, err
}
return windows.CreateFile(interface16, windows.GENERIC_READ|windows.GENERIC_WRITE, windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, nil, windows.OPEN_EXISTING, 0, 0)
}
// Devices implements wginternal.Client.
func (c *Client) Devices() ([]*wgtypes.Device, error) {
err := c.refreshInstanceIdCache()
if err != nil {
return nil, err
}
ds := make([]*wgtypes.Device, 0, len(c.cachedAdapters))
for name := range c.cachedAdapters {
d, err := c.Device(name)
if err != nil {
return nil, err
}
ds = append(ds, d)
}
return ds, nil
}
// New creates a new Client
func New() *Client {
return &Client{}
}
// Close implements wginternal.Client.
func (c *Client) Close() error {
return nil
}
// Device implements wginternal.Client.
func (c *Client) Device(name string) (*wgtypes.Device, error) {
handle, err := c.interfaceHandle(name)
if err != nil {
return nil, err
}
defer windows.CloseHandle(handle)
size := c.lastLenGuess
if size == 0 {
size = 512
}
var buf []byte
for {
buf = make([]byte, size)
err = windows.DeviceIoControl(handle, ioctl.IoctlGet, nil, 0, &buf[0], size, &size, nil)
if err == windows.ERROR_MORE_DATA {
continue
}
if err != nil {
return nil, err
}
break
}
c.lastLenGuess = size
interfaze := (*ioctl.Interface)(unsafe.Pointer(&buf[0]))
device := wgtypes.Device{Type: wgtypes.WindowsKernel, Name: name}
if interfaze.Flags&ioctl.InterfaceHasPrivateKey != 0 {
device.PrivateKey = interfaze.PrivateKey
}
if interfaze.Flags&ioctl.InterfaceHasPublicKey != 0 {
device.PublicKey = interfaze.PublicKey
}
if interfaze.Flags&ioctl.InterfaceHasListenPort != 0 {
device.ListenPort = int(interfaze.ListenPort)
}
var p *ioctl.Peer
for i := uint32(0); i < interfaze.PeerCount; i++ {
if p == nil {
p = interfaze.FirstPeer()
} else {
p = p.NextPeer()
}
peer := wgtypes.Peer{}
if p.Flags&ioctl.PeerHasPublicKey != 0 {
peer.PublicKey = p.PublicKey
}
if p.Flags&ioctl.PeerHasPresharedKey != 0 {
peer.PresharedKey = p.PresharedKey
}
if p.Flags&ioctl.PeerHasEndpoint != 0 {
peer.Endpoint = &net.UDPAddr{IP: p.Endpoint.IP(), Port: int(p.Endpoint.Port())}
}
if p.Flags&ioctl.PeerHasPersistentKeepalive != 0 {
peer.PersistentKeepaliveInterval = time.Duration(p.PersistentKeepalive) * time.Second
}
if p.Flags&ioctl.PeerHasProtocolVersion != 0 {
peer.ProtocolVersion = int(p.ProtocolVersion)
}
peer.TransmitBytes = int64(p.TxBytes)
peer.ReceiveBytes = int64(p.RxBytes)
if p.LastHandshake != 0 {
peer.LastHandshakeTime = time.Unix(0, int64((p.LastHandshake-116444736000000000)*100))
}
var a *ioctl.AllowedIP
for j := uint32(0); j < p.AllowedIPsCount; j++ {
if a == nil {
a = p.FirstAllowedIP()
} else {
a = a.NextAllowedIP()
}
var ip net.IP
var bits int
if a.AddressFamily == windows.AF_INET {
ip = a.Address[:4]
bits = 32
} else if a.AddressFamily == windows.AF_INET6 {
ip = a.Address[:16]
bits = 128
}
peer.AllowedIPs = append(peer.AllowedIPs, net.IPNet{
IP: ip,
Mask: net.CIDRMask(int(a.Cidr), bits),
})
}
device.Peers = append(device.Peers, peer)
}
return &device, nil
}
// ConfigureDevice implements wginternal.Client.
func (c *Client) ConfigureDevice(name string, cfg wgtypes.Config) error {
handle, err := c.interfaceHandle(name)
if err != nil {
return err
}
defer windows.CloseHandle(handle)
preallocation := unsafe.Sizeof(ioctl.Interface{}) + uintptr(len(cfg.Peers))*unsafe.Sizeof(ioctl.Peer{})
for i := range cfg.Peers {
preallocation += uintptr(len(cfg.Peers[i].AllowedIPs)) * unsafe.Sizeof(ioctl.AllowedIP{})
}
var b ioctl.ConfigBuilder
b.Preallocate(uint32(preallocation))
interfaze := &ioctl.Interface{PeerCount: uint32(len(cfg.Peers))}
if cfg.ReplacePeers {
interfaze.Flags |= ioctl.InterfaceReplacePeers
}
if cfg.PrivateKey != nil {
interfaze.PrivateKey = *cfg.PrivateKey
interfaze.Flags |= ioctl.InterfaceHasPrivateKey
}
if cfg.ListenPort != nil {
interfaze.ListenPort = uint16(*cfg.ListenPort)
interfaze.Flags |= ioctl.InterfaceHasListenPort
}
b.AppendInterface(interfaze)
for i := range cfg.Peers {
peer := &ioctl.Peer{
Flags: ioctl.PeerHasPublicKey,
PublicKey: cfg.Peers[i].PublicKey,
AllowedIPsCount: uint32(len(cfg.Peers[i].AllowedIPs)),
}
if cfg.Peers[i].ReplaceAllowedIPs {
peer.Flags |= ioctl.PeerReplaceAllowedIPs
}
if cfg.Peers[i].UpdateOnly {
peer.Flags |= ioctl.PeerUpdateOnly
}
if cfg.Peers[i].Remove {
peer.Flags |= ioctl.PeerRemove
}
if cfg.Peers[i].PresharedKey != nil {
peer.Flags |= ioctl.PeerHasPresharedKey
peer.PresharedKey = *cfg.Peers[i].PresharedKey
}
if cfg.Peers[i].Endpoint != nil {
peer.Flags |= ioctl.PeerHasEndpoint
peer.Endpoint.SetIP(cfg.Peers[i].Endpoint.IP, uint16(cfg.Peers[i].Endpoint.Port))
}
if cfg.Peers[i].PersistentKeepaliveInterval != nil {
peer.Flags |= ioctl.PeerHasPersistentKeepalive
peer.PersistentKeepalive = uint16(*cfg.Peers[i].PersistentKeepaliveInterval / time.Second)
}
b.AppendPeer(peer)
for j := range cfg.Peers[i].AllowedIPs {
var family ioctl.AddressFamily
var ip net.IP
if ip = cfg.Peers[i].AllowedIPs[j].IP.To4(); ip != nil {
family = windows.AF_INET
} else if ip = cfg.Peers[i].AllowedIPs[j].IP.To16(); ip != nil {
family = windows.AF_INET6
} else {
ip = cfg.Peers[i].AllowedIPs[j].IP
}
cidr, _ := cfg.Peers[i].AllowedIPs[j].Mask.Size()
a := &ioctl.AllowedIP{
AddressFamily: family,
Cidr: uint8(cidr),
}
copy(a.Address[:], ip)
b.AppendAllowedIP(a)
}
}
interfaze, size := b.Interface()
return windows.DeviceIoControl(handle, ioctl.IoctlSet, nil, 0, (*byte)(unsafe.Pointer(interfaze)), size, &size, nil)
}

View File

@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
*/
package ioctl
import "unsafe"
const (
IoctlGet = 0xb098c506
IoctlSet = 0xb098c509
)
type AllowedIP struct {
Address [16]byte
AddressFamily AddressFamily
Cidr uint8
_ [4]byte
}
type PeerFlag uint32
const (
PeerHasPublicKey PeerFlag = 1 << 0
PeerHasPresharedKey PeerFlag = 1 << 1
PeerHasPersistentKeepalive PeerFlag = 1 << 2
PeerHasEndpoint PeerFlag = 1 << 3
PeerHasProtocolVersion PeerFlag = 1 << 4
PeerReplaceAllowedIPs PeerFlag = 1 << 5
PeerRemove PeerFlag = 1 << 6
PeerUpdateOnly PeerFlag = 1 << 7
)
type Peer struct {
Flags PeerFlag
ProtocolVersion uint32
PublicKey [32]byte
PresharedKey [32]byte
PersistentKeepalive uint16
_ uint16
Endpoint RawSockaddrInet
TxBytes uint64
RxBytes uint64
LastHandshake uint64
AllowedIPsCount uint32
_ [4]byte
}
type InterfaceFlag uint32
const (
InterfaceHasPublicKey InterfaceFlag = 1 << 0
InterfaceHasPrivateKey InterfaceFlag = 1 << 1
InterfaceHasListenPort InterfaceFlag = 1 << 2
InterfaceReplacePeers InterfaceFlag = 1 << 3
)
type Interface struct {
Flags InterfaceFlag
ListenPort uint16
PrivateKey [32]byte
PublicKey [32]byte
PeerCount uint32
_ [4]byte
}
func (interfaze *Interface) FirstPeer() *Peer {
return (*Peer)(unsafe.Pointer(uintptr(unsafe.Pointer(interfaze)) + unsafe.Sizeof(*interfaze)))
}
func (peer *Peer) NextPeer() *Peer {
return (*Peer)(unsafe.Pointer(uintptr(unsafe.Pointer(peer)) + unsafe.Sizeof(*peer) + uintptr(peer.AllowedIPsCount)*unsafe.Sizeof(AllowedIP{})))
}
func (peer *Peer) FirstAllowedIP() *AllowedIP {
return (*AllowedIP)(unsafe.Pointer(uintptr(unsafe.Pointer(peer)) + unsafe.Sizeof(*peer)))
}
func (allowedIP *AllowedIP) NextAllowedIP() *AllowedIP {
return (*AllowedIP)(unsafe.Pointer(uintptr(unsafe.Pointer(allowedIP)) + unsafe.Sizeof(*allowedIP)))
}
type ConfigBuilder struct {
buffer []byte
}
func (builder *ConfigBuilder) Preallocate(size uint32) {
if builder.buffer == nil {
builder.buffer = make([]byte, 0, size)
}
}
func (builder *ConfigBuilder) AppendInterface(interfaze *Interface) {
var newBytes []byte
unsafeSlice(unsafe.Pointer(&newBytes), unsafe.Pointer(interfaze), int(unsafe.Sizeof(*interfaze)))
builder.buffer = append(builder.buffer, newBytes...)
}
func (builder *ConfigBuilder) AppendPeer(peer *Peer) {
var newBytes []byte
unsafeSlice(unsafe.Pointer(&newBytes), unsafe.Pointer(peer), int(unsafe.Sizeof(*peer)))
builder.buffer = append(builder.buffer, newBytes...)
}
func (builder *ConfigBuilder) AppendAllowedIP(allowedIP *AllowedIP) {
var newBytes []byte
unsafeSlice(unsafe.Pointer(&newBytes), unsafe.Pointer(allowedIP), int(unsafe.Sizeof(*allowedIP)))
builder.buffer = append(builder.buffer, newBytes...)
}
func (builder *ConfigBuilder) Interface() (*Interface, uint32) {
if builder.buffer == nil {
return nil, 0
}
return (*Interface)(unsafe.Pointer(&builder.buffer[0])), uint32(len(builder.buffer))
}
// unsafeSlice updates the slice slicePtr to be a slice
// referencing the provided data with its length & capacity set to
// lenCap.
//
// TODO: whenGo 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
}

View File

@@ -0,0 +1,87 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
*/
package ioctl
import (
"encoding/binary"
"net"
"unsafe"
"golang.org/x/sys/windows"
)
// AddressFamily enumeration specifies protocol family and is one of the windows.AF_* constants.
type AddressFamily uint16
// RawSockaddrInet union contains an IPv4, an IPv6 address, or an address family.
// https://docs.microsoft.com/en-us/windows/desktop/api/ws2ipdef/ns-ws2ipdef-_sockaddr_inet
type RawSockaddrInet struct {
Family AddressFamily
data [26]byte
}
func ntohs(i uint16) uint16 {
return binary.BigEndian.Uint16((*[2]byte)(unsafe.Pointer(&i))[:])
}
func htons(i uint16) uint16 {
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, i)
return *(*uint16)(unsafe.Pointer(&b[0]))
}
// SetIP method sets family, address, and port to the given IPv4 or IPv6 address and port.
// All other members of the structure are set to zero.
func (addr *RawSockaddrInet) SetIP(ip net.IP, port uint16) error {
if v4 := ip.To4(); v4 != nil {
addr4 := (*windows.RawSockaddrInet4)(unsafe.Pointer(addr))
addr4.Family = windows.AF_INET
copy(addr4.Addr[:], v4)
addr4.Port = htons(port)
for i := 0; i < 8; i++ {
addr4.Zero[i] = 0
}
return nil
}
if v6 := ip.To16(); v6 != nil {
addr6 := (*windows.RawSockaddrInet6)(unsafe.Pointer(addr))
addr6.Family = windows.AF_INET6
addr6.Port = htons(port)
addr6.Flowinfo = 0
copy(addr6.Addr[:], v6)
addr6.Scope_id = 0
return nil
}
return windows.ERROR_INVALID_PARAMETER
}
// IP returns IPv4 or IPv6 address, or nil if the address is neither.
func (addr *RawSockaddrInet) IP() net.IP {
switch addr.Family {
case windows.AF_INET:
return (*windows.RawSockaddrInet4)(unsafe.Pointer(addr)).Addr[:]
case windows.AF_INET6:
return (*windows.RawSockaddrInet6)(unsafe.Pointer(addr)).Addr[:]
}
return nil
}
// Port returns the port if the address if IPv4 or IPv6, or 0 if neither.
func (addr *RawSockaddrInet) Port() uint16 {
switch addr.Family {
case windows.AF_INET:
return ntohs((*windows.RawSockaddrInet4)(unsafe.Pointer(addr)).Port)
case windows.AF_INET6:
return ntohs((*windows.RawSockaddrInet6)(unsafe.Pointer(addr)).Port)
}
return 0
}

36
vendor/golang.zx2c4.com/wireguard/wgctrl/os_linux.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
//go:build linux
// +build linux
package wgctrl
import (
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/internal/wglinux"
"golang.zx2c4.com/wireguard/wgctrl/internal/wguser"
)
// newClients configures wginternal.Clients for Linux systems.
func newClients() ([]wginternal.Client, error) {
var clients []wginternal.Client
// Linux has an in-kernel WireGuard implementation. Determine if it is
// available and make use of it if so.
kc, ok, err := wglinux.New()
if err != nil {
return nil, err
}
if ok {
clients = append(clients, kc)
}
// Although it isn't recommended to use userspace implementations on Linux,
// it can be used. We make use of it in integration tests as well.
uc, err := wguser.New()
if err != nil {
return nil, err
}
// Kernel devices seem to appear first in wg(8).
clients = append(clients, uc)
return clients, nil
}

33
vendor/golang.zx2c4.com/wireguard/wgctrl/os_openbsd.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
//go:build openbsd
// +build openbsd
package wgctrl
import (
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/internal/wgopenbsd"
"golang.zx2c4.com/wireguard/wgctrl/internal/wguser"
)
// newClients configures wginternal.Clients for OpenBSD systems.
func newClients() ([]wginternal.Client, error) {
var clients []wginternal.Client
// OpenBSD has an in-kernel WireGuard implementation. Determine if it is
// available and make use of it if so.
kc, ok, err := wgopenbsd.New()
if err != nil {
return nil, err
}
if ok {
clients = append(clients, kc)
}
uc, err := wguser.New()
if err != nil {
return nil, err
}
clients = append(clients, uc)
return clients, nil
}

View File

@@ -0,0 +1,20 @@
//go:build !linux && !openbsd && !windows
// +build !linux,!openbsd,!windows
package wgctrl
import (
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/internal/wguser"
)
// newClients configures wginternal.Clients for systems which only support
// userspace WireGuard implementations.
func newClients() ([]wginternal.Client, error) {
c, err := wguser.New()
if err != nil {
return nil, err
}
return []wginternal.Client{c}, nil
}

27
vendor/golang.zx2c4.com/wireguard/wgctrl/os_windows.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
//go:build windows
// +build windows
package wgctrl
import (
"golang.zx2c4.com/wireguard/wgctrl/internal/wginternal"
"golang.zx2c4.com/wireguard/wgctrl/internal/wguser"
"golang.zx2c4.com/wireguard/wgctrl/internal/wgwindows"
)
// newClients configures wginternal.Clients for Windows systems.
func newClients() ([]wginternal.Client, error) {
var clients []wginternal.Client
// Windows has an in-kernel WireGuard implementation.
kc := wgwindows.New()
clients = append(clients, kc)
uc, err := wguser.New()
if err != nil {
return nil, err
}
clients = append(clients, uc)
return clients, nil
}

View File

@@ -0,0 +1,2 @@
// Package wgtypes provides shared types for the wgctrl family of packages.
package wgtypes // import "golang.zx2c4.com/wireguard/wgctrl/wgtypes"

View File

@@ -0,0 +1,273 @@
package wgtypes
import (
"crypto/rand"
"encoding/base64"
"fmt"
"net"
"time"
"golang.org/x/crypto/curve25519"
)
// A DeviceType specifies the underlying implementation of a WireGuard device.
type DeviceType int
// Possible DeviceType values.
const (
Unknown DeviceType = iota
LinuxKernel
OpenBSDKernel
WindowsKernel
Userspace
)
// String returns the string representation of a DeviceType.
func (dt DeviceType) String() string {
switch dt {
case LinuxKernel:
return "Linux kernel"
case OpenBSDKernel:
return "OpenBSD kernel"
case WindowsKernel:
return "Windows kernel"
case Userspace:
return "userspace"
default:
return "unknown"
}
}
// A Device is a WireGuard device.
type Device struct {
// Name is the name of the device.
Name string
// Type specifies the underlying implementation of the device.
Type DeviceType
// PrivateKey is the device's private key.
PrivateKey Key
// PublicKey is the device's public key, computed from its PrivateKey.
PublicKey Key
// ListenPort is the device's network listening port.
ListenPort int
// FirewallMark is the device's current firewall mark.
//
// The firewall mark can be used in conjunction with firewall software to
// take action on outgoing WireGuard packets.
FirewallMark int
// Peers is the list of network peers associated with this device.
Peers []Peer
}
// KeyLen is the expected key length for a WireGuard key.
const KeyLen = 32 // wgh.KeyLen
// A Key is a public, private, or pre-shared secret key. The Key constructor
// functions in this package can be used to create Keys suitable for each of
// these applications.
type Key [KeyLen]byte
// GenerateKey generates a Key suitable for use as a pre-shared secret key from
// a cryptographically safe source.
//
// The output Key should not be used as a private key; use GeneratePrivateKey
// instead.
func GenerateKey() (Key, error) {
b := make([]byte, KeyLen)
if _, err := rand.Read(b); err != nil {
return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %v", err)
}
return NewKey(b)
}
// GeneratePrivateKey generates a Key suitable for use as a private key from a
// cryptographically safe source.
func GeneratePrivateKey() (Key, error) {
key, err := GenerateKey()
if err != nil {
return Key{}, err
}
// Modify random bytes using algorithm described at:
// https://cr.yp.to/ecdh.html.
key[0] &= 248
key[31] &= 127
key[31] |= 64
return key, nil
}
// NewKey creates a Key from an existing byte slice. The byte slice must be
// exactly 32 bytes in length.
func NewKey(b []byte) (Key, error) {
if len(b) != KeyLen {
return Key{}, fmt.Errorf("wgtypes: incorrect key size: %d", len(b))
}
var k Key
copy(k[:], b)
return k, nil
}
// ParseKey parses a Key from a base64-encoded string, as produced by the
// Key.String method.
func ParseKey(s string) (Key, error) {
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return Key{}, fmt.Errorf("wgtypes: failed to parse base64-encoded key: %v", err)
}
return NewKey(b)
}
// PublicKey computes a public key from the private key k.
//
// PublicKey should only be called when k is a private key.
func (k Key) PublicKey() Key {
var (
pub [KeyLen]byte
priv = [KeyLen]byte(k)
)
// ScalarBaseMult uses the correct base value per https://cr.yp.to/ecdh.html,
// so no need to specify it.
curve25519.ScalarBaseMult(&pub, &priv)
return Key(pub)
}
// String returns the base64-encoded string representation of a Key.
//
// ParseKey can be used to produce a new Key from this string.
func (k Key) String() string {
return base64.StdEncoding.EncodeToString(k[:])
}
// A Peer is a WireGuard peer to a Device.
type Peer struct {
// PublicKey is the public key of a peer, computed from its private key.
//
// PublicKey is always present in a Peer.
PublicKey Key
// PresharedKey is an optional preshared key which may be used as an
// additional layer of security for peer communications.
//
// A zero-value Key means no preshared key is configured.
PresharedKey Key
// Endpoint is the most recent source address used for communication by
// this Peer.
Endpoint *net.UDPAddr
// PersistentKeepaliveInterval specifies how often an "empty" packet is sent
// to a peer to keep a connection alive.
//
// A value of 0 indicates that persistent keepalives are disabled.
PersistentKeepaliveInterval time.Duration
// LastHandshakeTime indicates the most recent time a handshake was performed
// with this peer.
//
// A zero-value time.Time indicates that no handshake has taken place with
// this peer.
LastHandshakeTime time.Time
// ReceiveBytes indicates the number of bytes received from this peer.
ReceiveBytes int64
// TransmitBytes indicates the number of bytes transmitted to this peer.
TransmitBytes int64
// AllowedIPs specifies which IPv4 and IPv6 addresses this peer is allowed
// to communicate on.
//
// 0.0.0.0/0 indicates that all IPv4 addresses are allowed, and ::/0
// indicates that all IPv6 addresses are allowed.
AllowedIPs []net.IPNet
// ProtocolVersion specifies which version of the WireGuard protocol is used
// for this Peer.
//
// A value of 0 indicates that the most recent protocol version will be used.
ProtocolVersion int
}
// A Config is a WireGuard device configuration.
//
// Because the zero value of some Go types may be significant to WireGuard for
// Config fields, pointer types are used for some of these fields. Only
// pointer fields which are not nil will be applied when configuring a device.
type Config struct {
// PrivateKey specifies a private key configuration, if not nil.
//
// A non-nil, zero-value Key will clear the private key.
PrivateKey *Key
// ListenPort specifies a device's listening port, if not nil.
ListenPort *int
// FirewallMark specifies a device's firewall mark, if not nil.
//
// If non-nil and set to 0, the firewall mark will be cleared.
FirewallMark *int
// ReplacePeers specifies if the Peers in this configuration should replace
// the existing peer list, instead of appending them to the existing list.
ReplacePeers bool
// Peers specifies a list of peer configurations to apply to a device.
Peers []PeerConfig
}
// TODO(mdlayher): consider adding ProtocolVersion in PeerConfig.
// A PeerConfig is a WireGuard device peer configuration.
//
// Because the zero value of some Go types may be significant to WireGuard for
// PeerConfig fields, pointer types are used for some of these fields. Only
// pointer fields which are not nil will be applied when configuring a peer.
type PeerConfig struct {
// PublicKey specifies the public key of this peer. PublicKey is a
// mandatory field for all PeerConfigs.
PublicKey Key
// Remove specifies if the peer with this public key should be removed
// from a device's peer list.
Remove bool
// UpdateOnly specifies that an operation will only occur on this peer
// if the peer already exists as part of the interface.
UpdateOnly bool
// PresharedKey specifies a peer's preshared key configuration, if not nil.
//
// A non-nil, zero-value Key will clear the preshared key.
PresharedKey *Key
// Endpoint specifies the endpoint of this peer entry, if not nil.
Endpoint *net.UDPAddr
// PersistentKeepaliveInterval specifies the persistent keepalive interval
// for this peer, if not nil.
//
// A non-nil value of 0 will clear the persistent keepalive interval.
PersistentKeepaliveInterval *time.Duration
// ReplaceAllowedIPs specifies if the allowed IPs specified in this peer
// configuration should replace any existing ones, instead of appending them
// to the allowed IPs list.
ReplaceAllowedIPs bool
// AllowedIPs specifies a list of allowed IP addresses in CIDR notation
// for this peer.
AllowedIPs []net.IPNet
}