diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go index 26435570e..ec7dc8bc4 100644 --- a/ipn/ipnlocal/c2n.go +++ b/ipn/ipnlocal/c2n.go @@ -300,6 +300,13 @@ func handleC2NUpdatePost(b *LocalBackend, w http.ResponseWriter, r *http.Request return } + // Do not update if we have active inbound SSH connections. Control can set + // force=true query parameter to override this. + if r.FormValue("force") != "true" && b.sshServer != nil && b.sshServer.NumActiveConns() > 0 { + res.Err = "not updating due to active SSH connections" + return + } + // Check if update was already started, and mark as started. if !b.trySetC2NUpdateStarted() { res.Err = "update already started" diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 4c57502aa..62e8c7905 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -123,6 +123,10 @@ func getControlDebugFlags() []string { type SSHServer interface { HandleSSHConn(net.Conn) error + // NumActiveConns returns the number of connections passed to HandleSSHConn + // that are still active. + NumActiveConns() int + // OnPolicyChange is called when the SSH access policy changes, // so that existing sessions can be re-evaluated for validity // and closed if they'd no longer be accepted. diff --git a/ssh/tailssh/tailssh.go b/ssh/tailssh/tailssh.go index 69df3add2..2bfb645f3 100644 --- a/ssh/tailssh/tailssh.go +++ b/ssh/tailssh/tailssh.go @@ -143,6 +143,13 @@ func (srv *server) trackActiveConn(c *conn, add bool) { delete(srv.activeConns, c) } +// NumActiveConns returns the number of active SSH connections. +func (srv *server) NumActiveConns() int { + srv.mu.Lock() + defer srv.mu.Unlock() + return len(srv.activeConns) +} + // HandleSSHConn handles a Tailscale SSH connection from c. // This is the entry point for all SSH connections. // When this returns, the connection is closed.