Add SSH keepalive and auto-reconnection to prevent idle disconnections
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,7 @@ type Client struct {
|
|||||||
Events chan string // Channel to send logs/events to TUI
|
Events chan string // Channel to send logs/events to TUI
|
||||||
Metrics chan int64 // Channel to send bytes transferred
|
Metrics chan int64 // Channel to send bytes transferred
|
||||||
PublicURL string // PublicURL is the URL accessible from the internet
|
PublicURL string // PublicURL is the URL accessible from the internet
|
||||||
|
stopKeepAlive chan struct{} // Signal to stop keepalive goroutine
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(serverAddr, localPort, authToken, hostHeader string, localHTTPS bool) *Client {
|
func NewClient(serverAddr, localPort, authToken, hostHeader string, localHTTPS bool) *Client {
|
||||||
@@ -45,7 +46,7 @@ func NewClient(serverAddr, localPort, authToken, hostHeader string, localHTTPS b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Start() error {
|
func (c *Client) connect() error {
|
||||||
config := &ssh.ClientConfig{
|
config := &ssh.ClientConfig{
|
||||||
User: "grokway",
|
User: "grokway",
|
||||||
Auth: []ssh.AuthMethod{
|
Auth: []ssh.AuthMethod{
|
||||||
@@ -93,17 +94,85 @@ func (c *Client) Start() error {
|
|||||||
c.PublicURL = fmt.Sprintf("https://%s.%s", slug, host)
|
c.PublicURL = fmt.Sprintf("https://%s.%s", slug, host)
|
||||||
c.Events <- fmt.Sprintf("Tunnel established! Public URL: %s", c.PublicURL)
|
c.Events <- fmt.Sprintf("Tunnel established! Public URL: %s", c.PublicURL)
|
||||||
|
|
||||||
go c.acceptLoop()
|
// Start SSH keepalive to prevent idle disconnection
|
||||||
|
c.stopKeepAlive = make(chan struct{})
|
||||||
|
go c.keepAlive()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) Start() error {
|
||||||
|
if err := c.connect(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go c.acceptLoop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// keepAlive sends periodic SSH keepalive requests to prevent
|
||||||
|
// firewalls/NATs from dropping idle connections.
|
||||||
|
func (c *Client) keepAlive() {
|
||||||
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.stopKeepAlive:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if c.SSHClient != nil {
|
||||||
|
_, _, err := c.SSHClient.SendRequest("keepalive@openssh.com", true, nil)
|
||||||
|
if err != nil {
|
||||||
|
logToFile(fmt.Sprintf("Keepalive failed: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) closeConnection() {
|
||||||
|
if c.stopKeepAlive != nil {
|
||||||
|
select {
|
||||||
|
case <-c.stopKeepAlive:
|
||||||
|
// Already closed
|
||||||
|
default:
|
||||||
|
close(c.stopKeepAlive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.Listener != nil {
|
||||||
|
c.Listener.Close()
|
||||||
|
}
|
||||||
|
if c.SSHClient != nil {
|
||||||
|
c.SSHClient.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) acceptLoop() {
|
func (c *Client) acceptLoop() {
|
||||||
for {
|
for {
|
||||||
remoteConn, err := c.Listener.Accept()
|
remoteConn, err := c.Listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Events <- fmt.Sprintf("Accept error: %s", err)
|
c.Events <- fmt.Sprintf("Connection lost: %s", err)
|
||||||
break
|
c.closeConnection()
|
||||||
|
|
||||||
|
// Reconnect with backoff
|
||||||
|
delay := 2 * time.Second
|
||||||
|
maxDelay := 60 * time.Second
|
||||||
|
for {
|
||||||
|
c.Events <- fmt.Sprintf("Reconnecting in %s...", delay)
|
||||||
|
time.Sleep(delay)
|
||||||
|
|
||||||
|
if err := c.connect(); err != nil {
|
||||||
|
c.Events <- fmt.Sprintf("Reconnection failed: %s", err)
|
||||||
|
delay *= 2
|
||||||
|
if delay > maxDelay {
|
||||||
|
delay = maxDelay
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c.Events <- "Reconnected successfully!"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Events <- "New Request received"
|
c.Events <- "New Request received"
|
||||||
|
|||||||
Reference in New Issue
Block a user