Compare commits
20 Commits
bradfitz/u
...
crawshaw/e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b1b50ac27 | ||
|
|
df8f02db3f | ||
|
|
16652ae52c | ||
|
|
aaba49ca10 | ||
|
|
e64cecac8e | ||
|
|
2a67beaacf | ||
|
|
0626cf4183 | ||
|
|
d7962e3bcf | ||
|
|
6eed2811b2 | ||
|
|
e3dccfd7ff | ||
|
|
fa612c28cf | ||
|
|
e5cd765e00 | ||
|
|
bd90781b34 | ||
|
|
e45d51b060 | ||
|
|
730aa1c89c | ||
|
|
f5ec916214 | ||
|
|
69392411d9 | ||
|
|
02bdc654d5 | ||
|
|
70d71ba1e7 | ||
|
|
1af26222b6 |
6
.github/workflows/cross-darwin.yml
vendored
6
.github/workflows/cross-darwin.yml
vendored
@@ -37,6 +37,12 @@ jobs:
|
||||
GOARCH: amd64
|
||||
run: for d in $(go list -f '{{if .TestGoFiles}}{{.Dir}}{{end}}' ./... ); do (echo $d; cd $d && go test -c ); done
|
||||
|
||||
- name: iOS build most
|
||||
env:
|
||||
GOOS: ios
|
||||
GOARCH: arm64
|
||||
run: go install ./ipn/... ./wgengine/ ./types/... ./control/controlclient
|
||||
|
||||
- uses: k0kubun/action-slack@v2.0.0
|
||||
with:
|
||||
payload: |
|
||||
|
||||
66
api.md
66
api.md
@@ -15,6 +15,10 @@ Currently based on {some authentication method}. Visit the [admin panel](https:/
|
||||
- [POST device routes](#device-routes-post)
|
||||
- Authorize machine
|
||||
- [POST device authorized](#device-authorized-post)
|
||||
- Tags
|
||||
- [POST device tags](#device-tags-post)
|
||||
- Key
|
||||
- [POST device key](#device-key-post)
|
||||
* **[Tailnets](#tailnet)**
|
||||
- ACLs
|
||||
- [GET tailnet ACL](#tailnet-acl-get)
|
||||
@@ -268,6 +272,68 @@ curl 'https://api.tailscale.com/api/v2/device/11055/authorized' \
|
||||
The response is 2xx on success. The response body is currently an empty JSON
|
||||
object.
|
||||
|
||||
<a name=device-tags-post></a>
|
||||
|
||||
#### `POST /api/v2/device/:deviceID/tags` - update tags on a device
|
||||
|
||||
Updates the tags set on a device.
|
||||
|
||||
##### Parameters
|
||||
|
||||
###### POST Body
|
||||
|
||||
`tags` - The new list of tags for the device.
|
||||
|
||||
```
|
||||
{
|
||||
"tags": ["tag:foo", "tag:bar"]
|
||||
}
|
||||
```
|
||||
|
||||
##### Example
|
||||
|
||||
```
|
||||
curl 'https://api.tailscale.com/api/v2/device/11055/tags' \
|
||||
-u "tskey-yourapikey123:" \
|
||||
--data-binary '{"tags": ["tag:foo", "tag:bar"]}'
|
||||
```
|
||||
|
||||
The response is 2xx on success. The response body is currently an empty JSON
|
||||
object.
|
||||
|
||||
<a name=device-key-post></a>
|
||||
|
||||
#### `POST /api/v2/device/:deviceID/key` - update device key
|
||||
|
||||
Allows for updating properties on the device key.
|
||||
|
||||
##### Parameters
|
||||
|
||||
###### POST Body
|
||||
|
||||
`keyExpiryDisabled`
|
||||
|
||||
- Provide `true` to disable the device's key expiry. The original key expiry time is still maintained. Upon re-enabling, the key will expire at that original time.
|
||||
- Provide `false` to enable the device's key expiry. Sets the key to expire at the original expiry time prior to disabling. The key may already have expired. In that case, the device must be re-authenticated.
|
||||
- Empty value will not change the key expiry.
|
||||
|
||||
```
|
||||
{
|
||||
"keyExpiryDisabled": true
|
||||
}
|
||||
```
|
||||
|
||||
##### Example
|
||||
|
||||
```
|
||||
curl 'https://api.tailscale.com/api/v2/device/11055/key' \
|
||||
-u "tskey-yourapikey123:" \
|
||||
--data-binary '{"keyExpiryDisabled": true}'
|
||||
```
|
||||
|
||||
The response is 2xx on success. The response body is currently an empty JSON
|
||||
object.
|
||||
|
||||
## Tailnet
|
||||
A tailnet is the name of your Tailscale network.
|
||||
You can find it in the top left corner of the [Admin Panel](https://login.tailscale.com/admin) beside the Tailscale logo.
|
||||
|
||||
@@ -19,9 +19,9 @@ func New(socket string) (*BIRDClient, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to BIRD: %w", err)
|
||||
}
|
||||
b := &BIRDClient{socket: socket, conn: conn, bs: bufio.NewScanner(conn)}
|
||||
b := &BIRDClient{socket: socket, conn: conn, scanner: bufio.NewScanner(conn)}
|
||||
// Read and discard the first line as that is the welcome message.
|
||||
if _, err := b.readLine(); err != nil {
|
||||
if _, err := b.readResponse(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
@@ -29,9 +29,9 @@ func New(socket string) (*BIRDClient, error) {
|
||||
|
||||
// BIRDClient handles communication with the BIRD Internet Routing Daemon.
|
||||
type BIRDClient struct {
|
||||
socket string
|
||||
conn net.Conn
|
||||
bs *bufio.Scanner
|
||||
socket string
|
||||
conn net.Conn
|
||||
scanner *bufio.Scanner
|
||||
}
|
||||
|
||||
// Close closes the underlying connection to BIRD.
|
||||
@@ -39,7 +39,7 @@ func (b *BIRDClient) Close() error { return b.conn.Close() }
|
||||
|
||||
// DisableProtocol disables the provided protocol.
|
||||
func (b *BIRDClient) DisableProtocol(protocol string) error {
|
||||
out, err := b.exec("disable %s\n", protocol)
|
||||
out, err := b.exec("disable %s", protocol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -53,7 +53,7 @@ func (b *BIRDClient) DisableProtocol(protocol string) error {
|
||||
|
||||
// EnableProtocol enables the provided protocol.
|
||||
func (b *BIRDClient) EnableProtocol(protocol string) error {
|
||||
out, err := b.exec("enable %s\n", protocol)
|
||||
out, err := b.exec("enable %s", protocol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -65,19 +65,65 @@ func (b *BIRDClient) EnableProtocol(protocol string) error {
|
||||
return fmt.Errorf("failed to enable %s: %v", protocol, out)
|
||||
}
|
||||
|
||||
// BIRD CLI docs from https://bird.network.cz/?get_doc&v=20&f=prog-2.html#ss2.9
|
||||
|
||||
// Each session of the CLI consists of a sequence of request and replies,
|
||||
// slightly resembling the FTP and SMTP protocols.
|
||||
// Requests are commands encoded as a single line of text,
|
||||
// replies are sequences of lines starting with a four-digit code
|
||||
// followed by either a space (if it's the last line of the reply) or
|
||||
// a minus sign (when the reply is going to continue with the next line),
|
||||
// the rest of the line contains a textual message semantics of which depends on the numeric code.
|
||||
// If a reply line has the same code as the previous one and it's a continuation line,
|
||||
// the whole prefix can be replaced by a single white space character.
|
||||
//
|
||||
// Reply codes starting with 0 stand for ‘action successfully completed’ messages,
|
||||
// 1 means ‘table entry’, 8 ‘runtime error’ and 9 ‘syntax error’.
|
||||
|
||||
func (b *BIRDClient) exec(cmd string, args ...interface{}) (string, error) {
|
||||
if _, err := fmt.Fprintf(b.conn, cmd, args...); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return b.readLine()
|
||||
fmt.Fprintln(b.conn)
|
||||
return b.readResponse()
|
||||
}
|
||||
|
||||
func (b *BIRDClient) readLine() (string, error) {
|
||||
if !b.bs.Scan() {
|
||||
return "", fmt.Errorf("reading response from bird failed")
|
||||
// hasResponseCode reports whether the provided byte slice is
|
||||
// prefixed with a BIRD response code.
|
||||
// Equivalent regex: `^\d{4}[ -]`.
|
||||
func hasResponseCode(s []byte) bool {
|
||||
if len(s) < 5 {
|
||||
return false
|
||||
}
|
||||
if err := b.bs.Err(); err != nil {
|
||||
return "", err
|
||||
for _, b := range s[:4] {
|
||||
if '0' <= b && b <= '9' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return b.bs.Text(), nil
|
||||
return s[4] == ' ' || s[4] == '-'
|
||||
}
|
||||
|
||||
func (b *BIRDClient) readResponse() (string, error) {
|
||||
var resp strings.Builder
|
||||
var done bool
|
||||
for !done {
|
||||
if !b.scanner.Scan() {
|
||||
return "", fmt.Errorf("reading response from bird failed: %q", resp.String())
|
||||
}
|
||||
if err := b.scanner.Err(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
out := b.scanner.Bytes()
|
||||
if _, err := resp.Write(out); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if hasResponseCode(out) {
|
||||
done = out[4] == ' '
|
||||
}
|
||||
if !done {
|
||||
resp.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
return resp.String(), nil
|
||||
}
|
||||
|
||||
111
chirp/chirp_test.go
Normal file
111
chirp/chirp_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
package chirp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type fakeBIRD struct {
|
||||
net.Listener
|
||||
protocolsEnabled map[string]bool
|
||||
sock string
|
||||
}
|
||||
|
||||
func newFakeBIRD(t *testing.T, protocols ...string) *fakeBIRD {
|
||||
sock := filepath.Join(t.TempDir(), "sock")
|
||||
l, err := net.Listen("unix", sock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pe := make(map[string]bool)
|
||||
for _, p := range protocols {
|
||||
pe[p] = false
|
||||
}
|
||||
return &fakeBIRD{
|
||||
Listener: l,
|
||||
protocolsEnabled: pe,
|
||||
sock: sock,
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *fakeBIRD) listen() error {
|
||||
for {
|
||||
c, err := fb.Accept()
|
||||
if err != nil {
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
go fb.handle(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (fb *fakeBIRD) handle(c net.Conn) {
|
||||
fmt.Fprintln(c, "0001 BIRD 2.0.8 ready.")
|
||||
sc := bufio.NewScanner(c)
|
||||
for sc.Scan() {
|
||||
cmd := sc.Text()
|
||||
args := strings.Split(cmd, " ")
|
||||
switch args[0] {
|
||||
case "enable":
|
||||
en, ok := fb.protocolsEnabled[args[1]]
|
||||
if !ok {
|
||||
fmt.Fprintln(c, "9001 syntax error, unexpected CF_SYM_UNDEFINED, expecting CF_SYM_KNOWN or TEXT or ALL")
|
||||
} else if en {
|
||||
fmt.Fprintf(c, "0010-%s: already enabled\n", args[1])
|
||||
} else {
|
||||
fmt.Fprintf(c, "0011-%s: enabled\n", args[1])
|
||||
}
|
||||
fmt.Fprintln(c, "0000 ")
|
||||
fb.protocolsEnabled[args[1]] = true
|
||||
case "disable":
|
||||
en, ok := fb.protocolsEnabled[args[1]]
|
||||
if !ok {
|
||||
fmt.Fprintln(c, "9001 syntax error, unexpected CF_SYM_UNDEFINED, expecting CF_SYM_KNOWN or TEXT or ALL")
|
||||
} else if !en {
|
||||
fmt.Fprintf(c, "0008-%s: already disabled\n", args[1])
|
||||
} else {
|
||||
fmt.Fprintf(c, "0009-%s: disabled\n", args[1])
|
||||
}
|
||||
fmt.Fprintln(c, "0000 ")
|
||||
fb.protocolsEnabled[args[1]] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestChirp(t *testing.T) {
|
||||
fb := newFakeBIRD(t, "tailscale")
|
||||
defer fb.Close()
|
||||
go fb.listen()
|
||||
c, err := New(fb.sock)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.EnableProtocol("tailscale"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.EnableProtocol("tailscale"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.DisableProtocol("tailscale"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.DisableProtocol("tailscale"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.EnableProtocol("rando"); err == nil {
|
||||
t.Fatalf("enabling %q succeded", "rando")
|
||||
}
|
||||
if err := c.DisableProtocol("rando"); err == nil {
|
||||
t.Fatalf("disabling %q succeded", "rando")
|
||||
}
|
||||
}
|
||||
@@ -36,14 +36,15 @@ import (
|
||||
|
||||
var (
|
||||
dev = flag.Bool("dev", false, "run in localhost development mode")
|
||||
addr = flag.String("a", ":443", "server address")
|
||||
addr = flag.String("a", ":443", "server HTTPS listen address, in form \":port\", \"ip:port\", or for IPv6 \"[ip]:port\". If the IP is omitted, it defaults to all interfaces.")
|
||||
httpPort = flag.Int("http-port", 80, "The port on which to serve HTTP. Set to -1 to disable")
|
||||
configPath = flag.String("c", "", "config file path")
|
||||
certMode = flag.String("certmode", "letsencrypt", "mode for getting a cert. possible options: manual, letsencrypt")
|
||||
certDir = flag.String("certdir", tsweb.DefaultCertDir("derper-certs"), "directory to store LetsEncrypt certs, if addr's port is :443")
|
||||
hostname = flag.String("hostname", "derp.tailscale.com", "LetsEncrypt host name, if addr's port is :443")
|
||||
logCollection = flag.String("logcollection", "", "If non-empty, logtail collection to log to")
|
||||
runSTUN = flag.Bool("stun", false, "also run a STUN server")
|
||||
runSTUN = flag.Bool("stun", true, "whether to run a STUN server. It will bind to the same IP (if any) as the --addr flag value.")
|
||||
|
||||
meshPSKFile = flag.String("mesh-psk-file", defaultMeshPSKFile(), "if non-empty, path to file containing the mesh pre-shared key file. It should contain some hex string; whitespace is trimmed.")
|
||||
meshWith = flag.String("mesh-with", "", "optional comma-separated list of hostnames to mesh with; the server's own hostname can be in the list")
|
||||
bootstrapDNS = flag.String("bootstrap-dns-names", "", "optional comma-separated list of hostnames to make available at /bootstrap-dns")
|
||||
@@ -241,6 +242,8 @@ func main() {
|
||||
cert.Certificate = append(cert.Certificate, s.MetaCert())
|
||||
return cert, nil
|
||||
}
|
||||
// Disable TLS 1.0 and 1.1, which are obsolete and have security issues.
|
||||
httpsrv.TLSConfig.MinVersion = tls.VersionTLS12
|
||||
httpsrv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.TLS != nil {
|
||||
label := "unknown"
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html"
|
||||
@@ -33,11 +35,21 @@ var (
|
||||
listen = flag.String("listen", ":8030", "HTTP listen address")
|
||||
)
|
||||
|
||||
// certReissueAfter is the time after which we expect all certs to be
|
||||
// reissued, at minimum.
|
||||
//
|
||||
// This is currently set to the date of the LetsEncrypt ALPN revocation event of Jan 2022:
|
||||
// https://community.letsencrypt.org/t/questions-about-renewing-before-tls-alpn-01-revocations/170449
|
||||
//
|
||||
// If there's another revocation event, bump this again.
|
||||
var certReissueAfter = time.Unix(1643226768, 0)
|
||||
|
||||
var (
|
||||
mu sync.Mutex
|
||||
state = map[nodePair]pairStatus{}
|
||||
lastDERPMap *tailcfg.DERPMap
|
||||
lastDERPMapAt time.Time
|
||||
certs = map[string]*x509.Certificate{}
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -46,6 +58,12 @@ func main() {
|
||||
log.Fatal(http.ListenAndServe(*listen, http.HandlerFunc(serve)))
|
||||
}
|
||||
|
||||
func setCert(name string, cert *x509.Certificate) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
certs[name] = cert
|
||||
}
|
||||
|
||||
type overallStatus struct {
|
||||
good, bad []string
|
||||
}
|
||||
@@ -93,6 +111,27 @@ func getOverallStatus() (o overallStatus) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var subjs []string
|
||||
for k := range certs {
|
||||
subjs = append(subjs, k)
|
||||
}
|
||||
sort.Strings(subjs)
|
||||
|
||||
soon := time.Now().Add(14 * 24 * time.Hour) // in 2 weeks; autocert does 30 days by default
|
||||
for _, s := range subjs {
|
||||
cert := certs[s]
|
||||
if cert.NotBefore.Before(certReissueAfter) {
|
||||
o.addBadf("cert %q needs reissuing; NotBefore=%v", s, cert.NotBefore.Format(time.RFC3339))
|
||||
continue
|
||||
}
|
||||
if cert.NotAfter.Before(soon) {
|
||||
o.addBadf("cert %q expiring soon (%v); wasn't auto-refreshed", s, cert.NotAfter.Format(time.RFC3339))
|
||||
continue
|
||||
}
|
||||
o.addGoodf("cert %q good %v - %v", s, cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -359,6 +398,21 @@ func newConn(ctx context.Context, dm *tailcfg.DERPMap, n *tailcfg.DERPNode) (*de
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs, ok := dc.TLSConnectionState()
|
||||
if !ok {
|
||||
dc.Close()
|
||||
return nil, errors.New("no TLS state")
|
||||
}
|
||||
if len(cs.PeerCertificates) == 0 {
|
||||
dc.Close()
|
||||
return nil, errors.New("no peer certificates")
|
||||
}
|
||||
if cs.ServerName != n.HostName {
|
||||
dc.Close()
|
||||
return nil, fmt.Errorf("TLS server name %q != derp hostname %q", cs.ServerName, n.HostName)
|
||||
}
|
||||
setCert(cs.ServerName, cs.PeerCertificates[0])
|
||||
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
m, err := dc.Recv()
|
||||
|
||||
@@ -793,8 +793,25 @@ func TestUpdatePrefs(t *testing.T) {
|
||||
ControlURL: ipn.DefaultControlURL,
|
||||
Persist: &persist.Persist{LoginName: "crawshaw.github"},
|
||||
},
|
||||
env: upCheckEnv{backendState: "Running"},
|
||||
wantJustEditMP: &ipn.MaskedPrefs{WantRunningSet: true},
|
||||
env: upCheckEnv{backendState: "Running"},
|
||||
wantJustEditMP: &ipn.MaskedPrefs{
|
||||
AdvertiseRoutesSet: true,
|
||||
AdvertiseTagsSet: true,
|
||||
AllowSingleHostsSet: true,
|
||||
ControlURLSet: true,
|
||||
CorpDNSSet: true,
|
||||
ExitNodeAllowLANAccessSet: true,
|
||||
ExitNodeIDSet: true,
|
||||
ExitNodeIPSet: true,
|
||||
HostnameSet: true,
|
||||
NetfilterModeSet: true,
|
||||
NoSNATSet: true,
|
||||
OperatorUserSet: true,
|
||||
RouteAllSet: true,
|
||||
RunSSHSet: true,
|
||||
ShieldsUpSet: true,
|
||||
WantRunningSet: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "control_synonym",
|
||||
@@ -860,16 +877,23 @@ func TestUpdatePrefs(t *testing.T) {
|
||||
if simpleUp != tt.wantSimpleUp {
|
||||
t.Fatalf("simpleUp=%v, want %v", simpleUp, tt.wantSimpleUp)
|
||||
}
|
||||
var oldEditPrefs ipn.Prefs
|
||||
if justEditMP != nil {
|
||||
oldEditPrefs = justEditMP.Prefs
|
||||
justEditMP.Prefs = ipn.Prefs{} // uninteresting
|
||||
}
|
||||
if !reflect.DeepEqual(justEditMP, tt.wantJustEditMP) {
|
||||
t.Fatalf("justEditMP: %v", cmp.Diff(justEditMP, tt.wantJustEditMP))
|
||||
t.Logf("justEditMP != wantJustEditMP; following diff omits the Prefs field, which was %+v", oldEditPrefs)
|
||||
t.Fatalf("justEditMP: %v\n\n: ", cmp.Diff(justEditMP, tt.wantJustEditMP, cmpIP))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var cmpIP = cmp.Comparer(func(a, b netaddr.IP) bool {
|
||||
return a == b
|
||||
})
|
||||
|
||||
func TestExitNodeIPOfArg(t *testing.T) {
|
||||
mustIP := netaddr.MustParseIP
|
||||
tests := []struct {
|
||||
|
||||
@@ -387,7 +387,8 @@ func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goo
|
||||
return prefs, nil
|
||||
}
|
||||
|
||||
// updatePrefs updates prefs based on curPrefs
|
||||
// updatePrefs returns how to edit preferences based on the
|
||||
// flag-provided 'prefs' and the currently active 'curPrefs'.
|
||||
//
|
||||
// It returns a non-nil justEditMP if we're already running and none of
|
||||
// the flags require a restart, so we can just do an EditPrefs call and
|
||||
@@ -424,11 +425,16 @@ func updatePrefs(prefs, curPrefs *ipn.Prefs, env upCheckEnv) (simpleUp bool, jus
|
||||
env.upArgs.authKeyOrFile == "" &&
|
||||
!controlURLChanged &&
|
||||
!tagsChanged
|
||||
|
||||
if justEdit {
|
||||
justEditMP = new(ipn.MaskedPrefs)
|
||||
justEditMP.WantRunningSet = true
|
||||
justEditMP.Prefs = *prefs
|
||||
env.flagSet.Visit(func(f *flag.Flag) {
|
||||
visitFlags := env.flagSet.Visit
|
||||
if env.upArgs.reset {
|
||||
visitFlags = env.flagSet.VisitAll
|
||||
}
|
||||
visitFlags(func(f *flag.Flag) {
|
||||
updateMaskedPrefsFromUpFlag(justEditMP, f.Name)
|
||||
})
|
||||
}
|
||||
@@ -520,7 +526,7 @@ func runUp(ctx context.Context, args []string) error {
|
||||
pumpErr := make(chan error, 1)
|
||||
go func() { pumpErr <- pump(pumpCtx, bc, c) }()
|
||||
|
||||
printed := !simpleUp
|
||||
var printed bool // whether we've yet printed anything to stdout or stderr
|
||||
var loginOnce sync.Once
|
||||
startLoginInteractive := func() { loginOnce.Do(func() { bc.StartLoginInteractive() }) }
|
||||
|
||||
@@ -546,7 +552,6 @@ func runUp(ctx context.Context, args []string) error {
|
||||
if s := n.State; s != nil {
|
||||
switch *s {
|
||||
case ipn.NeedsLogin:
|
||||
printed = true
|
||||
startLoginInteractive()
|
||||
case ipn.NeedsMachineAuth:
|
||||
printed = true
|
||||
|
||||
@@ -67,7 +67,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet
|
||||
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
github.com/google/btree from inet.af/netstack/tcpip/header+
|
||||
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header+
|
||||
L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun
|
||||
L github.com/insomniacslk/dhcp/iana from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/insomniacslk/dhcp/interfaces from github.com/insomniacslk/dhcp/dhcpv4
|
||||
@@ -118,46 +118,46 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.zx2c4.com/wireguard/tai64n from golang.zx2c4.com/wireguard/device
|
||||
💣 golang.zx2c4.com/wireguard/tun from golang.zx2c4.com/wireguard/device+
|
||||
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/cmd/tailscaled+
|
||||
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/tcpip+
|
||||
💣 gvisor.dev/gvisor/pkg/buffer from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs+
|
||||
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
|
||||
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
|
||||
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+
|
||||
gvisor.dev/gvisor/pkg/rand from gvisor.dev/gvisor/pkg/tcpip/network/hash+
|
||||
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/refsvfs2
|
||||
gvisor.dev/gvisor/pkg/refsvfs2 from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
💣 gvisor.dev/gvisor/pkg/sleep from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
||||
💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+
|
||||
gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
|
||||
💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/linewriter+
|
||||
gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
|
||||
gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack
|
||||
💣 gvisor.dev/gvisor/pkg/tcpip/buffer from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
|
||||
gvisor.dev/gvisor/pkg/tcpip/hash/jenkins from gvisor.dev/gvisor/pkg/tcpip/stack+
|
||||
gvisor.dev/gvisor/pkg/tcpip/header from gvisor.dev/gvisor/pkg/tcpip/header/parse+
|
||||
gvisor.dev/gvisor/pkg/tcpip/header/parse from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/internal/tcp from gvisor.dev/gvisor/pkg/tcpip/stack+
|
||||
gvisor.dev/gvisor/pkg/tcpip/link/channel from tailscale.com/wgengine/netstack
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/hash from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/internal/fragmentation from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/internal/ip from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/ipv4 from tailscale.com/net/tstun+
|
||||
gvisor.dev/gvisor/pkg/tcpip/network/ipv6 from tailscale.com/wgengine/netstack
|
||||
gvisor.dev/gvisor/pkg/tcpip/ports from gvisor.dev/gvisor/pkg/tcpip/stack+
|
||||
gvisor.dev/gvisor/pkg/tcpip/seqnum from gvisor.dev/gvisor/pkg/tcpip/header+
|
||||
💣 gvisor.dev/gvisor/pkg/tcpip/stack from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
|
||||
gvisor.dev/gvisor/pkg/tcpip/transport from gvisor.dev/gvisor/pkg/tcpip/transport/icmp+
|
||||
gvisor.dev/gvisor/pkg/tcpip/transport/icmp from tailscale.com/wgengine/netstack
|
||||
gvisor.dev/gvisor/pkg/tcpip/transport/internal/network from gvisor.dev/gvisor/pkg/tcpip/transport/icmp+
|
||||
gvisor.dev/gvisor/pkg/tcpip/transport/internal/noop from gvisor.dev/gvisor/pkg/tcpip/transport/raw
|
||||
gvisor.dev/gvisor/pkg/tcpip/transport/packet from gvisor.dev/gvisor/pkg/tcpip/transport/raw
|
||||
gvisor.dev/gvisor/pkg/tcpip/transport/raw from gvisor.dev/gvisor/pkg/tcpip/transport/icmp+
|
||||
💣 gvisor.dev/gvisor/pkg/tcpip/transport/tcp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
|
||||
gvisor.dev/gvisor/pkg/tcpip/transport/tcpconntrack from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
gvisor.dev/gvisor/pkg/tcpip/transport/udp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+
|
||||
gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+
|
||||
inet.af/netaddr from inet.af/wf+
|
||||
inet.af/netstack/atomicbitops from inet.af/netstack/tcpip+
|
||||
💣 inet.af/netstack/buffer from inet.af/netstack/tcpip/stack
|
||||
inet.af/netstack/context from inet.af/netstack/refs+
|
||||
💣 inet.af/netstack/gohacks from inet.af/netstack/state/wire+
|
||||
inet.af/netstack/linewriter from inet.af/netstack/log
|
||||
inet.af/netstack/log from inet.af/netstack/state+
|
||||
inet.af/netstack/rand from inet.af/netstack/tcpip/network/hash+
|
||||
inet.af/netstack/refs from inet.af/netstack/refsvfs2
|
||||
inet.af/netstack/refsvfs2 from inet.af/netstack/tcpip/stack
|
||||
💣 inet.af/netstack/sleep from inet.af/netstack/tcpip/transport/tcp
|
||||
💣 inet.af/netstack/state from inet.af/netstack/atomicbitops+
|
||||
inet.af/netstack/state/wire from inet.af/netstack/state
|
||||
💣 inet.af/netstack/sync from inet.af/netstack/linewriter+
|
||||
inet.af/netstack/tcpip from inet.af/netstack/tcpip/adapters/gonet+
|
||||
inet.af/netstack/tcpip/adapters/gonet from tailscale.com/wgengine/netstack
|
||||
💣 inet.af/netstack/tcpip/buffer from inet.af/netstack/tcpip/adapters/gonet+
|
||||
inet.af/netstack/tcpip/hash/jenkins from inet.af/netstack/tcpip/stack+
|
||||
inet.af/netstack/tcpip/header from inet.af/netstack/tcpip/header/parse+
|
||||
inet.af/netstack/tcpip/header/parse from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/internal/tcp from inet.af/netstack/tcpip/stack+
|
||||
inet.af/netstack/tcpip/link/channel from tailscale.com/wgengine/netstack
|
||||
inet.af/netstack/tcpip/network/hash from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/network/internal/fragmentation from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/network/internal/ip from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/network/ipv4 from tailscale.com/net/tstun+
|
||||
inet.af/netstack/tcpip/network/ipv6 from tailscale.com/wgengine/netstack
|
||||
inet.af/netstack/tcpip/ports from inet.af/netstack/tcpip/stack+
|
||||
inet.af/netstack/tcpip/seqnum from inet.af/netstack/tcpip/header+
|
||||
💣 inet.af/netstack/tcpip/stack from inet.af/netstack/tcpip/adapters/gonet+
|
||||
inet.af/netstack/tcpip/transport from inet.af/netstack/tcpip/transport/icmp+
|
||||
inet.af/netstack/tcpip/transport/icmp from tailscale.com/wgengine/netstack
|
||||
inet.af/netstack/tcpip/transport/internal/network from inet.af/netstack/tcpip/transport/icmp+
|
||||
inet.af/netstack/tcpip/transport/internal/noop from inet.af/netstack/tcpip/transport/raw
|
||||
inet.af/netstack/tcpip/transport/packet from inet.af/netstack/tcpip/transport/raw
|
||||
inet.af/netstack/tcpip/transport/raw from inet.af/netstack/tcpip/transport/icmp+
|
||||
💣 inet.af/netstack/tcpip/transport/tcp from inet.af/netstack/tcpip/adapters/gonet+
|
||||
inet.af/netstack/tcpip/transport/tcpconntrack from inet.af/netstack/tcpip/stack
|
||||
inet.af/netstack/tcpip/transport/udp from inet.af/netstack/tcpip/adapters/gonet+
|
||||
inet.af/netstack/waiter from inet.af/netstack/tcpip+
|
||||
inet.af/peercred from tailscale.com/ipn/ipnserver
|
||||
W 💣 inet.af/wf from tailscale.com/wf
|
||||
L nhooyr.io/websocket from tailscale.com/derp/derphttp+
|
||||
@@ -306,12 +306,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
|
||||
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
|
||||
golang.org/x/text/unicode/norm from golang.org/x/net/idna
|
||||
golang.org/x/time/rate from inet.af/netstack/tcpip/stack+
|
||||
golang.org/x/time/rate from gvisor.dev/gvisor/pkg/tcpip/stack+
|
||||
bufio from compress/flate+
|
||||
bytes from bufio+
|
||||
compress/flate from compress/gzip+
|
||||
compress/gzip from internal/profile+
|
||||
container/heap from inet.af/netstack/tcpip/transport/tcp
|
||||
container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp
|
||||
container/list from crypto/tls+
|
||||
context from crypto/tls+
|
||||
crypto from crypto/ecdsa+
|
||||
@@ -349,7 +349,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
fmt from compress/flate+
|
||||
hash from crypto+
|
||||
hash/crc32 from compress/gzip+
|
||||
hash/fnv from inet.af/netstack/tcpip/network/ipv6+
|
||||
hash/fnv from gvisor.dev/gvisor/pkg/tcpip/network/ipv6+
|
||||
hash/maphash from go4.org/mem
|
||||
html from net/http/pprof+
|
||||
io from bufio+
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"go4.org/mem"
|
||||
@@ -64,6 +65,12 @@ type Client struct {
|
||||
ctx context.Context // closed via cancelCtx in Client.Close
|
||||
cancelCtx context.CancelFunc
|
||||
|
||||
// addrFamSelAtomic is the last AddressFamilySelector set
|
||||
// by SetAddressFamilySelector. It's an atomic because it needs
|
||||
// to be accessed by multiple racing routines started while
|
||||
// Client.conn holds mu.
|
||||
addrFamSelAtomic atomic.Value // of AddressFamilySelector
|
||||
|
||||
mu sync.Mutex
|
||||
preferred bool
|
||||
canAckPings bool
|
||||
@@ -72,6 +79,7 @@ type Client struct {
|
||||
client *derp.Client
|
||||
connGen int // incremented once per new connection; valid values are >0
|
||||
serverPubKey key.NodePublic
|
||||
tlsState *tls.ConnectionState
|
||||
pingOut map[derp.PingMessage]chan<- bool // chan to send to on pong
|
||||
}
|
||||
|
||||
@@ -124,6 +132,17 @@ func (c *Client) Connect(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// TLSConnectionState returns the last TLS connection state, if any.
|
||||
// The client must already be connected.
|
||||
func (c *Client) TLSConnectionState() (_ *tls.ConnectionState, ok bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.closed || c.client == nil {
|
||||
return nil, false
|
||||
}
|
||||
return c.tlsState, c.tlsState != nil
|
||||
}
|
||||
|
||||
// ServerPublicKey returns the server's public key.
|
||||
//
|
||||
// It only returns a non-zero value once a connection has succeeded
|
||||
@@ -181,6 +200,32 @@ func (c *Client) urlString(node *tailcfg.DERPNode) string {
|
||||
return fmt.Sprintf("https://%s/derp", node.HostName)
|
||||
}
|
||||
|
||||
// AddressFamilySelector decides whethers IPv6 is preferred for
|
||||
// outbound dials.
|
||||
type AddressFamilySelector interface {
|
||||
// PreferIPv6 reports whether IPv4 dials should be slightly
|
||||
// delayed to give IPv6 a better chance of winning dial races.
|
||||
// Implementations should only return true if IPv6 is expected
|
||||
// to succeed. (otherwise delaying IPv4 will delay the
|
||||
// connection overall)
|
||||
PreferIPv6() bool
|
||||
}
|
||||
|
||||
// SetAddressFamilySelector sets the AddressFamilySelector that this
|
||||
// connection will use. It should be called before any dials.
|
||||
// The value must not be nil. If called more than once, s must
|
||||
// be the same concrete type as any prior calls.
|
||||
func (c *Client) SetAddressFamilySelector(s AddressFamilySelector) {
|
||||
c.addrFamSelAtomic.Store(s)
|
||||
}
|
||||
|
||||
func (c *Client) preferIPv6() bool {
|
||||
if s, ok := c.addrFamSelAtomic.Load().(AddressFamilySelector); ok {
|
||||
return s.PreferIPv6()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// dialWebsocketFunc is non-nil (set by websocket.go's init) when compiled in.
|
||||
var dialWebsocketFunc func(ctx context.Context, urlStr string) (net.Conn, error)
|
||||
|
||||
@@ -318,6 +363,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
||||
var httpConn net.Conn // a TCP conn or a TLS conn; what we speak HTTP to
|
||||
var serverPub key.NodePublic // or zero if unknown (if not using TLS or TLS middlebox eats it)
|
||||
var serverProtoVersion int
|
||||
var tlsState *tls.ConnectionState
|
||||
if c.useHTTPS() {
|
||||
tlsConn := c.tlsClient(tcpConn, node)
|
||||
httpConn = tlsConn
|
||||
@@ -340,9 +386,10 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
||||
// Note that we're not specifically concerned about TLS downgrade
|
||||
// attacks. TLS handles that fine:
|
||||
// https://blog.gypsyengineer.com/en/security/how-does-tls-1-3-protect-against-downgrade-attacks.html
|
||||
connState := tlsConn.ConnectionState()
|
||||
if connState.Version >= tls.VersionTLS13 {
|
||||
serverPub, serverProtoVersion = parseMetaCert(connState.PeerCertificates)
|
||||
cs := tlsConn.ConnectionState()
|
||||
tlsState = &cs
|
||||
if cs.Version >= tls.VersionTLS13 {
|
||||
serverPub, serverProtoVersion = parseMetaCert(cs.PeerCertificates)
|
||||
}
|
||||
} else {
|
||||
httpConn = tcpConn
|
||||
@@ -409,6 +456,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
||||
c.serverPubKey = derpClient.ServerPublicKey()
|
||||
c.client = derpClient
|
||||
c.netConn = tcpConn
|
||||
c.tlsState = tlsState
|
||||
c.connGen++
|
||||
return c.client, c.connGen, nil
|
||||
}
|
||||
@@ -568,6 +616,18 @@ func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, e
|
||||
startDial := func(dstPrimary, proto string) {
|
||||
nwait++
|
||||
go func() {
|
||||
if proto == "tcp4" && c.preferIPv6() {
|
||||
t := time.NewTimer(200 * time.Millisecond)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Either user canceled original context,
|
||||
// it timed out, or the v6 dial succeeded.
|
||||
t.Stop()
|
||||
return
|
||||
case <-t.C:
|
||||
// Start v4 dial
|
||||
}
|
||||
}
|
||||
dst := dstPrimary
|
||||
if dst == "" {
|
||||
dst = n.HostName
|
||||
|
||||
2
go.mod
2
go.mod
@@ -56,9 +56,9 @@ require (
|
||||
golang.org/x/tools v0.1.8
|
||||
golang.zx2c4.com/wireguard v0.0.0-20211116201604-de7c702ace45
|
||||
golang.zx2c4.com/wireguard/windows v0.4.10
|
||||
gvisor.dev/gvisor v0.0.0-20220126021142-d8aa030b2591
|
||||
honnef.co/go/tools v0.2.2
|
||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6
|
||||
inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
|
||||
inet.af/wf v0.0.0-20211204062712-86aaea0a7310
|
||||
nhooyr.io/websocket v1.8.7
|
||||
|
||||
12
go.sum
12
go.sum
@@ -189,7 +189,6 @@ github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacM
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc=
|
||||
github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
@@ -469,7 +468,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4=
|
||||
github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
@@ -1113,12 +1111,10 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tomarrell/wrapcheck v0.0.0-20200807122107-df9e8bcb914d/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0=
|
||||
github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756 h1:zV5mu0ESwb+WnzqVaW2z1DdbAP0S46UtjY8DHQupQP4=
|
||||
github.com/tomarrell/wrapcheck v0.0.0-20201130113247-1683564d9756/go.mod h1:yiFB6fFoV7saXirUGfuK+cPtUh4NX/Hf5y2WC2lehu0=
|
||||
github.com/tomarrell/wrapcheck/v2 v2.4.0 h1:mU4H9KsqqPZUALOUbVOpjy8qNQbWLoLI9fV68/1tq30=
|
||||
github.com/tomarrell/wrapcheck/v2 v2.4.0/go.mod h1:68bQ/eJg55BROaRTbMjC7vuhL2OgfoG8bLp9ZyoBfyY=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As=
|
||||
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.4.0 h1:1t0f8Uiaq+fqKteUR4N9Umr6E99R+lDnLnq7PwX2PPE=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.4.0/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
@@ -1178,7 +1174,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/ziutek/telnet v0.0.0-20180329124119-c3b780dc415b/go.mod h1:IZpXDfkJ6tWD3PhBK5YzgQT+xJWh7OsdwiG8hA2MkO4=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
@@ -1229,7 +1224,6 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
@@ -1351,7 +1345,6 @@ golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211205041911-012df41ee64c h1:7SfqwP5fxEtl/P02w5IhKc86ziJ+A25yFrkVgoy2FT8=
|
||||
@@ -1497,7 +1490,6 @@ golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
|
||||
@@ -1825,6 +1817,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20220126021142-d8aa030b2591 h1:acuXPUADpJMtawdLCUje9xKlQN/8utegCB/Hr/ZgEuY=
|
||||
gvisor.dev/gvisor v0.0.0-20220126021142-d8aa030b2591/go.mod h1:vmN0Pug/s8TJmpnt30DvrEfZ5vDl52psGLU04tFuK2U=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1842,8 +1836,6 @@ howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
inet.af/netaddr v0.0.0-20210515010201-ad03edc7c841/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw=
|
||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8=
|
||||
inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c h1:nr31qYr+91rWD8klUkPx3eGTZzumCC414UJG1QRKZTc=
|
||||
inet.af/netstack v0.0.0-20211120045802-8aa80cf23d3c/go.mod h1:KOJdAzQzMLKzwFEdOOnrnSrLIhaFVB+NQoME/e5wllA=
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a h1:qdkS8Q5/i10xU2ArJMKYhVa1DORzBfYS/qA2UK2jheg=
|
||||
inet.af/peercred v0.0.0-20210906144145-0893ea02156a/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
|
||||
inet.af/wf v0.0.0-20211204062712-86aaea0a7310 h1:0jKHTf+W75kYRyg5bto1UT+r18QmAz2u/5pAs/fx4zo=
|
||||
|
||||
@@ -35,6 +35,7 @@ func New() *tailcfg.Hostinfo {
|
||||
Package: packageType(),
|
||||
GoArch: runtime.GOARCH,
|
||||
DeviceModel: deviceModel(),
|
||||
EnvType: GetEnvType(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,25 +76,10 @@ func packageType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// EnvType represents a known environment type.
|
||||
// The empty string, the default, means unknown.
|
||||
type EnvType string
|
||||
var envType atomic.Value // of tailcfg.EnvType
|
||||
|
||||
const (
|
||||
KNative = EnvType("kn")
|
||||
AWSLambda = EnvType("lm")
|
||||
Heroku = EnvType("hr")
|
||||
AzureAppService = EnvType("az")
|
||||
AWSFargate = EnvType("fg")
|
||||
FlyDotIo = EnvType("fly")
|
||||
Kubernetes = EnvType("k8s")
|
||||
DockerDesktop = EnvType("dde")
|
||||
)
|
||||
|
||||
var envType atomic.Value // of EnvType
|
||||
|
||||
func GetEnvType() EnvType {
|
||||
if e, ok := envType.Load().(EnvType); ok {
|
||||
func GetEnvType() tailcfg.EnvType {
|
||||
if e, ok := envType.Load().(tailcfg.EnvType); ok {
|
||||
return e
|
||||
}
|
||||
e := getEnvType()
|
||||
@@ -123,30 +109,30 @@ func deviceModel() string {
|
||||
return s
|
||||
}
|
||||
|
||||
func getEnvType() EnvType {
|
||||
func getEnvType() tailcfg.EnvType {
|
||||
if inKnative() {
|
||||
return KNative
|
||||
return tailcfg.KNative
|
||||
}
|
||||
if inAWSLambda() {
|
||||
return AWSLambda
|
||||
return tailcfg.AWSLambda
|
||||
}
|
||||
if inHerokuDyno() {
|
||||
return Heroku
|
||||
return tailcfg.Heroku
|
||||
}
|
||||
if inAzureAppService() {
|
||||
return AzureAppService
|
||||
return tailcfg.AzureAppService
|
||||
}
|
||||
if inAWSFargate() {
|
||||
return AWSFargate
|
||||
return tailcfg.AWSFargate
|
||||
}
|
||||
if inFlyDotIo() {
|
||||
return FlyDotIo
|
||||
return tailcfg.FlyDotIo
|
||||
}
|
||||
if inKubernetes() {
|
||||
return Kubernetes
|
||||
return tailcfg.Kubernetes
|
||||
}
|
||||
if inDockerDesktop() {
|
||||
return DockerDesktop
|
||||
return tailcfg.DockerDesktop
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, diale
|
||||
wiredPeerAPIPort := false
|
||||
if ig, ok := e.(wgengine.InternalsGetter); ok {
|
||||
if tunWrap, _, ok := ig.GetInternals(); ok {
|
||||
tunWrap.PeerAPIPort = b.getPeerAPIPortForTSMPPing
|
||||
tunWrap.PeerAPIPort = b.GetPeerAPIPort
|
||||
wiredPeerAPIPort = true
|
||||
}
|
||||
}
|
||||
@@ -1788,7 +1788,9 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) {
|
||||
b.send(ipn.Notify{Prefs: newp})
|
||||
}
|
||||
|
||||
func (b *LocalBackend) getPeerAPIPortForTSMPPing(ip netaddr.IP) (port uint16, ok bool) {
|
||||
// GetPeerAPIPort returns the port number for the peerapi server
|
||||
// running on the provided IP.
|
||||
func (b *LocalBackend) GetPeerAPIPort(ip netaddr.IP) (port uint16, ok bool) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
for _, pln := range b.peerAPIListeners {
|
||||
@@ -1799,6 +1801,27 @@ func (b *LocalBackend) getPeerAPIPortForTSMPPing(ip netaddr.IP) (port uint16, ok
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// ServePeerAPIConnection serves an already-accepted connection c.
|
||||
//
|
||||
// The remote parameter is the remote address.
|
||||
// The local paramater is the local address (either a Tailscale IPv4
|
||||
// or IPv6 IP and the peerapi port for that address).
|
||||
//
|
||||
// The connection will be closed by ServePeerAPIConnection.
|
||||
func (b *LocalBackend) ServePeerAPIConnection(remote, local netaddr.IPPort, c net.Conn) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
for _, pln := range b.peerAPIListeners {
|
||||
if pln.ip == local.IP() {
|
||||
go pln.ServeConn(remote, c)
|
||||
return
|
||||
}
|
||||
}
|
||||
b.logf("[unexpected] no peerAPI listener found for %v", local)
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
|
||||
func (b *LocalBackend) peerAPIServicesLocked() (ret []tailcfg.Service) {
|
||||
for _, pln := range b.peerAPIListeners {
|
||||
proto := tailcfg.PeerAPI4
|
||||
|
||||
@@ -481,29 +481,34 @@ func (pln *peerAPIListener) serve() {
|
||||
c.Close()
|
||||
continue
|
||||
}
|
||||
peerNode, peerUser, ok := pln.lb.WhoIs(ipp)
|
||||
if !ok {
|
||||
logf("peerapi: unknown peer %v", ipp)
|
||||
c.Close()
|
||||
continue
|
||||
}
|
||||
h := &peerAPIHandler{
|
||||
ps: pln.ps,
|
||||
isSelf: pln.ps.selfNode.User == peerNode.User,
|
||||
remoteAddr: ipp,
|
||||
peerNode: peerNode,
|
||||
peerUser: peerUser,
|
||||
}
|
||||
httpServer := &http.Server{
|
||||
Handler: h,
|
||||
}
|
||||
if addH2C != nil {
|
||||
addH2C(httpServer)
|
||||
}
|
||||
go httpServer.Serve(&oneConnListener{Listener: pln.ln, conn: c})
|
||||
pln.ServeConn(ipp, c)
|
||||
}
|
||||
}
|
||||
|
||||
func (pln *peerAPIListener) ServeConn(src netaddr.IPPort, c net.Conn) {
|
||||
logf := pln.lb.logf
|
||||
peerNode, peerUser, ok := pln.lb.WhoIs(src)
|
||||
if !ok {
|
||||
logf("peerapi: unknown peer %v", src)
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
h := &peerAPIHandler{
|
||||
ps: pln.ps,
|
||||
isSelf: pln.ps.selfNode.User == peerNode.User,
|
||||
remoteAddr: src,
|
||||
peerNode: peerNode,
|
||||
peerUser: peerUser,
|
||||
}
|
||||
httpServer := &http.Server{
|
||||
Handler: h,
|
||||
}
|
||||
if addH2C != nil {
|
||||
addH2C(httpServer)
|
||||
}
|
||||
go httpServer.Serve(&oneConnListener{Listener: pln.ln, conn: c})
|
||||
}
|
||||
|
||||
type oneConnListener struct {
|
||||
net.Listener
|
||||
conn net.Conn
|
||||
|
||||
@@ -47,6 +47,7 @@ import (
|
||||
"tailscale.com/util/groupmember"
|
||||
"tailscale.com/util/pidowner"
|
||||
"tailscale.com/util/systemd"
|
||||
"tailscale.com/util/winutil"
|
||||
"tailscale.com/version"
|
||||
"tailscale.com/version/distro"
|
||||
"tailscale.com/wgengine"
|
||||
@@ -182,6 +183,13 @@ func (s *Server) getConnIdentity(c net.Conn) (ci connIdentity, err error) {
|
||||
func lookupUserFromID(logf logger.Logf, uid string) (*user.User, error) {
|
||||
u, err := user.LookupId(uid)
|
||||
if err != nil && runtime.GOOS == "windows" && errors.Is(err, syscall.Errno(0x534)) {
|
||||
// The below workaround is only applicable when uid represents a
|
||||
// valid security principal. Omitting this check causes us to succeed
|
||||
// even when uid represents a deleted user.
|
||||
if !winutil.IsSIDValidPrincipal(uid) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logf("[warning] issue 869: os/user.LookupId failed; ignoring")
|
||||
// Work around https://github.com/tailscale/tailscale/issues/869 for
|
||||
// now. We don't strictly need the username. It's just a nice-to-have.
|
||||
|
||||
@@ -7,6 +7,7 @@ package logtail
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -156,3 +157,21 @@ func fromHexChar(c byte) (byte, bool) {
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (id1 PublicID) Less(id2 PublicID) bool {
|
||||
for i, c1 := range id1[:] {
|
||||
c2 := id2[i]
|
||||
if c1 != c2 {
|
||||
return c1 < c2
|
||||
}
|
||||
}
|
||||
return false // equal
|
||||
}
|
||||
|
||||
func (id PublicID) IsZero() bool {
|
||||
return id == PublicID{}
|
||||
}
|
||||
|
||||
func (id PublicID) Prefix64() uint64 {
|
||||
return binary.BigEndian.Uint64(id[:8])
|
||||
}
|
||||
|
||||
@@ -60,7 +60,9 @@ func (m *resolvdManager) SetDNS(config OSConfig) error {
|
||||
newSearch = append(newSearch, s.WithoutTrailingDot())
|
||||
}
|
||||
|
||||
newResolvConf = append(newResolvConf, []byte(strings.Join(newSearch, " "))...)
|
||||
if len(newSearch) > 1 {
|
||||
newResolvConf = append(newResolvConf, []byte(strings.Join(newSearch, " "))...)
|
||||
}
|
||||
|
||||
err = m.fs.WriteFile(resolvConf, newResolvConf, 0644)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"tailscale.com/hostinfo"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
// LoginEndpointForProxyDetermination is the URL used for testing
|
||||
@@ -153,7 +154,7 @@ func LocalAddresses() (regular, loopback []netaddr.IP, err error) {
|
||||
// addresses we otherwise wouldn't, like:
|
||||
// + 169.254.x.x (AWS Lambda uses NAT with these)
|
||||
// + IPv6 ULA (Google Cloud Run uses these with address translation)
|
||||
if hostinfo.GetEnvType() == hostinfo.AWSLambda {
|
||||
if hostinfo.GetEnvType() == tailcfg.AWSLambda {
|
||||
regular4 = linklocal4
|
||||
}
|
||||
regular6 = ula6
|
||||
@@ -615,7 +616,7 @@ func isUsableV4(ip netaddr.IP) bool {
|
||||
return false
|
||||
}
|
||||
if ip.IsLinkLocalUnicast() {
|
||||
return hostinfo.GetEnvType() == hostinfo.AWSLambda
|
||||
return hostinfo.GetEnvType() == tailcfg.AWSLambda
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ func init() {
|
||||
|
||||
var procNetRouteErr syncs.AtomicBool
|
||||
|
||||
// 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:
|
||||
|
||||
@@ -47,12 +52,15 @@ func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) {
|
||||
}
|
||||
lineNum := 0
|
||||
var f []mem.RO
|
||||
err := lineread.File("/proc/net/route", func(line []byte) error {
|
||||
err := lineread.File(procNetRoutePath, func(line []byte) error {
|
||||
lineNum++
|
||||
if lineNum == 1 {
|
||||
// Skip header line.
|
||||
return nil
|
||||
}
|
||||
if lineNum > maxProcNetRouteRead {
|
||||
return errStopReading
|
||||
}
|
||||
f = mem.AppendFields(f[:0], mem.B(line))
|
||||
if len(f) < 4 {
|
||||
return nil
|
||||
@@ -74,9 +82,13 @@ func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) {
|
||||
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
|
||||
if ip.IsPrivate() {
|
||||
ret = ip
|
||||
return errStopReading
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if errors.Is(err, errStopReading) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
procNetRouteErr.Set(true)
|
||||
if runtime.GOOS == "android" {
|
||||
@@ -139,6 +151,10 @@ func defaultRoute() (d DefaultRouteDetails, err error) {
|
||||
var zeroRouteBytes = []byte("00000000")
|
||||
var procNetRoutePath = "/proc/net/route"
|
||||
|
||||
// maxProcNetRouteRead is the max number of lines to read from
|
||||
// /proc/net/route looking for a default route.
|
||||
const maxProcNetRouteRead = 1000
|
||||
|
||||
func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
|
||||
f, err := os.Open(procNetRoutePath)
|
||||
if err != nil {
|
||||
@@ -147,9 +163,11 @@ func defaultRouteInterfaceProcNetInternal(bufsize int) (string, error) {
|
||||
defer f.Close()
|
||||
|
||||
br := bufio.NewReaderSize(f, bufsize)
|
||||
lineNum := 0
|
||||
for {
|
||||
lineNum++
|
||||
line, err := br.ReadSlice('\n')
|
||||
if err == io.EOF {
|
||||
if err == io.EOF || lineNum > maxProcNetRouteRead {
|
||||
return "", fmt.Errorf("no default routes found: %w", err)
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestExtremelyLongProcNetRoute(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for n := 0; n <= 1000; n++ {
|
||||
for n := 0; n <= 900; n++ {
|
||||
line := fmt.Sprintf("eth%d\t8008FEA9\t00000000\t0001\t0\t0\t0\t01FFFFFF\t0\t0\t0\n", n)
|
||||
_, err := f.Write([]byte(line))
|
||||
if err != nil {
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||
"inet.af/netaddr"
|
||||
"inet.af/netstack/tcpip"
|
||||
"inet.af/netstack/tcpip/buffer"
|
||||
"inet.af/netstack/tcpip/header"
|
||||
"inet.af/netstack/tcpip/network/ipv4"
|
||||
"inet.af/netstack/tcpip/transport/udp"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
|
||||
@@ -41,11 +41,16 @@ main() {
|
||||
# - ID: the short name of the OS (e.g. "debian", "freebsd")
|
||||
# - VERSION_ID: the numeric release version for the OS, if any (e.g. "18.04")
|
||||
# - VERSION_CODENAME: the codename of the OS release, if any (e.g. "buster")
|
||||
# - UBUNTU_CODENAME: if it exists, as in linuxmint, use instead of VERSION_CODENAME
|
||||
. /etc/os-release
|
||||
case "$ID" in
|
||||
ubuntu|pop|neon|zorin|elementary|linuxmint)
|
||||
OS="ubuntu"
|
||||
VERSION="$VERSION_CODENAME"
|
||||
if [ "${UBUNTU_CODENAME:-}" != "" ]; then
|
||||
VERSION="$UBUNTU_CODENAME"
|
||||
else
|
||||
VERSION="$VERSION_CODENAME"
|
||||
fi
|
||||
PACKAGETYPE="apt"
|
||||
# Third-party keyrings became the preferred method of
|
||||
# installation in Ubuntu 20.04.
|
||||
|
||||
@@ -445,6 +445,7 @@ type Hostinfo struct {
|
||||
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
|
||||
ShareeNode bool `json:",omitempty"` // indicates this node exists in netmap because it's owned by a shared-to user
|
||||
GoArch string `json:",omitempty"` // the host's GOARCH value (of the running binary)
|
||||
EnvType EnvType `json:",omitempty"` // the host's environment type, if known
|
||||
RoutableIPs []netaddr.IPPrefix `json:",omitempty"` // set of IP ranges this client can route
|
||||
RequestTags []string `json:",omitempty"` // set of ACL tags this node wants to claim
|
||||
Services []Service `json:",omitempty"` // services advertised by this machine
|
||||
@@ -1369,3 +1370,18 @@ type SetDNSRequest struct {
|
||||
// Value is the value to add.
|
||||
Value string
|
||||
}
|
||||
|
||||
// EnvType represents a known environment type.
|
||||
// The empty string, the default, means unknown.
|
||||
type EnvType string
|
||||
|
||||
const (
|
||||
KNative = EnvType("kn")
|
||||
AWSLambda = EnvType("lm")
|
||||
Heroku = EnvType("hr")
|
||||
AzureAppService = EnvType("az")
|
||||
AWSFargate = EnvType("fg")
|
||||
FlyDotIo = EnvType("fly")
|
||||
Kubernetes = EnvType("k8s")
|
||||
DockerDesktop = EnvType("dde")
|
||||
)
|
||||
|
||||
@@ -122,6 +122,7 @@ var _HostinfoCloneNeedsRegeneration = Hostinfo(struct {
|
||||
ShieldsUp bool
|
||||
ShareeNode bool
|
||||
GoArch string
|
||||
EnvType EnvType
|
||||
RoutableIPs []netaddr.IPPrefix
|
||||
RequestTags []string
|
||||
Services []Service
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestHostinfoEqual(t *testing.T) {
|
||||
"IPNVersion", "FrontendLogID", "BackendLogID",
|
||||
"OS", "OSVersion", "Package", "DeviceModel", "Hostname",
|
||||
"ShieldsUp", "ShareeNode",
|
||||
"GoArch",
|
||||
"GoArch", "EnvType",
|
||||
"RoutableIPs", "RequestTags",
|
||||
"Services", "NetInfo",
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"inet.af/netstack/atomicbitops"
|
||||
"gvisor.dev/gvisor/pkg/atomicbitops"
|
||||
)
|
||||
|
||||
// tests netstack's AlignedAtomicInt64.
|
||||
|
||||
@@ -376,6 +376,7 @@ func (h *Harness) testDistro(t *testing.T, d Distro, ipm ipMapping) {
|
||||
t.Run("login", func(t *testing.T) {
|
||||
runTestCommands(t, timeout, cli, []expect.Batcher{
|
||||
&expect.BSnd{S: fmt.Sprintf("tailscale up --login-server=%s\n", loginServer)},
|
||||
&expect.BSnd{S: "echo Success.\n"},
|
||||
&expect.BExp{R: `Success.`},
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,9 +5,17 @@
|
||||
package tsweb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go4.org/mem"
|
||||
)
|
||||
|
||||
type response struct {
|
||||
@@ -85,7 +93,73 @@ func (fn JSONHandlerFunc) ServeHTTPReturn(w http.ResponseWriter, r *http.Request
|
||||
return jerr
|
||||
}
|
||||
|
||||
w.WriteHeader(status)
|
||||
w.Write(b)
|
||||
if AcceptsEncoding(r, "gzip") {
|
||||
encb, err := gzipBytes(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(encb)))
|
||||
w.Write(encb)
|
||||
} else {
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(b)))
|
||||
w.WriteHeader(status)
|
||||
w.Write(b)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var gzWriterPool sync.Pool // of *gzip.Writer
|
||||
|
||||
// gzipBytes returns the gzipped encoding of b.
|
||||
func gzipBytes(b []byte) (zb []byte, err error) {
|
||||
var buf bytes.Buffer
|
||||
zw, ok := gzWriterPool.Get().(*gzip.Writer)
|
||||
if ok {
|
||||
zw.Reset(&buf)
|
||||
} else {
|
||||
zw = gzip.NewWriter(&buf)
|
||||
}
|
||||
defer gzWriterPool.Put(zw)
|
||||
if _, err := zw.Write(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := zw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zb = buf.Bytes()
|
||||
zw.Reset(ioutil.Discard)
|
||||
return zb, nil
|
||||
}
|
||||
|
||||
// AcceptsEncoding reports whether r accepts the named encoding
|
||||
// ("gzip", "br", etc).
|
||||
func AcceptsEncoding(r *http.Request, enc string) bool {
|
||||
h := r.Header.Get("Accept-Encoding")
|
||||
if h == "" {
|
||||
return false
|
||||
}
|
||||
if !strings.Contains(h, enc) && !mem.ContainsFold(mem.S(h), mem.S(enc)) {
|
||||
return false
|
||||
}
|
||||
remain := h
|
||||
for len(remain) > 0 {
|
||||
comma := strings.Index(remain, ",")
|
||||
var part string
|
||||
if comma == -1 {
|
||||
part = remain
|
||||
remain = ""
|
||||
} else {
|
||||
part = remain[:comma]
|
||||
remain = remain[comma+1:]
|
||||
}
|
||||
part = strings.TrimSpace(part)
|
||||
if i := strings.Index(part, ";"); i != -1 {
|
||||
part = part[:i]
|
||||
}
|
||||
if part == enc {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
package tsweb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
@@ -27,13 +30,25 @@ type Response struct {
|
||||
}
|
||||
|
||||
func TestNewJSONHandler(t *testing.T) {
|
||||
checkStatus := func(w *httptest.ResponseRecorder, status string, code int) *Response {
|
||||
checkStatus := func(t *testing.T, w *httptest.ResponseRecorder, status string, code int) *Response {
|
||||
d := &Response{
|
||||
Data: &Data{},
|
||||
}
|
||||
|
||||
t.Logf("%s", w.Body.Bytes())
|
||||
err := json.Unmarshal(w.Body.Bytes(), d)
|
||||
bodyBytes := w.Body.Bytes()
|
||||
if w.Result().Header.Get("Content-Encoding") == "gzip" {
|
||||
zr, err := gzip.NewReader(bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
t.Fatalf("gzip read error at start: %v", err)
|
||||
}
|
||||
bodyBytes, err = io.ReadAll(zr)
|
||||
if err != nil {
|
||||
t.Fatalf("gzip read error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("%s", bodyBytes)
|
||||
err := json.Unmarshal(bodyBytes, d)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
return nil
|
||||
@@ -64,7 +79,7 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
h21.ServeHTTPReturn(w, r)
|
||||
checkStatus(w, "success", http.StatusOK)
|
||||
checkStatus(t, w, "success", http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("403 HTTPError", func(t *testing.T) {
|
||||
@@ -75,7 +90,7 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
h.ServeHTTPReturn(w, r)
|
||||
checkStatus(w, "error", http.StatusForbidden)
|
||||
checkStatus(t, w, "error", http.StatusForbidden)
|
||||
})
|
||||
|
||||
h22 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
|
||||
@@ -86,7 +101,7 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
h22.ServeHTTPReturn(w, r)
|
||||
checkStatus(w, "success", http.StatusOK)
|
||||
checkStatus(t, w, "success", http.StatusOK)
|
||||
})
|
||||
|
||||
h31 := JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
|
||||
@@ -105,21 +120,21 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Name": "tailscale"}`))
|
||||
h31.ServeHTTPReturn(w, r)
|
||||
checkStatus(w, "success", http.StatusOK)
|
||||
checkStatus(t, w, "success", http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("400 bad json", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/", strings.NewReader(`{`))
|
||||
h31.ServeHTTPReturn(w, r)
|
||||
checkStatus(w, "error", http.StatusBadRequest)
|
||||
checkStatus(t, w, "error", http.StatusBadRequest)
|
||||
})
|
||||
|
||||
t.Run("400 post data error", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/", strings.NewReader(`{}`))
|
||||
h31.ServeHTTPReturn(w, r)
|
||||
resp := checkStatus(w, "error", http.StatusBadRequest)
|
||||
resp := checkStatus(t, w, "error", http.StatusBadRequest)
|
||||
if resp.Error != "name is empty" {
|
||||
t.Fatalf("wrong error")
|
||||
}
|
||||
@@ -144,7 +159,23 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Price": 10}`))
|
||||
h32.ServeHTTPReturn(w, r)
|
||||
resp := checkStatus(w, "success", http.StatusOK)
|
||||
resp := checkStatus(t, w, "success", http.StatusOK)
|
||||
t.Log(resp.Data)
|
||||
if resp.Data.Price != 20 {
|
||||
t.Fatalf("wrong price: %d %d", resp.Data.Price, 10)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("gzipped", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Price": 10}`))
|
||||
r.Header.Set("Accept-Encoding", "gzip")
|
||||
h32.ServeHTTPReturn(w, r)
|
||||
res := w.Result()
|
||||
if ct := res.Header.Get("Content-Encoding"); ct != "gzip" {
|
||||
t.Fatalf("encoding = %q; want gzip", ct)
|
||||
}
|
||||
resp := checkStatus(t, w, "success", http.StatusOK)
|
||||
t.Log(resp.Data)
|
||||
if resp.Data.Price != 20 {
|
||||
t.Fatalf("wrong price: %d %d", resp.Data.Price, 10)
|
||||
@@ -155,7 +186,7 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/", strings.NewReader(`{}`))
|
||||
h32.ServeHTTPReturn(w, r)
|
||||
resp := checkStatus(w, "error", http.StatusBadRequest)
|
||||
resp := checkStatus(t, w, "error", http.StatusBadRequest)
|
||||
if resp.Error != "price is empty" {
|
||||
t.Fatalf("wrong error")
|
||||
}
|
||||
@@ -165,7 +196,7 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/", strings.NewReader(`{"Name": "root"}`))
|
||||
h32.ServeHTTPReturn(w, r)
|
||||
resp := checkStatus(w, "error", http.StatusInternalServerError)
|
||||
resp := checkStatus(t, w, "error", http.StatusInternalServerError)
|
||||
if resp.Error != "internal server error" {
|
||||
t.Fatalf("wrong error")
|
||||
}
|
||||
@@ -177,7 +208,7 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
JSONHandlerFunc(func(r *http.Request) (int, interface{}, error) {
|
||||
return http.StatusOK, make(chan int), nil
|
||||
}).ServeHTTPReturn(w, r)
|
||||
resp := checkStatus(w, "error", http.StatusInternalServerError)
|
||||
resp := checkStatus(t, w, "error", http.StatusInternalServerError)
|
||||
if resp.Error != "json marshal error" {
|
||||
t.Fatalf("wrong error")
|
||||
}
|
||||
@@ -189,7 +220,7 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
JSONHandlerFunc(func(r *http.Request) (status int, data interface{}, err error) {
|
||||
return
|
||||
}).ServeHTTPReturn(w, r)
|
||||
checkStatus(w, "error", http.StatusInternalServerError)
|
||||
checkStatus(t, w, "error", http.StatusInternalServerError)
|
||||
})
|
||||
|
||||
t.Run("403 forbidden, status returned by JSONHandlerFunc and HTTPError agree", func(t *testing.T) {
|
||||
@@ -203,7 +234,7 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
Data: &Data{},
|
||||
Error: "403 forbidden",
|
||||
}
|
||||
got := checkStatus(w, "error", http.StatusForbidden)
|
||||
got := checkStatus(t, w, "error", http.StatusForbidden)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf(diff)
|
||||
}
|
||||
@@ -223,9 +254,37 @@ func TestNewJSONHandler(t *testing.T) {
|
||||
Data: &Data{},
|
||||
Error: "403 forbidden",
|
||||
}
|
||||
got := checkStatus(w, "error", http.StatusForbidden)
|
||||
got := checkStatus(t, w, "error", http.StatusForbidden)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("(-want,+got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAcceptsEncoding(t *testing.T) {
|
||||
tests := []struct {
|
||||
in, enc string
|
||||
want bool
|
||||
}{
|
||||
{"", "gzip", false},
|
||||
{"gzip", "gzip", true},
|
||||
{"foo,gzip", "gzip", true},
|
||||
{"foo, gzip", "gzip", true},
|
||||
{"foo, gzip ", "gzip", true},
|
||||
{"gzip, foo ", "gzip", true},
|
||||
{"gzip, foo ", "br", false},
|
||||
{"gzip, foo ", "fo", false},
|
||||
{"gzip;q=1.2, foo ", "gzip", true},
|
||||
{" gzip;q=1.2, foo ", "gzip", true},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
h := make(http.Header)
|
||||
if tt.in != "" {
|
||||
h.Set("Accept-Encoding", tt.in)
|
||||
}
|
||||
got := AcceptsEncoding(&http.Request{Header: h}, tt.enc)
|
||||
if got != tt.want {
|
||||
t.Errorf("%d. got %v; want %v", i, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,33 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
// Package winuntil contains misc Windows/win32 helper functions.
|
||||
// Package winutil contains misc Windows/Win32 helper functions.
|
||||
package winutil
|
||||
|
||||
import (
|
||||
"log"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
const RegBase = `SOFTWARE\Tailscale IPN`
|
||||
|
||||
// GetDesktopPID searches the PID of the process that's running the
|
||||
// currently active desktop and whether it was found.
|
||||
// Usually the PID will be for explorer.exe.
|
||||
func GetDesktopPID() (pid uint32, ok bool) {
|
||||
hwnd := windows.GetShellWindow()
|
||||
if hwnd == 0 {
|
||||
return 0, false
|
||||
}
|
||||
windows.GetWindowThreadProcessId(hwnd, &pid)
|
||||
return pid, pid != 0
|
||||
}
|
||||
// RegBase is the registry path inside HKEY_LOCAL_MACHINE where registry settings
|
||||
// are stored. This constant is a non-empty string only when GOOS=windows.
|
||||
const RegBase = regBase
|
||||
|
||||
// GetRegString looks up a registry path in our local machine path, or returns
|
||||
// the given default if it can't.
|
||||
@@ -36,21 +15,7 @@ func GetDesktopPID() (pid uint32, ok bool) {
|
||||
// This function will only work on GOOS=windows. Trying to run it on any other
|
||||
// OS will always return the default value.
|
||||
func GetRegString(name, defval string) string {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, RegBase, registry.READ)
|
||||
if err != nil {
|
||||
log.Printf("registry.OpenKey(%v): %v", RegBase, err)
|
||||
return defval
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
val, _, err := key.GetStringValue(name)
|
||||
if err != nil {
|
||||
if err != registry.ErrNotExist {
|
||||
log.Printf("registry.GetStringValue(%v): %v", name, err)
|
||||
}
|
||||
return defval
|
||||
}
|
||||
return val
|
||||
return getRegString(name, defval)
|
||||
}
|
||||
|
||||
// GetRegInteger looks up a registry path in our local machine path, or returns
|
||||
@@ -59,31 +24,17 @@ func GetRegString(name, defval string) string {
|
||||
// This function will only work on GOOS=windows. Trying to run it on any other
|
||||
// OS will always return the default value.
|
||||
func GetRegInteger(name string, defval uint64) uint64 {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, RegBase, registry.READ)
|
||||
if err != nil {
|
||||
log.Printf("registry.OpenKey(%v): %v", RegBase, err)
|
||||
return defval
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
val, _, err := key.GetIntegerValue(name)
|
||||
if err != nil {
|
||||
if err != registry.ErrNotExist {
|
||||
log.Printf("registry.GetIntegerValue(%v): %v", name, err)
|
||||
}
|
||||
return defval
|
||||
}
|
||||
return val
|
||||
return getRegInteger(name, defval)
|
||||
}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procWTSGetActiveConsoleSessionId = kernel32.NewProc("WTSGetActiveConsoleSessionId")
|
||||
)
|
||||
|
||||
// TODO(crawshaw): replace with x/sys/windows... one day.
|
||||
// https://go-review.googlesource.com/c/sys/+/331909
|
||||
func WTSGetActiveConsoleSessionId() uint32 {
|
||||
r1, _, _ := procWTSGetActiveConsoleSessionId.Call()
|
||||
return uint32(r1)
|
||||
// IsSIDValidPrincipal determines whether the SID contained in uid represents a
|
||||
// type that is a valid security principal under Windows. This check helps us
|
||||
// work around a bug in the standard library's Windows implementation of
|
||||
// LookupId in os/user.
|
||||
// See https://github.com/tailscale/tailscale/issues/869
|
||||
//
|
||||
// This function will only work on GOOS=windows. Trying to run it on any other
|
||||
// OS will always return false.
|
||||
func IsSIDValidPrincipal(uid string) bool {
|
||||
return isSIDValidPrincipal(uid)
|
||||
}
|
||||
|
||||
@@ -7,18 +7,10 @@
|
||||
|
||||
package winutil
|
||||
|
||||
const RegBase = ``
|
||||
const regBase = ``
|
||||
|
||||
// GetRegString looks up a registry path in our local machine path, or returns
|
||||
// the given default if it can't.
|
||||
//
|
||||
// This function will only work on GOOS=windows. Trying to run it on any other
|
||||
// OS will always return the default value.
|
||||
func GetRegString(name, defval string) string { return defval }
|
||||
func getRegString(name, defval string) string { return defval }
|
||||
|
||||
// GetRegInteger looks up a registry path in our local machine path, or returns
|
||||
// the given default if it can't.
|
||||
//
|
||||
// This function will only work on GOOS=windows. Trying to run it on any other
|
||||
// OS will always return the default value.
|
||||
func GetRegInteger(name string, defval uint64) uint64 { return defval }
|
||||
func getRegInteger(name string, defval uint64) uint64 { return defval }
|
||||
|
||||
func isSIDValidPrincipal(uid string) bool { return false }
|
||||
|
||||
95
util/winutil/winutil_windows.go
Normal file
95
util/winutil/winutil_windows.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package winutil
|
||||
|
||||
import (
|
||||
"log"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
const regBase = `SOFTWARE\Tailscale IPN`
|
||||
|
||||
// GetDesktopPID searches the PID of the process that's running the
|
||||
// currently active desktop and whether it was found.
|
||||
// Usually the PID will be for explorer.exe.
|
||||
func GetDesktopPID() (pid uint32, ok bool) {
|
||||
hwnd := windows.GetShellWindow()
|
||||
if hwnd == 0 {
|
||||
return 0, false
|
||||
}
|
||||
windows.GetWindowThreadProcessId(hwnd, &pid)
|
||||
return pid, pid != 0
|
||||
}
|
||||
|
||||
func getRegString(name, defval string) string {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, RegBase, registry.READ)
|
||||
if err != nil {
|
||||
log.Printf("registry.OpenKey(%v): %v", RegBase, err)
|
||||
return defval
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
val, _, err := key.GetStringValue(name)
|
||||
if err != nil {
|
||||
if err != registry.ErrNotExist {
|
||||
log.Printf("registry.GetStringValue(%v): %v", name, err)
|
||||
}
|
||||
return defval
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func getRegInteger(name string, defval uint64) uint64 {
|
||||
key, err := registry.OpenKey(registry.LOCAL_MACHINE, RegBase, registry.READ)
|
||||
if err != nil {
|
||||
log.Printf("registry.OpenKey(%v): %v", RegBase, err)
|
||||
return defval
|
||||
}
|
||||
defer key.Close()
|
||||
|
||||
val, _, err := key.GetIntegerValue(name)
|
||||
if err != nil {
|
||||
if err != registry.ErrNotExist {
|
||||
log.Printf("registry.GetIntegerValue(%v): %v", name, err)
|
||||
}
|
||||
return defval
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procWTSGetActiveConsoleSessionId = kernel32.NewProc("WTSGetActiveConsoleSessionId")
|
||||
)
|
||||
|
||||
// TODO(crawshaw): replace with x/sys/windows... one day.
|
||||
// https://go-review.googlesource.com/c/sys/+/331909
|
||||
func WTSGetActiveConsoleSessionId() uint32 {
|
||||
r1, _, _ := procWTSGetActiveConsoleSessionId.Call()
|
||||
return uint32(r1)
|
||||
}
|
||||
|
||||
func isSIDValidPrincipal(uid string) bool {
|
||||
usid, err := syscall.StringToSid(uid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, _, accType, err := usid.LookupAccount("")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch accType {
|
||||
case syscall.SidTypeUser, syscall.SidTypeGroup, syscall.SidTypeDomain, syscall.SidTypeAlias, syscall.SidTypeWellKnownGroup, syscall.SidTypeComputer:
|
||||
return true
|
||||
default:
|
||||
// Reject deleted users, invalid SIDs, unknown SIDs, mandatory label SIDs, etc.
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,8 @@ type Conn struct {
|
||||
// lock ordering deadlocks. See issue 3726 and mu field docs.
|
||||
derpMapAtomic atomic.Value // of *tailcfg.DERPMap
|
||||
|
||||
lastNetCheckReport atomic.Value // of *netcheck.Report
|
||||
|
||||
// port is the preferred port from opts.Port; 0 means auto.
|
||||
port syncs.AtomicUint32
|
||||
|
||||
@@ -741,6 +743,7 @@ func (c *Conn) updateNetInfo(ctx context.Context) (*netcheck.Report, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.lastNetCheckReport.Store(report)
|
||||
c.noV4.Set(!report.IPv4)
|
||||
c.noV6.Set(!report.IPv6)
|
||||
c.noV4Send.Set(!report.IPv4CanSend)
|
||||
@@ -1380,6 +1383,7 @@ func (c *Conn) derpWriteChanOfAddr(addr netaddr.IPPort, peer key.NodePublic) cha
|
||||
|
||||
dc.SetCanAckPings(true)
|
||||
dc.NotePreferred(c.myDerp == regionID)
|
||||
dc.SetAddressFamilySelector(derpAddrFamSelector{c})
|
||||
dc.DNSCache = dnscache.Get()
|
||||
|
||||
ctx, cancel := context.WithCancel(c.connCtx)
|
||||
@@ -4125,6 +4129,22 @@ func (di *discoInfo) setNodeKey(nk key.NodePublic) {
|
||||
di.lastNodeKeyTime = time.Now()
|
||||
}
|
||||
|
||||
// derpAddrFamSelector is the derphttp.AddressFamilySelector we pass
|
||||
// to derphttp.Client.SetAddressFamilySelector.
|
||||
//
|
||||
// It provides the hint as to whether in an IPv4-vs-IPv6 race that
|
||||
// IPv4 should be held back a bit to give IPv6 a better-than-50/50
|
||||
// chance of winning. We only return true when we believe IPv6 will
|
||||
// work anyway, so we don't artificially delay the connection speed.
|
||||
type derpAddrFamSelector struct{ c *Conn }
|
||||
|
||||
func (s derpAddrFamSelector) PreferIPv6() bool {
|
||||
if r, ok := s.c.lastNetCheckReport.Load().(*netcheck.Report); ok {
|
||||
return r.IPv6
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
metricNumPeers = clientmetric.NewGauge("magicsock_netmap_num_peers")
|
||||
metricNumDERPConns = clientmetric.NewGauge("magicsock_num_derp_conns")
|
||||
|
||||
@@ -21,19 +21,19 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
"inet.af/netaddr"
|
||||
"inet.af/netstack/tcpip"
|
||||
"inet.af/netstack/tcpip/adapters/gonet"
|
||||
"inet.af/netstack/tcpip/buffer"
|
||||
"inet.af/netstack/tcpip/header"
|
||||
"inet.af/netstack/tcpip/link/channel"
|
||||
"inet.af/netstack/tcpip/network/ipv4"
|
||||
"inet.af/netstack/tcpip/network/ipv6"
|
||||
"inet.af/netstack/tcpip/stack"
|
||||
"inet.af/netstack/tcpip/transport/icmp"
|
||||
"inet.af/netstack/tcpip/transport/tcp"
|
||||
"inet.af/netstack/tcpip/transport/udp"
|
||||
"inet.af/netstack/waiter"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/net/packet"
|
||||
@@ -82,9 +82,12 @@ type Impl struct {
|
||||
mc *magicsock.Conn
|
||||
logf logger.Logf
|
||||
dialer *tsdial.Dialer
|
||||
ctx context.Context // alive until Close
|
||||
ctxCancel context.CancelFunc // called on Close
|
||||
lb *ipnlocal.LocalBackend
|
||||
ctx context.Context // alive until Close
|
||||
ctxCancel context.CancelFunc // called on Close
|
||||
lb *ipnlocal.LocalBackend // or nil
|
||||
|
||||
peerapiPort4Atomic uint32 // uint16 port number for IPv4 peerapi
|
||||
peerapiPort6Atomic uint32 // uint16 port number for IPv6 peerapi
|
||||
|
||||
// atomicIsLocalIPFunc holds a func that reports whether an IP
|
||||
// is a local (non-subnet) Tailscale IP address of this
|
||||
@@ -370,8 +373,8 @@ func (ns *Impl) DialContextUDP(ctx context.Context, ipp netaddr.IPPort) (*gonet.
|
||||
|
||||
func (ns *Impl) injectOutbound() {
|
||||
for {
|
||||
packetInfo, ok := ns.linkEP.ReadContext(ns.ctx)
|
||||
if !ok {
|
||||
pkt := ns.linkEP.ReadContext(ns.ctx)
|
||||
if pkt == nil {
|
||||
if ns.ctx.Err() != nil {
|
||||
// Return without logging.
|
||||
return
|
||||
@@ -379,7 +382,6 @@ func (ns *Impl) injectOutbound() {
|
||||
ns.logf("[v2] ReadContext-for-write = ok=false")
|
||||
continue
|
||||
}
|
||||
pkt := packetInfo.Pkt
|
||||
hdrNetwork := pkt.NetworkHeader()
|
||||
hdrTransport := pkt.TransportHeader()
|
||||
|
||||
@@ -408,9 +410,33 @@ func (ns *Impl) processSSH() bool {
|
||||
return ns.lb != nil && ns.lb.ShouldRunSSH()
|
||||
}
|
||||
|
||||
func (ns *Impl) peerAPIPortAtomic(ip netaddr.IP) *uint32 {
|
||||
if ip.Is4() {
|
||||
return &ns.peerapiPort4Atomic
|
||||
} else {
|
||||
return &ns.peerapiPort6Atomic
|
||||
}
|
||||
}
|
||||
|
||||
// shouldProcessInbound reports whether an inbound packet should be
|
||||
// handled by netstack.
|
||||
func (ns *Impl) shouldProcessInbound(p *packet.Parsed, t *tstun.Wrapper) bool {
|
||||
// Handle incoming peerapi connections in netstack.
|
||||
if ns.lb != nil && p.IPProto == ipproto.TCP {
|
||||
var peerAPIPort uint16
|
||||
dstIP := p.Dst.IP()
|
||||
if p.TCPFlags&packet.TCPSynAck == packet.TCPSyn && ns.isLocalIP(dstIP) {
|
||||
if port, ok := ns.lb.GetPeerAPIPort(p.Dst.IP()); ok {
|
||||
peerAPIPort = port
|
||||
atomic.StoreUint32(ns.peerAPIPortAtomic(dstIP), uint32(port))
|
||||
}
|
||||
} else {
|
||||
peerAPIPort = uint16(atomic.LoadUint32(ns.peerAPIPortAtomic(dstIP)))
|
||||
}
|
||||
if p.IPProto == ipproto.TCP && p.Dst.Port() == peerAPIPort {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if ns.isInboundTSSH(p) && ns.processSSH() {
|
||||
return true
|
||||
}
|
||||
@@ -622,6 +648,16 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if ns.lb != nil {
|
||||
if port, ok := ns.lb.GetPeerAPIPort(dialIP); ok {
|
||||
if reqDetails.LocalPort == port && ns.isLocalIP(dialIP) {
|
||||
src := netaddr.IPPortFrom(clientRemoteIP, reqDetails.RemotePort)
|
||||
dst := netaddr.IPPortFrom(dialIP, port)
|
||||
ns.lb.ServePeerAPIConnection(src, dst, c)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if ns.ForwardTCPIn != nil {
|
||||
ns.ForwardTCPIn(c, reqDetails.LocalPort)
|
||||
return
|
||||
|
||||
@@ -107,7 +107,13 @@ func (ns *Impl) handleSSH(s ssh.Session) {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command("/bin/bash")
|
||||
var cmd *exec.Cmd
|
||||
sshUser := s.User()
|
||||
if os.Getuid() != 0 || sshUser == "root" {
|
||||
cmd = exec.Command("/bin/bash")
|
||||
} else {
|
||||
cmd = exec.Command("/usr/bin/env", "su", "-", sshUser)
|
||||
}
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
|
||||
f, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user