Compare commits
13 Commits
lp
...
bradfitz/b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c23f1aedc | ||
|
|
6ff85846bc | ||
|
|
64d70fb718 | ||
|
|
020cacbe70 | ||
|
|
c3306bfd15 | ||
|
|
23880eb5b0 | ||
|
|
2c8859c2e7 | ||
|
|
3090461961 | ||
|
|
8ba9b558d2 | ||
|
|
8dcbd988f7 | ||
|
|
065825e94c | ||
|
|
01185e436f | ||
|
|
809a6eba80 |
@@ -1327,6 +1327,17 @@ func (lc *LocalClient) SetServeConfig(ctx context.Context, config *ipn.ServeConf
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (lc *LocalClient) DisconnectControl(ctx context.Context) error {
|
||||
_, _, err := lc.sendWithHeaders(ctx, "POST", "/localapi/v0/disconnect-control", 200, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error disconnecting control: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NetworkLockDisable shuts down network-lock across the tailnet.
|
||||
func (lc *LocalClient) NetworkLockDisable(ctx context.Context, secret []byte) error {
|
||||
if _, err := lc.send(ctx, "POST", "/localapi/v0/tka/disable", 200, bytes.NewReader(secret)); err != nil {
|
||||
|
||||
@@ -116,7 +116,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/net/tlsdial/blockblame from tailscale.com/net/tlsdial
|
||||
tailscale.com/net/tsaddr from tailscale.com/ipn+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
||||
tailscale.com/net/wsconn from tailscale.com/cmd/derper+
|
||||
tailscale.com/net/wsconn from tailscale.com/cmd/derper
|
||||
tailscale.com/paths from tailscale.com/client/tailscale
|
||||
💣 tailscale.com/safesocket from tailscale.com/client/tailscale
|
||||
tailscale.com/syncs from tailscale.com/cmd/derper+
|
||||
@@ -140,6 +140,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/types/persist from tailscale.com/ipn
|
||||
tailscale.com/types/preftype from tailscale.com/ipn
|
||||
tailscale.com/types/ptr from tailscale.com/hostinfo+
|
||||
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||
tailscale.com/types/structs from tailscale.com/ipn+
|
||||
tailscale.com/types/tkatype from tailscale.com/client/tailscale+
|
||||
tailscale.com/types/views from tailscale.com/ipn+
|
||||
@@ -154,7 +155,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
tailscale.com/util/fastuuid from tailscale.com/tsweb
|
||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||
tailscale.com/util/httpm from tailscale.com/client/tailscale
|
||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||
tailscale.com/util/lineiter from tailscale.com/hostinfo+
|
||||
L tailscale.com/util/linuxfw from tailscale.com/net/netns
|
||||
tailscale.com/util/mak from tailscale.com/health+
|
||||
tailscale.com/util/multierr from tailscale.com/health+
|
||||
@@ -263,6 +264,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
hash/fnv from google.golang.org/protobuf/internal/detrand
|
||||
hash/maphash from go4.org/mem
|
||||
html from net/http/pprof+
|
||||
html/template from tailscale.com/cmd/derper
|
||||
io from bufio+
|
||||
io/fs from crypto/x509+
|
||||
io/ioutil from github.com/mitchellh/go-ps+
|
||||
@@ -307,6 +309,8 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
||||
sync/atomic from context+
|
||||
syscall from crypto/rand+
|
||||
text/tabwriter from runtime/pprof
|
||||
text/template from html/template
|
||||
text/template/parse from html/template+
|
||||
time from compress/gzip+
|
||||
unicode from bytes+
|
||||
unicode/utf16 from crypto/x509+
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"expvar"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
@@ -212,25 +213,16 @@ func main() {
|
||||
tsweb.AddBrowserHeaders(w)
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(200)
|
||||
io.WriteString(w, `<html><body>
|
||||
<h1>DERP</h1>
|
||||
<p>
|
||||
This is a <a href="https://tailscale.com/">Tailscale</a> DERP server.
|
||||
</p>
|
||||
<p>
|
||||
Documentation:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="https://tailscale.com/kb/1232/derp-servers">About DERP</a></li>
|
||||
<li><a href="https://pkg.go.dev/tailscale.com/derp">Protocol & Go docs</a></li>
|
||||
<li><a href="https://github.com/tailscale/tailscale/tree/main/cmd/derper#derp">How to run a DERP server</a></li>
|
||||
</ul>
|
||||
`)
|
||||
if !*runDERP {
|
||||
io.WriteString(w, `<p>Status: <b>disabled</b></p>`)
|
||||
}
|
||||
if tsweb.AllowDebugAccess(r) {
|
||||
io.WriteString(w, "<p>Debug info at <a href='/debug/'>/debug/</a>.</p>\n")
|
||||
err := homePageTemplate.Execute(w, templateData{
|
||||
ShowAbuseInfo: validProdHostname.MatchString(*hostname),
|
||||
Disabled: !*runDERP,
|
||||
AllowDebug: tsweb.AllowDebugAccess(r),
|
||||
})
|
||||
if err != nil {
|
||||
if r.Context().Err() == nil {
|
||||
log.Printf("homePageTemplate.Execute: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}))
|
||||
mux.Handle("/robots.txt", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -468,3 +460,52 @@ func init() {
|
||||
return 0
|
||||
}))
|
||||
}
|
||||
|
||||
type templateData struct {
|
||||
ShowAbuseInfo bool
|
||||
Disabled bool
|
||||
AllowDebug bool
|
||||
}
|
||||
|
||||
// homePageTemplate renders the home page using [templateData].
|
||||
var homePageTemplate = template.Must(template.New("home").Parse(`<html><body>
|
||||
<h1>DERP</h1>
|
||||
<p>
|
||||
This is a <a href="https://tailscale.com/">Tailscale</a> DERP server.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It provides STUN, interactive connectivity establishment, and relaying of end-to-end encrypted traffic
|
||||
for Tailscale clients.
|
||||
</p>
|
||||
|
||||
{{if .ShowAbuseInfo }}
|
||||
<p>
|
||||
If you suspect abuse, please contact <a href="mailto:security@tailscale.com">security@tailscale.com</a>.
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
<p>
|
||||
Documentation:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
{{if .ShowAbuseInfo }}
|
||||
<li><a href="https://tailscale.com/security-policies">Tailscale Security Policies</a></li>
|
||||
<li><a href="https://tailscale.com/tailscale-aup">Tailscale Acceptable Use Policies</a></li>
|
||||
{{end}}
|
||||
<li><a href="https://tailscale.com/kb/1232/derp-servers">About DERP</a></li>
|
||||
<li><a href="https://pkg.go.dev/tailscale.com/derp">Protocol & Go docs</a></li>
|
||||
<li><a href="https://github.com/tailscale/tailscale/tree/main/cmd/derper#derp">How to run a DERP server</a></li>
|
||||
</ul>
|
||||
|
||||
{{if .Disabled}}
|
||||
<p>Status: <b>disabled</b></p>
|
||||
{{end}}
|
||||
|
||||
{{if .AllowDebug}}
|
||||
<p>Debug info at <a href='/debug/'>/debug/</a>.</p>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
`))
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -110,3 +112,30 @@ func TestDeps(t *testing.T) {
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
||||
func TestTemplate(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
err := homePageTemplate.Execute(buf, templateData{
|
||||
ShowAbuseInfo: true,
|
||||
Disabled: true,
|
||||
AllowDebug: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
str := buf.String()
|
||||
if !strings.Contains(str, "If you suspect abuse") {
|
||||
t.Error("Output is missing abuse mailto")
|
||||
}
|
||||
if !strings.Contains(str, "Tailscale Security Policies") {
|
||||
t.Error("Output is missing Tailscale Security Policies link")
|
||||
}
|
||||
if !strings.Contains(str, "Status:") {
|
||||
t.Error("Output is missing disabled status")
|
||||
}
|
||||
if !strings.Contains(str, "Debug info") {
|
||||
t.Error("Output is missing debug info")
|
||||
}
|
||||
fmt.Println(buf.String())
|
||||
}
|
||||
|
||||
@@ -80,10 +80,6 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
github.com/beorn7/perks/quantile from github.com/prometheus/client_golang/prometheus
|
||||
github.com/bits-and-blooms/bitset from github.com/gaissmai/bart
|
||||
💣 github.com/cespare/xxhash/v2 from github.com/prometheus/client_golang/prometheus
|
||||
github.com/coder/websocket from tailscale.com/control/controlhttp+
|
||||
github.com/coder/websocket/internal/errd from github.com/coder/websocket
|
||||
github.com/coder/websocket/internal/util from github.com/coder/websocket
|
||||
github.com/coder/websocket/internal/xsync from github.com/coder/websocket
|
||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
||||
💣 github.com/davecgh/go-spew/spew from k8s.io/apimachinery/pkg/util/dump
|
||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com+
|
||||
@@ -658,6 +654,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||
tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
|
||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
||||
tailscale.com/derp from tailscale.com/derp/derphttp+
|
||||
tailscale.com/derp/derphttp from tailscale.com/ipn/localapi+
|
||||
@@ -740,7 +737,6 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/clientupdate/distsign+
|
||||
tailscale.com/net/tstun from tailscale.com/tsd+
|
||||
tailscale.com/net/wsconn from tailscale.com/control/controlhttp+
|
||||
tailscale.com/omit from tailscale.com/ipn/conffile
|
||||
tailscale.com/paths from tailscale.com/client/tailscale+
|
||||
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal
|
||||
@@ -775,6 +771,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/types/persist from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/preftype from tailscale.com/ipn+
|
||||
tailscale.com/types/ptr from tailscale.com/cmd/k8s-operator+
|
||||
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||
tailscale.com/types/structs from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/tkatype from tailscale.com/client/tailscale+
|
||||
tailscale.com/types/views from tailscale.com/appc+
|
||||
@@ -792,7 +789,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||
tailscale.com/util/httphdr from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||
tailscale.com/util/lineiter from tailscale.com/hostinfo+
|
||||
L tailscale.com/util/linuxfw from tailscale.com/net/netns+
|
||||
tailscale.com/util/mak from tailscale.com/appc+
|
||||
tailscale.com/util/multierr from tailscale.com/control/controlclient+
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -150,6 +151,13 @@ func initTSNet(zlog *zap.SugaredLogger) (*tsnet.Server, *tailscale.Client) {
|
||||
Hostname: hostname,
|
||||
Logf: zlog.Named("tailscaled").Debugf,
|
||||
}
|
||||
if p := os.Getenv("TS_PORT"); p != "" {
|
||||
port, err := strconv.ParseUint(p, 10, 16)
|
||||
if err != nil {
|
||||
startlog.Fatalf("TS_PORT %q cannot be parsed as uint16: %v", p, err)
|
||||
}
|
||||
s.Port = uint16(port)
|
||||
}
|
||||
if kubeSecret != "" {
|
||||
st, err := kubestore.New(logger.Discard, kubeSecret)
|
||||
if err != nil {
|
||||
|
||||
@@ -47,7 +47,7 @@ const (
|
||||
reasonProxyGroupInvalid = "ProxyGroupInvalid"
|
||||
)
|
||||
|
||||
var gaugeProxyGroupResources = clientmetric.NewGauge(kubetypes.MetricProxyGroupCount)
|
||||
var gaugeProxyGroupResources = clientmetric.NewGauge(kubetypes.MetricProxyGroupEgressCount)
|
||||
|
||||
// ProxyGroupReconciler ensures cluster resources for a ProxyGroup definition.
|
||||
type ProxyGroupReconciler struct {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
tsapi "tailscale.com/k8s-operator/apis/v1alpha1"
|
||||
"tailscale.com/kube/egressservices"
|
||||
"tailscale.com/kube/kubetypes"
|
||||
"tailscale.com/types/ptr"
|
||||
)
|
||||
|
||||
@@ -146,6 +147,10 @@ func pgStatefulSet(pg *tsapi.ProxyGroup, namespace, image, tsFirewallMode, cfgHa
|
||||
Name: "TS_USERSPACE",
|
||||
Value: "false",
|
||||
},
|
||||
{
|
||||
Name: "TS_INTERNAL_APP",
|
||||
Value: kubetypes.AppProxyGroupEgress,
|
||||
},
|
||||
}
|
||||
|
||||
if tsFirewallMode != "" {
|
||||
|
||||
@@ -67,6 +67,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
tailscale.com/types/logger from tailscale.com/tsweb
|
||||
tailscale.com/types/opt from tailscale.com/envknob+
|
||||
tailscale.com/types/ptr from tailscale.com/tailcfg+
|
||||
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||
tailscale.com/types/structs from tailscale.com/tailcfg+
|
||||
tailscale.com/types/tkatype from tailscale.com/tailcfg+
|
||||
tailscale.com/types/views from tailscale.com/net/tsaddr+
|
||||
@@ -74,7 +75,7 @@ tailscale.com/cmd/stund dependencies: (generated by github.com/tailscale/depawar
|
||||
L 💣 tailscale.com/util/dirwalk from tailscale.com/metrics
|
||||
tailscale.com/util/dnsname from tailscale.com/tailcfg
|
||||
tailscale.com/util/fastuuid from tailscale.com/tsweb
|
||||
tailscale.com/util/lineread from tailscale.com/version/distro
|
||||
tailscale.com/util/lineiter from tailscale.com/version/distro
|
||||
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
|
||||
tailscale.com/util/slicesx from tailscale.com/tailcfg
|
||||
tailscale.com/util/vizerror from tailscale.com/tailcfg+
|
||||
|
||||
@@ -5,10 +5,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+
|
||||
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||
github.com/coder/websocket from tailscale.com/control/controlhttp+
|
||||
github.com/coder/websocket/internal/errd from github.com/coder/websocket
|
||||
github.com/coder/websocket/internal/util from github.com/coder/websocket
|
||||
github.com/coder/websocket/internal/xsync from github.com/coder/websocket
|
||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/pe+
|
||||
W 💣 github.com/dblohm7/wingoes/pe from tailscale.com/util/winutil/authenticode
|
||||
@@ -86,6 +82,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/cmd/tailscale/cli/ffcomplete/internal from tailscale.com/cmd/tailscale/cli/ffcomplete
|
||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||
tailscale.com/control/controlhttp from tailscale.com/cmd/tailscale/cli
|
||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||
tailscale.com/control/controlknobs from tailscale.com/net/portmapper
|
||||
tailscale.com/derp from tailscale.com/derp/derphttp
|
||||
tailscale.com/derp/derphttp from tailscale.com/net/netcheck
|
||||
@@ -124,7 +121,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/net/tlsdial/blockblame from tailscale.com/net/tlsdial
|
||||
tailscale.com/net/tsaddr from tailscale.com/client/web+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/clientupdate/distsign+
|
||||
tailscale.com/net/wsconn from tailscale.com/control/controlhttp+
|
||||
tailscale.com/paths from tailscale.com/client/tailscale+
|
||||
💣 tailscale.com/safesocket from tailscale.com/client/tailscale+
|
||||
tailscale.com/syncs from tailscale.com/cmd/tailscale/cli+
|
||||
@@ -148,6 +144,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/types/persist from tailscale.com/ipn
|
||||
tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/types/ptr from tailscale.com/hostinfo+
|
||||
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||
tailscale.com/types/structs from tailscale.com/ipn+
|
||||
tailscale.com/types/tkatype from tailscale.com/types/key+
|
||||
tailscale.com/types/views from tailscale.com/tailcfg+
|
||||
@@ -162,7 +159,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/util/groupmember from tailscale.com/client/web
|
||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||
tailscale.com/util/lineiter from tailscale.com/hostinfo+
|
||||
L tailscale.com/util/linuxfw from tailscale.com/net/netns
|
||||
tailscale.com/util/mak from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/util/multierr from tailscale.com/control/controlhttp+
|
||||
@@ -324,7 +321,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
reflect from archive/tar+
|
||||
regexp from github.com/coreos/go-iptables/iptables+
|
||||
regexp/syntax from regexp
|
||||
runtime/debug from github.com/coder/websocket/internal/xsync+
|
||||
runtime/debug from tailscale.com+
|
||||
slices from tailscale.com/client/web+
|
||||
sort from compress/flate+
|
||||
strconv from archive/tar+
|
||||
|
||||
@@ -79,10 +79,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L github.com/aws/smithy-go/transport/http/internal/io from github.com/aws/smithy-go/transport/http
|
||||
L github.com/aws/smithy-go/waiter from github.com/aws/aws-sdk-go-v2/service/ssm
|
||||
github.com/bits-and-blooms/bitset from github.com/gaissmai/bart
|
||||
github.com/coder/websocket from tailscale.com/control/controlhttp+
|
||||
github.com/coder/websocket/internal/errd from github.com/coder/websocket
|
||||
github.com/coder/websocket/internal/util from github.com/coder/websocket
|
||||
github.com/coder/websocket/internal/xsync from github.com/coder/websocket
|
||||
L github.com/coreos/go-iptables/iptables from tailscale.com/util/linuxfw
|
||||
LD 💣 github.com/creack/pty from tailscale.com/ssh/tailssh
|
||||
W 💣 github.com/dblohm7/wingoes from github.com/dblohm7/wingoes/com+
|
||||
@@ -249,6 +245,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/control/controlbase from tailscale.com/control/controlhttp+
|
||||
tailscale.com/control/controlclient from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/control/controlhttp from tailscale.com/control/controlclient
|
||||
tailscale.com/control/controlhttp/controlhttpcommon from tailscale.com/control/controlhttp
|
||||
tailscale.com/control/controlknobs from tailscale.com/control/controlclient+
|
||||
tailscale.com/derp from tailscale.com/derp/derphttp+
|
||||
tailscale.com/derp/derphttp from tailscale.com/cmd/tailscaled+
|
||||
@@ -327,7 +324,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/tsdial from tailscale.com/cmd/tailscaled+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/clientupdate/distsign+
|
||||
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/net/wsconn from tailscale.com/control/controlhttp+
|
||||
tailscale.com/omit from tailscale.com/ipn/conffile
|
||||
tailscale.com/paths from tailscale.com/client/tailscale+
|
||||
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal
|
||||
@@ -364,6 +360,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/types/persist from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/preftype from tailscale.com/ipn+
|
||||
tailscale.com/types/ptr from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/result from tailscale.com/util/lineiter
|
||||
tailscale.com/types/structs from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/tkatype from tailscale.com/tka+
|
||||
tailscale.com/types/views from tailscale.com/ipn/ipnlocal+
|
||||
@@ -381,7 +378,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
💣 tailscale.com/util/hashx from tailscale.com/util/deephash
|
||||
tailscale.com/util/httphdr from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/util/httpm from tailscale.com/client/tailscale+
|
||||
tailscale.com/util/lineread from tailscale.com/hostinfo+
|
||||
tailscale.com/util/lineiter from tailscale.com/hostinfo+
|
||||
L tailscale.com/util/linuxfw from tailscale.com/net/netns+
|
||||
tailscale.com/util/mak from tailscale.com/control/controlclient+
|
||||
tailscale.com/util/multierr from tailscale.com/cmd/tailscaled+
|
||||
|
||||
30
cmd/tailscaled/deps_test.go
Normal file
30
cmd/tailscaled/deps_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"tailscale.com/tstest/deptest"
|
||||
)
|
||||
|
||||
func TestOmitSSH(t *testing.T) {
|
||||
const msg = "unexpected with ts_omit_ssh"
|
||||
deptest.DepChecker{
|
||||
GOOS: "linux",
|
||||
GOARCH: "amd64",
|
||||
Tags: "ts_omit_ssh",
|
||||
BadDeps: map[string]string{
|
||||
"tailscale.com/ssh/tailssh": msg,
|
||||
"golang.org/x/crypto/ssh": msg,
|
||||
"tailscale.com/sessionrecording": msg,
|
||||
"github.com/anmitsu/go-shlex": msg,
|
||||
"github.com/creack/pty": msg,
|
||||
"github.com/kr/fs": msg,
|
||||
"github.com/pkg/sftp": msg,
|
||||
"github.com/u-root/u-root/pkg/termios": msg,
|
||||
"tempfork/gliderlabs/ssh": msg,
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux || darwin || freebsd || openbsd
|
||||
//go:build (linux || darwin || freebsd || openbsd) && !ts_omit_ssh
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"tailscale.com/control/controlhttp"
|
||||
"tailscale.com/control/controlhttp/controlhttpserver"
|
||||
"tailscale.com/internal/noiseconn"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/tsdial"
|
||||
@@ -201,7 +201,7 @@ func (up *Upgrader) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cbConn, err := controlhttp.AcceptHTTP(r.Context(), w, r, up.noiseKeyPriv, earlyWriteFn)
|
||||
cbConn, err := controlhttpserver.AcceptHTTP(r.Context(), w, r, up.noiseKeyPriv, earlyWriteFn)
|
||||
if err != nil {
|
||||
up.logf("controlhttp: Accept: %v", err)
|
||||
return
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/control/controlhttp/controlhttpcommon"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/health"
|
||||
"tailscale.com/net/dnscache"
|
||||
@@ -571,9 +572,9 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, optAddr netip.Ad
|
||||
Method: "POST",
|
||||
URL: u,
|
||||
Header: http.Header{
|
||||
"Upgrade": []string{upgradeHeaderValue},
|
||||
"Connection": []string{"upgrade"},
|
||||
handshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
|
||||
"Upgrade": []string{controlhttpcommon.UpgradeHeaderValue},
|
||||
"Connection": []string{"upgrade"},
|
||||
controlhttpcommon.HandshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
|
||||
},
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
@@ -597,7 +598,7 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, optAddr netip.Ad
|
||||
return nil, fmt.Errorf("httptrace didn't provide a connection")
|
||||
}
|
||||
|
||||
if next := resp.Header.Get("Upgrade"); next != upgradeHeaderValue {
|
||||
if next := resp.Header.Get("Upgrade"); next != controlhttpcommon.UpgradeHeaderValue {
|
||||
resp.Body.Close()
|
||||
return nil, fmt.Errorf("server switched to unexpected protocol %q", next)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/control/controlhttp/controlhttpcommon"
|
||||
"tailscale.com/net/wsconn"
|
||||
)
|
||||
|
||||
@@ -42,11 +43,11 @@ func (d *Dialer) Dial(ctx context.Context) (*ClientConn, error) {
|
||||
// Can't set HTTP headers on the websocket request, so we have to to send
|
||||
// the handshake via an HTTP header.
|
||||
RawQuery: url.Values{
|
||||
handshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
|
||||
controlhttpcommon.HandshakeHeaderName: []string{base64.StdEncoding.EncodeToString(init)},
|
||||
}.Encode(),
|
||||
}
|
||||
wsConn, _, err := websocket.Dial(ctx, wsURL.String(), &websocket.DialOptions{
|
||||
Subprotocols: []string{upgradeHeaderValue},
|
||||
Subprotocols: []string{controlhttpcommon.UpgradeHeaderValue},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -18,15 +18,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// upgradeHeader is the value of the Upgrade HTTP header used to
|
||||
// indicate the Tailscale control protocol.
|
||||
upgradeHeaderValue = "tailscale-control-protocol"
|
||||
|
||||
// handshakeHeaderName is the HTTP request header that can
|
||||
// optionally contain base64-encoded initial handshake
|
||||
// payload, to save an RTT.
|
||||
handshakeHeaderName = "X-Tailscale-Handshake"
|
||||
|
||||
// serverUpgradePath is where the server-side HTTP handler to
|
||||
// to do the protocol switch is located.
|
||||
serverUpgradePath = "/ts2021"
|
||||
|
||||
15
control/controlhttp/controlhttpcommon/controlhttpcommon.go
Normal file
15
control/controlhttp/controlhttpcommon/controlhttpcommon.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package controlhttpcommon contains common constants for used
|
||||
// by the controlhttp client and controlhttpserver packages.
|
||||
package controlhttpcommon
|
||||
|
||||
// UpgradeHeader is the value of the Upgrade HTTP header used to
|
||||
// indicate the Tailscale control protocol.
|
||||
const UpgradeHeaderValue = "tailscale-control-protocol"
|
||||
|
||||
// handshakeHeaderName is the HTTP request header that can
|
||||
// optionally contain base64-encoded initial handshake
|
||||
// payload, to save an RTT.
|
||||
const HandshakeHeaderName = "X-Tailscale-Handshake"
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
//go:build !ios
|
||||
|
||||
package controlhttp
|
||||
// Package controlhttpserver contains the HTTP server side of the ts2021 control protocol.
|
||||
package controlhttpserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -18,6 +19,7 @@ import (
|
||||
|
||||
"github.com/coder/websocket"
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/control/controlhttp/controlhttpcommon"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/wsconn"
|
||||
"tailscale.com/types/key"
|
||||
@@ -45,12 +47,12 @@ func acceptHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, pri
|
||||
if next == "websocket" {
|
||||
return acceptWebsocket(ctx, w, r, private)
|
||||
}
|
||||
if next != upgradeHeaderValue {
|
||||
if next != controlhttpcommon.UpgradeHeaderValue {
|
||||
http.Error(w, "unknown next protocol", http.StatusBadRequest)
|
||||
return nil, fmt.Errorf("client requested unhandled next protocol %q", next)
|
||||
}
|
||||
|
||||
initB64 := r.Header.Get(handshakeHeaderName)
|
||||
initB64 := r.Header.Get(controlhttpcommon.HandshakeHeaderName)
|
||||
if initB64 == "" {
|
||||
http.Error(w, "missing Tailscale handshake header", http.StatusBadRequest)
|
||||
return nil, errors.New("no tailscale handshake header in HTTP request")
|
||||
@@ -67,7 +69,7 @@ func acceptHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, pri
|
||||
return nil, errors.New("can't hijack client connection")
|
||||
}
|
||||
|
||||
w.Header().Set("Upgrade", upgradeHeaderValue)
|
||||
w.Header().Set("Upgrade", controlhttpcommon.UpgradeHeaderValue)
|
||||
w.Header().Set("Connection", "upgrade")
|
||||
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||
|
||||
@@ -117,7 +119,7 @@ func acceptHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request, pri
|
||||
// speak HTTP) to a Tailscale control protocol base transport connection.
|
||||
func acceptWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request, private key.MachinePrivate) (*controlbase.Conn, error) {
|
||||
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
|
||||
Subprotocols: []string{upgradeHeaderValue},
|
||||
Subprotocols: []string{controlhttpcommon.UpgradeHeaderValue},
|
||||
OriginPatterns: []string{"*"},
|
||||
// Disable compression because we transmit Noise messages that are not
|
||||
// compressible.
|
||||
@@ -129,7 +131,7 @@ func acceptWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not accept WebSocket connection %v", err)
|
||||
}
|
||||
if c.Subprotocol() != upgradeHeaderValue {
|
||||
if c.Subprotocol() != controlhttpcommon.UpgradeHeaderValue {
|
||||
c.Close(websocket.StatusPolicyViolation, "client must speak the control subprotocol")
|
||||
return nil, fmt.Errorf("Unexpected subprotocol %q", c.Subprotocol())
|
||||
}
|
||||
@@ -137,7 +139,7 @@ func acceptWebsocket(ctx context.Context, w http.ResponseWriter, r *http.Request
|
||||
c.Close(websocket.StatusPolicyViolation, "Could not parse parameters")
|
||||
return nil, fmt.Errorf("parse query parameters: %v", err)
|
||||
}
|
||||
initB64 := r.Form.Get(handshakeHeaderName)
|
||||
initB64 := r.Form.Get(controlhttpcommon.HandshakeHeaderName)
|
||||
if initB64 == "" {
|
||||
c.Close(websocket.StatusPolicyViolation, "missing Tailscale handshake parameter")
|
||||
return nil, errors.New("no tailscale handshake parameter in HTTP request")
|
||||
@@ -23,12 +23,15 @@ import (
|
||||
"time"
|
||||
|
||||
"tailscale.com/control/controlbase"
|
||||
"tailscale.com/control/controlhttp/controlhttpcommon"
|
||||
"tailscale.com/control/controlhttp/controlhttpserver"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/socks5"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/tstest/deptest"
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -158,7 +161,7 @@ func testControlHTTP(t *testing.T, param httpTestParam) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
conn, err := AcceptHTTP(context.Background(), w, r, server, earlyWriteFn)
|
||||
conn, err := controlhttpserver.AcceptHTTP(context.Background(), w, r, server, earlyWriteFn)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
@@ -529,7 +532,7 @@ EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==
|
||||
|
||||
func brokenMITMHandler(clock tstime.Clock) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Upgrade", upgradeHeaderValue)
|
||||
w.Header().Set("Upgrade", controlhttpcommon.UpgradeHeaderValue)
|
||||
w.Header().Set("Connection", "upgrade")
|
||||
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||
w.(http.Flusher).Flush()
|
||||
@@ -574,7 +577,7 @@ func TestDialPlan(t *testing.T) {
|
||||
close(done)
|
||||
})
|
||||
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := AcceptHTTP(context.Background(), w, r, server, nil)
|
||||
conn, err := controlhttpserver.AcceptHTTP(context.Background(), w, r, server, nil)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
} else {
|
||||
@@ -816,3 +819,14 @@ func (c *closeTrackConn) Close() error {
|
||||
c.d.noteClose(c)
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func TestDeps(t *testing.T) {
|
||||
deptest.DepChecker{
|
||||
GOOS: "darwin",
|
||||
GOARCH: "arm64",
|
||||
BadDeps: map[string]string{
|
||||
// Only the controlhttpserver needs WebSockets...
|
||||
"github.com/coder/websocket": "controlhttp client shouldn't need websockets",
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
"unique"
|
||||
|
||||
"go4.org/mem"
|
||||
"golang.org/x/time/rate"
|
||||
@@ -1598,3 +1599,40 @@ func TestServerRepliesToPing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnique(b *testing.B) {
|
||||
var key [32]byte
|
||||
for i := range key {
|
||||
key[i] = byte(i)
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
h := unique.Make(key)
|
||||
if h.Value() != key {
|
||||
b.Fatal("unexpected")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkLocalMap(b *testing.B) {
|
||||
var key [32]byte
|
||||
for i := range key {
|
||||
key[i] = byte(i)
|
||||
}
|
||||
m := map[[32]byte]bool{
|
||||
key: true,
|
||||
}
|
||||
k2 := key
|
||||
for i := range k2 {
|
||||
k2[0] = byte(i + 1)
|
||||
m[k2] = false
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
if !m[key] {
|
||||
b.Fatal("unexpected")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -313,6 +313,9 @@ func (c *Client) preferIPv6() bool {
|
||||
var dialWebsocketFunc func(ctx context.Context, urlStr string) (net.Conn, error)
|
||||
|
||||
func useWebsockets() bool {
|
||||
if !canWebsockets {
|
||||
return false
|
||||
}
|
||||
if runtime.GOOS == "js" {
|
||||
return true
|
||||
}
|
||||
@@ -383,7 +386,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
||||
var node *tailcfg.DERPNode // nil when using c.url to dial
|
||||
var idealNodeInRegion bool
|
||||
switch {
|
||||
case useWebsockets():
|
||||
case canWebsockets && useWebsockets():
|
||||
var urlStr string
|
||||
if c.url != nil {
|
||||
urlStr = c.url.String()
|
||||
|
||||
@@ -17,7 +17,9 @@ import (
|
||||
|
||||
"tailscale.com/derp"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/tstest/deptest"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
||||
func TestSendRecv(t *testing.T) {
|
||||
@@ -485,3 +487,23 @@ func TestProbe(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeps(t *testing.T) {
|
||||
deptest.DepChecker{
|
||||
GOOS: "darwin",
|
||||
GOARCH: "arm64",
|
||||
BadDeps: map[string]string{
|
||||
"github.com/coder/websocket": "shouldn't link websockets except on js/wasm",
|
||||
},
|
||||
}.Check(t)
|
||||
|
||||
deptest.DepChecker{
|
||||
GOOS: "darwin",
|
||||
GOARCH: "arm64",
|
||||
Tags: "ts_debug_websockets",
|
||||
WantDeps: set.Of(
|
||||
"github.com/coder/websocket",
|
||||
),
|
||||
}.Check(t)
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux || js
|
||||
//go:build js || ((linux || darwin) && ts_debug_websockets)
|
||||
|
||||
package derphttp
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"tailscale.com/net/wsconn"
|
||||
)
|
||||
|
||||
const canWebsockets = true
|
||||
|
||||
func init() {
|
||||
dialWebsocketFunc = dialWebsocket
|
||||
}
|
||||
|
||||
8
derp/derphttp/websocket_stub.go
Normal file
8
derp/derphttp/websocket_stub.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !(js || ((linux || darwin) && ts_debug_websockets))
|
||||
|
||||
package derphttp
|
||||
|
||||
const canWebsockets = false
|
||||
@@ -411,7 +411,7 @@ func TKASkipSignatureCheck() bool { return Bool("TS_UNSAFE_SKIP_NKS_VERIFICATION
|
||||
// Kubernetes Operator components.
|
||||
func App() string {
|
||||
a := os.Getenv("TS_INTERNAL_APP")
|
||||
if a == kubetypes.AppConnector || a == kubetypes.AppEgressProxy || a == kubetypes.AppIngressProxy || a == kubetypes.AppIngressResource {
|
||||
if a == kubetypes.AppConnector || a == kubetypes.AppEgressProxy || a == kubetypes.AppIngressProxy || a == kubetypes.AppIngressResource || a == kubetypes.AppProxyGroupEgress || a == kubetypes.AppProxyGroupIngress {
|
||||
return a
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"tailscale.com/types/ptr"
|
||||
"tailscale.com/util/cloudenv"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
@@ -231,12 +231,12 @@ func desktop() (ret opt.Bool) {
|
||||
}
|
||||
|
||||
seenDesktop := false
|
||||
lineread.File("/proc/net/unix", func(line []byte) error {
|
||||
for lr := range lineiter.File("/proc/net/unix") {
|
||||
line, _ := lr.Value()
|
||||
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S(" @/tmp/dbus-"))
|
||||
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S(".X11-unix"))
|
||||
seenDesktop = seenDesktop || mem.Contains(mem.B(line), mem.S("/wayland-1"))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
ret.Set(seenDesktop)
|
||||
|
||||
// Only cache after a minute - compositors might not have started yet.
|
||||
@@ -305,21 +305,21 @@ func inContainer() opt.Bool {
|
||||
ret.Set(true)
|
||||
return ret
|
||||
}
|
||||
lineread.File("/proc/1/cgroup", func(line []byte) error {
|
||||
for lr := range lineiter.File("/proc/1/cgroup") {
|
||||
line, _ := lr.Value()
|
||||
if mem.Contains(mem.B(line), mem.S("/docker/")) ||
|
||||
mem.Contains(mem.B(line), mem.S("/lxc/")) {
|
||||
ret.Set(true)
|
||||
return io.EOF // arbitrary non-nil error to stop loop
|
||||
break
|
||||
}
|
||||
return nil
|
||||
})
|
||||
lineread.File("/proc/mounts", func(line []byte) error {
|
||||
}
|
||||
for lr := range lineiter.File("/proc/mounts") {
|
||||
line, _ := lr.Value()
|
||||
if mem.Contains(mem.B(line), mem.S("lxcfs /proc/cpuinfo fuse.lxcfs")) {
|
||||
ret.Set(true)
|
||||
return io.EOF
|
||||
break
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/types/ptr"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
|
||||
@@ -106,15 +106,18 @@ func linuxVersionMeta() (meta versionMeta) {
|
||||
}
|
||||
|
||||
m := map[string]string{}
|
||||
lineread.File(propFile, func(line []byte) error {
|
||||
for lr := range lineiter.File(propFile) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
eq := bytes.IndexByte(line, '=')
|
||||
if eq == -1 {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
k, v := string(line[:eq]), strings.Trim(string(line[eq+1:]), `"'`)
|
||||
m[k] = v
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if v := m["VERSION_CODENAME"]; v != "" {
|
||||
meta.DistroCodeName = v
|
||||
|
||||
@@ -350,6 +350,8 @@ func handleC2NPostureIdentityGet(b *LocalBackend, w http.ResponseWriter, r *http
|
||||
res.PostureDisabled = true
|
||||
}
|
||||
|
||||
b.logf("c2n: posture identity disabled=%v reported %d serials %d hwaddrs", res.PostureDisabled, len(res.SerialNumbers), len(res.IfaceHardwareAddrs))
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(res)
|
||||
}
|
||||
|
||||
@@ -800,6 +800,19 @@ func (b *LocalBackend) pauseOrResumeControlClientLocked() {
|
||||
b.cc.SetPaused((b.state == ipn.Stopped && b.netMap != nil) || (!networkUp && !testenv.InTest() && !assumeNetworkUpdateForTest()))
|
||||
}
|
||||
|
||||
// DisconnectControl shuts down control client. This can be run before node shutdown to force control to consider this ndoe
|
||||
// inactive. This can be used to ensure that nodes that are HA subnet router or app connector replicas are shutting
|
||||
// down, clients switch over to other replicas whilst the existing connections are kept alive for some period of time.
|
||||
func (b *LocalBackend) DisconnectControl() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
cc := b.resetControlClientLocked()
|
||||
if cc == nil {
|
||||
return
|
||||
}
|
||||
cc.Shutdown()
|
||||
}
|
||||
|
||||
// captivePortalDetectionInterval is the duration to wait in an unhealthy state with connectivity broken
|
||||
// before running captive portal detection.
|
||||
const captivePortalDetectionInterval = 2 * time.Second
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/tailscale/golang-x-crypto/ssh"
|
||||
"go4.org/mem"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
"tailscale.com/util/mak"
|
||||
)
|
||||
|
||||
@@ -80,30 +80,32 @@ func (b *LocalBackend) getSSHUsernames(req *tailcfg.C2NSSHUsernamesRequest) (*ta
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lineread.Reader(bytes.NewReader(out), func(line []byte) error {
|
||||
for line := range lineiter.Bytes(out) {
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 || line[0] == '_' {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
add(string(line))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
default:
|
||||
lineread.File("/etc/passwd", func(line []byte) error {
|
||||
for lr := range lineiter.File("/etc/passwd") {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 || line[0] == '#' || line[0] == '_' {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
if mem.HasSuffix(mem.B(line), mem.S("/nologin")) ||
|
||||
mem.HasSuffix(mem.B(line), mem.S("/false")) {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
colon := bytes.IndexByte(line, ':')
|
||||
if colon != -1 {
|
||||
add(string(line[:colon]))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ var handler = map[string]localAPIHandler{
|
||||
"derpmap": (*Handler).serveDERPMap,
|
||||
"dev-set-state-store": (*Handler).serveDevSetStateStore,
|
||||
"dial": (*Handler).serveDial,
|
||||
"disconnect-control": (*Handler).disconnectControl,
|
||||
"dns-osconfig": (*Handler).serveDNSOSConfig,
|
||||
"dns-query": (*Handler).serveDNSQuery,
|
||||
"drive/fileserver-address": (*Handler).serveDriveServerAddr,
|
||||
@@ -952,6 +953,22 @@ func (h *Handler) servePprof(w http.ResponseWriter, r *http.Request) {
|
||||
servePprofFunc(w, r)
|
||||
}
|
||||
|
||||
// disconnectControl is the handler for local API /disconnect-control endpoint that shuts down control client, so that
|
||||
// node no longer communicates with control. Doing this makes control consider this node inactive. This can be used
|
||||
// before shutting down a replica of HA subnet router or app connector deployments to ensure that control tells the
|
||||
// peers to switch over to another replica whilst still maintaining th existing peer connections.
|
||||
func (h *Handler) disconnectControl(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if r.Method != httpm.POST {
|
||||
http.Error(w, "use POST", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
h.b.DisconnectControl()
|
||||
}
|
||||
|
||||
func (h *Handler) reloadConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if !h.PermitWrite {
|
||||
http.Error(w, "access denied", http.StatusForbidden)
|
||||
|
||||
@@ -5,12 +5,14 @@ package kubetypes
|
||||
|
||||
const (
|
||||
// Hostinfo App values for the Tailscale Kubernetes Operator components.
|
||||
AppOperator = "k8s-operator"
|
||||
AppAPIServerProxy = "k8s-operator-proxy"
|
||||
AppIngressProxy = "k8s-operator-ingress-proxy"
|
||||
AppIngressResource = "k8s-operator-ingress-resource"
|
||||
AppEgressProxy = "k8s-operator-egress-proxy"
|
||||
AppConnector = "k8s-operator-connector-resource"
|
||||
AppOperator = "k8s-operator"
|
||||
AppAPIServerProxy = "k8s-operator-proxy"
|
||||
AppIngressProxy = "k8s-operator-ingress-proxy"
|
||||
AppIngressResource = "k8s-operator-ingress-resource"
|
||||
AppEgressProxy = "k8s-operator-egress-proxy"
|
||||
AppConnector = "k8s-operator-connector-resource"
|
||||
AppProxyGroupEgress = "k8s-operator-proxygroup-egress"
|
||||
AppProxyGroupIngress = "k8s-operator-proxygroup-ingress"
|
||||
|
||||
// Clientmetrics for Tailscale Kubernetes Operator components
|
||||
MetricIngressProxyCount = "k8s_ingress_proxies" // L3
|
||||
@@ -22,5 +24,6 @@ const (
|
||||
MetricNameserverCount = "k8s_nameserver_resources"
|
||||
MetricRecorderCount = "k8s_recorder_resources"
|
||||
MetricEgressServiceCount = "k8s_egress_service_resources"
|
||||
MetricProxyGroupCount = "k8s_proxygroup_resources"
|
||||
MetricProxyGroupEgressCount = "k8s_proxygroup_egress_resources"
|
||||
MetricProxyGroupIngressCount = "k8s_proxygroup_ingress_resources"
|
||||
)
|
||||
|
||||
@@ -57,8 +57,8 @@ Windows][]. See also the dependencies in the [Tailscale CLI][].
|
||||
- [github.com/tailscale/go-winio](https://pkg.go.dev/github.com/tailscale/go-winio) ([MIT](https://github.com/tailscale/go-winio/blob/c4f33415bf55/LICENSE))
|
||||
- [github.com/tailscale/hujson](https://pkg.go.dev/github.com/tailscale/hujson) ([BSD-3-Clause](https://github.com/tailscale/hujson/blob/20486734a56a/LICENSE))
|
||||
- [github.com/tailscale/netlink](https://pkg.go.dev/github.com/tailscale/netlink) ([Apache-2.0](https://github.com/tailscale/netlink/blob/4d49adab4de7/LICENSE))
|
||||
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/52804fd3056a/LICENSE))
|
||||
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/6580b55d49ca/LICENSE))
|
||||
- [github.com/tailscale/walk](https://pkg.go.dev/github.com/tailscale/walk) ([BSD-3-Clause](https://github.com/tailscale/walk/blob/8865133fd3ef/LICENSE))
|
||||
- [github.com/tailscale/win](https://pkg.go.dev/github.com/tailscale/win) ([BSD-3-Clause](https://github.com/tailscale/win/blob/28f7e73c7afb/LICENSE))
|
||||
- [github.com/tailscale/xnet/webdav](https://pkg.go.dev/github.com/tailscale/xnet/webdav) ([BSD-3-Clause](https://github.com/tailscale/xnet/blob/8497ac4dab2e/LICENSE))
|
||||
- [github.com/tc-hib/winres](https://pkg.go.dev/github.com/tc-hib/winres) ([0BSD](https://github.com/tc-hib/winres/blob/v0.2.1/LICENSE))
|
||||
- [github.com/vishvananda/netns](https://pkg.go.dev/github.com/vishvananda/netns) ([Apache-2.0](https://github.com/vishvananda/netns/blob/v0.0.4/LICENSE))
|
||||
|
||||
@@ -5,7 +5,6 @@ package netmon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
@@ -15,7 +14,7 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -34,11 +33,6 @@ func init() {
|
||||
|
||||
var procNetRouteErr atomic.Bool
|
||||
|
||||
// errStopReading is a sentinel error value used internally by
|
||||
// lineread.File callers to stop reading. It doesn't escape to
|
||||
// callers/users.
|
||||
var errStopReading = errors.New("stop reading")
|
||||
|
||||
/*
|
||||
Parse 10.0.0.1 out of:
|
||||
|
||||
@@ -54,44 +48,42 @@ func likelyHomeRouterIPAndroid() (ret netip.Addr, myIP netip.Addr, ok bool) {
|
||||
}
|
||||
lineNum := 0
|
||||
var f []mem.RO
|
||||
err := lineread.File(procNetRoutePath, func(line []byte) error {
|
||||
for lr := range lineiter.File(procNetRoutePath) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
return likelyHomeRouterIP()
|
||||
}
|
||||
|
||||
lineNum++
|
||||
if lineNum == 1 {
|
||||
// Skip header line.
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
if lineNum > maxProcNetRouteRead {
|
||||
return errStopReading
|
||||
break
|
||||
}
|
||||
f = mem.AppendFields(f[:0], mem.B(line))
|
||||
if len(f) < 4 {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
gwHex, flagsHex := f[2], f[3]
|
||||
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
||||
if err != nil {
|
||||
return nil // ignore error, skip line and keep going
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
||||
if err != nil {
|
||||
return nil // ignore error, skip line and keep going
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
||||
if ip.IsPrivate() {
|
||||
ret = ip
|
||||
return errStopReading
|
||||
break
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if errors.Is(err, errStopReading) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
return likelyHomeRouterIP()
|
||||
}
|
||||
if ret.IsValid() {
|
||||
// Try to get the local IP of the interface associated with
|
||||
@@ -144,23 +136,26 @@ func likelyHomeRouterIPHelper() (ret netip.Addr, _ netip.Addr, ok bool) {
|
||||
return
|
||||
}
|
||||
// Search for line like "default via 10.0.2.2 dev radio0 table 1016 proto static mtu 1500 "
|
||||
lineread.Reader(out, func(line []byte) error {
|
||||
for lr := range lineiter.Reader(out) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
const pfx = "default via "
|
||||
if !mem.HasPrefix(mem.B(line), mem.S(pfx)) {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
line = line[len(pfx):]
|
||||
sp := bytes.IndexByte(line, ' ')
|
||||
if sp == -1 {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
ipb := line[:sp]
|
||||
if ip, err := netip.ParseAddr(string(ipb)); err == nil && ip.Is4() {
|
||||
ret = ip
|
||||
log.Printf("interfaces: found Android default route %v", ip)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
cmd.Process.Kill()
|
||||
cmd.Wait()
|
||||
return ret, netip.Addr{}, ret.IsValid()
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
package netmon
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"go4.org/mem"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
@@ -73,31 +72,34 @@ func likelyHomeRouterIPDarwinExec() (ret netip.Addr, netif string, ok bool) {
|
||||
defer io.Copy(io.Discard, stdout) // clear the pipe to prevent hangs
|
||||
|
||||
var f []mem.RO
|
||||
lineread.Reader(stdout, func(lineb []byte) error {
|
||||
for lr := range lineiter.Reader(stdout) {
|
||||
lineb, err := lr.Value()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
line := mem.B(lineb)
|
||||
if !mem.Contains(line, mem.S("default")) {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
f = mem.AppendFields(f[:0], line)
|
||||
if len(f) < 4 || !f[0].EqualString("default") {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
ipm, flagsm, netifm := f[1], f[2], f[3]
|
||||
if !mem.Contains(flagsm, mem.S("G")) {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
if mem.Contains(flagsm, mem.S("I")) {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
ip, err := netip.ParseAddr(string(mem.Append(nil, ipm)))
|
||||
if err == nil && ip.IsPrivate() {
|
||||
ret = ip
|
||||
netif = netifm.StringCopy()
|
||||
// We've found what we're looking for.
|
||||
return errStopReadingNetstatTable
|
||||
break
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return ret, netif, ret.IsValid()
|
||||
}
|
||||
|
||||
@@ -110,5 +112,3 @@ func TestFetchRoutingTable(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errStopReadingNetstatTable = errors.New("found private gateway")
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"go4.org/mem"
|
||||
"golang.org/x/sys/unix"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -32,11 +32,6 @@ func init() {
|
||||
|
||||
var procNetRouteErr atomic.Bool
|
||||
|
||||
// errStopReading is a sentinel error value used internally by
|
||||
// lineread.File callers to stop reading. It doesn't escape to
|
||||
// callers/users.
|
||||
var errStopReading = errors.New("stop reading")
|
||||
|
||||
/*
|
||||
Parse 10.0.0.1 out of:
|
||||
|
||||
@@ -52,44 +47,42 @@ func likelyHomeRouterIPLinux() (ret netip.Addr, myIP netip.Addr, ok bool) {
|
||||
}
|
||||
lineNum := 0
|
||||
var f []mem.RO
|
||||
err := lineread.File(procNetRoutePath, func(line []byte) error {
|
||||
for lr := range lineiter.File(procNetRoutePath) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
|
||||
return ret, myIP, false
|
||||
}
|
||||
lineNum++
|
||||
if lineNum == 1 {
|
||||
// Skip header line.
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
if lineNum > maxProcNetRouteRead {
|
||||
return errStopReading
|
||||
break
|
||||
}
|
||||
f = mem.AppendFields(f[:0], mem.B(line))
|
||||
if len(f) < 4 {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
gwHex, flagsHex := f[2], f[3]
|
||||
flags, err := mem.ParseUint(flagsHex, 16, 16)
|
||||
if err != nil {
|
||||
return nil // ignore error, skip line and keep going
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
if flags&(unix.RTF_UP|unix.RTF_GATEWAY) != unix.RTF_UP|unix.RTF_GATEWAY {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
ipu32, err := mem.ParseUint(gwHex, 16, 32)
|
||||
if err != nil {
|
||||
return nil // ignore error, skip line and keep going
|
||||
continue // ignore error, skip line and keep going
|
||||
}
|
||||
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
||||
if ip.IsPrivate() {
|
||||
ret = ip
|
||||
return errStopReading
|
||||
break
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if errors.Is(err, errStopReading) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
procNetRouteErr.Store(true)
|
||||
log.Printf("interfaces: failed to read /proc/net/route: %v", err)
|
||||
}
|
||||
if ret.IsValid() {
|
||||
// Try to get the local IP of the interface associated with
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && !android
|
||||
|
||||
package netmon
|
||||
|
||||
import (
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
|
||||
// These vars are overridden for tests.
|
||||
@@ -76,21 +76,22 @@ func synologyProxiesFromConfig() (*url.URL, *url.URL, error) {
|
||||
func parseSynologyConfig(r io.Reader) (*url.URL, *url.URL, error) {
|
||||
cfg := map[string]string{}
|
||||
|
||||
if err := lineread.Reader(r, func(line []byte) error {
|
||||
for lr := range lineiter.Reader(r) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// accept and skip over empty lines
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
|
||||
key, value, ok := strings.Cut(string(line), "=")
|
||||
if !ok {
|
||||
return fmt.Errorf("missing \"=\" in proxy.conf line: %q", line)
|
||||
return nil, nil, fmt.Errorf("missing \"=\" in proxy.conf line: %q", line)
|
||||
}
|
||||
cfg[string(key)] = string(value)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if cfg["proxy_enabled"] != "yes" {
|
||||
|
||||
@@ -71,6 +71,7 @@ package safeweb
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -416,3 +417,7 @@ func (s *Server) ListenAndServe(addr string) error {
|
||||
func (s *Server) Close() error {
|
||||
return s.h.Close()
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the server without interrupting any active
|
||||
// connections. It has the same semantics as[http.Server.Shutdown].
|
||||
func (s *Server) Shutdown(ctx context.Context) error { return s.h.Shutdown(ctx) }
|
||||
|
||||
@@ -48,7 +48,7 @@ import (
|
||||
"tailscale.com/types/netmap"
|
||||
"tailscale.com/types/ptr"
|
||||
"tailscale.com/util/cibuild"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
"tailscale.com/util/must"
|
||||
"tailscale.com/version/distro"
|
||||
"tailscale.com/wgengine"
|
||||
@@ -1123,14 +1123,11 @@ func TestSSH(t *testing.T) {
|
||||
|
||||
func parseEnv(out []byte) map[string]string {
|
||||
e := map[string]string{}
|
||||
lineread.Reader(bytes.NewReader(out), func(line []byte) error {
|
||||
i := bytes.IndexByte(line, '=')
|
||||
if i == -1 {
|
||||
return nil
|
||||
for line := range lineiter.Bytes(out) {
|
||||
if i := bytes.IndexByte(line, '='); i != -1 {
|
||||
e[string(line[:i])] = string(line[i+1:])
|
||||
}
|
||||
e[string(line[:i])] = string(line[i+1:])
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
package tailssh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
@@ -18,7 +17,7 @@ import (
|
||||
"go4.org/mem"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
"tailscale.com/util/osuser"
|
||||
"tailscale.com/version/distro"
|
||||
)
|
||||
@@ -110,15 +109,16 @@ func defaultPathForUser(u *user.User) string {
|
||||
}
|
||||
|
||||
func defaultPathForUserOnNixOS(u *user.User) string {
|
||||
var path string
|
||||
lineread.File("/etc/pam/environment", func(lineb []byte) error {
|
||||
if v := pathFromPAMEnvLine(lineb, u); v != "" {
|
||||
path = v
|
||||
return io.EOF // stop iteration
|
||||
for lr := range lineiter.File("/etc/pam/environment") {
|
||||
lineb, err := lr.Value()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return path
|
||||
if v := pathFromPAMEnvLine(lineb, u); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func pathFromPAMEnvLine(line []byte, u *user.User) (path string) {
|
||||
|
||||
@@ -13,14 +13,19 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"tailscale.com/util/set"
|
||||
)
|
||||
|
||||
type DepChecker struct {
|
||||
GOOS string // optional
|
||||
GOARCH string // optional
|
||||
BadDeps map[string]string // package => why
|
||||
GOOS string // optional
|
||||
GOARCH string // optional
|
||||
BadDeps map[string]string // package => why
|
||||
WantDeps set.Set[string] // packages expected
|
||||
Tags string // comma-separated
|
||||
}
|
||||
|
||||
func (c DepChecker) Check(t *testing.T) {
|
||||
@@ -29,7 +34,7 @@ func (c DepChecker) Check(t *testing.T) {
|
||||
t.Skip("skipping dep tests on windows hosts")
|
||||
}
|
||||
t.Helper()
|
||||
cmd := exec.Command("go", "list", "-json", ".")
|
||||
cmd := exec.Command("go", "list", "-json", "-tags="+c.Tags, ".")
|
||||
var extraEnv []string
|
||||
if c.GOOS != "" {
|
||||
extraEnv = append(extraEnv, "GOOS="+c.GOOS)
|
||||
@@ -54,6 +59,11 @@ func (c DepChecker) Check(t *testing.T) {
|
||||
t.Errorf("package %q is not allowed as a dependency (env: %q); reason: %s", dep, extraEnv, why)
|
||||
}
|
||||
}
|
||||
for dep := range c.WantDeps {
|
||||
if !slices.Contains(res.Deps, dep) {
|
||||
t.Errorf("expected package %q to be a dependency (env: %q)", dep, extraEnv)
|
||||
}
|
||||
}
|
||||
t.Logf("got %d dependencies", len(res.Deps))
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"tailscale.com/control/controlhttp"
|
||||
"tailscale.com/control/controlhttp/controlhttpserver"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -288,7 +288,7 @@ func (s *Server) serveNoiseUpgrade(w http.ResponseWriter, r *http.Request) {
|
||||
s.mu.Lock()
|
||||
noisePrivate := s.noisePrivKey
|
||||
s.mu.Unlock()
|
||||
cc, err := controlhttp.AcceptHTTP(ctx, w, r, noisePrivate, nil)
|
||||
cc, err := controlhttpserver.AcceptHTTP(ctx, w, r, noisePrivate, nil)
|
||||
if err != nil {
|
||||
log.Printf("AcceptHTTP: %v", err)
|
||||
return
|
||||
|
||||
@@ -23,10 +23,16 @@ import (
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
// StaticStringVar returns a new expvar.Var that always returns s.
|
||||
func StaticStringVar(s string) expvar.Var {
|
||||
var v any = s // box s into an interface just once
|
||||
return expvar.Func(func() any { return v })
|
||||
}
|
||||
|
||||
func init() {
|
||||
expvar.Publish("process_start_unix_time", expvar.Func(func() any { return timeStart.Unix() }))
|
||||
expvar.Publish("version", expvar.Func(func() any { return version.Long() }))
|
||||
expvar.Publish("go_version", expvar.Func(func() any { return runtime.Version() }))
|
||||
expvar.Publish("version", StaticStringVar(version.Long()))
|
||||
expvar.Publish("go_version", StaticStringVar(runtime.Version()))
|
||||
expvar.Publish("counter_uptime_sec", expvar.Func(func() any { return int64(Uptime().Seconds()) }))
|
||||
expvar.Publish("gauge_goroutines", expvar.Func(func() any { return runtime.NumGoroutine() }))
|
||||
}
|
||||
|
||||
49
types/result/result.go
Normal file
49
types/result/result.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package result contains the Of result type, which is
|
||||
// either a value or an error.
|
||||
package result
|
||||
|
||||
// Of is either a T value or an error.
|
||||
//
|
||||
// Think of it like Rust or Swift's result types.
|
||||
// It's named "Of" because the fully qualified name
|
||||
// for callers reads result.Of[T].
|
||||
type Of[T any] struct {
|
||||
v T // valid if Err is nil; invalid if Err is non-nil
|
||||
err error
|
||||
}
|
||||
|
||||
// Value returns a new result with value v,
|
||||
// without an error.
|
||||
func Value[T any](v T) Of[T] {
|
||||
return Of[T]{v: v}
|
||||
}
|
||||
|
||||
// Error returns a new result with error err.
|
||||
// If err is nil, the returned result is equivalent
|
||||
// to calling Value with T's zero value.
|
||||
func Error[T any](err error) Of[T] {
|
||||
return Of[T]{err: err}
|
||||
}
|
||||
|
||||
// MustValue returns r's result value.
|
||||
// It panics if r.Err returns non-nil.
|
||||
func (r Of[T]) MustValue() T {
|
||||
if r.err != nil {
|
||||
panic(r.err)
|
||||
}
|
||||
return r.v
|
||||
}
|
||||
|
||||
// Value returns r's result value and error.
|
||||
func (r Of[T]) Value() (T, error) {
|
||||
return r.v, r.err
|
||||
}
|
||||
|
||||
// Err returns r's error, if any.
|
||||
// When r.Err returns nil, it's safe to call r.MustValue without it panicking.
|
||||
func (r Of[T]) Err() error {
|
||||
return r.err
|
||||
}
|
||||
72
util/lineiter/lineiter.go
Normal file
72
util/lineiter/lineiter.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package lineiter iterates over lines in things.
|
||||
package lineiter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"iter"
|
||||
"os"
|
||||
|
||||
"tailscale.com/types/result"
|
||||
)
|
||||
|
||||
// File returns an iterator that reads lines from the named file.
|
||||
//
|
||||
// The returned substrings don't include the trailing newline.
|
||||
// Lines may be empty.
|
||||
func File(name string) iter.Seq[result.Of[[]byte]] {
|
||||
f, err := os.Open(name)
|
||||
return reader(f, f, err)
|
||||
}
|
||||
|
||||
// Bytes returns an iterator over the lines in bs.
|
||||
// The returned substrings don't include the trailing newline.
|
||||
// Lines may be empty.
|
||||
func Bytes(bs []byte) iter.Seq[[]byte] {
|
||||
return func(yield func([]byte) bool) {
|
||||
for len(bs) > 0 {
|
||||
i := bytes.IndexByte(bs, '\n')
|
||||
if i < 0 {
|
||||
yield(bs)
|
||||
return
|
||||
}
|
||||
if !yield(bs[:i]) {
|
||||
return
|
||||
}
|
||||
bs = bs[i+1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reader returns an iterator over the lines in r.
|
||||
//
|
||||
// The returned substrings don't include the trailing newline.
|
||||
// Lines may be empty.
|
||||
func Reader(r io.Reader) iter.Seq[result.Of[[]byte]] {
|
||||
return reader(r, nil, nil)
|
||||
}
|
||||
|
||||
func reader(r io.Reader, c io.Closer, err error) iter.Seq[result.Of[[]byte]] {
|
||||
return func(yield func(result.Of[[]byte]) bool) {
|
||||
if err != nil {
|
||||
yield(result.Error[[]byte](err))
|
||||
return
|
||||
}
|
||||
if c != nil {
|
||||
defer c.Close()
|
||||
}
|
||||
bs := bufio.NewScanner(r)
|
||||
for bs.Scan() {
|
||||
if !yield(result.Value(bs.Bytes())) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := bs.Err(); err != nil {
|
||||
yield(result.Error[[]byte](err))
|
||||
}
|
||||
}
|
||||
}
|
||||
32
util/lineiter/lineiter_test.go
Normal file
32
util/lineiter/lineiter_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package lineiter
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBytesLines(t *testing.T) {
|
||||
var got []string
|
||||
for line := range Bytes([]byte("foo\n\nbar\nbaz")) {
|
||||
got = append(got, string(line))
|
||||
}
|
||||
want := []string{"foo", "", "bar", "baz"}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Errorf("got %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
var got []string
|
||||
for line := range Reader(strings.NewReader("foo\n\nbar\nbaz")) {
|
||||
got = append(got, string(line.MustValue()))
|
||||
}
|
||||
want := []string{"foo", "", "bar", "baz"}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Errorf("got %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
@@ -8,26 +8,26 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
|
||||
func ownerOfPID(pid int) (userID string, err error) {
|
||||
file := fmt.Sprintf("/proc/%d/status", pid)
|
||||
err = lineread.File(file, func(line []byte) error {
|
||||
for lr := range lineiter.File(file) {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", ErrProcessNotFound
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
if len(line) < 4 || string(line[:4]) != "Uid:" {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
f := strings.Fields(string(line))
|
||||
if len(f) >= 2 {
|
||||
userID = f[1] // real userid
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if os.IsNotExist(err) {
|
||||
return "", ErrProcessNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if userID == "" {
|
||||
return "", fmt.Errorf("missing Uid line in %s", file)
|
||||
|
||||
@@ -6,13 +6,12 @@ package distro
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"tailscale.com/types/lazy"
|
||||
"tailscale.com/util/lineread"
|
||||
"tailscale.com/util/lineiter"
|
||||
)
|
||||
|
||||
type Distro string
|
||||
@@ -132,18 +131,19 @@ func DSMVersion() int {
|
||||
return v
|
||||
}
|
||||
// But when run from the command line, we have to read it from the file:
|
||||
lineread.File("/etc/VERSION", func(line []byte) error {
|
||||
for lr := range lineiter.File("/etc/VERSION") {
|
||||
line, err := lr.Value()
|
||||
if err != nil {
|
||||
break // but otherwise ignore
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if string(line) == `majorversion="7"` {
|
||||
v = 7
|
||||
return io.EOF
|
||||
return 7
|
||||
}
|
||||
if string(line) == `majorversion="6"` {
|
||||
v = 6
|
||||
return io.EOF
|
||||
return 6
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return v
|
||||
}
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user