tunnel: add -keepalive-timeout-secs for dead connection detection
Tracks last server receive time (atomic) and runs a watchdog goroutine that closes the connection if no data arrives within the timeout, triggering reconnection. Also fixes Close() to guard Conn.Close() inside sync.Once so concurrent/repeated calls are safe. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1d919aafc5
commit
1a397c363f
5 changed files with 57 additions and 18 deletions
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
|
@ -29,21 +30,26 @@ const (
|
|||
// Tunnel handles bidirectional TCP block framing for Ethernet frames over a
|
||||
// SoftEther VPN session. Each "block" is one Ethernet frame.
|
||||
type Tunnel struct {
|
||||
sess *Session
|
||||
writeCh chan []byte // serialized messages queued for the single writer
|
||||
writeErr atomic.Value // stores error from writeLoop
|
||||
stopCh chan struct{}
|
||||
stopped sync.Once
|
||||
sess *Session
|
||||
writeCh chan []byte // serialized messages queued for the single writer
|
||||
writeErr atomic.Value // stores error from writeLoop
|
||||
stopCh chan struct{}
|
||||
stopped sync.Once
|
||||
keepaliveTimeout time.Duration // 0 = disabled
|
||||
lastRecv atomic.Int64 // unix nano of last data received from server
|
||||
}
|
||||
|
||||
// NewTunnel creates a tunnel from an established session.
|
||||
// Call StartKeepalive() before reading/writing frames.
|
||||
func NewTunnel(sess *Session) *Tunnel {
|
||||
// keepaliveTimeout controls how long to wait for server data before closing the
|
||||
// connection (0 = disabled). Call StartKeepalive() before reading/writing frames.
|
||||
func NewTunnel(sess *Session, keepaliveTimeout time.Duration) *Tunnel {
|
||||
t := &Tunnel{
|
||||
sess: sess,
|
||||
writeCh: make(chan []byte, writeChanSize),
|
||||
stopCh: make(chan struct{}),
|
||||
sess: sess,
|
||||
writeCh: make(chan []byte, writeChanSize),
|
||||
stopCh: make(chan struct{}),
|
||||
keepaliveTimeout: keepaliveTimeout,
|
||||
}
|
||||
t.lastRecv.Store(time.Now().UnixNano())
|
||||
go t.writeLoop()
|
||||
return t
|
||||
}
|
||||
|
|
@ -65,10 +71,14 @@ func (t *Tunnel) writeLoop() {
|
|||
}
|
||||
|
||||
// Close stops the keepalive goroutine and closes the underlying connection.
|
||||
// The writeCh is not closed here — writeLoop exits when the connection write fails.
|
||||
// Safe for concurrent/repeated calls — only the first call actually closes.
|
||||
func (t *Tunnel) Close() error {
|
||||
t.stopped.Do(func() { close(t.stopCh) })
|
||||
return t.sess.Conn.Close()
|
||||
var err error
|
||||
t.stopped.Do(func() {
|
||||
close(t.stopCh)
|
||||
err = t.sess.Conn.Close()
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadFrames reads a batch of Ethernet frames from the server.
|
||||
|
|
@ -78,6 +88,7 @@ func (t *Tunnel) ReadFrames() ([][]byte, error) {
|
|||
if err := binary.Read(t.sess.Conn, binary.BigEndian, &numBlocks); err != nil {
|
||||
return nil, fmt.Errorf("read num blocks: %w", err)
|
||||
}
|
||||
t.lastRecv.Store(time.Now().UnixNano())
|
||||
|
||||
// Keepalive: server sends 0xFFFFFFFF + uint32(size) + random data
|
||||
if numBlocks == keepAliveMagic {
|
||||
|
|
@ -225,4 +236,25 @@ func (t *Tunnel) StartKeepalive() {
|
|||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Watchdog: close connection if no data received within timeout
|
||||
if t.keepaliveTimeout > 0 {
|
||||
go func() {
|
||||
ticker := time.NewTicker(t.keepaliveTimeout / 3)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.stopCh:
|
||||
return
|
||||
case <-ticker.C:
|
||||
last := time.Unix(0, t.lastRecv.Load())
|
||||
if time.Since(last) > t.keepaliveTimeout {
|
||||
log.Printf("keepalive timeout: no data from server for %v, closing connection", t.keepaliveTimeout)
|
||||
t.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue