Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions functions/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,12 +184,23 @@ func startGoRoutines(wg *sync.WaitGroup) context.CancelFunc {
}

if !netclientCfg.IsStaticPort {
if freeport, err := ncutils.GetFreePort(ncutils.NetclientDefaultPort, netclientCfg.ListenPort, false); err != nil {
slog.Warn("no free ports available for use by netclient", "error", err.Error())
} else if freeport != netclientCfg.ListenPort {
slog.Info("port has changed", "old port", netclientCfg.ListenPort, "new port", freeport)
netclientCfg.ListenPort = freeport
updateConfig = true
// Check if the WireGuard adapter already owns the desired port before
// doing a UDP bind test. On Windows the adapter from a previous run may
// still be alive, and its port looks "busy" to net.ListenUDP even though
// it belongs to us.
devicePort, devErr := wireguard.GetDeviceListenPort()
portOwnedByDevice := devErr == nil && devicePort == netclientCfg.ListenPort && devicePort != 0

if !portOwnedByDevice {
if freeport, err := ncutils.GetFreePort(ncutils.NetclientDefaultPort, netclientCfg.ListenPort, false); err != nil {
slog.Warn("no free ports available for use by netclient", "error", err.Error())
} else if freeport != netclientCfg.ListenPort {
slog.Info("port has changed", "old port", netclientCfg.ListenPort, "new port", freeport)
netclientCfg.ListenPort = freeport
updateConfig = true
}
} else {
slog.Info("listen port already held by wireguard adapter, keeping it", "port", devicePort)
}

if netclientCfg.WgPublicListenPort == 0 {
Expand Down
4 changes: 4 additions & 0 deletions functions/mqhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net"
"net/http"
"reflect"
"runtime"
"slices"
"strconv"
"strings"
Expand Down Expand Up @@ -572,6 +573,9 @@ func resetInterfaceFunc() {
defer mNMutex.Unlock()
nc := wireguard.GetInterface()
nc.Close()
if runtime.GOOS == "windows" {
time.Sleep(500 * time.Millisecond)
}
nc = wireguard.NewNCIface(config.Netclient(), config.GetNodes())
nc.Create()
if err := nc.Configure(); err != nil {
Expand Down
4 changes: 4 additions & 0 deletions functions/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"runtime"
"time"

"github.com/gravitl/netclient/auth"
"github.com/gravitl/netclient/config"
Expand Down Expand Up @@ -75,6 +76,9 @@ func resetInterfaceUninstall(faults []error) []error {
defer mNMutex.Unlock()
nc := wireguard.GetInterface()
nc.Close()
if runtime.GOOS == "windows" {
time.Sleep(500 * time.Millisecond)
}
nc = wireguard.NewNCIface(config.Netclient(), config.GetNodes())
nc.Create()
if err := nc.Configure(); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions wireguard/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ func GetInterface() *NCIface {

// NCIface.UpdatePeer - Updates Peers from provided PeerConfig
func (n *NCIface) UpdatePeer(p wgtypes.PeerConfig) {
wgMutex.Lock()
defer wgMutex.Unlock()
peers := []wgtypes.PeerConfig{}
peers = append(peers, p)
n.Config.ReplacePeers = false
Expand Down
70 changes: 65 additions & 5 deletions wireguard/wireguard.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"errors"
"fmt"
"net"
"runtime"
"strings"
"time"

"github.com/gravitl/netclient/cache"
"github.com/gravitl/netclient/config"
Expand Down Expand Up @@ -97,13 +100,49 @@ func UpdatePeer(p *wgtypes.PeerConfig) error {

func apply(c *wgtypes.Config) error {
slog.Debug("applying wireguard config")
wg, err := wgctrl.New()
if err != nil {
return fmt.Errorf("wgctrl %w", err)
ifaceName := ncutils.GetInterfaceName()

const maxRetries = 3
var lastErr error
for attempt := 0; attempt <= maxRetries; attempt++ {
wg, err := wgctrl.New()
if err != nil {
if runtime.GOOS == "windows" && isDeviceBusyError(err) {
lastErr = err
if attempt < maxRetries {
slog.Warn("wgctrl busy, retrying", "attempt", attempt+1, "error", err)
time.Sleep(time.Duration(500*(attempt+1)) * time.Millisecond)
}
continue
}
return fmt.Errorf("wgctrl %w", err)
}
err = wg.ConfigureDevice(ifaceName, *c)
wg.Close()
if err == nil {
return nil
}
if runtime.GOOS == "windows" && isDeviceBusyError(err) {
lastErr = err
if attempt < maxRetries {
slog.Warn("ConfigureDevice busy, retrying", "attempt", attempt+1, "error", err)
time.Sleep(time.Duration(500*(attempt+1)) * time.Millisecond)
}
continue
}
return err
}
defer wg.Close()
return fmt.Errorf("ConfigureDevice failed after %d retries: %w", maxRetries, lastErr)
}

return wg.ConfigureDevice(ncutils.GetInterfaceName(), *c)
func isDeviceBusyError(err error) bool {
if err == nil {
return false
}
msg := err.Error()
return strings.Contains(msg, "being used by another process") ||
strings.Contains(msg, "access is denied") ||
strings.Contains(msg, "The process cannot access the file")
}

// returns if better endpoint has been calculated for this peer already
Expand Down Expand Up @@ -140,6 +179,8 @@ func EndpointDetectedAlready(peerPubKey string) bool {
}

func GetPeersFromDevice(ifaceName string) (map[string]wgtypes.Peer, error) {
wgMutex.Lock()
defer wgMutex.Unlock()
peerMap := make(map[string]wgtypes.Peer)
wg, err := wgctrl.New()
if err != nil {
Expand All @@ -163,6 +204,8 @@ func GetPeersFromDevice(ifaceName string) (map[string]wgtypes.Peer, error) {

// GetPeer - gets the peerinfo from the wg interface
func GetPeer(ifaceName, peerPubKey string) (wgtypes.Peer, error) {
wgMutex.Lock()
defer wgMutex.Unlock()
wg, err := wgctrl.New()
if err != nil {
return wgtypes.Peer{}, err
Expand All @@ -185,6 +228,23 @@ func GetPeer(ifaceName, peerPubKey string) (wgtypes.Peer, error) {
return wgtypes.Peer{}, fmt.Errorf("peer not found")
}

// GetDeviceListenPort returns the current listen port from the WireGuard device.
// Returns 0 and an error if the device cannot be queried.
func GetDeviceListenPort() (int, error) {
wgMutex.Lock()
defer wgMutex.Unlock()
wg, err := wgctrl.New()
if err != nil {
return 0, err
}
defer wg.Close()
dev, err := wg.Device(ncutils.GetInterfaceName())
if err != nil {
return 0, err
}
return dev.ListenPort, nil
}

// GetOriginalDefaulGw - fetches system's original default gw
func GetOriginalDefaulGw() (gwIP net.IP, err error) {
gwIP = config.Netclient().OriginalDefaultGatewayIp
Expand Down