Compare commits

..

8 Commits

Author SHA1 Message Date
Zoltán Papp
6efc1a61fe Init static info in proper place 2025-09-04 16:44:58 +02:00
Zoltán Papp
4a45e40578 Cache os related information 2025-09-04 15:50:25 +02:00
Zoltán Papp
85ad236f6d Merge branch 'fix/missed-offers' into merged-fixes 2025-09-04 13:54:30 +02:00
Zoltán Papp
e54ab4b5e1 Merge branch 'debug-slow-conn' into merged-fixes 2025-09-04 13:54:13 +02:00
Zoltán Papp
23977c6409 Add debug ticker 2025-09-04 13:53:22 +02:00
Zoltán Papp
a73cb99045 Add async WireGuard handshake 2025-09-04 13:43:10 +02:00
Zoltán Papp
ccf40fefaf Fix proxy tests 2025-09-04 13:15:38 +02:00
Zoltán Papp
b2548a4037 Add verbose sys info gathering information 2025-09-04 12:11:57 +02:00
4 changed files with 210 additions and 51 deletions

View File

@@ -118,6 +118,8 @@ type Conn struct {
// debug purpose
dumpState *stateDump
endpointUpdater *endpointUpdater
}
// NewConn creates a new not opened Conn to the remote peer.
@@ -141,6 +143,11 @@ func NewConn(config ConnConfig, services ServiceDependencies) (*Conn, error) {
statusRelay: worker.NewAtomicStatus(),
statusICE: worker.NewAtomicStatus(),
dumpState: newStateDump(config.Key, connLog, services.StatusRecorder),
endpointUpdater: &endpointUpdater{
log: connLog,
wgConfig: config.WgConfig,
initiator: isWireGuardInitiator(config),
},
}
return conn, nil
@@ -250,7 +257,7 @@ func (conn *Conn) Close(signalToRemote bool) {
conn.wgProxyICE = nil
}
if err := conn.removeWgPeer(); err != nil {
if err := conn.endpointUpdater.removeWgPeer(); err != nil {
conn.Log.Errorf("failed to remove wg endpoint: %v", err)
}
@@ -377,13 +384,12 @@ func (conn *Conn) onICEConnectionIsReady(priority conntype.ConnPriority, iceConn
}
conn.Log.Infof("configure WireGuard endpoint to: %s", ep.String())
if err = conn.configureWGEndpoint(ep, iceConnInfo.RosenpassPubKey); err != nil {
if err = conn.endpointUpdater.configureWGEndpoint(ep, iceConnInfo.RosenpassPubKey); err != nil {
conn.handleConfigurationFailure(err, wgProxy)
return
}
wgConfigWorkaround()
if conn.wgProxyRelay != nil {
conn.Log.Debugf("redirect packages from relayed conn to WireGuard")
conn.wgProxyRelay.RedirectAs(ep)
@@ -417,7 +423,7 @@ func (conn *Conn) onICEStateDisconnected() {
conn.dumpState.SwitchToRelay()
conn.wgProxyRelay.Work()
if err := conn.configureWGEndpoint(conn.wgProxyRelay.EndpointAddr(), conn.rosenpassRemoteKey); err != nil {
if err := conn.endpointUpdater.configureWGEndpoint(conn.wgProxyRelay.EndpointAddr(), conn.rosenpassRemoteKey); err != nil {
conn.Log.Errorf("failed to switch to relay conn: %v", err)
}
@@ -486,7 +492,7 @@ func (conn *Conn) onRelayConnectionIsReady(rci RelayConnInfo) {
}
wgProxy.Work()
if err := conn.configureWGEndpoint(wgProxy.EndpointAddr(), rci.rosenpassPubKey); err != nil {
if err := conn.endpointUpdater.configureWGEndpoint(wgProxy.EndpointAddr(), rci.rosenpassPubKey); err != nil {
if err := wgProxy.CloseConn(); err != nil {
conn.Log.Warnf("Failed to close relay connection: %v", err)
}
@@ -554,17 +560,6 @@ func (conn *Conn) onGuardEvent() {
}
}
func (conn *Conn) configureWGEndpoint(addr *net.UDPAddr, remoteRPKey []byte) error {
presharedKey := conn.presharedKey(remoteRPKey)
return conn.config.WgConfig.WgInterface.UpdatePeer(
conn.config.WgConfig.RemoteKey,
conn.config.WgConfig.AllowedIps,
defaultWgKeepAlive,
addr,
presharedKey,
)
}
func (conn *Conn) updateRelayStatus(relayServerAddr string, rosenpassPubKey []byte) {
peerState := State{
PubKey: conn.config.Key,
@@ -707,10 +702,6 @@ func (conn *Conn) isICEActive() bool {
return (conn.currentConnPriority == conntype.ICEP2P || conn.currentConnPriority == conntype.ICETurn) && conn.statusICE.Get() == worker.StatusConnected
}
func (conn *Conn) removeWgPeer() error {
return conn.config.WgConfig.WgInterface.RemovePeer(conn.config.WgConfig.RemoteKey)
}
func (conn *Conn) handleConfigurationFailure(err error, wgProxy wgproxy.Proxy) {
conn.Log.Warnf("Failed to update wg peer configuration: %v", err)
if wgProxy != nil {
@@ -791,6 +782,10 @@ func isController(config ConnConfig) bool {
return config.LocalKey > config.Key
}
func isWireGuardInitiator(config ConnConfig) bool {
return isController(config)
}
func isRosenpassEnabled(remoteRosenpassPubKey []byte) bool {
return remoteRosenpassPubKey != nil
}

View File

@@ -0,0 +1,88 @@
package peer
import (
"context"
"net"
"sync"
"time"
"github.com/sirupsen/logrus"
)
// fallbackDelay could be const but because of testing it is a var
var fallbackDelay = 5 * time.Second
type endpointUpdater struct {
log *logrus.Entry
wgConfig WgConfig
initiator bool
cancelFunc func()
configUpdateMutex sync.Mutex
}
// configureWGEndpoint sets up the WireGuard endpoint configuration.
// The initiator immediately configures the endpoint, while the non-initiator
// waits for a fallback period before configuring to avoid handshake congestion.
func (e *endpointUpdater) configureWGEndpoint(addr *net.UDPAddr, remoteRPKey []byte) error {
if e.initiator {
return e.updateWireGuardPeer(addr, remoteRPKey)
}
// prevent to run new update while cancel the previous update
e.configUpdateMutex.Lock()
if e.cancelFunc != nil {
e.cancelFunc()
}
e.configUpdateMutex.Unlock()
var ctx context.Context
ctx, e.cancelFunc = context.WithCancel(context.Background())
go e.scheduleDelayedUpdate(ctx, addr, remoteRPKey)
return e.updateWireGuardPeer(nil, remoteRPKey)
}
func (e *endpointUpdater) removeWgPeer() error {
e.configUpdateMutex.Lock()
defer e.configUpdateMutex.Unlock()
if e.cancelFunc != nil {
e.cancelFunc()
}
return e.wgConfig.WgInterface.RemovePeer(e.wgConfig.RemoteKey)
}
// scheduleDelayedUpdate waits for the fallback period before updating the endpoint
func (e *endpointUpdater) scheduleDelayedUpdate(ctx context.Context, addr *net.UDPAddr, remoteRPKey []byte) {
t := time.NewTimer(fallbackDelay)
defer t.Stop()
select {
case <-ctx.Done():
return
case <-t.C:
e.configUpdateMutex.Lock()
defer e.configUpdateMutex.Unlock()
if ctx.Err() != nil {
return
}
if err := e.updateWireGuardPeer(addr, remoteRPKey); err != nil {
e.log.Errorf("failed to update WireGuard peer, address: %s, error: %v", addr, err)
}
}
}
func (e *endpointUpdater) updateWireGuardPeer(endpoint *net.UDPAddr, remoteRPKey []byte) error {
// todo add, "presharedKey := e.presharedKey(remote)"
return e.wgConfig.WgInterface.UpdatePeer(
e.wgConfig.RemoteKey,
e.wgConfig.AllowedIps,
defaultWgKeepAlive,
endpoint,
e.wgConfig.PreSharedKey,
)
}

View File

@@ -33,6 +33,7 @@ type WGWatcher struct {
ctx context.Context
ctxCancel context.CancelFunc
ctxLock sync.Mutex
enabled time.Time
}
func NewWGWatcher(log *log.Entry, wgIfaceStater WGInterfaceStater, peerKey string, stateDump *stateDump) *WGWatcher {
@@ -48,6 +49,7 @@ func NewWGWatcher(log *log.Entry, wgIfaceStater WGInterfaceStater, peerKey strin
func (w *WGWatcher) EnableWgWatcher(parentCtx context.Context, onDisconnectedFn func()) {
w.log.Debugf("enable WireGuard watcher")
w.ctxLock.Lock()
w.enabled = time.Now()
if w.ctx != nil && w.ctx.Err() == nil {
w.log.Errorf("WireGuard watcher already enabled")
@@ -87,6 +89,8 @@ func (w *WGWatcher) DisableWgWatcher() {
func (w *WGWatcher) periodicHandshakeCheck(ctx context.Context, ctxCancel context.CancelFunc, onDisconnectedFn func(), initialHandshake time.Time) {
w.log.Infof("WireGuard watcher started")
debugTicker := time.NewTicker(time.Second)
timer := time.NewTimer(wgHandshakeOvertime)
defer timer.Stop()
defer ctxCancel()
@@ -95,15 +99,28 @@ func (w *WGWatcher) periodicHandshakeCheck(ctx context.Context, ctxCancel contex
for {
select {
case <-debugTicker.C:
handshake, err := w.wgState()
if err != nil {
w.log.Errorf("failed to read wg stats: %v", err)
continue
}
if !handshake.IsZero() {
w.log.Infof("first wg handshake detected at: %s, %s", handshake, time.Since(w.enabled))
debugTicker.Stop()
}
case <-timer.C:
handshake, ok := w.handshakeCheck(lastHandshake)
if !ok {
onDisconnectedFn()
return
}
if lastHandshake.IsZero() {
w.log.Infof("first wg handshake detected at: %s", handshake)
}
/*
// todo: put it back if remove debug ticker
if lastHandshake.IsZero() {
w.log.Infof("first wg handshake detected at: %s", handshake)
}
*/
lastHandshake = *handshake

View File

@@ -6,6 +6,7 @@ import (
"os"
"runtime"
"strings"
"sync"
"time"
log "github.com/sirupsen/logrus"
@@ -31,42 +32,100 @@ type Win32_BIOS struct {
SerialNumber string
}
// GetInfo retrieves and parses the system information
func GetInfo(ctx context.Context) *Info {
osName, osVersion := getOSNameAndVersion()
buildVersion := getBuildVersion()
// CachedStaticInfo holds all the static system information that never changes
type CachedStaticInfo struct {
OSName string
OSVersion string
KernelVersion string
SystemSerialNumber string
SystemProductName string
SystemManufacturer string
Environment Environment // Assuming this is from your StaticInfo struct
GoOS string
CPUs int
Kernel string
Platform string
}
var (
cachedStaticInfo *CachedStaticInfo
staticInfoOnce sync.Once
)
func init() {
go initStaticInfo()
}
// initStaticInfo initializes all static system information once
func initStaticInfo() {
staticInfoOnce.Do(func() {
log.Debugf("initializing static system information (one-time operation)")
start := time.Now()
// Get OS info
osName, osVersion := getOSNameAndVersion()
buildVersion := getBuildVersion()
// Get hardware info
si := updateStaticInfo()
cachedStaticInfo = &CachedStaticInfo{
OSName: osName,
OSVersion: osVersion,
KernelVersion: buildVersion,
SystemSerialNumber: si.SystemSerialNumber,
SystemProductName: si.SystemProductName,
SystemManufacturer: si.SystemManufacturer,
Environment: si.Environment,
GoOS: runtime.GOOS,
CPUs: runtime.NumCPU(),
Kernel: "windows",
Platform: "unknown",
}
log.Debugf("static system information initialized in %s", time.Since(start))
})
}
// GetInfo retrieves system information (static info cached, dynamic info fresh)
func GetInfo(ctx context.Context) *Info {
initStaticInfo()
log.Debugf("gathering dynamic system information")
start := time.Now()
// Only gather dynamic information that might change
log.Debugf("gathering networkAddresses")
addrs, err := networkAddresses()
if err != nil {
log.Warnf("failed to discover network addresses: %s", err)
}
start := time.Now()
si := updateStaticInfo()
if time.Since(start) > 1*time.Second {
log.Warnf("updateStaticInfo took %s", time.Since(start))
}
gio := &Info{
Kernel: "windows",
OSVersion: osVersion,
Platform: "unknown",
OS: osName,
GoOS: runtime.GOOS,
CPUs: runtime.NumCPU(),
KernelVersion: buildVersion,
NetworkAddresses: addrs,
SystemSerialNumber: si.SystemSerialNumber,
SystemProductName: si.SystemProductName,
SystemManufacturer: si.SystemManufacturer,
Environment: si.Environment,
}
log.Debugf("gathering Hostname")
systemHostname, _ := os.Hostname()
gio.Hostname = extractDeviceName(ctx, systemHostname)
gio.NetbirdVersion = version.NetbirdVersion()
gio.UIVersion = extractUserAgent(ctx)
// Create Info struct using cached static info + fresh dynamic info
gio := &Info{
// Static information (cached)
Kernel: cachedStaticInfo.Kernel,
OSVersion: cachedStaticInfo.OSVersion,
Platform: cachedStaticInfo.Platform,
OS: cachedStaticInfo.OSName,
GoOS: cachedStaticInfo.GoOS,
CPUs: cachedStaticInfo.CPUs,
KernelVersion: cachedStaticInfo.KernelVersion,
SystemSerialNumber: cachedStaticInfo.SystemSerialNumber,
SystemProductName: cachedStaticInfo.SystemProductName,
SystemManufacturer: cachedStaticInfo.SystemManufacturer,
Environment: cachedStaticInfo.Environment,
// Dynamic information (fresh each call)
NetworkAddresses: addrs,
Hostname: extractDeviceName(ctx, systemHostname),
NetbirdVersion: version.NetbirdVersion(), // This might change with updates
UIVersion: extractUserAgent(ctx), // This might change
}
log.Debugf("dynamic system information gathered in %s", time.Since(start))
return gio
}