feat(wireguard): amneziawg implementation (#3150)

This commit is contained in:
Zhurik
2026-03-11 16:55:28 +03:00
committed by GitHub
parent f4eeffe79a
commit e6fc792f4f
20 changed files with 635 additions and 68 deletions
+58
View File
@@ -0,0 +1,58 @@
package wireguard
import (
"fmt"
"strings"
)
type AmneziaSettings struct {
JunkPacketCount uint16
JunkPacketMin uint16
JunkPacketMax uint16
PaddingS1 uint16
PaddingS2 uint16
PaddingS3 uint16
PaddingS4 uint16
HeaderH1 string
HeaderH2 string
HeaderH3 string
HeaderH4 string
InitPacketI1 string
InitPacketI2 string
InitPacketI3 string
InitPacketI4 string
InitPacketI5 string
}
func (s AmneziaSettings) uapiConfig() string {
uintFields := map[string]uint16{
"jc": s.JunkPacketCount,
"jmin": s.JunkPacketMin,
"jmax": s.JunkPacketMax,
"s1": s.PaddingS1,
"s2": s.PaddingS2,
"s3": s.PaddingS3,
"s4": s.PaddingS4,
}
stringFields := map[string]string{
"h1": s.HeaderH1,
"h2": s.HeaderH2,
"h3": s.HeaderH3,
"h4": s.HeaderH4,
"i1": s.InitPacketI1,
"i2": s.InitPacketI2,
"i3": s.InitPacketI3,
"i4": s.InitPacketI4,
"i5": s.InitPacketI5,
}
lines := make([]string, 0, len(uintFields)+len(stringFields))
for key, val := range uintFields {
lines = append(lines, fmt.Sprintf("%s=%d", key, val))
}
for key, val := range stringFields {
lines = append(lines, key+"="+val)
}
return strings.Join(lines, "\n")
}
+28
View File
@@ -0,0 +1,28 @@
package wireguard
import (
"net"
)
type tunDevice interface {
Close() error
Name() (string, error)
}
type bind interface {
Close() error
}
type userspaceDevice interface {
Close()
Wait() chan struct{}
IpcHandle(net.Conn)
IpcSet(string) error
}
type userSpaceBackend struct {
createTun func(string, int) (tunDevice, error)
createBind func() bind
createDevice func(tunDevice, bind, Logger) userspaceDevice
preStart func(userspaceDevice, Settings) error
}
-11
View File
@@ -1,9 +1,5 @@
package wireguard
import (
"golang.zx2c4.com/wireguard/device"
)
//go:generate mockgen -destination=log_mock_test.go -package wireguard . Logger
type Logger interface {
@@ -13,10 +9,3 @@ type Logger interface {
Error(s string)
Errorf(format string, args ...interface{})
}
func makeDeviceLogger(logger Logger) (deviceLogger *device.Logger) {
return &device.Logger{
Verbosef: logger.Debugf,
Errorf: logger.Errorf,
}
}
-23
View File
@@ -1,23 +0,0 @@
package wireguard
import (
"testing"
"github.com/golang/mock/gomock"
)
func Test_makeDeviceLogger(t *testing.T) {
t.Parallel()
ctrl := gomock.NewController(t)
logger := NewMockLogger(ctrl)
deviceLogger := makeDeviceLogger(logger)
logger.EXPECT().Debugf("test %d", 1)
deviceLogger.Verbosef("test %d", 1)
logger.EXPECT().Errorf("test %d", 2)
deviceLogger.Errorf("test %d", 2)
}
+22 -13
View File
@@ -7,9 +7,6 @@ import (
"net"
"github.com/qdm12/gluetun/internal/netlink"
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
"golang.zx2c4.com/wireguard/wgctrl"
)
@@ -29,6 +26,7 @@ var (
ErrRouteAdd = errors.New("cannot add route for interface")
ErrDeviceWaited = errors.New("device waited for")
ErrKernelSupport = errors.New("kernel does not support Wireguard")
ErrAmneziaConfigure = errors.New("cannot configure AmneziaWG")
)
// See https://git.zx2c4.com/wireguard-go/tree/main.go
@@ -39,7 +37,8 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan<
return
}
setupFunction := setupUserSpace
userspaceBackend := defaultUserSpaceBackend()
setupFunction := setupUserSpaceCommon
switch w.settings.Implementation {
case "auto": //nolint:goconst
if !kernelSupported {
@@ -55,6 +54,8 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan<
return
}
setupFunction = setupKernelSpace
case "amneziawg":
userspaceBackend = amneziaUserSpaceBackend()
default:
panic(fmt.Sprintf("unknown implementation %q", w.settings.Implementation))
}
@@ -71,7 +72,7 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan<
defer closers.cleanup(w.logger)
linkIndex, waitAndCleanup, err := setupFunction(ctx,
w.settings.InterfaceName, w.netlink, w.settings.MTU, &closers, w.logger)
w.settings.InterfaceName, w.netlink, w.settings.MTU, &closers, w.logger, w.settings, userspaceBackend)
if err != nil {
waitError <- err
return
@@ -136,7 +137,7 @@ type waitAndCleanupFunc func() error
func setupKernelSpace(ctx context.Context,
interfaceName string, netLinker NetLinker, mtu uint32,
closers *closers, logger Logger) (
closers *closers, logger Logger, _ Settings, _ userSpaceBackend) (
linkIndex uint32, waitAndCleanup waitAndCleanupFunc, err error,
) {
links, err := netLinker.LinkList()
@@ -178,12 +179,14 @@ func setupKernelSpace(ctx context.Context,
return linkIndex, waitAndCleanup, nil
}
func setupUserSpace(ctx context.Context,
func setupUserSpaceCommon(ctx context.Context,
interfaceName string, netLinker NetLinker, mtu uint32,
closers *closers, logger Logger) (
closers *closers, logger Logger,
settings Settings, b userSpaceBackend,
) (
linkIndex uint32, waitAndCleanup waitAndCleanupFunc, err error,
) {
tun, err := tun.CreateTUN(interfaceName, int(mtu))
tun, err := b.createTun(interfaceName, int(mtu))
if err != nil {
return 0, nil, fmt.Errorf("%w: %s", ErrCreateTun, err)
}
@@ -206,12 +209,11 @@ func setupUserSpace(ctx context.Context,
return netLinker.LinkDel(link.Index)
})
bind := conn.NewDefaultBind()
bind := b.createBind()
closers.add("closing bind", stepSeven, bind.Close)
deviceLogger := makeDeviceLogger(logger)
device := device.NewDevice(tun, bind, deviceLogger)
device := b.createDevice(tun, bind, logger)
closers.add("closing Wireguard device", stepSix, func() error {
device.Close()
@@ -232,6 +234,13 @@ func setupUserSpace(ctx context.Context,
closers.add("closing UAPI listener", stepTwo, uapiListener.Close)
if b.preStart != nil {
err = b.preStart(device, settings)
if err != nil {
return 0, nil, err
}
}
// acceptAndHandle exits when uapiListener is closed
uapiAcceptErrorCh := make(chan error)
go acceptAndHandle(uapiListener, device, uapiAcceptErrorCh)
@@ -255,7 +264,7 @@ func setupUserSpace(ctx context.Context,
return link.Index, waitAndCleanup, nil
}
func acceptAndHandle(uapi net.Listener, device *device.Device,
func acceptAndHandle(uapi net.Listener, device userspaceDevice,
uapiAcceptErrorCh chan<- error,
) {
for { // stopped by uapiFile.Close()
+5 -2
View File
@@ -46,8 +46,11 @@ type Settings struct {
// It defaults to false if left unset.
IPv6 *bool
// Implementation is the implementation to use.
// It can be auto, kernelspace or userspace, and defaults to auto.
// It can be auto, kernelspace, userspace or amneziawg,
// and defaults to auto.
Implementation string
// AmneziaWG settings are extra obfuscation parameters
AmneziaWG AmneziaSettings
}
func (s *Settings) SetDefaults() {
@@ -178,7 +181,7 @@ func (s *Settings) Check() (err error) {
}
switch s.Implementation {
case "auto", "kernelspace", "userspace":
case "auto", "kernelspace", "userspace", "amneziawg":
default:
return fmt.Errorf("%w: %s", ErrImplementationInvalid, s.Implementation)
}
+58
View File
@@ -0,0 +1,58 @@
package wireguard
import (
amneziaconn "github.com/amnezia-vpn/amneziawg-go/conn"
amneziadevice "github.com/amnezia-vpn/amneziawg-go/device"
amneziatun "github.com/amnezia-vpn/amneziawg-go/tun"
wgconn "golang.zx2c4.com/wireguard/conn"
wgdevice "golang.zx2c4.com/wireguard/device"
wgtun "golang.zx2c4.com/wireguard/tun"
)
func defaultUserSpaceBackend() userSpaceBackend {
return userSpaceBackend{
createTun: func(name string, mtu int) (tunDevice, error) {
return wgtun.CreateTUN(name, mtu)
},
createBind: func() bind {
return wgconn.NewDefaultBind()
},
createDevice: func(td tunDevice, b bind, logger Logger) userspaceDevice {
wgtun, _ := td.(wgtun.Device)
wgBind, _ := b.(wgconn.Bind)
wgLogger := wgdevice.Logger{
Verbosef: logger.Debugf,
Errorf: logger.Errorf,
}
device := wgdevice.NewDevice(wgtun, wgBind, &wgLogger)
return device
},
preStart: nil,
}
}
func amneziaUserSpaceBackend() userSpaceBackend {
return userSpaceBackend{
createTun: func(name string, mtu int) (tunDevice, error) {
return amneziatun.CreateTUN(name, mtu)
},
createBind: func() bind {
return amneziaconn.NewDefaultBind()
},
createDevice: func(td tunDevice, b bind, logger Logger) userspaceDevice {
wgamneziaTun, _ := td.(amneziatun.Device)
wgamneziaBind, _ := b.(amneziaconn.Bind)
wgamneziaLogger := amneziadevice.Logger{
Verbosef: logger.Debugf,
Errorf: logger.Errorf,
}
device := amneziadevice.NewDevice(wgamneziaTun, wgamneziaBind, &wgamneziaLogger)
return device
},
preStart: func(ud userspaceDevice, s Settings) error {
uapiConfig := s.AmneziaWG.uapiConfig()
err := ud.IpcSet(uapiConfig)
return err
},
}
}