controlclient

package
v0.0.0-...-113f59a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 16, 2024 License: BSD-3-Clause Imports: 70 Imported by: 0

Documentation

Overview

Package controlclient implements the client for the Tailscale control plane.

It handles authentication, port picking, and collects the local network configuration.

Index

Constants

View Source
const (
	LoginDefault     = LoginFlags(0)
	LoginInteractive = LoginFlags(1 << iota) // force user login and key refresh
	LoginEphemeral                           // set RegisterRequest.Ephemeral

	// LocalBackendStartKeyOSNeutral instructs NewLocalBackend to start the
	// LocalBackend without any OS-dependent StateStore StartKey behavior.
	//
	// See https://github.com/tailscale/tailscale/issues/6973.
	LocalBackendStartKeyOSNeutral
)
View Source
const (
	StateNew = State(iota)
	StateNotAuthenticated
	StateAuthenticating
	StateURLVisitRequired
	StateAuthenticated
	StateSynchronized // connected and received map update
)

Variables

View Source
var DevKnob = initDevKnob()

DevKnob contains temporary internal-only debug knobs. They're unexported to not draw attention to them.

View Source
var ErrClientClosed = errors.New("client closed")

Functions

func HashRegisterRequest

func HashRegisterRequest(
	version tailcfg.SignatureType, ts time.Time, serverURL string, deviceCert []byte,
	serverPubKey, machinePubKey key.MachinePublic) ([]byte, error)

HashRegisterRequest generates the hash required sign or verify a tailcfg.RegisterRequest.

Types

type Auto

type Auto struct {
	// contains filtered or unexported fields
}

Auto connects to a tailcontrol server for a node. It's a concrete implementation of the Client interface.

func New

func New(opts Options) (*Auto, error)

New creates and starts a new Auto.

func NewNoStart

func NewNoStart(opts Options) (_ *Auto, err error)

NewNoStart creates a new Auto, but without calling Start on it.

func (*Auto) AuthCantContinue

func (c *Auto) AuthCantContinue() bool

func (*Auto) DirectForTest

func (c *Auto) DirectForTest() *Direct

DirectForTest returns the underlying direct client object. It's used in tests only.

func (*Auto) DoNoiseRequest

func (c *Auto) DoNoiseRequest(req *http.Request) (*http.Response, error)

func (*Auto) ExpiryForTests

func (c *Auto) ExpiryForTests() time.Time

ExpiryForTests returns the credential expiration time, or the zero value if the expiration time isn't known. It's used in tests only.

func (*Auto) GetSingleUseNoiseRoundTripper

func (c *Auto) GetSingleUseNoiseRoundTripper(ctx context.Context) (http.RoundTripper, *tailcfg.EarlyNoise, error)

GetSingleUseNoiseRoundTripper returns a RoundTripper that can be only be used once (and must be used once) to make a single HTTP request over the noise channel to the coordination server.

In addition to the RoundTripper, it returns the HTTP/2 channel's early noise payload, if any.

func (*Auto) Login

func (c *Auto) Login(t *tailcfg.Oauth2Token, flags LoginFlags)

func (*Auto) Logout

func (c *Auto) Logout(ctx context.Context) error

func (*Auto) SetDNS

func (c *Auto) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) error

SetDNS sends the SetDNSRequest request to the control plane server, requesting a DNS record be created or updated.

func (*Auto) SetExpirySooner

func (c *Auto) SetExpirySooner(ctx context.Context, expiry time.Time) error

func (*Auto) SetHostinfo

func (c *Auto) SetHostinfo(hi *tailcfg.Hostinfo)

func (*Auto) SetNetInfo

func (c *Auto) SetNetInfo(ni *tailcfg.NetInfo)

func (*Auto) SetPaused

func (c *Auto) SetPaused(paused bool)

SetPaused controls whether HTTP activity should be paused.

The client can be paused and unpaused repeatedly, unlike Start and Shutdown, which can only be used once.

func (*Auto) SetTKAHead

func (c *Auto) SetTKAHead(headHash string)

SetTKAHead updates the TKA head hash that map-request infrastructure sends.

func (*Auto) Shutdown

func (c *Auto) Shutdown()

func (*Auto) Start

func (c *Auto) Start()

Start starts the client's goroutines.

It should only be called for clients created by NewNoStart.

func (*Auto) TestOnlyNodePublicKey

func (c *Auto) TestOnlyNodePublicKey() key.NodePublic

NodePublicKey returns the node public key currently in use. This is used exclusively in tests.

func (*Auto) TestOnlySetAuthKey

func (c *Auto) TestOnlySetAuthKey(authkey string)

func (*Auto) TestOnlyTimeNow

func (c *Auto) TestOnlyTimeNow() time.Time

func (*Auto) UpdateEndpoints

func (c *Auto) UpdateEndpoints(endpoints []tailcfg.Endpoint)

UpdateEndpoints sets the client's discovered endpoints and sends them to the control server if they've changed.

It does not retain the provided slice.

type Client

type Client interface {
	// Shutdown closes this session, which should not be used any further
	// afterwards.
	Shutdown()
	// Login begins an interactive or non-interactive login process.
	// Client will eventually call the Status callback with either a
	// LoginFinished flag (on success) or an auth URL (if further
	// interaction is needed).
	Login(*tailcfg.Oauth2Token, LoginFlags)
	// Logout starts a synchronous logout process. It doesn't return
	// until the logout operation has been completed.
	Logout(context.Context) error
	// SetPaused pauses or unpauses the controlclient activity as much
	// as possible, without losing its internal state, to minimize
	// unnecessary network activity.
	// TODO: It might be better to simply shutdown the controlclient and
	// make a new one when it's time to unpause.
	SetPaused(bool)
	// AuthCantContinue returns whether authentication is blocked. If it
	// is, you either need to visit the auth URL (previously sent in a
	// Status callback) or call the Login function appropriately.
	// TODO: this probably belongs in the Status itself instead.
	AuthCantContinue() bool
	// SetHostinfo changes the Hostinfo structure that will be sent in
	// subsequent node registration requests.
	// TODO: a server-side change would let us simply upload this
	// in a separate http request. It has nothing to do with the rest of
	// the state machine.
	SetHostinfo(*tailcfg.Hostinfo)
	// SetNetinfo changes the NetIinfo structure that will be sent in
	// subsequent node registration requests.
	// TODO: a server-side change would let us simply upload this
	// in a separate http request. It has nothing to do with the rest of
	// the state machine.
	SetNetInfo(*tailcfg.NetInfo)
	// SetTKAHead changes the TKA head hash value that will be sent in
	// subsequent netmap requests.
	SetTKAHead(headHash string)
	// UpdateEndpoints changes the Endpoint structure that will be sent
	// in subsequent node registration requests.
	// TODO: a server-side change would let us simply upload this
	// in a separate http request. It has nothing to do with the rest of
	// the state machine.
	UpdateEndpoints(endpoints []tailcfg.Endpoint)
}

Client represents a client connection to the control server. Currently this is done through a pair of polling https requests in the Auto client, but that might change eventually.

The Client must be comparable as it is used by the Observer to detect stale clients.

type ControlDialPlanner

type ControlDialPlanner interface {
	// Load returns the current plan for how to connect to control.
	//
	// The returned plan can be nil. If so, connections should be made by
	// resolving the control URL using DNS.
	Load() *tailcfg.ControlDialPlan

	// Store updates the dial plan with new directions from the control
	// server.
	//
	// The dial plan can span multiple connections to the control server.
	// That is, a dial plan received when connected over Wi-Fi is still
	// valid for a subsequent connection over LTE after a network switch.
	Store(*tailcfg.ControlDialPlan)
}

ControlDialPlanner is the interface optionally supplied when creating a control client to control exactly how TCP connections to the control plane are dialed.

It is usually implemented by an atomic.Pointer.

type Decompressor

type Decompressor interface {
	DecodeAll(input, dst []byte) ([]byte, error)
	Close()
}

type Direct

type Direct struct {
	// contains filtered or unexported fields
}

Direct is the client that connects to a tailcontrol server for a node.

func NewDirect

func NewDirect(opts Options) (*Direct, error)

NewDirect returns a new Direct client.

func (*Direct) Close

func (c *Direct) Close() error

Close closes the underlying Noise connection(s).

func (*Direct) DoNoiseRequest

func (c *Direct) DoNoiseRequest(req *http.Request) (*http.Response, error)

func (*Direct) FetchNetMapForTest

func (c *Direct) FetchNetMapForTest(ctx context.Context) (*netmap.NetworkMap, error)

FetchNetMapForTest fetches the netmap once.

func (*Direct) GetPersist

func (c *Direct) GetPersist() persist.PersistView

func (*Direct) GetSingleUseNoiseRoundTripper

func (c *Direct) GetSingleUseNoiseRoundTripper(ctx context.Context) (http.RoundTripper, *tailcfg.EarlyNoise, error)

GetSingleUseNoiseRoundTripper returns a RoundTripper that can be only be used once (and must be used once) to make a single HTTP request over the noise channel to the coordination server.

In addition to the RoundTripper, it returns the HTTP/2 channel's early noise payload, if any.

func (*Direct) PollNetMap

func (c *Direct) PollNetMap(ctx context.Context, nu NetmapUpdater) error

PollNetMap makes a /map request to download the network map, calling NetmapUpdater on each update from the control plane.

It always returns a non-nil error describing the reason for the failure or why the request ended.

func (*Direct) ReportHealthChange

func (c *Direct) ReportHealthChange(sys health.Subsystem, sysErr error)

ReportHealthChange reports to the control plane a change to this node's health.

func (*Direct) SendUpdate

func (c *Direct) SendUpdate(ctx context.Context) error

SendUpdate makes a /map request to update the server of our latest state, but does not fetch anything. It returns an error if the server did not return a successful 200 OK response.

func (*Direct) SetDNS

func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) (err error)

SetDNS sends the SetDNSRequest request to the control plane server, requesting a DNS record be created or updated.

func (*Direct) SetEndpoints

func (c *Direct) SetEndpoints(endpoints []tailcfg.Endpoint) (changed bool)

SetEndpoints updates the list of locally advertised endpoints. It won't be replicated to the server until a *fresh* call to PollNetMap(). You don't need to restart PollNetMap if we return changed==false.

func (*Direct) SetExpirySooner

func (c *Direct) SetExpirySooner(ctx context.Context, expiry time.Time) error

SetExpirySooner attempts to shorten the expiry to the specified time.

func (*Direct) SetHostinfo

func (c *Direct) SetHostinfo(hi *tailcfg.Hostinfo) bool

SetHostinfo clones the provided Hostinfo and remembers it for the next update. It reports whether the Hostinfo has changed.

func (*Direct) SetNetInfo

func (c *Direct) SetNetInfo(ni *tailcfg.NetInfo) bool

SetNetInfo clones the provided NetInfo and remembers it for the next update. It reports whether the NetInfo has changed.

func (*Direct) SetTKAHead

func (c *Direct) SetTKAHead(tkaHead string) bool

SetNetInfo stores a new TKA head value for next update. It reports whether the TKA head changed.

func (*Direct) TryLogin

func (c *Direct) TryLogin(ctx context.Context, t *tailcfg.Oauth2Token, flags LoginFlags) (url string, err error)

func (*Direct) TryLogout

func (c *Direct) TryLogout(ctx context.Context) error

func (*Direct) WaitLoginURL

func (c *Direct) WaitLoginURL(ctx context.Context, url string) (newURL string, err error)

WaitLoginURL sits in a long poll waiting for the user to authenticate at url.

On success, newURL and err will both be nil.

type LoginFlags

type LoginFlags int

LoginFlags is a bitmask of options to change the behavior of Client.Login and LocalBackend.

type LoginGoal

type LoginGoal struct {
	// contains filtered or unexported fields
}

type NetmapDeltaUpdater

type NetmapDeltaUpdater interface {
	// UpdateNetmapDelta is called with discrete changes to the network map.
	//
	// The ok result is whether the implementation was able to apply the
	// mutations. It might return false if its internal state doesn't
	// support applying them or a NetmapUpdater it's wrapping doesn't
	// implement the NetmapDeltaUpdater optional method.
	UpdateNetmapDelta([]netmap.NodeMutation) (ok bool)
}

NetmapDeltaUpdater is an optional interface that can be implemented by NetmapUpdater implementations to receive delta updates from the controlclient rather than just full updates.

type NetmapUpdater

type NetmapUpdater interface {
	UpdateFullNetmap(*netmap.NetworkMap)
}

NetmapUpdater is the interface needed by the controlclient to enact change in the world as a function of updates received from the network.

type NoiseClient

type NoiseClient struct {
	// Client is an HTTP client to talk to the coordination server.
	// It automatically makes a new Noise connection as needed.
	// It does not support node key proofs. To do that, call
	// noiseClient.getConn instead to make a connection.
	*http.Client
	// contains filtered or unexported fields
}

NoiseClient provides a http.Client to connect to tailcontrol over the ts2021 protocol.

func NewNoiseClient

func NewNoiseClient(opts NoiseOpts) (*NoiseClient, error)

NewNoiseClient returns a new noiseClient for the provided server and machine key. serverURL is of the form https://<host>:<port> (no trailing slash).

netMon may be nil, if non-nil it's used to do faster interface lookups. dialPlan may be nil

func (*NoiseClient) Close

func (nc *NoiseClient) Close() error

Close closes all the underlying noise connections. It is a no-op and returns nil if the connection is already closed.

func (*NoiseClient) GetSingleUseRoundTripper

func (nc *NoiseClient) GetSingleUseRoundTripper(ctx context.Context) (http.RoundTripper, *tailcfg.EarlyNoise, error)

GetSingleUseRoundTripper returns a RoundTripper that can be only be used once (and must be used once) to make a single HTTP request over the noise channel to the coordination server.

In addition to the RoundTripper, it returns the HTTP/2 channel's early noise payload, if any.

func (*NoiseClient) RoundTrip

func (nc *NoiseClient) RoundTrip(req *http.Request) (*http.Response, error)

type NoiseOpts

type NoiseOpts struct {
	// PrivKey is this node's private key.
	PrivKey key.MachinePrivate
	// ServerPubKey is the public key of the server.
	ServerPubKey key.MachinePublic
	// ServerURL is the URL of the server to connect to.
	ServerURL string
	// Dialer's SystemDial function is used to connect to the server.
	Dialer *tsdial.Dialer
	// DNSCache is the caching Resolver to use to connect to the server.
	//
	// This field can be nil.
	DNSCache *dnscache.Resolver
	// Logf is the log function to use. This field can be nil.
	Logf logger.Logf
	// NetMon is the network monitor that, if set, will be used to get the
	// network interface state. This field can be nil; if so, the current
	// state will be looked up dynamically.
	NetMon *netmon.Monitor
	// DialPlan, if set, is a function that should return an explicit plan
	// on how to connect to the server.
	DialPlan func() *tailcfg.ControlDialPlan
}

NoiseOpts contains options for the NewNoiseClient function. All fields are required unless otherwise specified.

type Observer

type Observer interface {
	// SetControlClientStatus is called when the client has a new status to
	// report. The Client is provided to allow the Observer to track which
	// Client is reporting the status, allowing it to ignore stale status
	// reports from previous Clients.
	SetControlClientStatus(Client, Status)
}

Observer is implemented by users of the control client (such as LocalBackend) to get notified of changes in the control client's status.

type Options

type Options struct {
	Persist                    persist.Persist                    // initial persistent data
	GetMachinePrivateKey       func() (key.MachinePrivate, error) // returns the machine key to use
	ServerURL                  string                             // URL of the tailcontrol server
	AuthKey                    string                             // optional node auth key for auto registration
	Clock                      tstime.Clock
	Hostinfo                   *tailcfg.Hostinfo // non-nil passes ownership, nil means to use default using os.Hostname, etc
	DiscoPublicKey             key.DiscoPublic
	Logf                       logger.Logf
	HTTPTestClient             *http.Client                 // optional HTTP client to use (for tests only)
	NoiseTestClient            *http.Client                 // optional HTTP client to use for noise RPCs (tests only)
	DebugFlags                 []string                     // debug settings to send to control
	NetMon                     *netmon.Monitor              // optional network monitor
	PopBrowserURL              func(url string)             // optional func to open browser
	OnClientVersion            func(*tailcfg.ClientVersion) // optional func to inform GUI of client version status
	OnControlTime              func(time.Time)              // optional func to notify callers of new time from control
	OnTailnetDefaultAutoUpdate func(bool)                   // optional func to inform GUI of default auto-update setting for the tailnet
	Dialer                     *tsdial.Dialer               // non-nil
	C2NHandler                 http.Handler                 // or nil
	ControlKnobs               *controlknobs.Knobs          // or nil to ignore

	// Observer is called when there's a change in status to report
	// from the control client.
	Observer Observer

	// SkipIPForwardingCheck declares that the host's IP
	// forwarding works and should not be double-checked by the
	// controlclient package.
	SkipIPForwardingCheck bool

	// Pinger optionally specifies the Pinger to use to satisfy
	// MapResponse.PingRequest queries from the control plane.
	// If nil, PingRequest queries are not answered.
	Pinger Pinger

	// DialPlan contains and stores a previous dial plan that we received
	// from the control server; if nil, we fall back to using DNS.
	//
	// If we receive a new DialPlan from the server, this value will be
	// updated.
	DialPlan ControlDialPlanner
}

type Pinger

type Pinger interface {
	// Ping is a request to do a ping with the peer handling the given IP.
	Ping(ctx context.Context, ip netip.Addr, pingType tailcfg.PingType, size int) (*ipnstate.PingResult, error)
}

Pinger is the LocalBackend.Ping method.

type State

type State int

State is the high-level state of the client. It is used only in unit tests for proper sequencing, don't depend on it anywhere else.

TODO(apenwarr): eliminate the state, as it's now obsolete.

apenwarr: Historical note: controlclient.Auto was originally intended to be the state machine for the whole tailscale client, but that turned out to not be the right abstraction layer, and it moved to ipn.Backend. Since ipn.Backend now has a state machine, it would be much better if controlclient could be a simple stateless API. But the current server-side API (two interlocking polling https calls) makes that very hard to implement. A server side API change could untangle this and remove all the statefulness.

func (State) AppendText

func (s State) AppendText(b []byte) ([]byte, error)

func (State) MarshalText

func (s State) MarshalText() ([]byte, error)

func (State) String

func (s State) String() string

type Status

type Status struct {

	// Err, if non-nil, is an error that occurred while logging in.
	//
	// If it's of type UserVisibleError then it's meant to be shown to users in
	// their Tailscale client. Otherwise it's just logged to tailscaled's logs.
	Err error

	// URL, if non-empty, is the interactive URL to visit to finish logging in.
	URL string

	// NetMap is the latest server-pushed state of the tailnet network.
	NetMap *netmap.NetworkMap

	// Persist, when Valid, is the locally persisted configuration.
	//
	// TODO(bradfitz,maisem): clarify this.
	Persist persist.PersistView
	// contains filtered or unexported fields
}

func (*Status) Equal

func (s *Status) Equal(s2 *Status) bool

Equal reports whether s and s2 are equal.

func (*Status) LoginFinished

func (s *Status) LoginFinished() bool

LoginFinished reports whether the controlclient is in its "StateAuthenticated" state where it's in a happy register state but not yet in a map poll.

TODO(bradfitz): delete this and everything around Status.state.

func (*Status) SetStateForTest

func (s *Status) SetStateForTest(state State)

SetStateForTest sets the internal state of s for tests only.

func (*Status) StateForTest

func (s *Status) StateForTest() State

StateForTest returns the internal state of s for tests only.

func (Status) String

func (s Status) String() string

type UserVisibleError

type UserVisibleError string

UserVisibleError is an error that should be shown to users.

func (UserVisibleError) Error

func (e UserVisibleError) Error() string

func (UserVisibleError) UserVisibleError

func (e UserVisibleError) UserVisibleError() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL