tsnet: run the LocalAPI handler

Signed-off-by: Maisem Ali <maisem@tailscale.com>
pull/3019/head
Maisem Ali 3 years ago committed by Maisem Ali
parent 830f641c6b
commit 52be1c0c78

@ -21,6 +21,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"go4.org/mem" "go4.org/mem"
@ -33,30 +34,39 @@ import (
"tailscale.com/version" "tailscale.com/version"
) )
// TailscaledSocket is the tailscaled Unix socket. var (
var TailscaledSocket = paths.DefaultTailscaledSocket() // TailscaledSocket is the tailscaled Unix socket. It's used by the TailscaledDialer.
TailscaledSocket = paths.DefaultTailscaledSocket()
// tsClient does HTTP requests to the local Tailscale daemon. // TailscaledDialer is the DialContext func that connects to the local machine's
var tsClient = &http.Client{ // tailscaled or equivalent.
Transport: &http.Transport{ TailscaledDialer = defaultDialer
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { )
if addr != "local-tailscaled.sock:80" {
return nil, fmt.Errorf("unexpected URL address %q", addr) func defaultDialer(ctx context.Context, network, addr string) (net.Conn, error) {
} if addr != "local-tailscaled.sock:80" {
if TailscaledSocket == paths.DefaultTailscaledSocket() { return nil, fmt.Errorf("unexpected URL address %q", addr)
// On macOS, when dialing from non-sandboxed program to sandboxed GUI running }
// a TCP server on a random port, find the random port. For HTTP connections, if TailscaledSocket == paths.DefaultTailscaledSocket() {
// we don't send the token. It gets added in an HTTP Basic-Auth header. // On macOS, when dialing from non-sandboxed program to sandboxed GUI running
if port, _, err := safesocket.LocalTCPPortAndToken(); err == nil { // a TCP server on a random port, find the random port. For HTTP connections,
var d net.Dialer // we don't send the token. It gets added in an HTTP Basic-Auth header.
return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port)) if port, _, err := safesocket.LocalTCPPortAndToken(); err == nil {
} var d net.Dialer
} return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port))
return safesocket.Connect(TailscaledSocket, 41112) }
}, }
}, return safesocket.Connect(TailscaledSocket, 41112)
} }
var (
// tsClient does HTTP requests to the local Tailscale daemon.
// We lazily initialize the client in case the caller wants to
// override TailscaledDialer.
tsClient *http.Client
tsClientOnce sync.Once
)
// DoLocalRequest makes an HTTP request to the local machine's Tailscale daemon. // DoLocalRequest makes an HTTP request to the local machine's Tailscale daemon.
// //
// URLs are of the form http://local-tailscaled.sock/localapi/v0/whois?ip=1.2.3.4. // URLs are of the form http://local-tailscaled.sock/localapi/v0/whois?ip=1.2.3.4.
@ -67,6 +77,13 @@ var tsClient = &http.Client{
// //
// DoLocalRequest may mutate the request to add Authorization headers. // DoLocalRequest may mutate the request to add Authorization headers.
func DoLocalRequest(req *http.Request) (*http.Response, error) { func DoLocalRequest(req *http.Request) (*http.Response, error) {
tsClientOnce.Do(func() {
tsClient = &http.Client{
Transport: &http.Transport{
DialContext: TailscaledDialer,
},
}
})
if _, token, err := safesocket.LocalTCPPortAndToken(); err == nil { if _, token, err := safesocket.LocalTCPPortAndToken(); err == nil {
req.SetBasicAuth("", token) req.SetBasicAuth("", token)
} }

@ -12,6 +12,7 @@ import (
"net/http" "net/http"
"strings" "strings"
"tailscale.com/client/tailscale"
"tailscale.com/tsnet" "tailscale.com/tsnet"
) )
@ -22,9 +23,9 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Fatal(http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
who, ok := s.WhoIs(r.RemoteAddr) who, err := tailscale.WhoIs(r.Context(), r.RemoteAddr)
if !ok { if err != nil {
http.Error(w, "WhoIs failed", 500) http.Error(w, err.Error(), 500)
return return
} }
fmt.Fprintf(w, "<html><body><h1>Hello, world!</h1>\n") fmt.Fprintf(w, "<html><body><h1>Hello, world!</h1>\n")

@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"log" "log"
"net" "net"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -19,11 +20,12 @@ import (
"sync" "sync"
"time" "time"
"inet.af/netaddr" "tailscale.com/client/tailscale"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/control/controlclient" "tailscale.com/control/controlclient"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/localapi"
"tailscale.com/net/nettest"
"tailscale.com/smallzstd" "tailscale.com/smallzstd"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/wgengine" "tailscale.com/wgengine"
@ -60,28 +62,6 @@ type Server struct {
listeners map[listenKey]*listener listeners map[listenKey]*listener
} }
// WhoIs reports the node and user who owns the node with the given
// address. The addr may be an ip:port (as from an
// http.Request.RemoteAddr) or just an IP address.
func (s *Server) WhoIs(addr string) (w *apitype.WhoIsResponse, ok bool) {
ipp, err := netaddr.ParseIPPort(addr)
if err != nil {
ip, err := netaddr.ParseIP(addr)
if err != nil {
return nil, false
}
ipp = ipp.WithIP(ip)
}
n, up, ok := s.lb.WhoIs(ipp)
if !ok {
return nil, false
}
return &apitype.WhoIsResponse{
Node: n,
UserProfile: &up,
}, true
}
func (s *Server) doInit() { func (s *Server) doInit() {
if err := s.start(); err != nil { if err := s.start(); err != nil {
s.initErr = fmt.Errorf("tsnet: %w", err) s.initErr = fmt.Errorf("tsnet: %w", err)
@ -185,6 +165,24 @@ func (s *Server) start() error {
if os.Getenv("TS_LOGIN") == "1" || os.Getenv("TS_AUTHKEY") != "" { if os.Getenv("TS_LOGIN") == "1" || os.Getenv("TS_AUTHKEY") != "" {
s.lb.StartLoginInteractive() s.lb.StartLoginInteractive()
} }
// Run the localapi handler, to allow fetching LetsEncrypt certs.
lah := localapi.NewHandler(lb, logf, logid)
lah.PermitWrite = true
lah.PermitRead = true
// Create an in-process listener.
// nettest.Listen provides a in-memory pipe based implementation for net.Conn.
// TODO(maisem): Rename nettest package to remove "test".
lal := nettest.Listen("local-tailscaled.sock:80")
// Override the Tailscale client to use the in-process listener.
tailscale.TailscaledDialer = lal.Dial
go func() {
if err := http.Serve(lal, lah); err != nil {
logf("localapi serve error: %v", err)
}
}()
return nil return nil
} }

Loading…
Cancel
Save