diff --git a/common.go b/common.go index 1f8501d..d0bea54 100644 --- a/common.go +++ b/common.go @@ -14,8 +14,8 @@ import ( "time" "github.com/fosrl/newt/logger" + "github.com/fosrl/newt/websocket" "github.com/fosrl/olm/peermonitor" - "github.com/fosrl/olm/websocket" "github.com/vishvananda/netlink" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/curve25519" @@ -596,9 +596,56 @@ func configureWindows(interfaceName string, ip net.IP, ipNet *net.IPNet) error { return fmt.Errorf("netsh enable interface command failed: %v, output: %s", err, out) } + // delay 2 seconds + time.Sleep(8 * time.Second) + + // Wait for the interface to be up and have the correct IP + err = waitForInterfaceUp(interfaceName, ip, 30*time.Second) + if err != nil { + return fmt.Errorf("interface did not come up within timeout: %v", err) + } + return nil } +// waitForInterfaceUp polls the network interface until it's up or times out +func waitForInterfaceUp(interfaceName string, expectedIP net.IP, timeout time.Duration) error { + logger.Info("Waiting for interface %s to be up with IP %s", interfaceName, expectedIP) + deadline := time.Now().Add(timeout) + pollInterval := 500 * time.Millisecond + + for time.Now().Before(deadline) { + // Check if interface exists and is up + iface, err := net.InterfaceByName(interfaceName) + if err == nil { + // Check if interface is up + if iface.Flags&net.FlagUp != 0 { + // Check if it has the expected IP + addrs, err := iface.Addrs() + if err == nil { + for _, addr := range addrs { + ipNet, ok := addr.(*net.IPNet) + if ok && ipNet.IP.Equal(expectedIP) { + logger.Info("Interface %s is up with correct IP", interfaceName) + return nil // Interface is up with correct IP + } + } + logger.Info("Interface %s is up but doesn't have expected IP yet", interfaceName) + } + } else { + logger.Info("Interface %s exists but is not up yet", interfaceName) + } + } else { + logger.Info("Interface %s not found yet: %v", interfaceName, err) + } + + // Wait before next check + time.Sleep(pollInterval) + } + + return fmt.Errorf("timed out waiting for interface %s to be up with IP %s", interfaceName, expectedIP) +} + func WindowsAddRoute(destination string, gateway string, interfaceName string) error { if runtime.GOOS != "windows" { return nil diff --git a/go.mod b/go.mod index 830a1f3..36e40ce 100644 --- a/go.mod +++ b/go.mod @@ -5,18 +5,51 @@ go 1.23.1 toolchain go1.23.2 require ( - github.com/fosrl/newt v0.0.0-20250215225251-76503f3f2cd8 - golang.org/x/net v0.33.0 - golang.org/x/sys v0.33.0 - golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 + github.com/vishvananda/netlink v1.3.1 + golang.org/x/crypto v0.40.0 + golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 + golang.org/x/net v0.42.0 + golang.org/x/sys v0.34.0 + golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 ) require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v28.3.2+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fosrl/newt v0.0.0-20250718235538-510e78437ca4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/gopacket v1.1.19 // indirect github.com/gorilla/websocket v1.5.3 // indirect - github.com/vishvananda/netlink v1.3.0 // indirect - github.com/vishvananda/netns v0.0.4 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/mdlayher/genetlink v1.3.2 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.5.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/vishvananda/netns v0.0.5 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/time v0.12.0 // indirect + golang.org/x/tools v0.35.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect + gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56 // indirect + software.sslmate.com/src/go-pkcs12 v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index 6c52fcc..414ae98 100644 --- a/go.sum +++ b/go.sum @@ -1,39 +1,123 @@ -github.com/fosrl/newt v0.0.0-20250215225251-76503f3f2cd8 h1:SEZcXV/++6+YMPkSXa59EevKSz1ZxmNDd04amswFPqE= -github.com/fosrl/newt v0.0.0-20250215225251-76503f3f2cd8/go.mod h1:EeJ6hdGqHhrDJWlZBlNYto7U2yzxjzh1+F5Otyi/2O8= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.3.2+incompatible h1:wn66NJ6pWB1vBZIilP8G3qQPqHy5XymfYn5vsqeA5oA= +github.com/docker/docker v28.3.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fosrl/newt v0.0.0-20250717220102-cd86e6b6de83 h1:jI6tP2sJNNb70Y+Ixq+oI06fDPnGUbarz/r67g7KvB8= +github.com/fosrl/newt v0.0.0-20250717220102-cd86e6b6de83/go.mod h1:oqHsCx1rsEc8hAVGVXemfeolIwlr19biJSQiLYi7Mvo= +github.com/fosrl/newt v0.0.0-20250718235538-510e78437ca4 h1:bK/MQyTOLGthrXZ7ExvOCdW0EH0o9b5vwk/+UKnNdg0= +github.com/fosrl/newt v0.0.0-20250718235538-510e78437ca4/go.mod h1:oqHsCx1rsEc8hAVGVXemfeolIwlr19biJSQiLYi7Mvo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60= -github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= -github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= -github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= -github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= -github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= -golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= +github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= +github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= +github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= +github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= +github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= +github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= +github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= +github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4= -golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= +golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A= +golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= -gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ= -gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY= +gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56 h1:H+qymc2ndLKNFR5TcaPmsHGiJnhJMqeofBYSRq4oG3c= +gvisor.dev/gvisor v0.0.0-20250718192347-d7830d968c56/go.mod h1:i8iCZyAdwRnLZYaIi2NUL1gfNtAveqxkKAe0JfAv9Bs= +software.sslmate.com/src/go-pkcs12 v0.5.0 h1:EC6R394xgENTpZ4RltKydeDUjtlM5drOYIG9c6TVj2M= +software.sslmate.com/src/go-pkcs12 v0.5.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/main.go b/main.go index d11418d..2f50f18 100644 --- a/main.go +++ b/main.go @@ -12,9 +12,9 @@ import ( "time" "github.com/fosrl/newt/logger" + "github.com/fosrl/newt/websocket" "github.com/fosrl/olm/httpserver" "github.com/fosrl/olm/peermonitor" - "github.com/fosrl/olm/websocket" "github.com/fosrl/olm/wgtester" "golang.zx2c4.com/wireguard/device" @@ -39,6 +39,8 @@ func main() { httpAddr string testMode bool // Add this var for the test flag testTarget string // Add this var for test target + pingInterval time.Duration + pingTimeout time.Duration ) stopHolepunch = make(chan struct{}) @@ -54,6 +56,8 @@ func main() { logLevel = os.Getenv("LOG_LEVEL") interfaceName = os.Getenv("INTERFACE") httpAddr = os.Getenv("HTTP_ADDR") + pingIntervalStr := os.Getenv("PING_INTERVAL") + pingTimeoutStr := os.Getenv("PING_TIMEOUT") if endpoint == "" { flag.StringVar(&endpoint, "endpoint", "", "Endpoint of your Pangolin server") @@ -79,6 +83,32 @@ func main() { if httpAddr == "" { flag.StringVar(&httpAddr, "http-addr", ":9452", "HTTP server address (e.g., ':9452')") } + if pingIntervalStr == "" { + flag.StringVar(&pingIntervalStr, "ping-interval", "3s", "Interval for pinging the server (default 3s)") + } + if pingTimeoutStr == "" { + flag.StringVar(&pingTimeoutStr, "ping-timeout", "5s", " Timeout for each ping (default 3s)") + } + + if pingIntervalStr != "" { + pingInterval, err = time.ParseDuration(pingIntervalStr) + if err != nil { + fmt.Printf("Invalid PING_INTERVAL value: %s, using default 3 seconds\n", pingIntervalStr) + pingInterval = 3 * time.Second + } + } else { + pingInterval = 3 * time.Second + } + + if pingTimeoutStr != "" { + pingTimeout, err = time.ParseDuration(pingTimeoutStr) + if err != nil { + fmt.Printf("Invalid PING_TIMEOUT value: %s, using default 5 seconds\n", pingTimeoutStr) + pingTimeout = 5 * time.Second + } + } else { + pingTimeout = 5 * time.Second + } flag.BoolVar(&enableHTTP, "http", false, "Enable HTTP server") flag.BoolVar(&testMode, "test", false, "Test WireGuard connectivity to a target") @@ -165,9 +195,12 @@ func main() { // Create a new olm olm, err := websocket.NewClient( + "olm", id, // CLI arg takes precedence secret, // CLI arg takes precedence endpoint, + pingInterval, + pingTimeout, ) if err != nil { logger.Fatal("Failed to create olm: %v", err) diff --git a/peermonitor/peermonitor.go b/peermonitor/peermonitor.go index fa11324..4f523b6 100644 --- a/peermonitor/peermonitor.go +++ b/peermonitor/peermonitor.go @@ -7,7 +7,7 @@ import ( "time" "github.com/fosrl/newt/logger" - "github.com/fosrl/olm/websocket" + "github.com/fosrl/newt/websocket" "github.com/fosrl/olm/wgtester" "golang.zx2c4.com/wireguard/device" ) diff --git a/websocket/client.go b/websocket/client.go deleted file mode 100644 index 14d36bd..0000000 --- a/websocket/client.go +++ /dev/null @@ -1,365 +0,0 @@ -package websocket - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - "sync" - "time" - - "github.com/fosrl/newt/logger" - - "github.com/gorilla/websocket" -) - -type Client struct { - conn *websocket.Conn - config *Config - baseURL string - handlers map[string]MessageHandler - done chan struct{} - handlersMux sync.RWMutex - writeMux sync.Mutex - - reconnectInterval time.Duration - isConnected bool - reconnectMux sync.RWMutex - - onConnect func() error - onTokenUpdate func(token string) -} - -type ClientOption func(*Client) - -type MessageHandler func(message WSMessage) - -// WithBaseURL sets the base URL for the client -func WithBaseURL(url string) ClientOption { - return func(c *Client) { - c.baseURL = url - } -} - -func (c *Client) OnConnect(callback func() error) { - c.onConnect = callback -} - -func (c *Client) OnTokenUpdate(callback func(token string)) { - c.onTokenUpdate = callback -} - -// NewClient creates a new Olm client -func NewClient(olmID, secret string, endpoint string, opts ...ClientOption) (*Client, error) { - config := &Config{ - OlmID: olmID, - Secret: secret, - Endpoint: endpoint, - } - - client := &Client{ - config: config, - baseURL: endpoint, // default value - handlers: make(map[string]MessageHandler), - done: make(chan struct{}), - reconnectInterval: 10 * time.Second, - isConnected: false, - } - - // Apply options before loading config - for _, opt := range opts { - opt(client) - } - - // Load existing config if available - if err := client.loadConfig(); err != nil { - return nil, fmt.Errorf("failed to load config: %w", err) - } - - return client, nil -} - -// Connect establishes the WebSocket connection -func (c *Client) Connect() error { - go c.connectWithRetry() - return nil -} - -// Close closes the WebSocket connection -func (c *Client) Close() error { - close(c.done) - if c.conn != nil { - return c.conn.Close() - } - - // stop the ping monitor - c.setConnected(false) - - return nil -} - -// SendMessage sends a message through the WebSocket connection -func (c *Client) SendMessage(messageType string, data interface{}) error { - if c.conn == nil { - return fmt.Errorf("not connected") - } - - msg := WSMessage{ - Type: messageType, - Data: data, - } - - c.writeMux.Lock() - defer c.writeMux.Unlock() - return c.conn.WriteJSON(msg) -} - -// RegisterHandler registers a handler for a specific message type -func (c *Client) RegisterHandler(messageType string, handler MessageHandler) { - c.handlersMux.Lock() - defer c.handlersMux.Unlock() - c.handlers[messageType] = handler -} - -// readPump pumps messages from the WebSocket connection -func (c *Client) readPump() { - defer c.conn.Close() - - for { - select { - case <-c.done: - return - default: - var msg WSMessage - err := c.conn.ReadJSON(&msg) - if err != nil { - return - } - - c.handlersMux.RLock() - if handler, ok := c.handlers[msg.Type]; ok { - handler(msg) - } - c.handlersMux.RUnlock() - } - } -} - -func (c *Client) getToken() (string, error) { - // Parse the base URL to ensure we have the correct hostname - baseURL, err := url.Parse(c.baseURL) - if err != nil { - return "", fmt.Errorf("failed to parse base URL: %w", err) - } - - // Ensure we have the base URL without trailing slashes - baseEndpoint := strings.TrimRight(baseURL.String(), "/") - - // If we already have a token, try to use it - if c.config.Token != "" { - tokenCheckData := map[string]interface{}{ - "olmId": c.config.OlmID, - "secret": c.config.Secret, - "token": c.config.Token, - } - jsonData, err := json.Marshal(tokenCheckData) - if err != nil { - return "", fmt.Errorf("failed to marshal token check data: %w", err) - } - - // Create a new request - req, err := http.NewRequest( - "POST", - baseEndpoint+"/api/v1/auth/olm/get-token", - bytes.NewBuffer(jsonData), - ) - if err != nil { - return "", fmt.Errorf("failed to create request: %w", err) - } - - // Set headers - req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-CSRF-Token", "x-csrf-protection") - - // Make the request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", fmt.Errorf("failed to check token validity: %w", err) - } - defer resp.Body.Close() - - var tokenResp TokenResponse - if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { - return "", fmt.Errorf("failed to decode token check response: %w", err) - } - - // If token is still valid, return it - if tokenResp.Success && tokenResp.Message == "Token session already valid" { - return c.config.Token, nil - } - } - - // Get a new token - tokenData := map[string]interface{}{ - "olmId": c.config.OlmID, - "secret": c.config.Secret, - } - jsonData, err := json.Marshal(tokenData) - if err != nil { - return "", fmt.Errorf("failed to marshal token request data: %w", err) - } - - // Create a new request - req, err := http.NewRequest( - "POST", - baseEndpoint+"/api/v1/auth/olm/get-token", - bytes.NewBuffer(jsonData), - ) - if err != nil { - return "", fmt.Errorf("failed to create request: %w", err) - } - - // Set headers - req.Header.Set("Content-Type", "application/json") - req.Header.Set("X-CSRF-Token", "x-csrf-protection") - - // Make the request - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", fmt.Errorf("failed to request new token: %w", err) - } - defer resp.Body.Close() - - var tokenResp TokenResponse - if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil { - return "", fmt.Errorf("failed to decode token response: %w", err) - } - - if !tokenResp.Success { - return "", fmt.Errorf("failed to get token: %s", tokenResp.Message) - } - - if tokenResp.Data.Token == "" { - return "", fmt.Errorf("received empty token from server") - } - - return tokenResp.Data.Token, nil -} - -func (c *Client) connectWithRetry() { - for { - select { - case <-c.done: - return - default: - err := c.establishConnection() - if err != nil { - logger.Error("Failed to connect: %v. Retrying in %v...", err, c.reconnectInterval) - time.Sleep(c.reconnectInterval) - continue - } - return - } - } -} - -func (c *Client) establishConnection() error { - // Get token for authentication - token, err := c.getToken() - if err != nil { - return fmt.Errorf("failed to get token: %w", err) - } - - c.onTokenUpdate(token) - - // Parse the base URL to determine protocol and hostname - baseURL, err := url.Parse(c.baseURL) - if err != nil { - return fmt.Errorf("failed to parse base URL: %w", err) - } - - // Determine WebSocket protocol based on HTTP protocol - wsProtocol := "wss" - if baseURL.Scheme == "http" { - wsProtocol = "ws" - } - - // Create WebSocket URL - wsURL := fmt.Sprintf("%s://%s/api/v1/ws", wsProtocol, baseURL.Host) - u, err := url.Parse(wsURL) - if err != nil { - return fmt.Errorf("failed to parse WebSocket URL: %w", err) - } - - // Add token to query parameters - q := u.Query() - q.Set("token", token) - q.Set("clientType", "olm") - u.RawQuery = q.Encode() - - // Connect to WebSocket - conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil) - if err != nil { - return fmt.Errorf("failed to connect to WebSocket: %w", err) - } - - c.conn = conn - c.setConnected(true) - - // Start the ping monitor - go c.pingMonitor() - // Start the read pump - go c.readPump() - - if c.onConnect != nil { - err := c.saveConfig() - if err != nil { - logger.Error("Failed to save config: %v", err) - } - if err := c.onConnect(); err != nil { - logger.Error("OnConnect callback failed: %v", err) - } - } - - return nil -} - -func (c *Client) pingMonitor() { - ticker := time.NewTicker(30 * time.Second) - defer ticker.Stop() - - for { - select { - case <-c.done: - return - case <-ticker.C: - c.writeMux.Lock() - err := c.conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(10*time.Second)) - c.writeMux.Unlock() - if err != nil { - logger.Error("Ping failed: %v", err) - c.reconnect() - return - } - } - } -} - -func (c *Client) reconnect() { - c.setConnected(false) - if c.conn != nil { - c.conn.Close() - } - - go c.connectWithRetry() -} - -func (c *Client) setConnected(status bool) { - c.reconnectMux.Lock() - defer c.reconnectMux.Unlock() - c.isConnected = status -} diff --git a/websocket/config.go b/websocket/config.go deleted file mode 100644 index 6e54042..0000000 --- a/websocket/config.go +++ /dev/null @@ -1,72 +0,0 @@ -package websocket - -import ( - "encoding/json" - "log" - "os" - "path/filepath" - "runtime" -) - -func getConfigPath() string { - var configDir string - switch runtime.GOOS { - case "darwin": - configDir = filepath.Join(os.Getenv("HOME"), "Library", "Application Support", "olm-client") - case "windows": - configDir = filepath.Join(os.Getenv("APPDATA"), "olm-client") - default: // linux and others - configDir = filepath.Join(os.Getenv("HOME"), ".config", "olm-client") - } - - if err := os.MkdirAll(configDir, 0755); err != nil { - log.Printf("Failed to create config directory: %v", err) - } - - return filepath.Join(configDir, "config.json") -} - -func (c *Client) loadConfig() error { - if c.config.OlmID != "" && c.config.Secret != "" && c.config.Endpoint != "" { - return nil - } - - configPath := getConfigPath() - data, err := os.ReadFile(configPath) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - - var config Config - if err := json.Unmarshal(data, &config); err != nil { - return err - } - - if c.config.OlmID == "" { - c.config.OlmID = config.OlmID - } - if c.config.Token == "" { - c.config.Token = config.Token - } - if c.config.Secret == "" { - c.config.Secret = config.Secret - } - if c.config.Endpoint == "" { - c.config.Endpoint = config.Endpoint - c.baseURL = config.Endpoint - } - - return nil -} - -func (c *Client) saveConfig() error { - configPath := getConfigPath() - data, err := json.MarshalIndent(c.config, "", " ") - if err != nil { - return err - } - return os.WriteFile(configPath, data, 0644) -} diff --git a/websocket/types.go b/websocket/types.go deleted file mode 100644 index e93c9f9..0000000 --- a/websocket/types.go +++ /dev/null @@ -1,22 +0,0 @@ -package websocket - -type Config struct { - OlmID string `json:"olmId"` - Secret string `json:"secret"` - Token string `json:"token"` - Endpoint string `json:"endpoint"` -} - -type TokenResponse struct { - Data struct { - Token string `json:"token"` - ServerPubKey string `json:"serverPubKey"` - } `json:"data"` - Success bool `json:"success"` - Message string `json:"message"` -} - -type WSMessage struct { - Type string `json:"type"` - Data interface{} `json:"data"` -}