2023-10-11 18:35:22 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
//go:build !ios && !android
|
|
|
|
|
|
|
|
package ipnlocal
|
|
|
|
|
|
|
|
import (
|
2023-11-09 00:05:33 +00:00
|
|
|
"context"
|
2023-10-11 18:35:22 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-10-26 18:39:20 +00:00
|
|
|
"net"
|
2023-10-11 18:35:22 +00:00
|
|
|
"net/http"
|
2023-11-09 00:05:33 +00:00
|
|
|
"net/netip"
|
|
|
|
"time"
|
2023-10-11 18:35:22 +00:00
|
|
|
|
|
|
|
"tailscale.com/client/tailscale"
|
|
|
|
"tailscale.com/client/web"
|
2023-11-09 00:05:33 +00:00
|
|
|
"tailscale.com/logtail/backoff"
|
2023-10-26 18:39:20 +00:00
|
|
|
"tailscale.com/net/netutil"
|
2023-11-09 00:05:33 +00:00
|
|
|
"tailscale.com/types/logger"
|
|
|
|
"tailscale.com/util/mak"
|
2023-10-11 18:35:22 +00:00
|
|
|
)
|
|
|
|
|
2023-11-03 03:05:40 +00:00
|
|
|
const webClientPort = web.ListenPort
|
|
|
|
|
2023-10-31 18:56:20 +00:00
|
|
|
// webClient holds state for the web interface for managing
|
2023-10-11 18:35:22 +00:00
|
|
|
// this tailscale instance. The web interface is not used by
|
|
|
|
// default, but initialized by calling LocalBackend.WebOrInit.
|
2023-10-31 18:56:20 +00:00
|
|
|
type webClient struct {
|
|
|
|
server *web.Server // or nil, initialized lazily
|
2023-10-11 18:35:22 +00:00
|
|
|
|
|
|
|
// lc optionally specifies a LocalClient to use to connect
|
|
|
|
// to the localapi for this tailscaled instance.
|
|
|
|
// If nil, a default is used.
|
|
|
|
lc *tailscale.LocalClient
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetWebLocalClient sets the b.web.lc function.
|
|
|
|
// If lc is provided as nil, b.web.lc is cleared out.
|
|
|
|
func (b *LocalBackend) SetWebLocalClient(lc *tailscale.LocalClient) {
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2023-10-31 18:56:20 +00:00
|
|
|
b.webClient.lc = lc
|
2023-10-11 18:35:22 +00:00
|
|
|
}
|
|
|
|
|
2023-10-31 18:56:20 +00:00
|
|
|
// WebClientInit initializes the web interface for managing this
|
|
|
|
// tailscaled instance.
|
|
|
|
// If the web interface is already running, WebClientInit is a no-op.
|
|
|
|
func (b *LocalBackend) WebClientInit() (err error) {
|
2023-11-02 16:55:01 +00:00
|
|
|
if !b.ShouldRunWebClient() {
|
2023-10-31 18:41:39 +00:00
|
|
|
return errors.New("web client not enabled for this device")
|
2023-10-11 18:35:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
b.mu.Lock()
|
|
|
|
defer b.mu.Unlock()
|
2023-10-31 18:56:20 +00:00
|
|
|
if b.webClient.server != nil {
|
2023-10-11 18:35:22 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-10-31 18:56:20 +00:00
|
|
|
b.logf("WebClientInit: initializing web ui")
|
|
|
|
if b.webClient.server, err = web.NewServer(web.ServerOpts{
|
2023-11-02 22:19:16 +00:00
|
|
|
Mode: web.ManageServerMode,
|
2023-10-31 18:56:20 +00:00
|
|
|
LocalClient: b.webClient.lc,
|
2023-10-11 18:35:22 +00:00
|
|
|
Logf: b.logf,
|
|
|
|
}); err != nil {
|
|
|
|
return fmt.Errorf("web.NewServer: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-10-31 18:56:20 +00:00
|
|
|
b.logf("WebClientInit: started web ui")
|
2023-10-11 18:35:22 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-10-31 18:56:20 +00:00
|
|
|
// WebClientShutdown shuts down any running b.webClient servers and
|
|
|
|
// clears out b.webClient state (besides the b.webClient.lc field,
|
|
|
|
// which is left untouched because required for future web startups).
|
|
|
|
// WebClientShutdown obtains the b.mu lock.
|
|
|
|
func (b *LocalBackend) WebClientShutdown() {
|
2023-10-11 18:35:22 +00:00
|
|
|
b.mu.Lock()
|
2023-10-31 18:56:20 +00:00
|
|
|
server := b.webClient.server
|
|
|
|
b.webClient.server = nil
|
2023-11-14 20:07:51 +00:00
|
|
|
for ap, ln := range b.webClientListeners {
|
2023-11-09 00:05:33 +00:00
|
|
|
ln.Close()
|
2023-11-14 20:07:51 +00:00
|
|
|
delete(b.webClientListeners, ap)
|
2023-11-09 00:05:33 +00:00
|
|
|
}
|
2023-10-11 18:35:22 +00:00
|
|
|
b.mu.Unlock() // release lock before shutdown
|
2023-10-31 18:56:20 +00:00
|
|
|
if server != nil {
|
|
|
|
server.Shutdown()
|
2023-11-02 22:19:16 +00:00
|
|
|
b.logf("WebClientShutdown: shut down web ui")
|
2023-10-11 18:35:22 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-26 18:39:20 +00:00
|
|
|
|
|
|
|
// handleWebClientConn serves web client requests.
|
|
|
|
func (b *LocalBackend) handleWebClientConn(c net.Conn) error {
|
2023-10-31 18:56:20 +00:00
|
|
|
if err := b.WebClientInit(); err != nil {
|
2023-10-26 18:39:20 +00:00
|
|
|
return err
|
|
|
|
}
|
2023-10-31 18:56:20 +00:00
|
|
|
s := http.Server{Handler: b.webClient.server}
|
2023-10-26 18:39:20 +00:00
|
|
|
return s.Serve(netutil.NewOneConnListener(c, nil))
|
|
|
|
}
|
2023-11-09 00:05:33 +00:00
|
|
|
|
|
|
|
// updateWebClientListenersLocked creates listeners on the web client port (5252)
|
|
|
|
// for each of the local device's Tailscale IP addresses. This is needed to properly
|
|
|
|
// route local traffic when using kernel networking mode.
|
|
|
|
func (b *LocalBackend) updateWebClientListenersLocked() {
|
|
|
|
if b.netMap == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
addrs := b.netMap.GetAddresses()
|
|
|
|
for i := range addrs.LenIter() {
|
|
|
|
addrPort := netip.AddrPortFrom(addrs.At(i).Addr(), webClientPort)
|
|
|
|
if _, ok := b.webClientListeners[addrPort]; ok {
|
|
|
|
continue // already listening
|
|
|
|
}
|
|
|
|
|
|
|
|
sl := b.newWebClientListener(context.Background(), addrPort, b.logf)
|
|
|
|
mak.Set(&b.webClientListeners, addrPort, sl)
|
|
|
|
|
|
|
|
go sl.Run()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newWebClientListener returns a listener for local connections to the built-in web client
|
|
|
|
// used to manage this Tailscale instance.
|
|
|
|
func (b *LocalBackend) newWebClientListener(ctx context.Context, ap netip.AddrPort, logf logger.Logf) *localListener {
|
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
|
|
return &localListener{
|
|
|
|
b: b,
|
|
|
|
ap: ap,
|
|
|
|
ctx: ctx,
|
|
|
|
cancel: cancel,
|
|
|
|
logf: logf,
|
|
|
|
|
|
|
|
handler: b.handleWebClientConn,
|
|
|
|
bo: backoff.NewBackoff("webclient-listener", logf, 30*time.Second),
|
|
|
|
}
|
|
|
|
}
|