@ -12,6 +12,7 @@ import (
"net"
"net"
"net/http"
"net/http"
"net/netip"
"net/netip"
"sync"
"time"
"time"
"tailscale.com/client/tailscale"
"tailscale.com/client/tailscale"
@ -24,10 +25,12 @@ import (
const webClientPort = web . ListenPort
const webClientPort = web . ListenPort
// webClient holds state for the web interface for managing
// webClient holds state for the web interface for managing this
// t his t ailscale instance. The web interface is not used by
// t ailscale instance. The web interface is not used by default,
// default, but initialized by calling LocalBackend.WebClientInit.
// but initialized by calling LocalBackend.WebClientGetOr Init.
type webClient struct {
type webClient struct {
mu sync . Mutex // protects webClient fields
server * web . Server // or nil, initialized lazily
server * web . Server // or nil, initialized lazily
// lc optionally specifies a LocalClient to use to connect
// lc optionally specifies a LocalClient to use to connect
@ -40,51 +43,54 @@ type webClient struct {
// Specifially, it sets b.web.lc to the provided LocalClient.
// Specifially, it sets b.web.lc to the provided LocalClient.
// If provided as nil, b.web.lc is cleared out.
// If provided as nil, b.web.lc is cleared out.
func ( b * LocalBackend ) ConfigureWebClient ( lc * tailscale . LocalClient ) {
func ( b * LocalBackend ) ConfigureWebClient ( lc * tailscale . LocalClient ) {
b . mu. Lock ( )
b . webClient. mu. Lock ( )
defer b . mu. Unlock ( )
defer b . webClient. mu. Unlock ( )
b . webClient . lc = lc
b . webClient . lc = lc
}
}
// WebClientInit initializes the web interface for managing this
// webClientGetOrInit gets or initializes the web server for managing
// t ailscaled instance.
// t his t ailscaled instance.
// If the web interface is already running, WebClientInit is a no-op .
// s is always non-nil if err is empty .
func ( b * LocalBackend ) WebClientInit( ) ( err error ) {
func ( b * LocalBackend ) webClientGetOrInit( ) ( s * web . Server , err error ) {
if ! b . ShouldRunWebClient ( ) {
if ! b . ShouldRunWebClient ( ) {
return errors . New ( "web client not enabled for this device" )
return nil , errors . New ( "web client not enabled for this device" )
}
}
b . mu. Lock ( )
b . webClient. mu. Lock ( )
defer b . mu. Unlock ( )
defer b . webClient. mu. Unlock ( )
if b . webClient . server != nil {
if b . webClient . server != nil {
return nil
return b . webClient . server , nil
}
}
b . logf ( " WebClient Init: initializing web ui")
b . logf ( " webClientGetOr Init: initializing web ui")
if b . webClient . server , err = web . NewServer ( web . ServerOpts {
if b . webClient . server , err = web . NewServer ( web . ServerOpts {
Mode : web . ManageServerMode ,
Mode : web . ManageServerMode ,
LocalClient : b . webClient . lc ,
LocalClient : b . webClient . lc ,
Logf : b . logf ,
Logf : b . logf ,
} ) ; err != nil {
} ) ; err != nil {
return fmt . Errorf ( "web.NewServer: %w" , err )
return nil , fmt . Errorf ( "web.NewServer: %w" , err )
}
}
b . logf ( " WebClient Init: started web ui")
b . logf ( " webClientGetOr Init: started web ui")
return nil
return b . webClient . server , nil
}
}
// WebClientShutdown shuts down any running b.webClient servers and
// WebClientShutdown shuts down any running b.webClient servers and
// clears out b.webClient state (besides the b.webClient.lc field,
// clears out b.webClient state (besides the b.webClient.lc field,
// which is left untouched because required for future web startups).
// which is left untouched because required for future web startups).
// WebClientShutdown obtains the b.mu lock.
// WebClientShutdown obtains the b.mu lock.
func ( b * LocalBackend ) W ebClientShutdown( ) {
func ( b * LocalBackend ) w ebClientShutdown( ) {
b . mu . Lock ( )
b . mu . Lock ( )
server := b . webClient . server
b . webClient . server = nil
for ap , ln := range b . webClientListeners {
for ap , ln := range b . webClientListeners {
ln . Close ( )
ln . Close ( )
delete ( b . webClientListeners , ap )
delete ( b . webClientListeners , ap )
}
}
b . mu . Unlock ( ) // release lock before shutdown
b . mu . Unlock ( )
b . webClient . mu . Lock ( ) // webClient struct uses its own mutext
server := b . webClient . server
b . webClient . server = nil
b . webClient . mu . Unlock ( ) // release lock before shutdown
if server != nil {
if server != nil {
server . Shutdown ( )
server . Shutdown ( )
b . logf ( "WebClientShutdown: shut down web ui" )
b . logf ( "WebClientShutdown: shut down web ui" )
@ -93,10 +99,11 @@ func (b *LocalBackend) WebClientShutdown() {
// handleWebClientConn serves web client requests.
// handleWebClientConn serves web client requests.
func ( b * LocalBackend ) handleWebClientConn ( c net . Conn ) error {
func ( b * LocalBackend ) handleWebClientConn ( c net . Conn ) error {
if err := b . WebClientInit ( ) ; err != nil {
webServer , err := b . webClientGetOrInit ( )
if err != nil {
return err
return err
}
}
s := http . Server { Handler : b. webClient . s erver}
s := http . Server { Handler : webS erver}
return s . Serve ( netutil . NewOneConnListener ( c , nil ) )
return s . Serve ( netutil . NewOneConnListener ( c , nil ) )
}
}