feat(internal/wireguard): opportunistic kernelspace
- Auto detect if kernelspace implementation is available - Fallback to Go userspace implementation if kernel is not available
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/netlink"
|
||||
"golang.zx2c4.com/wireguard/conn"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/ipc"
|
||||
@@ -15,7 +16,9 @@ import (
|
||||
|
||||
var (
|
||||
ErrDetectIPv6 = errors.New("cannot detect IPv6 support")
|
||||
ErrDetectKernel = errors.New("cannot detect Kernel support")
|
||||
ErrCreateTun = errors.New("cannot create TUN device")
|
||||
ErrAddLink = errors.New("cannot add Wireguard link")
|
||||
ErrFindLink = errors.New("cannot find link")
|
||||
ErrFindDevice = errors.New("cannot find Wireguard device")
|
||||
ErrUAPISocketOpening = errors.New("cannot open UAPI socket")
|
||||
@@ -23,6 +26,7 @@ var (
|
||||
ErrUAPIListen = errors.New("cannot listen on UAPI socket")
|
||||
ErrAddAddress = errors.New("cannot add address to wireguard interface")
|
||||
ErrConfigure = errors.New("cannot configure wireguard interface")
|
||||
ErrDeviceInfo = errors.New("cannot get wireguard device information")
|
||||
ErrIfaceUp = errors.New("cannot set the interface to UP")
|
||||
ErrRouteAdd = errors.New("cannot add route for interface")
|
||||
ErrRuleAdd = errors.New("cannot add rule for interface")
|
||||
@@ -41,6 +45,12 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan<
|
||||
return
|
||||
}
|
||||
|
||||
doKernel, err := w.netlink.IsWireguardSupported()
|
||||
if err != nil {
|
||||
waitError <- fmt.Errorf("%w: %s", ErrDetectKernel, err)
|
||||
return
|
||||
}
|
||||
|
||||
client, err := wgctrl.New()
|
||||
if err != nil {
|
||||
waitError <- fmt.Errorf("%w: %s", ErrWgctrlOpen, err)
|
||||
@@ -52,62 +62,21 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan<
|
||||
|
||||
defer closers.cleanup(w.logger)
|
||||
|
||||
tun, err := tun.CreateTUN(w.settings.InterfaceName, device.DefaultMTU)
|
||||
if err != nil {
|
||||
waitError <- fmt.Errorf("%w: %s", ErrCreateTun, err)
|
||||
return
|
||||
setupFunction := setupUserSpace
|
||||
if doKernel {
|
||||
w.logger.Info("Using available kernelspace implementation")
|
||||
setupFunction = setupKernelSpace
|
||||
} else {
|
||||
w.logger.Info("Using userspace implementation since Kernel support does not exist")
|
||||
}
|
||||
|
||||
closers.add("closing TUN device", stepSeven, tun.Close)
|
||||
|
||||
tunName, err := tun.Name()
|
||||
link, waitAndCleanup, err := setupFunction(ctx,
|
||||
w.settings.InterfaceName, w.netlink, &closers, w.logger)
|
||||
if err != nil {
|
||||
waitError <- fmt.Errorf("%w: cannot get TUN name: %s", ErrCreateTun, err)
|
||||
return
|
||||
} else if tunName != w.settings.InterfaceName {
|
||||
waitError <- fmt.Errorf("%w: names don't match: expected %q and got %q",
|
||||
ErrCreateTun, w.settings.InterfaceName, tunName)
|
||||
waitError <- err
|
||||
return
|
||||
}
|
||||
|
||||
link, err := w.netlink.LinkByName(w.settings.InterfaceName)
|
||||
if err != nil {
|
||||
waitError <- fmt.Errorf("%w: %s: %s", ErrFindLink, w.settings.InterfaceName, err)
|
||||
return
|
||||
}
|
||||
|
||||
bind := conn.NewDefaultBind()
|
||||
|
||||
closers.add("closing bind", stepSeven, bind.Close)
|
||||
|
||||
deviceLogger := makeDeviceLogger(w.logger)
|
||||
device := device.NewDevice(tun, bind, deviceLogger)
|
||||
|
||||
closers.add("closing Wireguard device", stepSix, func() error {
|
||||
device.Close()
|
||||
return nil
|
||||
})
|
||||
|
||||
uapiFile, err := ipc.UAPIOpen(w.settings.InterfaceName)
|
||||
if err != nil {
|
||||
waitError <- fmt.Errorf("%w: %s", ErrUAPISocketOpening, err)
|
||||
return
|
||||
}
|
||||
|
||||
closers.add("closing UAPI file", stepThree, uapiFile.Close)
|
||||
|
||||
uapiListener, err := ipc.UAPIListen(w.settings.InterfaceName, uapiFile)
|
||||
if err != nil {
|
||||
waitError <- fmt.Errorf("%w: %s", ErrUAPIListen, err)
|
||||
return
|
||||
}
|
||||
|
||||
closers.add("closing UAPI listener", stepTwo, uapiListener.Close)
|
||||
|
||||
// acceptAndHandle exits when uapiListener is closed
|
||||
uapiAcceptErrorCh := make(chan error)
|
||||
go acceptAndHandle(uapiListener, device, uapiAcceptErrorCh)
|
||||
|
||||
err = w.addAddresses(link, w.settings.Addresses)
|
||||
if err != nil {
|
||||
waitError <- fmt.Errorf("%w: %s", ErrAddAddress, err)
|
||||
@@ -128,9 +97,6 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan<
|
||||
closers.add("shutting down link", stepFour, func() error {
|
||||
return w.netlink.LinkSetDown(link)
|
||||
})
|
||||
closers.add("deleting link", stepFive, func() error {
|
||||
return w.netlink.LinkDel(link)
|
||||
})
|
||||
|
||||
err = w.addRoute(link, allIPv4(), w.settings.FirewallMark)
|
||||
if err != nil {
|
||||
@@ -158,20 +124,113 @@ func (w *Wireguard) Run(ctx context.Context, waitError chan<- error, ready chan<
|
||||
w.logger.Info("Wireguard is up")
|
||||
ready <- struct{}{}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
case err = <-uapiAcceptErrorCh:
|
||||
close(uapiAcceptErrorCh)
|
||||
case <-device.Wait():
|
||||
err = ErrDeviceWaited
|
||||
waitError <- waitAndCleanup()
|
||||
}
|
||||
|
||||
type waitAndCleanupFunc func() error
|
||||
|
||||
func setupKernelSpace(ctx context.Context,
|
||||
interfaceName string, netLinker NetLinker,
|
||||
closers *closers, logger Logger) (
|
||||
link netlink.Link, waitAndCleanup waitAndCleanupFunc, err error) {
|
||||
linkAttrs := netlink.LinkAttrs{
|
||||
Name: interfaceName,
|
||||
MTU: device.DefaultMTU, // TODO
|
||||
}
|
||||
link = &netlink.Wireguard{
|
||||
LinkAttrs: linkAttrs,
|
||||
}
|
||||
err = netLinker.LinkAdd(link)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %s", ErrAddLink, err)
|
||||
}
|
||||
closers.add("deleting link", stepFive, func() error {
|
||||
return netLinker.LinkDel(link)
|
||||
})
|
||||
|
||||
waitAndCleanup = func() error {
|
||||
<-ctx.Done()
|
||||
closers.cleanup(logger)
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
closers.cleanup(w.logger)
|
||||
return link, waitAndCleanup, nil
|
||||
}
|
||||
|
||||
<-uapiAcceptErrorCh // wait for acceptAndHandle to exit
|
||||
func setupUserSpace(ctx context.Context,
|
||||
interfaceName string, netLinker NetLinker,
|
||||
closers *closers, logger Logger) (
|
||||
link netlink.Link, waitAndCleanup waitAndCleanupFunc, err error) {
|
||||
tun, err := tun.CreateTUN(interfaceName, device.DefaultMTU)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %s", ErrCreateTun, err)
|
||||
}
|
||||
|
||||
waitError <- err
|
||||
closers.add("closing TUN device", stepSeven, tun.Close)
|
||||
|
||||
tunName, err := tun.Name()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: cannot get TUN name: %s", ErrCreateTun, err)
|
||||
} else if tunName != interfaceName {
|
||||
return nil, nil, fmt.Errorf("%w: names don't match: expected %q and got %q",
|
||||
ErrCreateTun, interfaceName, tunName)
|
||||
}
|
||||
|
||||
link, err = netLinker.LinkByName(interfaceName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %s: %s", ErrFindLink, interfaceName, err)
|
||||
}
|
||||
closers.add("deleting link", stepFive, func() error {
|
||||
return netLinker.LinkDel(link)
|
||||
})
|
||||
|
||||
bind := conn.NewDefaultBind()
|
||||
|
||||
closers.add("closing bind", stepSeven, bind.Close)
|
||||
|
||||
deviceLogger := makeDeviceLogger(logger)
|
||||
device := device.NewDevice(tun, bind, deviceLogger)
|
||||
|
||||
closers.add("closing Wireguard device", stepSix, func() error {
|
||||
device.Close()
|
||||
return nil
|
||||
})
|
||||
|
||||
uapiFile, err := ipc.UAPIOpen(interfaceName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %s", ErrUAPISocketOpening, err)
|
||||
}
|
||||
|
||||
closers.add("closing UAPI file", stepThree, uapiFile.Close)
|
||||
|
||||
uapiListener, err := ipc.UAPIListen(interfaceName, uapiFile)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %s", ErrUAPIListen, err)
|
||||
}
|
||||
|
||||
closers.add("closing UAPI listener", stepTwo, uapiListener.Close)
|
||||
|
||||
// acceptAndHandle exits when uapiListener is closed
|
||||
uapiAcceptErrorCh := make(chan error)
|
||||
go acceptAndHandle(uapiListener, device, uapiAcceptErrorCh)
|
||||
waitAndCleanup = func() error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
case err = <-uapiAcceptErrorCh:
|
||||
close(uapiAcceptErrorCh)
|
||||
case <-device.Wait():
|
||||
err = ErrDeviceWaited
|
||||
}
|
||||
|
||||
closers.cleanup(logger)
|
||||
|
||||
<-uapiAcceptErrorCh // wait for acceptAndHandle to exit
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return link, waitAndCleanup, nil
|
||||
}
|
||||
|
||||
func acceptAndHandle(uapi net.Listener, device *device.Device,
|
||||
|
||||
Reference in New Issue
Block a user