@ -38,6 +38,8 @@ import (
"tailscale.com/logtail"
"tailscale.com/logtail"
"tailscale.com/logtail/filch"
"tailscale.com/logtail/filch"
"tailscale.com/net/memnet"
"tailscale.com/net/memnet"
"tailscale.com/net/proxymux"
"tailscale.com/net/socks5"
"tailscale.com/net/tsdial"
"tailscale.com/net/tsdial"
"tailscale.com/smallzstd"
"tailscale.com/smallzstd"
"tailscale.com/types/logger"
"tailscale.com/types/logger"
@ -91,22 +93,23 @@ type Server struct {
// If empty, the Tailscale default is used.
// If empty, the Tailscale default is used.
ControlURL string
ControlURL string
initOnce sync . Once
initOnce sync . Once
initErr error
initErr error
lb * ipnlocal . LocalBackend
lb * ipnlocal . LocalBackend
netstack * netstack . Impl
netstack * netstack . Impl
linkMon * monitor . Mon
linkMon * monitor . Mon
rootPath string // the state directory
rootPath string // the state directory
hostname string
hostname string
shutdownCtx context . Context
shutdownCtx context . Context
shutdownCancel context . CancelFunc
shutdownCancel context . CancelFunc
localAPICred string // basic auth password for localAPITCPListener
proxyCred string // SOCKS5 proxy auth for loopbackListener
localAPITCPListener net . Listener // optional loopback, restricted to PID
localAPICred string // basic auth password for loopbackListener
localAPIListener net . Listener // in-memory, used by localClient
loopbackListener net . Listener // optional loopback for localapi and proxies
localClient * tailscale . LocalClient // in-memory
localAPIListener net . Listener // in-memory, used by localClient
logbuffer * filch . Filch
localClient * tailscale . LocalClient // in-memory
logtail * logtail . Logger
logbuffer * filch . Filch
logid string
logtail * logtail . Logger
logid string
mu sync . Mutex
mu sync . Mutex
listeners map [ listenKey ] * listener
listeners map [ listenKey ] * listener
@ -145,34 +148,49 @@ func (s *Server) LocalClient() (*tailscale.LocalClient, error) {
return s . localClient , nil
return s . localClient , nil
}
}
// Loopback LocalAPI returns a loopback ip:port listening for the "LocalAPI" .
// Loopback starts a routing server on a loopback address .
//
//
// As the LocalAPI is powerful, access to endpoints requires BOTH passing a
// The server has multiple functions.
// "Sec-Tailscale: localapi" HTTP header and passing cred as a basic auth.
//
// It can be used as a SOCKS5 proxy onto the tailnet.
// Authentication is required with the username "tsnet" and
// the value of proxyCred used as the password.
//
//
// It will start the server and the local client listener if they have not
// The HTTP server also serves out the "LocalAPI" on /localapi.
// been started yet.
// As the LocalAPI is powerful, access to endpoints requires BOTH passing a
// "Sec-Tailscale: localapi" HTTP header and passing localAPICred as basic auth.
//
//
// If you only need to use the LocalAPI from Go, then prefer LocalClient
// If you only need to use the LocalAPI from Go, then prefer LocalClient
// as it does not require communication via TCP.
// as it does not require communication via TCP.
func ( s * Server ) Loopback LocalAPI ( ) ( addr string , cred string , err error ) {
func ( s * Server ) Loopback ( ) ( addr string , proxyCred, lo calAPIC red string , err error ) {
if err := s . Start ( ) ; err != nil {
if err := s . Start ( ) ; err != nil {
return "" , "" , err
return "" , "" , "" , err
}
}
if s . localAPITCPListener == nil {
if s . loopbackListener == nil {
var proxyCred [ 16 ] byte
if _ , err := crand . Read ( proxyCred [ : ] ) ; err != nil {
return "" , "" , "" , err
}
s . proxyCred = hex . EncodeToString ( proxyCred [ : ] )
var cred [ 16 ] byte
var cred [ 16 ] byte
if _ , err := crand . Read ( cred [ : ] ) ; err != nil {
if _ , err := crand . Read ( cred [ : ] ) ; err != nil {
return "" , "" , err
return "" , "" , "" , err
}
}
s . localAPICred = hex . EncodeToString ( cred [ : ] )
s . localAPICred = hex . EncodeToString ( cred [ : ] )
ln , err := net . Listen ( "tcp" , "127.0.0.1:0" )
ln , err := net . Listen ( "tcp" , "127.0.0.1:0" )
if err != nil {
if err != nil {
return "" , "" , err
return "" , "" , "" , err
}
}
s . lo calAPITCP Listener = ln
s . lo opback Listener = ln
socksLn , httpLn := proxymux . SplitSOCKSAndHTTP ( ln )
// TODO: add HTTP proxy support. Probably requires factoring
// out the CONNECT code from tailscaled/proxy.go that uses
// httputil.ReverseProxy and adding auth support.
go func ( ) {
go func ( ) {
lah := localapi . NewHandler ( s . lb , s . logf , s . logid )
lah := localapi . NewHandler ( s . lb , s . logf , s . logid )
lah . PermitWrite = true
lah . PermitWrite = true
@ -180,13 +198,23 @@ func (s *Server) LoopbackLocalAPI() (addr string, cred string, err error) {
lah . RequiredPassword = s . localAPICred
lah . RequiredPassword = s . localAPICred
h := & localSecHandler { h : lah , cred : s . localAPICred }
h := & localSecHandler { h : lah , cred : s . localAPICred }
if err := http . Serve ( s. localAPITCPListener , h ) ; err != nil {
if err := http . Serve ( httpLn , h ) ; err != nil {
s . logf ( "localapi tcp serve error: %v" , err )
s . logf ( "localapi tcp serve error: %v" , err )
}
}
} ( )
} ( )
s5s := & socks5 . Server {
Logf : logger . WithPrefix ( s . logf , "socks5: " ) ,
Dialer : s . dialer . UserDial ,
Username : "tsnet" ,
Password : s . proxyCred ,
}
go func ( ) {
s . logf ( "SOCKS5 server exited: %v" , s5s . Serve ( socksLn ) )
} ( )
}
}
return s . localAPITCPListener . Addr ( ) . String ( ) , s . localAPICred , nil
return s . lo opback Listener. Addr ( ) . String ( ) , s . proxyCred , s . localAPICred , nil
}
}
type localSecHandler struct {
type localSecHandler struct {
@ -301,8 +329,8 @@ func (s *Server) Close() error {
if s . localAPIListener != nil {
if s . localAPIListener != nil {
s . localAPIListener . Close ( )
s . localAPIListener . Close ( )
}
}
if s . lo calAPITCP Listener != nil {
if s . lo opback Listener != nil {
s . lo calAPITCP Listener. Close ( )
s . lo opback Listener. Close ( )
}
}
s . mu . Lock ( )
s . mu . Lock ( )