ipn/ipnlocal: start of peerapi between nodes

Also some necessary refactoring of the ipn/ipnstate too.

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/1593/head
Brad Fitzpatrick 3 years ago committed by Brad Fitzpatrick
parent dad10fee9c
commit 81143b6d9a

@ -95,15 +95,16 @@ type LocalBackend struct {
// hostinfo is mutated in-place while mu is held.
hostinfo *tailcfg.Hostinfo
// netMap is not mutated in-place once set.
netMap *netmap.NetworkMap
nodeByAddr map[netaddr.IP]*tailcfg.Node
activeLogin string // last logged LoginName from netMap
engineStatus ipn.EngineStatus
endpoints []string
blocked bool
authURL string
interact bool
prevIfState *interfaces.State
netMap *netmap.NetworkMap
nodeByAddr map[netaddr.IP]*tailcfg.Node
activeLogin string // last logged LoginName from netMap
engineStatus ipn.EngineStatus
endpoints []string
blocked bool
authURL string
interact bool
prevIfState *interfaces.State
peerAPIListeners []*peerAPIListener
// statusLock must be held before calling statusChanged.Wait() or
// statusChanged.Broadcast().
@ -237,16 +238,22 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) {
func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func(*ipnstate.StatusBuilder)) {
b.mu.Lock()
defer b.mu.Unlock()
sb.SetVersion(version.Long)
sb.SetBackendState(b.state.String())
sb.SetAuthURL(b.authURL)
sb.MutateStatus(func(s *ipnstate.Status) {
s.Version = version.Long
s.BackendState = b.state.String()
s.AuthURL = b.authURL
if b.netMap != nil {
s.MagicDNSSuffix = b.netMap.MagicDNSSuffix()
}
})
sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
for _, pln := range b.peerAPIListeners {
ss.PeerAPIURL = append(ss.PeerAPIURL, "http://"+pln.ln.Addr().String())
}
})
// TODO: hostinfo, and its networkinfo
// TODO: EngineStatus copy (and deprecate it?)
if b.netMap != nil {
sb.SetMagicDNSSuffix(b.netMap.MagicDNSSuffix())
}
if extraLocked != nil {
extraLocked(sb)
}
@ -1426,6 +1433,32 @@ func (b *LocalBackend) authReconfig() {
return
}
b.logf("[v1] authReconfig: ra=%v dns=%v 0x%02x: %v", uc.RouteAll, uc.CorpDNS, flags, err)
b.initPeerAPIListener()
}
func (b *LocalBackend) initPeerAPIListener() {
b.mu.Lock()
defer b.mu.Unlock()
for _, pln := range b.peerAPIListeners {
pln.ln.Close()
}
b.peerAPIListeners = nil
for _, a := range b.netMap.Addresses {
ln, err := peerAPIListen(a.IP)
if err != nil {
b.logf("[unexpected] peerAPI listen(%q) error: %v", a.IP, err)
continue
}
pln := &peerAPIListener{
ln: ln,
lb: b,
}
go pln.serve()
b.peerAPIListeners = append(b.peerAPIListeners, pln)
}
}
// magicDNSRootDomains returns the subset of nm.DNS.Domains that are the search domains for MagicDNS.

@ -0,0 +1,137 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ipnlocal
import (
"context"
"errors"
"fmt"
"hash/crc32"
"html"
"io"
"net"
"net/http"
"strconv"
"inet.af/netaddr"
"tailscale.com/tailcfg"
)
var initListenConfig func(*net.ListenConfig, netaddr.IP) error
func peerAPIListen(ip netaddr.IP) (ln net.Listener, err error) {
var lc net.ListenConfig
if initListenConfig != nil {
// On iOS/macOS, this sets the lc.Control hook to
// setsockopt the interface index to bind to, to get
// out of the network sandbox.
if err := initListenConfig(&lc, ip); err != nil {
return nil, err
}
}
// Make a best effort to pick a deterministic port number for
// the ip The lower three bytes are the same for IPv4 and IPv6
// Tailscale addresses (at least currently), so we'll usually
// get the same port number on both address families for
// dev/debugging purposes, which is nice. But it's not so
// deterministic that people will bake this into clients.
// We try a few times just in case something's already
// listening on that port (on all interfaces, probably).
for try := uint8(0); try < 5; try++ {
a16 := ip.As16()
hashData := a16[len(a16)-3:]
hashData[0] += try
tryPort := (32 << 10) | uint16(crc32.ChecksumIEEE(hashData))
ln, err = lc.Listen(context.Background(), "tcp", net.JoinHostPort(ip.String(), strconv.Itoa(int(tryPort))))
if err == nil {
return ln, nil
}
}
// Fall back to random ephemeral port.
return lc.Listen(context.Background(), "tcp", net.JoinHostPort(ip.String(), "0"))
}
type peerAPIListener struct {
ln net.Listener
lb *LocalBackend
}
func (pln *peerAPIListener) serve() {
defer pln.ln.Close()
logf := pln.lb.logf
for {
c, err := pln.ln.Accept()
if errors.Is(err, net.ErrClosed) {
return
}
if err != nil {
logf("peerapi.Accept: %v", err)
return
}
ta, ok := c.RemoteAddr().(*net.TCPAddr)
if !ok {
c.Close()
logf("peerapi: unexpected RemoteAddr %#v", c.RemoteAddr())
continue
}
ipp, ok := netaddr.FromStdAddr(ta.IP, ta.Port, "")
if !ok {
logf("peerapi: bogus TCPAddr %#v", ta)
c.Close()
continue
}
peerNode, peerUser, ok := pln.lb.WhoIs(ipp)
if !ok {
logf("peerapi: unknown peer %v", ipp)
c.Close()
continue
}
pas := &peerAPIServer{
remoteAddr: ipp,
peerNode: peerNode,
peerUser: peerUser,
lb: pln.lb,
}
httpServer := &http.Server{
Handler: pas,
}
go httpServer.Serve(&oneConnListener{Listener: pln.ln, conn: c})
}
}
type oneConnListener struct {
net.Listener
conn net.Conn
}
func (l *oneConnListener) Accept() (c net.Conn, err error) {
c = l.conn
if c == nil {
err = io.EOF
return
}
err = nil
l.conn = nil
return
}
func (l *oneConnListener) Close() error { return nil }
type peerAPIServer struct {
remoteAddr netaddr.IPPort
peerNode *tailcfg.Node
peerUser tailcfg.UserProfile
lb *LocalBackend
}
func (s *peerAPIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `<html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<body>
<h1>Hello, %s (%v)</h1>
This is my Tailscale device. Your device is %v.
`, html.EscapeString(s.peerUser.DisplayName), s.remoteAddr.IP, html.EscapeString(s.peerNode.ComputedName))
}

@ -87,6 +87,8 @@ type PeerStatus struct {
KeepAlive bool
ExitNode bool // true if this is the currently selected exit node.
PeerAPIURL []string
// ShareeNode indicates this node exists in the netmap because
// it's owned by a shared-to user and that node might connect
// to us. These nodes should be hidden by "tailscale status"
@ -112,28 +114,16 @@ type StatusBuilder struct {
st Status
}
func (sb *StatusBuilder) SetVersion(v string) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.Version = v
}
func (sb *StatusBuilder) SetBackendState(v string) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.BackendState = v
}
func (sb *StatusBuilder) SetAuthURL(v string) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.AuthURL = v
}
func (sb *StatusBuilder) SetMagicDNSSuffix(v string) {
// MutateStatus calls f with the status to mutate.
//
// It may not assume other fields of status are already populated, and
// may not retain or write to the Status after f returns.
//
// MutateStatus acquires a lock so f must not call back into sb.
func (sb *StatusBuilder) MutateStatus(f func(*Status)) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.MagicDNSSuffix = v
f(&sb.st)
}
func (sb *StatusBuilder) Status() *Status {
@ -143,11 +133,19 @@ func (sb *StatusBuilder) Status() *Status {
return &sb.st
}
// SetSelfStatus sets the status of the local machine.
func (sb *StatusBuilder) SetSelfStatus(ss *PeerStatus) {
// MutateSelfStatus calls f with the PeerStatus of our own node to mutate.
//
// It may not assume other fields of status are already populated, and
// may not retain or write to the Status after f returns.
//
// MutateStatus acquires a lock so f must not call back into sb.
func (sb *StatusBuilder) MutateSelfStatus(f func(*PeerStatus)) {
sb.mu.Lock()
defer sb.mu.Unlock()
sb.st.Self = ss
if sb.st.Self == nil {
sb.st.Self = new(PeerStatus)
}
f(sb.st.Self)
}
// AddUser adds a user profile to the status.

@ -2985,25 +2985,7 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
c.mu.Lock()
defer c.mu.Unlock()
ss := &ipnstate.PeerStatus{
PublicKey: c.privateKey.Public(),
Addrs: c.lastEndpoints,
OS: version.OS(),
}
if c.netMap != nil {
ss.HostName = c.netMap.Hostinfo.Hostname
ss.DNSName = c.netMap.Name
ss.UserID = c.netMap.User
} else {
ss.HostName, _ = os.Hostname()
}
if c.derpMap != nil {
derpRegion, ok := c.derpMap.Regions[c.myDerp]
if ok {
ss.Relay = derpRegion.RegionCode
}
}
var tailAddr string
if c.netMap != nil {
for _, addr := range c.netMap.Addresses {
if !addr.IsSingleIP() {
@ -3014,11 +2996,30 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
// readability of `tailscale status`, make it the IPv4
// address.
if addr.IP.Is4() {
ss.TailAddr = addr.IP.String()
tailAddr = addr.IP.String()
}
}
}
sb.SetSelfStatus(ss)
sb.MutateSelfStatus(func(ss *ipnstate.PeerStatus) {
ss.PublicKey = c.privateKey.Public()
ss.Addrs = c.lastEndpoints
ss.OS = version.OS()
if c.netMap != nil {
ss.HostName = c.netMap.Hostinfo.Hostname
ss.DNSName = c.netMap.Name
ss.UserID = c.netMap.User
} else {
ss.HostName, _ = os.Hostname()
}
if c.derpMap != nil {
derpRegion, ok := c.derpMap.Regions[c.myDerp]
if ok {
ss.Relay = derpRegion.RegionCode
}
}
ss.TailAddr = tailAddr
})
for dk, n := range c.nodeOfDisco {
ps := &ipnstate.PeerStatus{InMagicSock: true}

Loading…
Cancel
Save