@@ -115,7 +115,12 @@ func ping(tnet *netstack.Net, dst string) error {
115
115
}
116
116
117
117
func startPingCheck (tnet * netstack.Net , serverIP string , stopChan chan struct {}) {
118
- ticker := time .NewTicker (10 * time .Second )
118
+ initialInterval := 10 * time .Second
119
+ maxInterval := 60 * time .Second
120
+ currentInterval := initialInterval
121
+ consecutiveFailures := 0
122
+
123
+ ticker := time .NewTicker (currentInterval )
119
124
defer ticker .Stop ()
120
125
121
126
go func () {
@@ -124,8 +129,34 @@ func startPingCheck(tnet *netstack.Net, serverIP string, stopChan chan struct{})
124
129
case <- ticker .C :
125
130
err := ping (tnet , serverIP )
126
131
if err != nil {
127
- logger .Warn ("Periodic ping failed: %v" , err )
132
+ consecutiveFailures ++
133
+ logger .Warn ("Periodic ping failed (%d consecutive failures): %v" ,
134
+ consecutiveFailures , err )
128
135
logger .Warn ("HINT: Do you have UDP port 51820 (or the port in config.yml) open on your Pangolin server?" )
136
+
137
+ // Increase interval if we have consistent failures, with a maximum cap
138
+ if consecutiveFailures >= 3 && currentInterval < maxInterval {
139
+ // Increase by 50% each time, up to the maximum
140
+ currentInterval = time .Duration (float64 (currentInterval ) * 1.5 )
141
+ if currentInterval > maxInterval {
142
+ currentInterval = maxInterval
143
+ }
144
+ ticker .Reset (currentInterval )
145
+ logger .Info ("Increased ping check interval to %v due to consecutive failures" ,
146
+ currentInterval )
147
+ }
148
+ } else {
149
+ // On success, if we've backed off, gradually return to normal interval
150
+ if currentInterval > initialInterval {
151
+ currentInterval = time .Duration (float64 (currentInterval ) * 0.8 )
152
+ if currentInterval < initialInterval {
153
+ currentInterval = initialInterval
154
+ }
155
+ ticker .Reset (currentInterval )
156
+ logger .Info ("Decreased ping check interval to %v after successful ping" ,
157
+ currentInterval )
158
+ }
159
+ consecutiveFailures = 0
129
160
}
130
161
case <- stopChan :
131
162
logger .Info ("Stopping ping check" )
@@ -135,34 +166,97 @@ func startPingCheck(tnet *netstack.Net, serverIP string, stopChan chan struct{})
135
166
}()
136
167
}
137
168
169
+ // Function to track connection status and trigger reconnection as needed
170
+ func monitorConnectionStatus (tnet * netstack.Net , serverIP string , client * websocket.Client ) {
171
+ const checkInterval = 30 * time .Second
172
+ connectionLost := false
173
+ ticker := time .NewTicker (checkInterval )
174
+ defer ticker .Stop ()
175
+
176
+ for {
177
+ select {
178
+ case <- ticker .C :
179
+ // Try a ping to see if connection is alive
180
+ err := ping (tnet , serverIP )
181
+
182
+ if err != nil && ! connectionLost {
183
+ // We just lost connection
184
+ connectionLost = true
185
+ logger .Warn ("Connection to server lost. Continuous reconnection attempts will be made." )
186
+
187
+ // Notify the user they might need to check their network
188
+ logger .Warn ("Please check your internet connection and ensure the Pangolin server is online." )
189
+ logger .Warn ("Newt will continue reconnection attempts automatically when connectivity is restored." )
190
+ } else if err == nil && connectionLost {
191
+ // Connection has been restored
192
+ connectionLost = false
193
+ logger .Info ("Connection to server restored!" )
194
+
195
+ // Tell the server we're back
196
+ err := client .SendMessage ("newt/wg/register" , map [string ]interface {}{
197
+ "publicKey" : fmt .Sprintf ("%s" , privateKey .PublicKey ()),
198
+ })
199
+
200
+ if err != nil {
201
+ logger .Error ("Failed to send registration message after reconnection: %v" , err )
202
+ } else {
203
+ logger .Info ("Successfully re-registered with server after reconnection" )
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+
138
210
func pingWithRetry (tnet * netstack.Net , dst string ) error {
139
211
const (
140
- maxAttempts = 15
141
- retryDelay = 2 * time .Second
212
+ initialMaxAttempts = 15
213
+ initialRetryDelay = 2 * time .Second
214
+ maxRetryDelay = 60 * time .Second // Cap the maximum delay
142
215
)
143
216
144
- var lastErr error
145
- for attempt := 1 ; attempt <= maxAttempts ; attempt ++ {
146
- logger .Info ("Ping attempt %d of %d" , attempt , maxAttempts )
217
+ attempt := 1
218
+ retryDelay := initialRetryDelay
147
219
148
- if err := ping (tnet , dst ); err != nil {
149
- lastErr = err
150
- logger .Warn ("Ping attempt %d failed: %v" , attempt , err )
220
+ // First try with the initial parameters
221
+ logger .Info ("Ping attempt %d" , attempt )
222
+ if err := ping (tnet , dst ); err == nil {
223
+ // Successful ping
224
+ return nil
225
+ } else {
226
+ logger .Warn ("Ping attempt %d failed: %v" , attempt , err )
227
+ }
228
+
229
+ // Start a goroutine that will attempt pings indefinitely with increasing delays
230
+ go func () {
231
+ attempt = 2 // Continue from attempt 2
232
+
233
+ for {
234
+ logger .Info ("Ping attempt %d" , attempt )
235
+
236
+ if err := ping (tnet , dst ); err != nil {
237
+ logger .Warn ("Ping attempt %d failed: %v" , attempt , err )
238
+
239
+ // Increase delay after certain thresholds but cap it
240
+ if attempt % 5 == 0 && retryDelay < maxRetryDelay {
241
+ retryDelay = time .Duration (float64 (retryDelay ) * 1.5 )
242
+ if retryDelay > maxRetryDelay {
243
+ retryDelay = maxRetryDelay
244
+ }
245
+ logger .Info ("Increasing ping retry delay to %v" , retryDelay )
246
+ }
151
247
152
- if attempt < maxAttempts {
153
248
time .Sleep (retryDelay )
154
- continue
249
+ attempt ++
250
+ } else {
251
+ // Successful ping
252
+ logger .Info ("Ping succeeded after %d attempts" , attempt )
253
+ return
155
254
}
156
- return fmt .Errorf ("all ping attempts failed after %d tries, last error: %w" ,
157
- maxAttempts , lastErr )
158
255
}
256
+ }()
159
257
160
- // Successful ping
161
- return nil
162
- }
163
-
164
- // This shouldn't be reached due to the return in the loop, but added for completeness
165
- return fmt .Errorf ("unexpected error: all ping attempts failed" )
258
+ // Return an error for the first batch of attempts (to maintain compatibility with existing code)
259
+ return fmt .Errorf ("initial ping attempts failed, continuing in background" )
166
260
}
167
261
168
262
func parseLogLevel (level string ) logger.LogLevel {
@@ -246,16 +340,17 @@ func resolveDomain(domain string) (string, error) {
246
340
}
247
341
248
342
var (
249
- endpoint string
250
- id string
251
- secret string
252
- mtu string
253
- mtuInt int
254
- dns string
255
- privateKey wgtypes.Key
256
- err error
257
- logLevel string
258
- updownScript string
343
+ endpoint string
344
+ id string
345
+ secret string
346
+ mtu string
347
+ mtuInt int
348
+ dns string
349
+ privateKey wgtypes.Key
350
+ err error
351
+ logLevel string
352
+ updownScript string
353
+ tlsPrivateKey string
259
354
)
260
355
261
356
func main () {
@@ -267,6 +362,7 @@ func main() {
267
362
dns = os .Getenv ("DNS" )
268
363
logLevel = os .Getenv ("LOG_LEVEL" )
269
364
updownScript = os .Getenv ("UPDOWN_SCRIPT" )
365
+ tlsPrivateKey = os .Getenv ("TLS_CLIENT_CERT" )
270
366
271
367
if endpoint == "" {
272
368
flag .StringVar (& endpoint , "endpoint" , "" , "Endpoint of your pangolin server" )
@@ -289,6 +385,9 @@ func main() {
289
385
if updownScript == "" {
290
386
flag .StringVar (& updownScript , "updown" , "" , "Path to updown script to be called when targets are added or removed" )
291
387
}
388
+ if tlsPrivateKey == "" {
389
+ flag .StringVar (& tlsPrivateKey , "tls-client-cert" , "" , "Path to client certificate used for mTLS" )
390
+ }
292
391
293
392
// do a --version check
294
393
version := flag .Bool ("version" , false , "Print the version" )
@@ -314,12 +413,16 @@ func main() {
314
413
if err != nil {
315
414
logger .Fatal ("Failed to generate private key: %v" , err )
316
415
}
317
-
416
+ var opt websocket.ClientOption
417
+ if tlsPrivateKey != "" {
418
+ opt = websocket .WithTLSConfig (tlsPrivateKey )
419
+ }
318
420
// Create a new client
319
421
client , err := websocket .NewClient (
320
422
id , // CLI arg takes precedence
321
423
secret , // CLI arg takes precedence
322
424
endpoint ,
425
+ opt ,
323
426
)
324
427
if err != nil {
325
428
logger .Fatal ("Failed to create client: %v" , err )
@@ -353,13 +456,8 @@ func main() {
353
456
354
457
if connected {
355
458
logger .Info ("Already connected! But I will send a ping anyway..." )
356
- // ping(tnet, wgData.ServerIP)
357
- err = pingWithRetry (tnet , wgData .ServerIP )
358
- if err != nil {
359
- // Handle complete failure after all retries
360
- logger .Warn ("Failed to ping %s: %v" , wgData .ServerIP , err )
361
- logger .Warn ("HINT: Do you have UDP port 51820 (or the port in config.yml) open on your Pangolin server?" )
362
- }
459
+ // Even if pingWithRetry returns an error, it will continue trying in the background
460
+ _ = pingWithRetry (tnet , wgData .ServerIP ) // Ignoring initial error as pings will continue
363
461
return
364
462
}
365
463
@@ -414,17 +512,18 @@ persistent_keepalive_interval=5`, fixKey(fmt.Sprintf("%s", privateKey)), fixKey(
414
512
}
415
513
416
514
logger .Info ("WireGuard device created. Lets ping the server now..." )
417
- // Ping to bring the tunnel up on the server side quickly
418
- // ping(tnet, wgData.ServerIP)
419
- err = pingWithRetry (tnet , wgData .ServerIP )
420
- if err != nil {
421
- // Handle complete failure after all retries
422
- logger .Error ("Failed to ping %s: %v" , wgData .ServerIP , err )
423
- }
424
515
516
+ // Even if pingWithRetry returns an error, it will continue trying in the background
517
+ _ = pingWithRetry (tnet , wgData .ServerIP )
518
+
519
+ // Always mark as connected and start the proxy manager regardless of initial ping result
520
+ // as the pings will continue in the background
425
521
if ! connected {
426
522
logger .Info ("Starting ping check" )
427
523
startPingCheck (tnet , wgData .ServerIP , pingStopChan )
524
+
525
+ // Start connection monitoring in a separate goroutine
526
+ go monitorConnectionStatus (tnet , wgData .ServerIP , client )
428
527
}
429
528
430
529
// Create proxy manager
@@ -552,10 +651,13 @@ persistent_keepalive_interval=5`, fixKey(fmt.Sprintf("%s", privateKey)), fixKey(
552
651
// Wait for interrupt signal
553
652
sigCh := make (chan os.Signal , 1 )
554
653
signal .Notify (sigCh , syscall .SIGINT , syscall .SIGTERM )
555
- <- sigCh
654
+ sigReceived := <- sigCh
556
655
557
656
// Cleanup
558
- dev .Close ()
657
+ logger .Info ("Received %s signal, stopping" , sigReceived .String ())
658
+ if dev != nil {
659
+ dev .Close ()
660
+ }
559
661
}
560
662
561
663
func parseTargetData (data interface {}) (TargetData , error ) {
0 commit comments