|
|
|
@@ -2,6 +2,12 @@
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
// Package local contains a Go client for the Tailscale LocalAPI.
|
|
|
|
|
//
|
|
|
|
|
// The APIs in this package vary in maturity: some methods are considered
|
|
|
|
|
// stable APIs and are documented as such, while others are not necessarily
|
|
|
|
|
// stable and are subject to change between releases. Methods without an
|
|
|
|
|
// explicit "API maturity" note in their documentation should be assumed
|
|
|
|
|
// to be unstable.
|
|
|
|
|
package local
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
@@ -135,6 +141,9 @@ func (lc *Client) defaultDialer(ctx context.Context, network, addr string) (net.
|
|
|
|
|
// authenticating to the local Tailscale daemon vary by platform.
|
|
|
|
|
//
|
|
|
|
|
// DoLocalRequest may mutate the request to add Authorization headers.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DoLocalRequest(req *http.Request) (*http.Response, error) {
|
|
|
|
|
req.Header.Set("Tailscale-Cap", strconv.Itoa(int(tailcfg.CurrentCapabilityVersion)))
|
|
|
|
|
lc.tsClientOnce.Do(func() {
|
|
|
|
@@ -317,6 +326,8 @@ func decodeJSON[T any](b []byte) (ret T, err error) {
|
|
|
|
|
// For connections proxied by tailscaled, this looks up the owner of the given
|
|
|
|
|
// address as TCP first, falling back to UDP; if you want to only check a
|
|
|
|
|
// specific address family, use WhoIsProto.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsResponse, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/whois?addr="+url.QueryEscape(remoteAddr))
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -331,6 +342,8 @@ func (lc *Client) WhoIs(ctx context.Context, remoteAddr string) (*apitype.WhoIsR
|
|
|
|
|
// WhoIsForService is like [Client.WhoIs] but scopes the returned CapMap to
|
|
|
|
|
// capabilities that apply to the named VIP service. This enables per-service
|
|
|
|
|
// capability resolution on hosts that advertise multiple VIP services.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) WhoIsForService(ctx context.Context, remoteAddr string, svcName tailcfg.ServiceName) (*apitype.WhoIsResponse, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/whois?addr="+url.QueryEscape(remoteAddr)+"&svc_name="+url.QueryEscape(string(svcName)))
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -346,6 +359,8 @@ func (lc *Client) WhoIsForService(ctx context.Context, remoteAddr string, svcNam
|
|
|
|
|
// capabilities that apply to the given destination IP. The IP may be a
|
|
|
|
|
// VIP service address, the node's own tailnet address, or any other
|
|
|
|
|
// routable IP the node handles.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) WhoIsForIP(ctx context.Context, remoteAddr string, dst netip.Addr) (*apitype.WhoIsResponse, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/whois?addr="+url.QueryEscape(remoteAddr)+"&dst_ip="+url.QueryEscape(dst.String()))
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -364,6 +379,8 @@ var ErrPeerNotFound = errors.New("peer not found")
|
|
|
|
|
// WhoIsNodeKey returns the owner of the given wireguard public key.
|
|
|
|
|
//
|
|
|
|
|
// If not found, the error is ErrPeerNotFound.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) WhoIsNodeKey(ctx context.Context, key key.NodePublic) (*apitype.WhoIsResponse, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/whois?addr="+url.QueryEscape(key.String()))
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -379,6 +396,8 @@ func (lc *Client) WhoIsNodeKey(ctx context.Context, key key.NodePublic) (*apityp
|
|
|
|
|
// IP:port, for the given protocol (tcp or udp).
|
|
|
|
|
//
|
|
|
|
|
// If not found, the error is [ErrPeerNotFound].
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) WhoIsProto(ctx context.Context, proto, remoteAddr string) (*apitype.WhoIsResponse, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/whois?proto="+url.QueryEscape(proto)+"&addr="+url.QueryEscape(remoteAddr))
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -455,6 +474,9 @@ func (lc *Client) SetGauge(ctx context.Context, name string, value int) error {
|
|
|
|
|
|
|
|
|
|
// TailDaemonLogs returns a stream the Tailscale daemon's logs as they arrive.
|
|
|
|
|
// Close the context to stop the stream.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) TailDaemonLogs(ctx context.Context) (io.Reader, error) {
|
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+apitype.LocalAPIHost+"/localapi/v0/logtap", nil)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -471,12 +493,18 @@ func (lc *Client) TailDaemonLogs(ctx context.Context) (io.Reader, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EventBusGraph returns a graph of active publishers and subscribers in the eventbus
|
|
|
|
|
// as a [eventbus.DebugTopics]
|
|
|
|
|
// as a [eventbus.DebugTopics].
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) EventBusGraph(ctx context.Context) ([]byte, error) {
|
|
|
|
|
return lc.get200(ctx, "/localapi/v0/debug-bus-graph")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EventBusQueues returns a JSON snapshot of event bus queue depths per client.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) EventBusQueues(ctx context.Context) ([]byte, error) {
|
|
|
|
|
return lc.get200(ctx, "/localapi/v0/debug-bus-queues")
|
|
|
|
|
}
|
|
|
|
@@ -485,6 +513,9 @@ func (lc *Client) EventBusQueues(ctx context.Context) ([]byte, error) {
|
|
|
|
|
// Each pair is a valid event and a nil error, or a zero event a non-nil error.
|
|
|
|
|
// In case of error, the iterator ends after the pair reporting the error.
|
|
|
|
|
// Iteration stops if ctx ends.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) StreamBusEvents(ctx context.Context) iter.Seq2[eventbus.DebugEvent, error] {
|
|
|
|
|
return func(yield func(eventbus.DebugEvent, error) bool) {
|
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET",
|
|
|
|
@@ -553,6 +584,8 @@ type BugReportOpts struct {
|
|
|
|
|
//
|
|
|
|
|
// The opts type specifies options to pass to the Tailscale daemon when
|
|
|
|
|
// generating this bug report.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) BugReportWithOpts(ctx context.Context, opts BugReportOpts) (string, error) {
|
|
|
|
|
qparams := make(url.Values)
|
|
|
|
|
if opts.Note != "" {
|
|
|
|
@@ -598,12 +631,17 @@ func (lc *Client) BugReportWithOpts(ctx context.Context, opts BugReportOpts) (st
|
|
|
|
|
//
|
|
|
|
|
// This is the same as calling [Client.BugReportWithOpts] and only specifying the Note
|
|
|
|
|
// field.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) BugReport(ctx context.Context, note string) (string, error) {
|
|
|
|
|
return lc.BugReportWithOpts(ctx, BugReportOpts{Note: note})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DebugAction invokes a debug action, such as "rebind" or "restun".
|
|
|
|
|
// These are development tools and subject to change or removal over time.
|
|
|
|
|
// These are development tools.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change or removal between releases.
|
|
|
|
|
func (lc *Client) DebugAction(ctx context.Context, action string) error {
|
|
|
|
|
body, err := lc.send(ctx, "POST", "/localapi/v0/debug?action="+url.QueryEscape(action), 200, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -614,7 +652,10 @@ func (lc *Client) DebugAction(ctx context.Context, action string) error {
|
|
|
|
|
|
|
|
|
|
// DebugActionBody invokes a debug action with a body parameter, such as
|
|
|
|
|
// "debug-force-prefer-derp".
|
|
|
|
|
// These are development tools and subject to change or removal over time.
|
|
|
|
|
// These are development tools.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change or removal between releases.
|
|
|
|
|
func (lc *Client) DebugActionBody(ctx context.Context, action string, rbody io.Reader) error {
|
|
|
|
|
body, err := lc.send(ctx, "POST", "/localapi/v0/debug?action="+url.QueryEscape(action), 200, rbody)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -624,7 +665,10 @@ func (lc *Client) DebugActionBody(ctx context.Context, action string, rbody io.R
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DebugResultJSON invokes a debug action and returns its result as something JSON-able.
|
|
|
|
|
// These are development tools and subject to change or removal over time.
|
|
|
|
|
// These are development tools.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change or removal between releases.
|
|
|
|
|
func (lc *Client) DebugResultJSON(ctx context.Context, action string) (any, error) {
|
|
|
|
|
body, err := lc.send(ctx, "POST", "/localapi/v0/debug?action="+url.QueryEscape(action), 200, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -642,7 +686,10 @@ func (lc *Client) DebugResultJSON(ctx context.Context, action string) (any, erro
|
|
|
|
|
// callers of [Client.DebugResultJSON] otherwise need to do to get a typed
|
|
|
|
|
// value.
|
|
|
|
|
//
|
|
|
|
|
// These are development tools and subject to change or removal over time.
|
|
|
|
|
// These are development tools.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this function is not considered a stable API and is
|
|
|
|
|
// subject to change or removal between releases.
|
|
|
|
|
func GetDebugResultJSON[T any](ctx context.Context, lc *Client, action string) (T, error) {
|
|
|
|
|
var v T
|
|
|
|
|
body, err := lc.send(ctx, "POST", "/localapi/v0/debug?action="+url.QueryEscape(action), 200, nil)
|
|
|
|
@@ -684,6 +731,9 @@ func (lc *Client) SetDevStoreKeyValue(ctx context.Context, key, value string) er
|
|
|
|
|
// SetComponentDebugLogging sets component's debug logging enabled for
|
|
|
|
|
// the provided duration. If the duration is in the past, the debug logging
|
|
|
|
|
// is disabled.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) SetComponentDebugLogging(ctx context.Context, component string, d time.Duration) error {
|
|
|
|
|
if !buildfeatures.HasDebug {
|
|
|
|
|
return feature.ErrUnavailable
|
|
|
|
@@ -712,6 +762,8 @@ func Status(ctx context.Context) (*ipnstate.Status, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Status returns the Tailscale daemon's status.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) Status(ctx context.Context) (*ipnstate.Status, error) {
|
|
|
|
|
return lc.status(ctx, "")
|
|
|
|
|
}
|
|
|
|
@@ -722,6 +774,8 @@ func StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// StatusWithoutPeers returns the Tailscale daemon's status, without the peer info.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) StatusWithoutPeers(ctx context.Context) (*ipnstate.Status, error) {
|
|
|
|
|
return lc.status(ctx, "?peers=false")
|
|
|
|
|
}
|
|
|
|
@@ -826,6 +880,9 @@ func (lc *Client) PushFile(ctx context.Context, target tailcfg.StableNodeID, siz
|
|
|
|
|
// CheckIPForwarding asks the local Tailscale daemon whether it looks like the
|
|
|
|
|
// machine is properly configured to forward IP packets as a subnet router
|
|
|
|
|
// or exit node.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) CheckIPForwarding(ctx context.Context) error {
|
|
|
|
|
if !buildfeatures.HasAdvertiseRoutes {
|
|
|
|
|
return nil
|
|
|
|
@@ -849,6 +906,9 @@ func (lc *Client) CheckIPForwarding(ctx context.Context) error {
|
|
|
|
|
// CheckUDPGROForwarding asks the local Tailscale daemon whether it looks like
|
|
|
|
|
// the machine is optimally configured to forward UDP packets as a subnet router
|
|
|
|
|
// or exit node.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) CheckUDPGROForwarding(ctx context.Context) error {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/check-udp-gro-forwarding")
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -898,6 +958,9 @@ func (lc *Client) CheckPrefs(ctx context.Context, p *ipn.Prefs) error {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetPrefs returns the [ipn.Prefs] of the current Tailscale profile.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) GetPrefs(ctx context.Context) (*ipn.Prefs, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/prefs")
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -915,6 +978,8 @@ func (lc *Client) GetPrefs(ctx context.Context) (*ipn.Prefs, error) {
|
|
|
|
|
// or a policy restriction. An optional reason or justification for the request can be
|
|
|
|
|
// provided as a context value using [apitype.RequestReasonKey]. If permitted by policy,
|
|
|
|
|
// access may be granted, and the reason will be logged for auditing purposes.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Prefs, error) {
|
|
|
|
|
body, err := lc.send(ctx, "PATCH", "/localapi/v0/prefs", http.StatusOK, jsonBody(mp))
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -925,6 +990,9 @@ func (lc *Client) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn.Pref
|
|
|
|
|
|
|
|
|
|
// GetDNSOSConfig returns the system DNS configuration for the current device.
|
|
|
|
|
// That is, it returns the DNS configuration that the system would use if Tailscale weren't being used.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) GetDNSOSConfig(ctx context.Context) (*apitype.DNSOSConfig, error) {
|
|
|
|
|
if !buildfeatures.HasDNS {
|
|
|
|
|
return nil, feature.ErrUnavailable
|
|
|
|
@@ -958,7 +1026,26 @@ func (lc *Client) QueryDNS(ctx context.Context, name string, queryType string) (
|
|
|
|
|
return res.Bytes, res.Resolvers, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// StartLoginInteractive starts an interactive login.
|
|
|
|
|
// StartLoginInteractive starts an interactive login, requesting a new
|
|
|
|
|
// auth URL from the control plane if a login flow is not already in
|
|
|
|
|
// progress. If one is, the existing auth URL is re-sent.
|
|
|
|
|
//
|
|
|
|
|
// The auth URL is not returned by this method; it is delivered
|
|
|
|
|
// asynchronously to IPN bus watchers (see [Client.WatchIPNBus]) as an
|
|
|
|
|
// [ipn.Notify] with a non-empty BrowseToURL field. StartLoginInteractive
|
|
|
|
|
// returns as soon as the login has been requested; it does not wait for
|
|
|
|
|
// the login to complete.
|
|
|
|
|
//
|
|
|
|
|
// Calling StartLoginInteractive does not itself change the node's
|
|
|
|
|
// desired run state, but successfully completing the login does: the
|
|
|
|
|
// node's WantRunning pref is set to true, so a stopped node
|
|
|
|
|
// ("tailscale down") starts once the login finishes. If the login is
|
|
|
|
|
// completed as a different user or node identity than the current
|
|
|
|
|
// profile's, the node switches to an existing profile matching the new
|
|
|
|
|
// identity if one exists, or else updates the current profile to the
|
|
|
|
|
// new identity.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) StartLoginInteractive(ctx context.Context) error {
|
|
|
|
|
_, err := lc.send(ctx, "POST", "/localapi/v0/login-interactive", http.StatusNoContent, nil)
|
|
|
|
|
return err
|
|
|
|
@@ -983,6 +1070,8 @@ func (lc *Client) Logout(ctx context.Context) error {
|
|
|
|
|
// tailscaled), a FQDN, or an IP address.
|
|
|
|
|
//
|
|
|
|
|
// The ctx is only used for the duration of the call, not the lifetime of the [net.Conn].
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) DialTCP(ctx context.Context, host string, port uint16) (net.Conn, error) {
|
|
|
|
|
return lc.UserDial(ctx, "tcp", host, port)
|
|
|
|
|
}
|
|
|
|
@@ -994,6 +1083,8 @@ func (lc *Client) DialTCP(ctx context.Context, host string, port uint16) (net.Co
|
|
|
|
|
//
|
|
|
|
|
// The ctx is only used for the duration of the call, not the lifetime of the
|
|
|
|
|
// [net.Conn].
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) UserDial(ctx context.Context, network, host string, port uint16) (net.Conn, error) {
|
|
|
|
|
connCh := make(chan net.Conn, 1)
|
|
|
|
|
trace := httptrace.ClientTrace{
|
|
|
|
@@ -1058,6 +1149,10 @@ func (lc *Client) UserDial(ctx context.Context, network, host string, port uint1
|
|
|
|
|
|
|
|
|
|
// CurrentDERPMap returns the current DERPMap that is being used by the local tailscaled.
|
|
|
|
|
// It is intended to be used with netcheck to see availability of DERPs.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API, though the returned
|
|
|
|
|
// [tailcfg.DERPMap] type is subject to minor changes over time; its
|
|
|
|
|
// general shape is stable.
|
|
|
|
|
func (lc *Client) CurrentDERPMap(ctx context.Context) (*tailcfg.DERPMap, error) {
|
|
|
|
|
var derpMap tailcfg.DERPMap
|
|
|
|
|
res, err := lc.send(ctx, "GET", "/localapi/v0/derpmap", 200, nil)
|
|
|
|
@@ -1074,6 +1169,8 @@ func (lc *Client) CurrentDERPMap(ctx context.Context) (*tailcfg.DERPMap, error)
|
|
|
|
|
// fetch TLS certificates, equivalent to the DNS.CertDomains field of the
|
|
|
|
|
// current netmap. The returned list is sorted in ascending order, and is
|
|
|
|
|
// empty if no netmap has been received yet.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) CertDomains(ctx context.Context) ([]string, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/cert-domains")
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1118,6 +1215,8 @@ func (lc *Client) PeerByID(ctx context.Context, id tailcfg.NodeID) (*tailcfg.Nod
|
|
|
|
|
// referenced by a peer Node and want to resolve it to a UserProfile. Sessions
|
|
|
|
|
// opted in to [ipn.NotifyPeerChanges] / [ipn.NotifyPeerPatches] also receive
|
|
|
|
|
// UserProfiles automatically via [ipn.Notify.UserProfiles].
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) UserProfile(ctx context.Context, id tailcfg.UserID) (*tailcfg.UserProfile, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/user-profile?id="+strconv.FormatInt(int64(id), 10))
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1162,6 +1261,8 @@ func (lc *Client) Ping(ctx context.Context, ip netip.Addr, pingtype tailcfg.Ping
|
|
|
|
|
// DisconnectControl shuts down all connections to control, thus making control consider this node inactive. This can be
|
|
|
|
|
// run on HA subnet router or app connector replicas before shutting them down to ensure peers get told to switch over
|
|
|
|
|
// to another replica whilst there is still some grace period for the existing connections to terminate.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) DisconnectControl(ctx context.Context) error {
|
|
|
|
|
_, _, err := lc.sendWithHeaders(ctx, "POST", "/localapi/v0/disconnect-control", 200, nil, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1257,13 +1358,18 @@ func (lc *Client) ReloadConfig(ctx context.Context) (ok bool, err error) {
|
|
|
|
|
|
|
|
|
|
// SwitchToEmptyProfile creates and switches to a new unnamed profile. The new
|
|
|
|
|
// profile is not assigned an ID until it is persisted after a successful login.
|
|
|
|
|
// In order to login to the new profile, the user must call LoginInteractive.
|
|
|
|
|
// In order to login to the new profile, the user must call
|
|
|
|
|
// [Client.StartLoginInteractive].
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) SwitchToEmptyProfile(ctx context.Context) error {
|
|
|
|
|
_, err := lc.send(ctx, "PUT", "/localapi/v0/profiles/", http.StatusCreated, nil)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SwitchProfile switches to the given profile.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) SwitchProfile(ctx context.Context, profile ipn.ProfileID) error {
|
|
|
|
|
_, err := lc.send(ctx, "POST", "/localapi/v0/profiles/"+url.PathEscape(string(profile)), 204, nil)
|
|
|
|
|
return err
|
|
|
|
@@ -1298,6 +1404,11 @@ func (lc *Client) QueryFeature(ctx context.Context, feature string) (*tailcfg.Qu
|
|
|
|
|
return decodeJSON[*tailcfg.QueryFeatureResponse](body)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DebugDERPRegion reports diagnostic information about the DERP region with
|
|
|
|
|
// the given ID or code.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DebugDERPRegion(ctx context.Context, regionIDOrCode string) (*ipnstate.DebugDERPRegionReport, error) {
|
|
|
|
|
v := url.Values{"region": {regionIDOrCode}}
|
|
|
|
|
body, err := lc.send(ctx, "POST", "/localapi/v0/debug-derp-region?"+v.Encode(), 200, nil)
|
|
|
|
@@ -1308,6 +1419,9 @@ func (lc *Client) DebugDERPRegion(ctx context.Context, regionIDOrCode string) (*
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DebugPacketFilterRules returns the packet filter rules for the current device.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DebugPacketFilterRules(ctx context.Context) ([]tailcfg.FilterRule, error) {
|
|
|
|
|
body, err := lc.send(ctx, "POST", "/localapi/v0/debug-packet-filter-rules", 200, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1319,6 +1433,9 @@ func (lc *Client) DebugPacketFilterRules(ctx context.Context) ([]tailcfg.FilterR
|
|
|
|
|
// DebugSetExpireIn marks the current node key to expire in d.
|
|
|
|
|
//
|
|
|
|
|
// This is meant primarily for debug and testing.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DebugSetExpireIn(ctx context.Context, d time.Duration) error {
|
|
|
|
|
v := url.Values{"expiry": {fmt.Sprint(time.Now().Add(d).Unix())}}
|
|
|
|
|
_, err := lc.send(ctx, "POST", "/localapi/v0/set-expiry-sooner?"+v.Encode(), 200, nil)
|
|
|
|
@@ -1327,6 +1444,9 @@ func (lc *Client) DebugSetExpireIn(ctx context.Context, d time.Duration) error {
|
|
|
|
|
|
|
|
|
|
// DebugPeerRelaySessions returns debug information about the current peer
|
|
|
|
|
// relay sessions running through this node.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DebugPeerRelaySessions(ctx context.Context) (*status.ServerStatus, error) {
|
|
|
|
|
body, err := lc.send(ctx, "GET", "/localapi/v0/debug-peer-relay-sessions", 200, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1339,6 +1459,9 @@ func (lc *Client) DebugPeerRelaySessions(ctx context.Context) (*status.ServerSta
|
|
|
|
|
//
|
|
|
|
|
// The provided context does not determine the lifetime of the
|
|
|
|
|
// returned [io.ReadCloser].
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) StreamDebugCapture(ctx context.Context) (io.ReadCloser, error) {
|
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", "http://"+apitype.LocalAPIHost+"/localapi/v0/debug-capture", nil)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1365,6 +1488,9 @@ func (lc *Client) StreamDebugCapture(ctx context.Context) (io.ReadCloser, error)
|
|
|
|
|
// resources.
|
|
|
|
|
//
|
|
|
|
|
// A default set of ipn.Notify messages are returned but the set can be modified by mask.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (*IPNBusWatcher, error) {
|
|
|
|
|
m, err := mask.MarshalText()
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1395,6 +1521,8 @@ func (lc *Client) WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (*IP
|
|
|
|
|
// CheckUpdate returns a [*tailcfg.ClientVersion] indicating whether or not an update is available
|
|
|
|
|
// to be installed via the LocalAPI. In case the LocalAPI can't install updates, it returns a
|
|
|
|
|
// ClientVersion that says that we are up to date.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) CheckUpdate(ctx context.Context) (*tailcfg.ClientVersion, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/update/check")
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1411,6 +1539,8 @@ func (lc *Client) CheckUpdate(ctx context.Context) (*tailcfg.ClientVersion, erro
|
|
|
|
|
// To turn it on, there must have been a previously used exit node.
|
|
|
|
|
// The most previously used one is reused.
|
|
|
|
|
// This is a convenience method for GUIs. To select an actual one, update the prefs.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this is considered a stable API.
|
|
|
|
|
func (lc *Client) SetUseExitNode(ctx context.Context, on bool) error {
|
|
|
|
|
_, err := lc.send(ctx, "POST", "/localapi/v0/set-use-exit-node-enabled?enabled="+strconv.FormatBool(on), http.StatusOK, nil)
|
|
|
|
|
return err
|
|
|
|
@@ -1419,6 +1549,9 @@ func (lc *Client) SetUseExitNode(ctx context.Context, on bool) error {
|
|
|
|
|
// DriveSetServerAddr instructs Taildrive to use the server at addr to access
|
|
|
|
|
// the filesystem. This is used on platforms like Windows and MacOS to let
|
|
|
|
|
// Taildrive know to use the file server running in the GUI app.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DriveSetServerAddr(ctx context.Context, addr string) error {
|
|
|
|
|
_, err := lc.send(ctx, "PUT", "/localapi/v0/drive/fileserver-address", http.StatusCreated, strings.NewReader(addr))
|
|
|
|
|
return err
|
|
|
|
@@ -1427,6 +1560,9 @@ func (lc *Client) DriveSetServerAddr(ctx context.Context, addr string) error {
|
|
|
|
|
// DriveShareSet adds or updates the given share in the list of shares that
|
|
|
|
|
// Taildrive will serve to remote nodes. If a share with the same name already
|
|
|
|
|
// exists, the existing share is replaced/updated.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DriveShareSet(ctx context.Context, share *drive.Share) error {
|
|
|
|
|
_, err := lc.send(ctx, "PUT", "/localapi/v0/drive/shares", http.StatusCreated, jsonBody(share))
|
|
|
|
|
return err
|
|
|
|
@@ -1434,6 +1570,9 @@ func (lc *Client) DriveShareSet(ctx context.Context, share *drive.Share) error {
|
|
|
|
|
|
|
|
|
|
// DriveShareRemove removes the share with the given name from the list of
|
|
|
|
|
// shares that Taildrive will serve to remote nodes.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DriveShareRemove(ctx context.Context, name string) error {
|
|
|
|
|
_, err := lc.send(
|
|
|
|
|
ctx,
|
|
|
|
@@ -1445,6 +1584,9 @@ func (lc *Client) DriveShareRemove(ctx context.Context, name string) error {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DriveShareRename renames the share from old to new name.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DriveShareRename(ctx context.Context, oldName, newName string) error {
|
|
|
|
|
_, err := lc.send(
|
|
|
|
|
ctx,
|
|
|
|
@@ -1457,6 +1599,9 @@ func (lc *Client) DriveShareRename(ctx context.Context, oldName, newName string)
|
|
|
|
|
|
|
|
|
|
// DriveShareList returns the list of shares that drive is currently serving
|
|
|
|
|
// to remote nodes.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) DriveShareList(ctx context.Context) ([]*drive.Share, error) {
|
|
|
|
|
result, err := lc.get200(ctx, "/localapi/v0/drive/shares")
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1529,6 +1674,9 @@ func (lc *Client) SuggestExitNodeWithProbe(ctx context.Context) (apitype.ExitNod
|
|
|
|
|
|
|
|
|
|
// CheckSOMarkInUse reports whether the socket mark option is in use. This will only
|
|
|
|
|
// be true if tailscale is running on Linux and tailscaled uses SO_MARK.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) CheckSOMarkInUse(ctx context.Context) (bool, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/check-so-mark-in-use")
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1545,11 +1693,19 @@ func (lc *Client) CheckSOMarkInUse(ctx context.Context) (bool, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ShutdownTailscaled requests a graceful shutdown of tailscaled.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) ShutdownTailscaled(ctx context.Context) error {
|
|
|
|
|
_, err := lc.send(ctx, "POST", "/localapi/v0/shutdown", 200, nil)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetAppConnectorRouteInfo returns the current [appctype.RouteInfo] for this
|
|
|
|
|
// node's app connector.
|
|
|
|
|
//
|
|
|
|
|
// API maturity: this method is not considered a stable API and is
|
|
|
|
|
// subject to change between releases.
|
|
|
|
|
func (lc *Client) GetAppConnectorRouteInfo(ctx context.Context) (appctype.RouteInfo, error) {
|
|
|
|
|
body, err := lc.get200(ctx, "/localapi/v0/appc-route-info")
|
|
|
|
|
if err != nil {
|
|
|
|
|