mirror of
https://github.com/fosrl/newt.git
synced 2026-03-12 18:04:28 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
230c34e4e0 | ||
|
|
a038ce1458 | ||
|
|
cd83efd365 | ||
|
|
702f39e870 | ||
|
|
02b7ea51af | ||
|
|
e8421364fc | ||
|
|
7264bb7001 | ||
|
|
86e262ac1e | ||
|
|
dcacc03e96 | ||
|
|
6f4469a5a4 | ||
|
|
663e28329b | ||
|
|
f513f97fc3 | ||
|
|
ce4f3e4cdf | ||
|
|
58a74fce6f | ||
|
|
fc965abbc4 | ||
|
|
6160e4c8a6 | ||
|
|
4d343e3541 | ||
|
|
9348842e2c |
@@ -40,6 +40,7 @@ When Newt receives WireGuard control messages, it will use the information encod
|
||||
- `tls-client-cert` (optional): Client certificate (p12 or pfx) for mTLS. See [mTLS](#mtls)
|
||||
- `docker-socket` (optional): Set the Docker socket to use the container discovery integration
|
||||
- `docker-enforce-network-validation` (optional): Validate the container target is on the same network as the newt process
|
||||
- `health-file` (optional): Check if connection to WG server (pangolin) is ok. creates a file if ok, removes it if not ok. Can be used with docker healtcheck to restart newt
|
||||
|
||||
- Example:
|
||||
|
||||
@@ -61,7 +62,8 @@ services:
|
||||
environment:
|
||||
- PANGOLIN_ENDPOINT=https://example.com
|
||||
- NEWT_ID=2ix2t8xk22ubpfy
|
||||
- NEWT_SECRET=nnisrfsdfc7prqsp9ewo1dvtvci50j5uiqotez00dgap0ii2
|
||||
- NEWT_SECRET=nnisrfsdfc7prqsp9ewo1dvtvci50j5uiqotez00dgap0ii2
|
||||
- HEALTH_FILE=/tmp/healthy
|
||||
```
|
||||
|
||||
You can also pass the CLI args to the container:
|
||||
@@ -76,6 +78,7 @@ services:
|
||||
- --id 31frd0uzbjvp721
|
||||
- --secret h51mmlknrvrwv8s4r1i210azhumt6isgbpyavxodibx1k2d6
|
||||
- --endpoint https://example.com
|
||||
- --health-file /tmp/healthy
|
||||
```
|
||||
|
||||
### Docker Socket Integration
|
||||
|
||||
@@ -26,6 +26,8 @@ type Container struct {
|
||||
Labels map[string]string `json:"labels"`
|
||||
Created int64 `json:"created"`
|
||||
Networks map[string]Network `json:"networks"`
|
||||
Hostname string `json:"hostname"` // added to use hostname if available instead of network address
|
||||
|
||||
}
|
||||
|
||||
// Port represents a port mapping for a Docker container
|
||||
@@ -173,6 +175,14 @@ func ListContainers(socketPath string, enforceNetworkValidation bool) ([]Contain
|
||||
// Short ID like docker ps
|
||||
shortId := c.ID[:12]
|
||||
|
||||
// Inspect container to get hostname
|
||||
hostname := ""
|
||||
containerInfo, err := cli.ContainerInspect(ctx, c.ID)
|
||||
if err == nil && containerInfo.Config != nil {
|
||||
hostname = containerInfo.Config.Hostname
|
||||
}
|
||||
|
||||
|
||||
// Skip host container if set
|
||||
if hostContainerId != "" && c.ID == hostContainerId {
|
||||
continue
|
||||
@@ -238,6 +248,7 @@ func ListContainers(socketPath string, enforceNetworkValidation bool) ([]Contain
|
||||
Labels: c.Labels,
|
||||
Created: c.Created,
|
||||
Networks: networks,
|
||||
Hostname: hostname, // added
|
||||
}
|
||||
|
||||
dockerContainers = append(dockerContainers, dockerContainer)
|
||||
|
||||
6
flake.lock
generated
6
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1749086602,
|
||||
"narHash": "sha256-DJcgJMekoxVesl9kKjfLPix2Nbr42i7cpEHJiTnBUwU=",
|
||||
"lastModified": 1752308619,
|
||||
"narHash": "sha256-pzrVLKRQNPrii06Rm09Q0i0dq3wt2t2pciT/GNq5EZQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4792576cb003c994bd7cc1edada3129def20b27d",
|
||||
"rev": "650e572363c091045cdbc5b36b0f4c1f614d3058",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
default = self.packages.${system}.pangolin-newt;
|
||||
pangolin-newt = pkgs.buildGoModule {
|
||||
pname = "pangolin-newt";
|
||||
version = "1.2.1";
|
||||
version = "1.3.2";
|
||||
|
||||
src = ./.;
|
||||
|
||||
vendorHash = "sha256-Yc5IXnShciek/bKkVezkAcaq47zGiZP8vUHFb9p09LI=";
|
||||
vendorHash = "sha256-Y/f7GCO7Kf1iQiDR32DIEIGJdcN+PKS0OrhBvXiHvwo=";
|
||||
|
||||
meta = with pkgs.lib; {
|
||||
description = "A tunneling client for Pangolin";
|
||||
|
||||
10
go.mod
10
go.mod
@@ -8,10 +8,10 @@ require (
|
||||
github.com/docker/docker v28.3.2+incompatible
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/vishvananda/netlink v1.3.0
|
||||
golang.org/x/crypto v0.39.0
|
||||
github.com/vishvananda/netlink v1.3.1
|
||||
golang.org/x/crypto v0.40.0
|
||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa
|
||||
golang.org/x/net v0.41.0
|
||||
golang.org/x/net v0.42.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
|
||||
@@ -42,7 +42,7 @@ require (
|
||||
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.4 // 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.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.36.0 // indirect
|
||||
@@ -51,7 +51,7 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.36.0 // indirect
|
||||
golang.org/x/mod v0.23.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/tools v0.30.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
|
||||
24
go.sum
24
go.sum
@@ -76,10 +76,10 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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=
|
||||
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=
|
||||
@@ -105,8 +105,8 @@ go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAj
|
||||
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.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
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-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
|
||||
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
@@ -119,8 +119,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
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.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
||||
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=
|
||||
@@ -131,12 +131,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
59
main.go
59
main.go
@@ -154,23 +154,27 @@ func main() {
|
||||
flag.StringVar(&pingIntervalStr, "ping-interval", "3s", "Interval for pinging the server (default 3s)")
|
||||
}
|
||||
if pingTimeoutStr == "" {
|
||||
flag.StringVar(&pingTimeoutStr, "ping-timeout", "3s", " Timeout for each ping (default 3s)")
|
||||
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 1 second\n", pingIntervalStr)
|
||||
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 2 seconds\n", pingTimeoutStr)
|
||||
pingTimeout = 3 * time.Second
|
||||
fmt.Printf("Invalid PING_TIMEOUT value: %s, using default 5 seconds\n", pingTimeoutStr)
|
||||
pingTimeout = 5 * time.Second
|
||||
}
|
||||
} else {
|
||||
pingTimeout = 5 * time.Second
|
||||
}
|
||||
|
||||
if dockerEnforceNetworkValidation == "" {
|
||||
@@ -386,6 +390,15 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
|
||||
close(pingWithRetryStopChan)
|
||||
pingWithRetryStopChan = nil
|
||||
}
|
||||
// Use reliable ping for initial connection test
|
||||
logger.Debug("Testing initial connection with reliable ping...")
|
||||
_, err = reliablePing(tnet, wgData.ServerIP, pingTimeout, 5)
|
||||
if err != nil {
|
||||
logger.Warn("Initial reliable ping failed, but continuing: %v", err)
|
||||
} else {
|
||||
logger.Info("Initial connection test successful!")
|
||||
}
|
||||
|
||||
pingWithRetryStopChan, _ = pingWithRetry(tnet, wgData.ServerIP, pingTimeout)
|
||||
|
||||
// Always mark as connected and start the proxy manager regardless of initial ping result
|
||||
@@ -484,11 +497,6 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
|
||||
results := make([]nodeResult, len(exitNodes))
|
||||
const pingAttempts = 3
|
||||
for i, node := range exitNodes {
|
||||
if connected && node.WasPreviouslyConnected {
|
||||
logger.Info("Skipping ping for previously connected exit node so we pick another %d (%s)", node.ID, node.Endpoint)
|
||||
continue
|
||||
}
|
||||
|
||||
var totalLatency time.Duration
|
||||
var lastErr error
|
||||
successes := 0
|
||||
@@ -543,6 +551,31 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
|
||||
WasPreviouslyConnected: res.Node.WasPreviouslyConnected,
|
||||
})
|
||||
}
|
||||
// If we were previously connected and there is at least one other good node,
|
||||
// exclude the previously connected node from pingResults sent to the cloud.
|
||||
var filteredPingResults []ExitNodePingResult
|
||||
previouslyConnectedNodeIdx := -1
|
||||
for i, res := range pingResults {
|
||||
if res.WasPreviouslyConnected {
|
||||
previouslyConnectedNodeIdx = i
|
||||
}
|
||||
}
|
||||
// Count good nodes (latency > 0, no error, not previously connected)
|
||||
goodNodeCount := 0
|
||||
for i, res := range pingResults {
|
||||
if i != previouslyConnectedNodeIdx && res.LatencyMs > 0 && res.Error == "" {
|
||||
goodNodeCount++
|
||||
}
|
||||
}
|
||||
if previouslyConnectedNodeIdx != -1 && goodNodeCount > 0 {
|
||||
for i, res := range pingResults {
|
||||
if i != previouslyConnectedNodeIdx {
|
||||
filteredPingResults = append(filteredPingResults, res)
|
||||
}
|
||||
}
|
||||
pingResults = filteredPingResults
|
||||
logger.Info("Excluding previously connected exit node from ping results due to other available nodes")
|
||||
}
|
||||
|
||||
// Send the ping results to the cloud for selection
|
||||
stopFunc = client.SendMessageInterval("newt/wg/register", map[string]interface{}{
|
||||
@@ -636,10 +669,10 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
|
||||
|
||||
// Register handler for Docker socket check
|
||||
client.RegisterHandler("newt/socket/check", func(msg websocket.WSMessage) {
|
||||
logger.Info("Received Docker socket check request")
|
||||
logger.Debug("Received Docker socket check request")
|
||||
|
||||
if dockerSocket == "" {
|
||||
logger.Info("Docker socket path is not set")
|
||||
logger.Debug("Docker socket path is not set")
|
||||
err := client.SendMessage("newt/socket/status", map[string]interface{}{
|
||||
"available": false,
|
||||
"socketPath": dockerSocket,
|
||||
@@ -667,10 +700,10 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub
|
||||
|
||||
// Register handler for Docker container listing
|
||||
client.RegisterHandler("newt/socket/fetch", func(msg websocket.WSMessage) {
|
||||
logger.Info("Received Docker container fetch request")
|
||||
logger.Debug("Received Docker container fetch request")
|
||||
|
||||
if dockerSocket == "" {
|
||||
logger.Info("Docker socket path is not set")
|
||||
logger.Debug("Docker socket path is not set")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
120
util.go
120
util.go
@@ -45,9 +45,17 @@ func ping(tnet *netstack.Net, dst string, timeout time.Duration) (time.Duration,
|
||||
}
|
||||
defer socket.Close()
|
||||
|
||||
// Set socket buffer sizes to handle high bandwidth scenarios
|
||||
if tcpConn, ok := socket.(interface{ SetReadBuffer(int) error }); ok {
|
||||
tcpConn.SetReadBuffer(64 * 1024)
|
||||
}
|
||||
if tcpConn, ok := socket.(interface{ SetWriteBuffer(int) error }); ok {
|
||||
tcpConn.SetWriteBuffer(64 * 1024)
|
||||
}
|
||||
|
||||
requestPing := icmp.Echo{
|
||||
Seq: rand.Intn(1 << 16),
|
||||
Data: []byte("f"),
|
||||
Data: []byte("newtping"),
|
||||
}
|
||||
|
||||
icmpBytes, err := (&icmp.Message{Type: ipv4.ICMPTypeEcho, Code: 0, Body: &requestPing}).Marshal(nil)
|
||||
@@ -65,12 +73,14 @@ func ping(tnet *netstack.Net, dst string, timeout time.Duration) (time.Duration,
|
||||
return 0, fmt.Errorf("failed to write ICMP packet: %w", err)
|
||||
}
|
||||
|
||||
n, err := socket.Read(icmpBytes[:])
|
||||
// Use larger buffer for reading to handle potential network congestion
|
||||
readBuffer := make([]byte, 1500)
|
||||
n, err := socket.Read(readBuffer)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to read ICMP packet: %w", err)
|
||||
}
|
||||
|
||||
replyPacket, err := icmp.ParseMessage(1, icmpBytes[:n])
|
||||
replyPacket, err := icmp.ParseMessage(1, readBuffer[:n])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to parse ICMP packet: %w", err)
|
||||
}
|
||||
@@ -92,6 +102,51 @@ func ping(tnet *netstack.Net, dst string, timeout time.Duration) (time.Duration,
|
||||
return latency, nil
|
||||
}
|
||||
|
||||
// reliablePing performs multiple ping attempts with adaptive timeout
|
||||
func reliablePing(tnet *netstack.Net, dst string, baseTimeout time.Duration, maxAttempts int) (time.Duration, error) {
|
||||
var lastErr error
|
||||
var totalLatency time.Duration
|
||||
successCount := 0
|
||||
|
||||
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||
// Adaptive timeout: increase timeout for later attempts
|
||||
timeout := baseTimeout + time.Duration(attempt-1)*500*time.Millisecond
|
||||
|
||||
// Add jitter to prevent thundering herd
|
||||
jitter := time.Duration(rand.Intn(100)) * time.Millisecond
|
||||
timeout += jitter
|
||||
|
||||
latency, err := ping(tnet, dst, timeout)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
logger.Debug("Ping attempt %d/%d failed: %v", attempt, maxAttempts, err)
|
||||
|
||||
// Brief pause between attempts with exponential backoff
|
||||
if attempt < maxAttempts {
|
||||
backoff := time.Duration(attempt) * 50 * time.Millisecond
|
||||
time.Sleep(backoff)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
totalLatency += latency
|
||||
successCount++
|
||||
|
||||
// If we get at least one success, we can return early for health checks
|
||||
if successCount > 0 {
|
||||
avgLatency := totalLatency / time.Duration(successCount)
|
||||
logger.Debug("Reliable ping succeeded after %d attempts, avg latency: %v", attempt, avgLatency)
|
||||
return avgLatency, nil
|
||||
}
|
||||
}
|
||||
|
||||
if successCount == 0 {
|
||||
return 0, fmt.Errorf("all %d ping attempts failed, last error: %v", maxAttempts, lastErr)
|
||||
}
|
||||
|
||||
return totalLatency / time.Duration(successCount), nil
|
||||
}
|
||||
|
||||
func pingWithRetry(tnet *netstack.Net, dst string, timeout time.Duration) (stopChan chan struct{}, err error) {
|
||||
|
||||
if healthFile != "" {
|
||||
@@ -180,6 +235,9 @@ func startPingCheck(tnet *netstack.Net, serverIP string, client *websocket.Clien
|
||||
consecutiveFailures := 0
|
||||
connectionLost := false
|
||||
|
||||
// Track recent latencies for adaptive timeout calculation
|
||||
recentLatencies := make([]time.Duration, 0, 10)
|
||||
|
||||
pingStopChan := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
@@ -188,18 +246,52 @@ func startPingCheck(tnet *netstack.Net, serverIP string, client *websocket.Clien
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
_, err := ping(tnet, serverIP, pingTimeout)
|
||||
// Calculate adaptive timeout based on recent latencies
|
||||
adaptiveTimeout := pingTimeout
|
||||
if len(recentLatencies) > 0 {
|
||||
var sum time.Duration
|
||||
for _, lat := range recentLatencies {
|
||||
sum += lat
|
||||
}
|
||||
avgLatency := sum / time.Duration(len(recentLatencies))
|
||||
// Use 3x average latency as timeout, with minimum of pingTimeout
|
||||
adaptiveTimeout = avgLatency * 3
|
||||
if adaptiveTimeout < pingTimeout {
|
||||
adaptiveTimeout = pingTimeout
|
||||
}
|
||||
if adaptiveTimeout > 15*time.Second {
|
||||
adaptiveTimeout = 15 * time.Second
|
||||
}
|
||||
}
|
||||
|
||||
// Use reliable ping with multiple attempts
|
||||
maxAttempts := 2
|
||||
if consecutiveFailures > 4 {
|
||||
maxAttempts = 4 // More attempts when connection is unstable
|
||||
}
|
||||
|
||||
latency, err := reliablePing(tnet, serverIP, adaptiveTimeout, maxAttempts)
|
||||
if err != nil {
|
||||
consecutiveFailures++
|
||||
if consecutiveFailures < 4 {
|
||||
|
||||
// Track recent latencies (add a high value for failures)
|
||||
recentLatencies = append(recentLatencies, adaptiveTimeout)
|
||||
if len(recentLatencies) > 10 {
|
||||
recentLatencies = recentLatencies[1:]
|
||||
}
|
||||
|
||||
if consecutiveFailures < 2 {
|
||||
logger.Debug("Periodic ping failed (%d consecutive failures): %v", consecutiveFailures, err)
|
||||
} else {
|
||||
logger.Warn("Periodic ping failed (%d consecutive failures): %v", consecutiveFailures, err)
|
||||
}
|
||||
if consecutiveFailures >= 8 && currentInterval < maxInterval {
|
||||
|
||||
// More lenient threshold for declaring connection lost under load
|
||||
failureThreshold := 4
|
||||
if consecutiveFailures >= failureThreshold && currentInterval < maxInterval {
|
||||
if !connectionLost {
|
||||
connectionLost = true
|
||||
logger.Warn("Connection to server lost. Continuous reconnection attempts will be made.")
|
||||
logger.Warn("Connection to server lost after %d failures. Continuous reconnection attempts will be made.", consecutiveFailures)
|
||||
stopFunc = client.SendMessageInterval("newt/ping/request", map[string]interface{}{}, 3*time.Second)
|
||||
// Send registration message to the server for backward compatibility
|
||||
err := client.SendMessage("newt/wg/register", map[string]interface{}{
|
||||
@@ -216,7 +308,7 @@ func startPingCheck(tnet *netstack.Net, serverIP string, client *websocket.Clien
|
||||
}
|
||||
}
|
||||
}
|
||||
currentInterval = time.Duration(float64(currentInterval) * 1.5)
|
||||
currentInterval = time.Duration(float64(currentInterval) * 1.3) // Slower increase
|
||||
if currentInterval > maxInterval {
|
||||
currentInterval = maxInterval
|
||||
}
|
||||
@@ -224,9 +316,15 @@ func startPingCheck(tnet *netstack.Net, serverIP string, client *websocket.Clien
|
||||
logger.Debug("Increased ping check interval to %v due to consecutive failures", currentInterval)
|
||||
}
|
||||
} else {
|
||||
// Track recent latencies
|
||||
recentLatencies = append(recentLatencies, latency)
|
||||
if len(recentLatencies) > 10 {
|
||||
recentLatencies = recentLatencies[1:]
|
||||
}
|
||||
|
||||
if connectionLost {
|
||||
connectionLost = false
|
||||
logger.Info("Connection to server restored!")
|
||||
logger.Info("Connection to server restored after %d failures!", consecutiveFailures)
|
||||
if healthFile != "" {
|
||||
err := os.WriteFile(healthFile, []byte("ok"), 0644)
|
||||
if err != nil {
|
||||
@@ -235,12 +333,12 @@ func startPingCheck(tnet *netstack.Net, serverIP string, client *websocket.Clien
|
||||
}
|
||||
}
|
||||
if currentInterval > pingInterval {
|
||||
currentInterval = time.Duration(float64(currentInterval) * 0.8)
|
||||
currentInterval = time.Duration(float64(currentInterval) * 0.9) // Slower decrease
|
||||
if currentInterval < pingInterval {
|
||||
currentInterval = pingInterval
|
||||
}
|
||||
ticker.Reset(currentInterval)
|
||||
logger.Info("Decreased ping check interval to %v after successful ping", currentInterval)
|
||||
logger.Debug("Decreased ping check interval to %v after successful ping", currentInterval)
|
||||
}
|
||||
consecutiveFailures = 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user