Compare commits
38 Commits
nickkhyl/t
...
release-br
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfc4042ecb | ||
|
|
fd7e7ed5d3 | ||
|
|
349015098d | ||
|
|
ea2536ab3f | ||
|
|
db2e9ada10 | ||
|
|
fe5162a366 | ||
|
|
a151ef021d | ||
|
|
7bb2fd5c76 | ||
|
|
22b881e82b | ||
|
|
56f7013628 | ||
|
|
c293be6434 | ||
|
|
3ff68e6784 | ||
|
|
f121bb0c8a | ||
|
|
1469105ab9 | ||
|
|
3020e58f57 | ||
|
|
f3be05e6ea | ||
|
|
6b5081ab31 | ||
|
|
7afb4a1f43 | ||
|
|
4bc90fee03 | ||
|
|
46e42292a5 | ||
|
|
a5b1456410 | ||
|
|
27d0e7cb0a | ||
|
|
f0b70ff186 | ||
|
|
f5d17dae18 | ||
|
|
ceaecdd4d5 | ||
|
|
8704fb308d | ||
|
|
afb95d7246 | ||
|
|
277bf8f48c | ||
|
|
c995ac72a3 | ||
|
|
e699226e80 | ||
|
|
d8e37edb40 | ||
|
|
0744d75238 | ||
|
|
15835f03b3 | ||
|
|
e78ac523da | ||
|
|
15c87017b8 | ||
|
|
bd911fdb12 | ||
|
|
0111d33eb8 | ||
|
|
62a458f7f4 |
@@ -1 +1 @@
|
||||
1.13.0
|
||||
1.14.6
|
||||
|
||||
@@ -76,6 +76,20 @@ type errorJSON struct {
|
||||
Error string
|
||||
}
|
||||
|
||||
// AccessDeniedError is an error due to permissions.
|
||||
type AccessDeniedError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *AccessDeniedError) Error() string { return fmt.Sprintf("Access denied: %v", e.err) }
|
||||
func (e *AccessDeniedError) Unwrap() error { return e.err }
|
||||
|
||||
// IsAccessDeniedError reports whether err is or wraps an AccessDeniedError.
|
||||
func IsAccessDeniedError(err error) bool {
|
||||
var ae *AccessDeniedError
|
||||
return errors.As(err, &ae)
|
||||
}
|
||||
|
||||
// bestError returns either err, or if body contains a valid JSON
|
||||
// object of type errorJSON, its non-empty error body.
|
||||
func bestError(err error, body []byte) error {
|
||||
@@ -86,6 +100,14 @@ func bestError(err error, body []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func errorMessageFromBody(body []byte) string {
|
||||
var j errorJSON
|
||||
if err := json.Unmarshal(body, &j); err == nil && j.Error != "" {
|
||||
return j.Error
|
||||
}
|
||||
return strings.TrimSpace(string(body))
|
||||
}
|
||||
|
||||
func send(ctx context.Context, method, path string, wantStatus int, body io.Reader) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, method, "http://local-tailscaled.sock"+path, body)
|
||||
if err != nil {
|
||||
@@ -104,6 +126,9 @@ func send(ctx context.Context, method, path string, wantStatus int, body io.Read
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != wantStatus {
|
||||
if res.StatusCode == 403 {
|
||||
return nil, &AccessDeniedError{errors.New(errorMessageFromBody(slurp))}
|
||||
}
|
||||
err := fmt.Errorf("HTTP %s: %s (expected %v)", res.Status, slurp, wantStatus)
|
||||
return nil, bestError(err, slurp)
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/peterbourgon/ff/v2/ffcli"
|
||||
"tailscale.com/atomicfile"
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/ipn"
|
||||
)
|
||||
|
||||
var certCmd = &ffcli.Command{
|
||||
@@ -61,7 +63,19 @@ func runCert(ctx context.Context, args []string) error {
|
||||
}
|
||||
|
||||
if len(args) != 1 {
|
||||
return fmt.Errorf("Usage: tailscale cert [flags] <domain>")
|
||||
var hint bytes.Buffer
|
||||
if st, err := tailscale.Status(ctx); err == nil {
|
||||
if st.BackendState != ipn.Running.String() {
|
||||
fmt.Fprintf(&hint, "\nTailscale is not running.\n")
|
||||
} else if len(st.CertDomains) == 0 {
|
||||
fmt.Fprintf(&hint, "\nHTTPS cert support is not enabled/configured for your tailnet.\n")
|
||||
} else if len(st.CertDomains) == 1 {
|
||||
fmt.Fprintf(&hint, "\nFor domain, use %q.\n", st.CertDomains[0])
|
||||
} else {
|
||||
fmt.Fprintf(&hint, "\nValid domain options: %q.\n", st.CertDomains)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Usage: tailscale cert [flags] <domain>%s", hint.Bytes())
|
||||
}
|
||||
domain := args[0]
|
||||
|
||||
@@ -72,6 +86,9 @@ func runCert(ctx context.Context, args []string) error {
|
||||
certArgs.keyFile = domain + ".key"
|
||||
}
|
||||
certPEM, keyPEM, err := tailscale.CertPair(ctx, domain)
|
||||
if tailscale.IsAccessDeniedError(err) && os.Getuid() != 0 && runtime.GOOS != "windows" {
|
||||
return fmt.Errorf("%v\n\nUse 'sudo tailscale cert' or 'tailscale up --operator=$USER' to not require root.", err)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
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/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
|
||||
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
|
||||
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
@@ -43,7 +43,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
|
||||
tailscale.com/net/tsaddr from tailscale.com/net/interfaces+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+
|
||||
tailscale.com/paths from tailscale.com/cmd/tailscale/cli+
|
||||
💣 tailscale.com/paths from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+
|
||||
💣 tailscale.com/syncs from tailscale.com/net/interfaces+
|
||||
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
||||
|
||||
@@ -25,6 +25,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L 💣 github.com/mdlayher/netlink/nlenc from github.com/mdlayher/netlink+
|
||||
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
|
||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
|
||||
W github.com/pkg/errors from github.com/tailscale/certstore
|
||||
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
@@ -128,7 +129,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/paths from tailscale.com/cmd/tailscaled+
|
||||
💣 tailscale.com/paths from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/portlist from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/safesocket from tailscale.com/ipn/ipnserver+
|
||||
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
|
||||
|
||||
@@ -15,7 +15,7 @@ Restart=on-failure
|
||||
RuntimeDirectory=tailscale
|
||||
RuntimeDirectoryMode=0755
|
||||
StateDirectory=tailscale
|
||||
StateDirectoryMode=0750
|
||||
StateDirectoryMode=0700
|
||||
CacheDirectory=tailscale
|
||||
CacheDirectoryMode=0750
|
||||
Type=notify
|
||||
|
||||
@@ -19,11 +19,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -430,19 +428,14 @@ func (c *Client) dialRegion(ctx context.Context, reg *tailcfg.DERPRegion) (net.C
|
||||
func (c *Client) tlsClient(nc net.Conn, node *tailcfg.DERPNode) *tls.Conn {
|
||||
tlsConf := tlsdial.Config(c.tlsServerName(node), c.TLSConfig)
|
||||
if node != nil {
|
||||
tlsConf.InsecureSkipVerify = node.InsecureForTests
|
||||
if node.InsecureForTests {
|
||||
tlsConf.InsecureSkipVerify = true
|
||||
tlsConf.VerifyConnection = nil
|
||||
}
|
||||
if node.CertName != "" {
|
||||
tlsdial.SetConfigExpectedCert(tlsConf, node.CertName)
|
||||
}
|
||||
}
|
||||
if n := os.Getenv("SSLKEYLOGFILE"); n != "" {
|
||||
f, err := os.OpenFile(n, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("WARNING: writing to SSLKEYLOGFILE %v", n)
|
||||
tlsConf.KeyLogWriter = f
|
||||
}
|
||||
return tls.Client(nc, tlsConf)
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,16 @@ func LooksLikeDiscoWrapper(p []byte) bool {
|
||||
return string(p[:len(Magic)]) == Magic
|
||||
}
|
||||
|
||||
// Source returns the slice of p that represents the
|
||||
// disco public key source, and whether p looks like
|
||||
// a disco message.
|
||||
func Source(p []byte) (src []byte, ok bool) {
|
||||
if !LooksLikeDiscoWrapper(p) {
|
||||
return nil, false
|
||||
}
|
||||
return p[len(Magic):][:keyLen], true
|
||||
}
|
||||
|
||||
// Parse parses the encrypted part of the message from inside the
|
||||
// nacl secretbox.
|
||||
func Parse(p []byte) (Message, error) {
|
||||
|
||||
4
go.mod
4
go.mod
@@ -3,6 +3,7 @@ module tailscale.com
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
filippo.io/mkcert v1.4.3
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
|
||||
github.com/aws/aws-sdk-go v1.38.52
|
||||
@@ -23,6 +24,7 @@ require (
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.12.2
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/mdlayher/netlink v1.4.1
|
||||
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697
|
||||
github.com/miekg/dns v1.1.42
|
||||
@@ -30,6 +32,7 @@ require (
|
||||
github.com/pborman/getopt v1.1.0
|
||||
github.com/peterbourgon/ff/v2 v2.0.0
|
||||
github.com/pkg/sftp v1.13.0
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3
|
||||
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
|
||||
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
|
||||
@@ -42,6 +45,7 @@ require (
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
|
||||
golang.org/x/tools v0.1.2
|
||||
golang.zx2c4.com/wireguard v0.0.0-20210624150102-15b24b6179e0
|
||||
|
||||
17
go.sum
17
go.sum
@@ -14,6 +14,8 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/mkcert v1.4.3 h1:axpnmtrZMM8u5Hf4N3UXxboGemMOV+Tn+e+pkHM6E3o=
|
||||
filippo.io/mkcert v1.4.3/go.mod h1:64ke566uBwAQcdK3vRDABgsgVHqrfORPTw6YytZCTxk=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
@@ -360,8 +362,9 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -516,6 +519,7 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0=
|
||||
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryancurrah/gomodguard v1.1.0 h1:DWbye9KyMgytn8uYpuHkwf0RHqAYO6Ay/D0TbCpPtVU=
|
||||
@@ -581,8 +585,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3 h1:fEubocuQkrlcuYeXelhYq/YcKvVVe1Ah7saQEtj98Mo=
|
||||
@@ -823,8 +828,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7-0.20210524175448-3115f89c4b99 h1:ZEXtoJu1S0ie/EmdYnjY3CqaCCZxnldL+K1ftMITD2Q=
|
||||
golang.org/x/text v0.3.7-0.20210524175448-3115f89c4b99/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 h1:Vv0JUPWTyeqUq42B2WJ1FeIDjjvGKoA2Ss+Ts0lAVbs=
|
||||
@@ -881,6 +887,7 @@ golang.org/x/tools v0.0.0-20201011145850-ed2f50202694/go.mod h1:z6u4i615ZeAfBE4X
|
||||
golang.org/x/tools v0.0.0-20201013201025-64a9e34f3752/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201118003311-bd56c0adb394/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201124202034-299f270db459/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
@@ -969,6 +976,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
|
||||
honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY=
|
||||
honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ=
|
||||
honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
inet.af/netaddr v0.0.0-20210515010201-ad03edc7c841/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||
inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1 h1:mxmfTV6kjXTlFqqFETnG9FQZzNFc6AKunZVAgQ3b7WA=
|
||||
inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
|
||||
@@ -988,3 +997,5 @@ mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jC
|
||||
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7 h1:kAREL6MPwpsk1/PQPFD3Eg7WAQR5mPTWZJaBiG5LDbY=
|
||||
mvdan.cc/unparam v0.0.0-20200501210554-b37ab49443f7/go.mod h1:HGC5lll35J70Y5v7vCGb9oLhHoScFwkHDJm/05RdSTc=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237 h1:iAEkCBPbRaflBgZ7o9gjVUuWuvWeV4sytFWg9o+Pj2k=
|
||||
software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ=
|
||||
|
||||
@@ -1837,6 +1837,17 @@ func (b *LocalBackend) authReconfig() {
|
||||
if err != nil {
|
||||
b.logf("[unexpected] non-FQDN route suffix %q", suffix)
|
||||
}
|
||||
|
||||
// Create map entry even if len(resolvers) == 0; Issue 2706.
|
||||
// This lets the control plane send ExtraRecords for which we
|
||||
// can authoritatively answer "name not exists" for when the
|
||||
// control plane also sends this explicit but empty route
|
||||
// making it as something we handle.
|
||||
//
|
||||
// While we're already populating it, might as well size the
|
||||
// slice appropriately.
|
||||
dcfg.Routes[fqdn] = make([]netaddr.IPPort, 0, len(resolvers))
|
||||
|
||||
for _, resolver := range resolvers {
|
||||
res, err := parseResolver(resolver)
|
||||
if err != nil {
|
||||
@@ -1904,14 +1915,29 @@ func parseResolver(cfg dnstype.Resolver) (netaddr.IPPort, error) {
|
||||
return netaddr.IPPortFrom(ip, 53), nil
|
||||
}
|
||||
|
||||
// tailscaleVarRoot returns the root directory of Tailscale's writable
|
||||
// TailscaleVarRoot returns the root directory of Tailscale's writable
|
||||
// storage area. (e.g. "/var/lib/tailscale")
|
||||
func tailscaleVarRoot() string {
|
||||
//
|
||||
// It returns an empty string if there's no configured or discovered
|
||||
// location.
|
||||
func (b *LocalBackend) TailscaleVarRoot() string {
|
||||
switch runtime.GOOS {
|
||||
case "ios", "android":
|
||||
dir, _ := paths.AppSharedDir.Load().(string)
|
||||
return dir
|
||||
}
|
||||
// Temporary (2021-09-27) transitional fix for #2927 (Synology
|
||||
// cert dir) on the way towards a more complete fix
|
||||
// (#2932). It fixes any case where the state file is provided
|
||||
// to tailscaled explicitly when it's not in the default
|
||||
// location.
|
||||
if fs, ok := b.store.(*ipn.FileStore); ok {
|
||||
if fp := fs.Path(); fp != "" {
|
||||
if dir := filepath.Dir(fp); strings.EqualFold(filepath.Base(dir), "tailscale") {
|
||||
return dir
|
||||
}
|
||||
}
|
||||
}
|
||||
stateFile := paths.DefaultTailscaledStateFile()
|
||||
if stateFile == "" {
|
||||
return ""
|
||||
@@ -1923,7 +1949,7 @@ func (b *LocalBackend) fileRootLocked(uid tailcfg.UserID) string {
|
||||
if v := b.directFileRoot; v != "" {
|
||||
return v
|
||||
}
|
||||
varRoot := tailscaleVarRoot()
|
||||
varRoot := b.TailscaleVarRoot()
|
||||
if varRoot == "" {
|
||||
b.logf("peerapi disabled; no state directory")
|
||||
return ""
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
"tailscale.com/log/filelogger"
|
||||
"tailscale.com/logtail/backoff"
|
||||
"tailscale.com/net/netstat"
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/safesocket"
|
||||
"tailscale.com/smallzstd"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -581,6 +583,28 @@ func (s *server) writeToClients(n ipn.Notify) {
|
||||
}
|
||||
}
|
||||
|
||||
// tryWindowsAppDataMigration attempts to copy the Windows state file
|
||||
// from its old location to the new location. (Issue 2856)
|
||||
//
|
||||
// Tailscale 1.14 and before stored state under %LocalAppData%
|
||||
// (usually "C:\WINDOWS\system32\config\systemprofile\AppData\Local"
|
||||
// when tailscaled.exe is running as a non-user system service).
|
||||
// However it is frequently cleared for almost any reason: Windows
|
||||
// updates, System Restore, even various System Cleaner utilities.
|
||||
//
|
||||
// Returns a string of the path to use for the state file.
|
||||
// This will be a fallback %LocalAppData% path if migration fails,
|
||||
// a %ProgramData% path otherwise.
|
||||
func tryWindowsAppDataMigration(logf logger.Logf, path string) string {
|
||||
if path != paths.DefaultTailscaledStateFile() {
|
||||
// If they're specifying a non-default path, just trust that they know
|
||||
// what they are doing.
|
||||
return path
|
||||
}
|
||||
oldFile := filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf")
|
||||
return paths.TryConfigFileMigration(logf, oldFile, path)
|
||||
}
|
||||
|
||||
// Run runs a Tailscale backend service.
|
||||
// The getEngine func is called repeatedly, once per connection, until it returns an engine successfully.
|
||||
func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (wgengine.Engine, error), opts Options) error {
|
||||
@@ -613,14 +637,18 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
|
||||
|
||||
var store ipn.StateStore
|
||||
if opts.StatePath != "" {
|
||||
store, err = ipn.NewFileStore(opts.StatePath)
|
||||
path := opts.StatePath
|
||||
if runtime.GOOS == "windows" {
|
||||
path = tryWindowsAppDataMigration(logf, path)
|
||||
}
|
||||
store, err = ipn.NewFileStore(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err)
|
||||
return fmt.Errorf("ipn.NewFileStore(%q): %v", path, err)
|
||||
}
|
||||
if opts.AutostartStateKey == "" {
|
||||
autoStartKey, err := store.ReadState(ipn.ServerModeStartKey)
|
||||
if err != nil && err != ipn.ErrStateNotExist {
|
||||
return fmt.Errorf("calling ReadState on %s: %w", opts.StatePath, err)
|
||||
return fmt.Errorf("calling ReadState on %s: %w", path, err)
|
||||
}
|
||||
key := string(autoStartKey)
|
||||
if strings.HasPrefix(key, "user-") {
|
||||
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
@@ -53,11 +52,11 @@ var (
|
||||
)
|
||||
|
||||
func (h *Handler) certDir() (string, error) {
|
||||
base := paths.DefaultTailscaledStateFile()
|
||||
if base == "" {
|
||||
return "", errors.New("no default DefaultTailscaledStateFile")
|
||||
d := h.b.TailscaleVarRoot()
|
||||
if d == "" {
|
||||
return "", errors.New("no TailscaleVarRoot")
|
||||
}
|
||||
full := filepath.Join(filepath.Dir(base), "certs")
|
||||
full := filepath.Join(d, "certs")
|
||||
if err := os.MkdirAll(full, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
10
ipn/store.go
10
ipn/store.go
@@ -16,6 +16,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"tailscale.com/atomicfile"
|
||||
"tailscale.com/paths"
|
||||
)
|
||||
|
||||
// ErrStateNotExist is returned by StateStore.ReadState when the
|
||||
@@ -93,10 +94,18 @@ type FileStore struct {
|
||||
cache map[StateKey][]byte
|
||||
}
|
||||
|
||||
// Path returns the path that NewFileStore was called with.
|
||||
func (s *FileStore) Path() string { return s.path }
|
||||
|
||||
func (s *FileStore) String() string { return fmt.Sprintf("FileStore(%q)", s.path) }
|
||||
|
||||
// NewFileStore returns a new file store that persists to path.
|
||||
func NewFileStore(path string) (*FileStore, error) {
|
||||
// We unconditionally call this to ensure that our perms are correct
|
||||
if err := paths.MkStateDir(filepath.Dir(path)); err != nil {
|
||||
return nil, fmt.Errorf("creating state directory: %w", err)
|
||||
}
|
||||
|
||||
bs, err := ioutil.ReadFile(path)
|
||||
|
||||
// Treat an empty file as a missing file.
|
||||
@@ -110,7 +119,6 @@ func NewFileStore(path string) (*FileStore, error) {
|
||||
if os.IsNotExist(err) {
|
||||
// Write out an initial file, to verify that we can write
|
||||
// to the path.
|
||||
os.MkdirAll(filepath.Dir(path), 0755) // best effort
|
||||
if err = atomicfile.WriteFile(path, []byte("{}"), 0600); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func New(fileBasePrefix, logID string, logf logger.Logf) logger.Logf {
|
||||
if logf == nil {
|
||||
panic("nil logf")
|
||||
}
|
||||
dir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "Logs")
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale", "Logs")
|
||||
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
log.Printf("failed to create local log directory; not writing logs to disk: %v", err)
|
||||
|
||||
@@ -135,12 +135,33 @@ func logsDir(logf logger.Logf) string {
|
||||
}
|
||||
}
|
||||
|
||||
// STATE_DIRECTORY is set by systemd 240+ but we support older
|
||||
// systems-d. For example, Ubuntu 18.04 (Bionic Beaver) is 237.
|
||||
systemdStateDir := os.Getenv("STATE_DIRECTORY")
|
||||
if systemdStateDir != "" {
|
||||
logf("logpolicy: using $STATE_DIRECTORY, %q", systemdStateDir)
|
||||
return systemdStateDir
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
if version.CmdName() == "tailscaled" {
|
||||
// In the common case, when tailscaled is run as the Local System (as a service),
|
||||
// we want to use %ProgramData% (C:\ProgramData\Tailscale), aside the
|
||||
// system state config with the machine key, etc. But if that directory's
|
||||
// not accessible, then it's probably because the user is running tailscaled
|
||||
// as a regular user (perhaps in userspace-networking/SOCK5 mode) and we should
|
||||
// just use the %LocalAppData% instead. In a user context, %LocalAppData% isn't
|
||||
// subject to random deletions from Windows system updates.
|
||||
dir := filepath.Join(os.Getenv("ProgramData"), "Tailscale")
|
||||
if winProgramDataAccessible(dir) {
|
||||
logf("logpolicy: using dir %v", dir)
|
||||
return dir
|
||||
}
|
||||
}
|
||||
dir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale")
|
||||
logf("logpolicy: using LocalAppData dir %v", dir)
|
||||
return dir
|
||||
case "linux":
|
||||
// STATE_DIRECTORY is set by systemd 240+ but we support older
|
||||
// systems-d. For example, Ubuntu 18.04 (Bionic Beaver) is 237.
|
||||
systemdStateDir := os.Getenv("STATE_DIRECTORY")
|
||||
if systemdStateDir != "" {
|
||||
logf("logpolicy: using $STATE_DIRECTORY, %q", systemdStateDir)
|
||||
return systemdStateDir
|
||||
}
|
||||
}
|
||||
|
||||
// Default to e.g. /var/lib/tailscale or /var/db/tailscale on Unix.
|
||||
@@ -191,6 +212,23 @@ func redirectStderrToLogPanics() bool {
|
||||
return runningUnderSystemd() || os.Getenv("TS_PLEASE_PANIC") != ""
|
||||
}
|
||||
|
||||
// winProgramDataAccessible reports whether the directory (assumed to
|
||||
// be a Windows %ProgramData% directory) is accessible to the current
|
||||
// process. It's created if needed.
|
||||
func winProgramDataAccessible(dir string) bool {
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
// TODO: windows ACLs
|
||||
return false
|
||||
}
|
||||
// The C:\ProgramData\Tailscale directory should be locked down
|
||||
// by with ACLs to only be readable by the local system so a
|
||||
// regular user shouldn't be able to do this operation:
|
||||
if _, err := os.ReadDir(dir); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// tryFixLogStateLocation is a temporary fixup for
|
||||
// https://github.com/tailscale/tailscale/issues/247 . We accidentally
|
||||
// wrote logging state files to /, and then later to $CACHE_DIRECTORY
|
||||
@@ -372,14 +410,44 @@ func New(collection string) *Policy {
|
||||
|
||||
cfgPath := filepath.Join(dir, fmt.Sprintf("%s.log.conf", cmdName))
|
||||
|
||||
// The Windows service previously ran as tailscale-ipn.exe, so
|
||||
// let's keep using that log base name if it exists.
|
||||
if runtime.GOOS == "windows" && cmdName == "tailscaled" {
|
||||
const oldCmdName = "tailscale-ipn"
|
||||
oldPath := filepath.Join(dir, oldCmdName+".log.conf")
|
||||
if fi, err := os.Stat(oldPath); err == nil && fi.Mode().IsRegular() {
|
||||
cfgPath = oldPath
|
||||
cmdName = oldCmdName
|
||||
if runtime.GOOS == "windows" {
|
||||
switch cmdName {
|
||||
case "tailscaled":
|
||||
// Tailscale 1.14 and before stored state under %LocalAppData%
|
||||
// (usually "C:\WINDOWS\system32\config\systemprofile\AppData\Local"
|
||||
// when tailscaled.exe is running as a non-user system service).
|
||||
// However it is frequently cleared for almost any reason: Windows
|
||||
// updates, System Restore, even various System Cleaner utilities.
|
||||
//
|
||||
// The Windows service previously ran as tailscale-ipn.exe, so
|
||||
// machines which ran very old versions might still have their
|
||||
// log conf named %LocalAppData%\tailscale-ipn.log.conf
|
||||
//
|
||||
// Machines which started using Tailscale more recently will have
|
||||
// %LocalAppData%\tailscaled.log.conf
|
||||
//
|
||||
// Attempt to migrate the log conf to C:\ProgramData\Tailscale
|
||||
oldDir := filepath.Join(os.Getenv("LocalAppData"), "Tailscale")
|
||||
|
||||
oldPath := filepath.Join(oldDir, "tailscaled.log.conf")
|
||||
if fi, err := os.Stat(oldPath); err != nil || !fi.Mode().IsRegular() {
|
||||
// *Only* if tailscaled.log.conf does not exist,
|
||||
// check for tailscale-ipn.log.conf
|
||||
oldPathOldCmd := filepath.Join(oldDir, "tailscale-ipn.log.conf")
|
||||
if fi, err := os.Stat(oldPathOldCmd); err == nil && fi.Mode().IsRegular() {
|
||||
oldPath = oldPathOldCmd
|
||||
}
|
||||
}
|
||||
|
||||
cfgPath = paths.TryConfigFileMigration(earlyLogf, oldPath, cfgPath)
|
||||
case "tailscale-ipn":
|
||||
for _, oldBase := range []string{"wg64.log.conf", "wg32.log.conf"} {
|
||||
oldConf := filepath.Join(dir, oldBase)
|
||||
if fi, err := os.Stat(oldConf); err == nil && fi.Mode().IsRegular() {
|
||||
cfgPath = paths.TryConfigFileMigration(earlyLogf, oldConf, cfgPath)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -696,4 +696,10 @@ func init() {
|
||||
addDoH("149.112.112.112", "https://dns.quad9.net/dns-query")
|
||||
addDoH("2620:fe::fe", "https://dns.quad9.net/dns-query")
|
||||
addDoH("2620:fe::fe:9", "https://dns.quad9.net/dns-query")
|
||||
|
||||
// Quad9 -DNSSEC
|
||||
addDoH("9.9.9.10", "https://dns10.quad9.net/dns-query")
|
||||
addDoH("149.112.112.10", "https://dns10.quad9.net/dns-query")
|
||||
addDoH("2620:fe::10", "https://dns10.quad9.net/dns-query")
|
||||
addDoH("2620:fe::fe:10", "https://dns10.quad9.net/dns-query")
|
||||
}
|
||||
|
||||
@@ -49,6 +49,34 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"12": {
|
||||
"RegionID": 12,
|
||||
"RegionCode": "r12",
|
||||
"RegionName": "r12",
|
||||
"Nodes": [
|
||||
{
|
||||
"Name": "12a",
|
||||
"RegionID": 12,
|
||||
"HostName": "derp12.tailscale.com",
|
||||
"IPv4": "216.128.144.130",
|
||||
"IPv6": "2001:19f0:5c01:289:5400:3ff:fe8d:cb5e"
|
||||
},
|
||||
{
|
||||
"Name": "12b",
|
||||
"RegionID": 12,
|
||||
"HostName": "derp12b.tailscale.com",
|
||||
"IPv4": "45.63.71.144",
|
||||
"IPv6": "2001:19f0:5c01:48a:5400:3ff:fe8d:cb5f"
|
||||
},
|
||||
{
|
||||
"Name": "12c",
|
||||
"RegionID": 12,
|
||||
"HostName": "derp12c.tailscale.com",
|
||||
"IPv4": "149.28.119.105",
|
||||
"IPv6": "2001:19f0:5c01:2cb:5400:3ff:fe8d:cb60"
|
||||
}
|
||||
]
|
||||
},
|
||||
"2": {
|
||||
"RegionID": 2,
|
||||
"RegionCode": "r2",
|
||||
@@ -193,6 +221,20 @@
|
||||
"HostName": "derp9.tailscale.com",
|
||||
"IPv4": "207.148.3.137",
|
||||
"IPv6": "2001:19f0:6401:1d9c:5400:2ff:feef:bb82"
|
||||
},
|
||||
{
|
||||
"Name": "9b",
|
||||
"RegionID": 9,
|
||||
"HostName": "derp9b.tailscale.com",
|
||||
"IPv4": "144.202.67.195",
|
||||
"IPv6": "2001:19f0:6401:eb5:5400:3ff:fe8d:6d9b"
|
||||
},
|
||||
{
|
||||
"Name": "9c",
|
||||
"RegionID": 9,
|
||||
"HostName": "derp9c.tailscale.com",
|
||||
"IPv4": "155.138.243.219",
|
||||
"IPv6": "2001:19f0:6401:fe7:5400:3ff:fe8d:6d9c"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/tlsdial"
|
||||
"tailscale.com/net/tshttpproxy"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
@@ -95,6 +96,7 @@ func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netaddr.IP
|
||||
tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(serverIP.String(), "443"))
|
||||
}
|
||||
tr.TLSClientConfig = tlsdial.Config(serverName, tr.TLSClientConfig)
|
||||
c := &http.Client{Transport: tr}
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", "https://"+serverName+"/bootstrap-dns?q="+url.QueryEscape(queryName), nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -112,22 +112,36 @@ func NonTailscaleMTUs() (map[winipcfg.LUID]uint32, error) {
|
||||
return mtus, err
|
||||
}
|
||||
|
||||
func notTailscaleInterface(iface *winipcfg.IPAdapterAddresses) bool {
|
||||
// TODO(bradfitz): do this without the Description method's
|
||||
// utf16-to-string allocation. But at least we only do it for
|
||||
// the virtual interfaces, for which there won't be many.
|
||||
return !(iface.IfType == winipcfg.IfTypePropVirtual &&
|
||||
iface.Description() == tsconst.WintunInterfaceDesc)
|
||||
}
|
||||
|
||||
// NonTailscaleInterfaces returns a map of interface LUID to interface
|
||||
// for all interfaces except Tailscale tunnels.
|
||||
func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
|
||||
ifs, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludeAllInterfaces)
|
||||
return getInterfaces(windows.AF_UNSPEC, winipcfg.GAAFlagIncludeAllInterfaces, notTailscaleInterface)
|
||||
}
|
||||
|
||||
// getInterfaces returns a map of interfaces keyed by their LUID for
|
||||
// all interfaces matching the provided match predicate.
|
||||
//
|
||||
// The family (AF_UNSPEC, AF_INET, or AF_INET6) and flags are passed
|
||||
// to winipcfg.GetAdaptersAddresses.
|
||||
func getInterfaces(family winipcfg.AddressFamily, flags winipcfg.GAAFlags, match func(*winipcfg.IPAdapterAddresses) bool) (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
|
||||
ifs, err := winipcfg.GetAdaptersAddresses(family, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := map[winipcfg.LUID]*winipcfg.IPAdapterAddresses{}
|
||||
for _, iface := range ifs {
|
||||
if iface.Description() == tsconst.WintunInterfaceDesc {
|
||||
continue
|
||||
if match(iface) {
|
||||
ret[iface.LUID] = iface
|
||||
}
|
||||
ret[iface.LUID] = iface
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -135,8 +149,26 @@ func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, e
|
||||
// default route for the given address family.
|
||||
//
|
||||
// It returns (nil, nil) if no interface is found.
|
||||
//
|
||||
// The family must be one of AF_INET or AF_INET6.
|
||||
func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddresses, error) {
|
||||
ifs, err := NonTailscaleInterfaces()
|
||||
ifs, err := getInterfaces(family, winipcfg.GAAFlagIncludeAllInterfaces, func(iface *winipcfg.IPAdapterAddresses) bool {
|
||||
switch iface.IfType {
|
||||
case winipcfg.IfTypeSoftwareLoopback:
|
||||
return false
|
||||
}
|
||||
switch family {
|
||||
case windows.AF_INET:
|
||||
if iface.Flags&winipcfg.IPAAFlagIpv4Enabled == 0 {
|
||||
return false
|
||||
}
|
||||
case windows.AF_INET6:
|
||||
if iface.Flags&winipcfg.IPAAFlagIpv6Enabled == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return iface.OperStatus == winipcfg.IfOperStatusUp && notTailscaleInterface(iface)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -149,12 +181,31 @@ func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddres
|
||||
bestMetric := ^uint32(0)
|
||||
var bestIface *winipcfg.IPAdapterAddresses
|
||||
for _, route := range routes {
|
||||
iface := ifs[route.InterfaceLUID]
|
||||
if route.DestinationPrefix.PrefixLength != 0 || iface == nil {
|
||||
if route.DestinationPrefix.PrefixLength != 0 {
|
||||
// Not a default route.
|
||||
continue
|
||||
}
|
||||
if iface.OperStatus == winipcfg.IfOperStatusUp && route.Metric < bestMetric {
|
||||
bestMetric = route.Metric
|
||||
iface := ifs[route.InterfaceLUID]
|
||||
if iface == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Microsoft docs say:
|
||||
//
|
||||
// "The actual route metric used to compute the route
|
||||
// preferences for IPv4 is the summation of the route
|
||||
// metric offset specified in the Metric member of the
|
||||
// MIB_IPFORWARD_ROW2 structure and the interface
|
||||
// metric specified in this member for IPv4"
|
||||
metric := route.Metric
|
||||
switch family {
|
||||
case windows.AF_INET:
|
||||
metric += iface.Ipv4Metric
|
||||
case windows.AF_INET6:
|
||||
metric += iface.Ipv6Metric
|
||||
}
|
||||
if metric < bestMetric {
|
||||
bestMetric = metric
|
||||
bestIface = iface
|
||||
}
|
||||
}
|
||||
@@ -163,6 +214,9 @@ func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddres
|
||||
}
|
||||
|
||||
func DefaultRouteInterface() (string, error) {
|
||||
// We always return the IPv4 default route.
|
||||
// TODO(bradfitz): adjust API if/when anything cares. They could in theory differ, though,
|
||||
// in which case we might send traffic to the wrong interface.
|
||||
iface, err := GetWindowsDefault(windows.AF_INET)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -236,7 +236,19 @@ func (c *Client) upnpPort() uint16 {
|
||||
}
|
||||
|
||||
func (c *Client) listenPacket(ctx context.Context, network, addr string) (net.PacketConn, error) {
|
||||
if (c.testPxPPort != 0 || c.testUPnPPort != 0) && os.Getenv("GITHUB_ACTIONS") == "true" {
|
||||
// When running under testing conditions, we bind the IGD server
|
||||
// to localhost, and may be running in an environment where our
|
||||
// netns code would decide that binding the portmapper client
|
||||
// socket to the default route interface is the correct way to
|
||||
// ensure connectivity. This can result in us trying to send
|
||||
// packets for 127.0.0.1 out the machine's LAN interface, which
|
||||
// obviously gets dropped on the floor.
|
||||
//
|
||||
// So, under those testing conditions, do _not_ use netns to
|
||||
// create listening sockets. Such sockets are vulnerable to
|
||||
// routing loops, but it's tests that don't set up routing loops,
|
||||
// so we don't care.
|
||||
if c.testPxPPort != 0 || c.testUPnPPort != 0 || os.Getenv("GITHUB_ACTIONS") == "true" {
|
||||
var lc net.ListenConfig
|
||||
return lc.ListenPacket(ctx, network, addr)
|
||||
}
|
||||
|
||||
10
net/tlsdial/deps_test.go
Normal file
10
net/tlsdial/deps_test.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// 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.
|
||||
|
||||
//go:build for_go_mod_tidy_only
|
||||
// +build for_go_mod_tidy_only
|
||||
|
||||
package tlsdial
|
||||
|
||||
import _ "filippo.io/mkcert"
|
||||
@@ -15,9 +15,24 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
var counterFallbackOK int32 // atomic
|
||||
|
||||
// If SSLKEYLOGFILE is set, it's a file to which we write our TLS private keys
|
||||
// in a way that WireShark can read.
|
||||
//
|
||||
// See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format
|
||||
var sslKeyLogFile = os.Getenv("SSLKEYLOGFILE")
|
||||
|
||||
var debug, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_TLS_DIAL"))
|
||||
|
||||
// Config returns a tls.Config for connecting to a server.
|
||||
// If base is non-nil, it's cloned as the base config before
|
||||
// being configured and returned.
|
||||
@@ -30,11 +45,65 @@ func Config(host string, base *tls.Config) *tls.Config {
|
||||
}
|
||||
conf.ServerName = host
|
||||
|
||||
if n := sslKeyLogFile; n != "" {
|
||||
f, err := os.OpenFile(n, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("WARNING: writing to SSLKEYLOGFILE %v", n)
|
||||
conf.KeyLogWriter = f
|
||||
}
|
||||
|
||||
if conf.InsecureSkipVerify {
|
||||
panic("unexpected base.InsecureSkipVerify")
|
||||
}
|
||||
if conf.VerifyConnection != nil {
|
||||
panic("unexpected base.VerifyConnection")
|
||||
}
|
||||
|
||||
// Set InsecureSkipVerify to prevent crypto/tls from doing its
|
||||
// own cert verification, as do the same work that it'd do
|
||||
// (with the baked-in fallback root) in the VerifyConnection hook.
|
||||
conf.InsecureSkipVerify = true
|
||||
conf.VerifyConnection = func(cs tls.ConnectionState) error {
|
||||
// First try doing x509 verification with the system's
|
||||
// root CA pool.
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: cs.ServerName,
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
for _, cert := range cs.PeerCertificates[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, errSys := cs.PeerCertificates[0].Verify(opts)
|
||||
if debug {
|
||||
log.Printf("tlsdial(sys %q): %v", host, errSys)
|
||||
}
|
||||
if errSys == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If that failed, because the system's CA roots are old
|
||||
// or broken, fall back to trying LetsEncrypt at least.
|
||||
opts.Roots = bakedInRoots()
|
||||
_, err := cs.PeerCertificates[0].Verify(opts)
|
||||
if debug {
|
||||
log.Printf("tlsdial(bake %q): %v", host, err)
|
||||
}
|
||||
if err == nil {
|
||||
atomic.AddInt32(&counterFallbackOK, 1)
|
||||
return nil
|
||||
}
|
||||
return errSys
|
||||
}
|
||||
return conf
|
||||
}
|
||||
|
||||
// SetConfigExpectedCert modifies c to expect and verify that the server returns
|
||||
// a certificate for the provided certDNSName.
|
||||
//
|
||||
// This is for user-configurable client-side domain fronting support,
|
||||
// where we send one SNI value but validate a different cert.
|
||||
func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
|
||||
if c.ServerName == certDNSName {
|
||||
return
|
||||
@@ -50,6 +119,7 @@ func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
|
||||
// own cert verification, but do the same work that it'd do
|
||||
// (but using certDNSName) in the VerifyPeerCertificate hook.
|
||||
c.InsecureSkipVerify = true
|
||||
c.VerifyConnection = nil
|
||||
c.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
|
||||
if len(rawCerts) == 0 {
|
||||
return errors.New("no certs presented")
|
||||
@@ -70,7 +140,102 @@ func SetConfigExpectedCert(c *tls.Config, certDNSName string) {
|
||||
for _, cert := range certs[1:] {
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
_, errSys := certs[0].Verify(opts)
|
||||
if debug {
|
||||
log.Printf("tlsdial(sys %q/%q): %v", c.ServerName, certDNSName, errSys)
|
||||
}
|
||||
if errSys == nil {
|
||||
return nil
|
||||
}
|
||||
opts.Roots = bakedInRoots()
|
||||
_, err := certs[0].Verify(opts)
|
||||
return err
|
||||
if debug {
|
||||
log.Printf("tlsdial(bake %q/%q): %v", c.ServerName, certDNSName, err)
|
||||
}
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return errSys
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
letsEncryptX1 is the LetsEncrypt X1 root:
|
||||
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number:
|
||||
82:10:cf:b0:d2:40:e3:59:44:63:e0:bb:63:82:8b:00
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
Validity
|
||||
Not Before: Jun 4 11:04:38 2015 GMT
|
||||
Not After : Jun 4 11:04:38 2035 GMT
|
||||
Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
RSA Public-Key: (4096 bit)
|
||||
|
||||
We bake it into the binary as a fallback verification root,
|
||||
in case the system we're running on doesn't have it.
|
||||
(Tailscale runs on some ancient devices.)
|
||||
|
||||
To test that this code is working on Debian/Ubuntu:
|
||||
|
||||
$ sudo mv /usr/share/ca-certificates/mozilla/ISRG_Root_X1.crt{,.old}
|
||||
$ sudo update-ca-certificates
|
||||
|
||||
Then restart tailscaled. To also test dnsfallback's use of it, nuke
|
||||
your /etc/resolv.conf and it should still start & run fine.
|
||||
|
||||
*/
|
||||
const letsEncryptX1 = `
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
var bakedInRootsOnce struct {
|
||||
sync.Once
|
||||
p *x509.CertPool
|
||||
}
|
||||
|
||||
func bakedInRoots() *x509.CertPool {
|
||||
bakedInRootsOnce.Do(func() {
|
||||
p := x509.NewCertPool()
|
||||
if !p.AppendCertsFromPEM([]byte(letsEncryptX1)) {
|
||||
panic("bogus PEM")
|
||||
}
|
||||
bakedInRootsOnce.p = p
|
||||
})
|
||||
return bakedInRootsOnce.p
|
||||
}
|
||||
|
||||
131
net/tlsdial/tlsdial_test.go
Normal file
131
net/tlsdial/tlsdial_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// 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 tlsdial
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func resetOnce() {
|
||||
rv := reflect.ValueOf(&bakedInRootsOnce).Elem()
|
||||
rv.Set(reflect.Zero(rv.Type()))
|
||||
}
|
||||
|
||||
func TestBakedInRoots(t *testing.T) {
|
||||
resetOnce()
|
||||
p := bakedInRoots()
|
||||
got := p.Subjects()
|
||||
if len(got) != 1 {
|
||||
t.Errorf("subjects = %v; want 1", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallbackRootWorks(t *testing.T) {
|
||||
defer resetOnce()
|
||||
|
||||
const debug = false
|
||||
if runtime.GOOS != "linux" {
|
||||
t.Skip("test assumes Linux")
|
||||
}
|
||||
d := t.TempDir()
|
||||
crtFile := filepath.Join(d, "tlsdial.test.crt")
|
||||
keyFile := filepath.Join(d, "tlsdial.test.key")
|
||||
caFile := filepath.Join(d, "rootCA.pem")
|
||||
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"),
|
||||
"run", "filippo.io/mkcert",
|
||||
"--cert-file="+crtFile,
|
||||
"--key-file="+keyFile,
|
||||
"tlsdial.test")
|
||||
cmd.Env = append(os.Environ(), "CAROOT="+d)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("mkcert: %v, %s", err, out)
|
||||
}
|
||||
if debug {
|
||||
t.Logf("Ran: %s", out)
|
||||
dents, err := os.ReadDir(d)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, de := range dents {
|
||||
t.Logf(" - %v", de)
|
||||
}
|
||||
}
|
||||
|
||||
caPEM, err := os.ReadFile(caFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resetOnce()
|
||||
bakedInRootsOnce.Do(func() {
|
||||
p := x509.NewCertPool()
|
||||
if !p.AppendCertsFromPEM(caPEM) {
|
||||
t.Fatal("failed to add")
|
||||
}
|
||||
bakedInRootsOnce.p = p
|
||||
})
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
if debug {
|
||||
t.Logf("listener running at %v", ln.Addr())
|
||||
}
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
err := http.ServeTLS(ln, http.HandlerFunc(sayHi), crtFile, keyFile)
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
t.Logf("ServeTLS: %v", err)
|
||||
errc <- err
|
||||
}
|
||||
}()
|
||||
|
||||
tr := &http.Transport{
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
return net.Dial("tcp", ln.Addr().String())
|
||||
},
|
||||
DisableKeepAlives: true, // for test cleanup ease
|
||||
}
|
||||
tr.TLSClientConfig = Config("tlsdial.test", tr.TLSClientConfig)
|
||||
c := &http.Client{Transport: tr}
|
||||
|
||||
ctr0 := atomic.LoadInt32(&counterFallbackOK)
|
||||
|
||||
res, err := c.Get("https://tlsdial.test/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
t.Fatal(res.Status)
|
||||
}
|
||||
|
||||
ctrDelta := atomic.LoadInt32(&counterFallbackOK) - ctr0
|
||||
if ctrDelta != 1 {
|
||||
t.Errorf("fallback root success count = %d; want 1", ctrDelta)
|
||||
}
|
||||
}
|
||||
|
||||
func sayHi(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, "hi")
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
package tstun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -19,7 +20,9 @@ import (
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/disco"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -76,6 +79,7 @@ type Wrapper struct {
|
||||
|
||||
destIPActivity atomic.Value // of map[netaddr.IP]func()
|
||||
destMACAtomic atomic.Value // of [6]byte
|
||||
discoKey atomic.Value // of tailcfg.DiscoKey
|
||||
|
||||
// buffer stores the oldest unconsumed packet from tdev.
|
||||
// It is made a static buffer in order to avoid allocations.
|
||||
@@ -196,6 +200,30 @@ func (t *Wrapper) SetDestIPActivityFuncs(m map[netaddr.IP]func()) {
|
||||
t.destIPActivity.Store(m)
|
||||
}
|
||||
|
||||
// SetDiscoKey sets the current discovery key.
|
||||
//
|
||||
// It is only used for filtering out bogus traffic when network
|
||||
// stack(s) get confused; see Issue 1526.
|
||||
func (t *Wrapper) SetDiscoKey(k tailcfg.DiscoKey) {
|
||||
t.discoKey.Store(k)
|
||||
}
|
||||
|
||||
// isSelfDisco reports whether packet p
|
||||
// looks like a Disco packet from ourselves.
|
||||
// See Issue 1526.
|
||||
func (t *Wrapper) isSelfDisco(p *packet.Parsed) bool {
|
||||
if p.IPProto != ipproto.UDP {
|
||||
return false
|
||||
}
|
||||
pkt := p.Payload()
|
||||
discoSrc, ok := disco.Source(pkt)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
selfDiscoPub, ok := t.discoKey.Load().(tailcfg.DiscoKey)
|
||||
return ok && bytes.Equal(selfDiscoPub[:], discoSrc)
|
||||
}
|
||||
|
||||
func (t *Wrapper) Close() error {
|
||||
var err error
|
||||
t.closeOnce.Do(func() {
|
||||
@@ -385,6 +413,16 @@ func (t *Wrapper) filterOut(p *packet.Parsed) filter.Response {
|
||||
return filter.DropSilently // don't pass on to OS; already handled
|
||||
}
|
||||
|
||||
// Issue 1526 workaround: if we sent disco packets over
|
||||
// Tailscale from ourselves, then drop them, as that shouldn't
|
||||
// happen unless a networking stack is confused, as it seems
|
||||
// macOS in Network Extension mode might be.
|
||||
if p.IPProto == ipproto.UDP && // disco is over UDP; avoid isSelfDisco call for TCP/etc
|
||||
t.isSelfDisco(p) {
|
||||
t.logf("[unexpected] received self disco out packet over tstun; dropping")
|
||||
return filter.DropSilently
|
||||
}
|
||||
|
||||
if t.PreFilterOut != nil {
|
||||
if res := t.PreFilterOut(p, t); res.IsDrop() {
|
||||
return res
|
||||
@@ -483,6 +521,16 @@ func (t *Wrapper) filterIn(buf []byte) filter.Response {
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1526 workaround: if we see disco packets over
|
||||
// Tailscale from ourselves, then drop them, as that shouldn't
|
||||
// happen unless a networking stack is confused, as it seems
|
||||
// macOS in Network Extension mode might be.
|
||||
if p.IPProto == ipproto.UDP && // disco is over UDP; avoid isSelfDisco call for TCP/etc
|
||||
t.isSelfDisco(p) {
|
||||
t.logf("[unexpected] received self disco in packet over tstun; dropping")
|
||||
return filter.DropSilently
|
||||
}
|
||||
|
||||
if t.PreFilterIn != nil {
|
||||
if res := t.PreFilterIn(p, t); res.IsDrop() {
|
||||
return res
|
||||
|
||||
@@ -15,7 +15,10 @@ import (
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun/tuntest"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/disco"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -485,3 +488,43 @@ func TestPeerAPIBypass(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1526: drop disco frames from ourselves.
|
||||
func TestFilterDiscoLoop(t *testing.T) {
|
||||
var memLog tstest.MemLogger
|
||||
discoPub := tailcfg.DiscoKey{1: 1, 2: 2}
|
||||
tw := &Wrapper{logf: memLog.Logf}
|
||||
tw.SetDiscoKey(discoPub)
|
||||
uh := packet.UDP4Header{
|
||||
IP4Header: packet.IP4Header{
|
||||
IPProto: ipproto.UDP,
|
||||
Src: netaddr.IPv4(1, 2, 3, 4),
|
||||
Dst: netaddr.IPv4(5, 6, 7, 8),
|
||||
},
|
||||
SrcPort: 9,
|
||||
DstPort: 10,
|
||||
}
|
||||
discoPayload := fmt.Sprintf("%s%s%s", disco.Magic, discoPub[:], [disco.NonceLen]byte{})
|
||||
pkt := make([]byte, uh.Len()+len(discoPayload))
|
||||
uh.Marshal(pkt)
|
||||
copy(pkt[uh.Len():], discoPayload)
|
||||
|
||||
got := tw.filterIn(pkt)
|
||||
if got != filter.DropSilently {
|
||||
t.Errorf("got %v; want DropSilently", got)
|
||||
}
|
||||
if got, want := memLog.String(), "[unexpected] received self disco in packet over tstun; dropping\n"; got != want {
|
||||
t.Errorf("log output mismatch\n got: %q\nwant: %q\n", got, want)
|
||||
}
|
||||
|
||||
memLog.Reset()
|
||||
pp := new(packet.Parsed)
|
||||
pp.Decode(pkt)
|
||||
got = tw.filterOut(pp)
|
||||
if got != filter.DropSilently {
|
||||
t.Errorf("got %v; want DropSilently", got)
|
||||
}
|
||||
if got, want := memLog.String(), "[unexpected] received self disco out packet over tstun; dropping\n"; got != want {
|
||||
t.Errorf("log output mismatch\n got: %q\nwant: %q\n", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
59
paths/migrate.go
Normal file
59
paths/migrate.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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 paths
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// TryConfigFileMigration carefully copies the contents of oldFile to
|
||||
// newFile, returning the path which should be used to read the config.
|
||||
// - if newFile already exists, don't modify it just return its path
|
||||
// - if neither oldFile nor newFile exist, return newFile for a fresh
|
||||
// default config to be written to.
|
||||
// - if oldFile exists but copying to newFile fails, return oldFile so
|
||||
// there will at least be some config to work with.
|
||||
func TryConfigFileMigration(logf logger.Logf, oldFile, newFile string) string {
|
||||
_, err := os.Stat(newFile)
|
||||
if err == nil {
|
||||
// Common case for a system which has already been migrated.
|
||||
return newFile
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
logf("TryConfigFileMigration failed; new file: %v", err)
|
||||
return newFile
|
||||
}
|
||||
|
||||
contents, err := os.ReadFile(oldFile)
|
||||
if err != nil {
|
||||
// Common case for a new user.
|
||||
return newFile
|
||||
}
|
||||
|
||||
if err = MkStateDir(filepath.Dir(newFile)); err != nil {
|
||||
logf("TryConfigFileMigration failed; MkStateDir: %v", err)
|
||||
return oldFile
|
||||
}
|
||||
|
||||
err = os.WriteFile(newFile, contents, 0600)
|
||||
if err != nil {
|
||||
removeErr := os.Remove(newFile)
|
||||
if removeErr != nil {
|
||||
logf("TryConfigFileMigration failed; write newFile no cleanup: %v, remove err: %v",
|
||||
err, removeErr)
|
||||
return oldFile
|
||||
}
|
||||
logf("TryConfigFileMigration failed; write newFile: %v", err)
|
||||
return oldFile
|
||||
}
|
||||
|
||||
logf("TryConfigFileMigration: successfully migrated: from %v to %v",
|
||||
oldFile, newFile)
|
||||
|
||||
return newFile
|
||||
}
|
||||
@@ -55,7 +55,18 @@ func DefaultTailscaledStateFile() string {
|
||||
return f()
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
return filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf")
|
||||
return filepath.Join(os.Getenv("ProgramData"), "Tailscale", "server-state.conf")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// MkStateDir ensures that dirPath, the daemon's configurtaion directory
|
||||
// containing machine keys etc, both exists and has the correct permissions.
|
||||
// We want it to only be accessible to the user the daemon is running under.
|
||||
func MkStateDir(dirPath string) error {
|
||||
if err := os.MkdirAll(dirPath, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ensureStateDirPerms(dirPath)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
package paths
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -61,3 +62,22 @@ func xdgDataHome() string {
|
||||
}
|
||||
return filepath.Join(os.Getenv("HOME"), ".local/share")
|
||||
}
|
||||
|
||||
func ensureStateDirPerms(dir string) error {
|
||||
if filepath.Base(dir) != "tailscale" {
|
||||
return nil
|
||||
}
|
||||
fi, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return fmt.Errorf("expected %q to be a directory; is %v", dir, fi.Mode())
|
||||
}
|
||||
const perm = 0700
|
||||
if fi.Mode().Perm() == perm {
|
||||
// Already correct.
|
||||
return nil
|
||||
}
|
||||
return os.Chmod(dir, perm)
|
||||
}
|
||||
|
||||
150
paths/paths_windows.go
Normal file
150
paths/paths_windows.go
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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 paths
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func getTokenInfo(token windows.Token, infoClass uint32) ([]byte, error) {
|
||||
var desiredLen uint32
|
||||
err := windows.GetTokenInformation(token, infoClass, nil, 0, &desiredLen)
|
||||
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, desiredLen)
|
||||
actualLen := desiredLen
|
||||
err = windows.GetTokenInformation(token, infoClass, &buf[0], desiredLen, &actualLen)
|
||||
return buf, err
|
||||
}
|
||||
|
||||
func getTokenUserInfo(token windows.Token) (*windows.Tokenuser, error) {
|
||||
buf, err := getTokenInfo(token, windows.TokenUser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (*windows.Tokenuser)(unsafe.Pointer(&buf[0])), nil
|
||||
}
|
||||
|
||||
func getTokenPrimaryGroupInfo(token windows.Token) (*windows.Tokenprimarygroup, error) {
|
||||
buf, err := getTokenInfo(token, windows.TokenPrimaryGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (*windows.Tokenprimarygroup)(unsafe.Pointer(&buf[0])), nil
|
||||
}
|
||||
|
||||
type userSids struct {
|
||||
User *windows.SID
|
||||
PrimaryGroup *windows.SID
|
||||
}
|
||||
|
||||
func getCurrentUserSids() (*userSids, error) {
|
||||
token, err := windows.OpenCurrentProcessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer token.Close()
|
||||
|
||||
userInfo, err := getTokenUserInfo(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
primaryGroup, err := getTokenPrimaryGroupInfo(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &userSids{userInfo.User.Sid, primaryGroup.PrimaryGroup}, nil
|
||||
}
|
||||
|
||||
// ensureStateDirPerms applies a restrictive ACL to the directory specified by dirPath.
|
||||
// It sets the following security attributes on the directory:
|
||||
// Owner: The user for the current process;
|
||||
// Primary Group: The primary group for the current process;
|
||||
// DACL: Full control to the current user and to the Administrators group.
|
||||
// (We include Administrators so that admin users may still access logs;
|
||||
// granting access exclusively to LocalSystem would require admins to use
|
||||
// special tools to access the Log directory)
|
||||
// Inheritance: The directory does not inherit the ACL from its parent.
|
||||
// However, any directories and/or files created within this
|
||||
// directory *do* inherit the ACL that we are setting.
|
||||
func ensureStateDirPerms(dirPath string) error {
|
||||
fi, err := os.Stat(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if strings.ToLower(filepath.Base(dirPath)) != "tailscale" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We need the info for our current user as SIDs
|
||||
sids, err := getCurrentUserSids()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We also need the SID for the Administrators group so that admins may
|
||||
// easily access logs.
|
||||
adminGroupSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Munge the SIDs into the format required by EXPLICIT_ACCESS.
|
||||
userTrustee := windows.TRUSTEE{nil, windows.NO_MULTIPLE_TRUSTEE,
|
||||
windows.TRUSTEE_IS_SID, windows.TRUSTEE_IS_USER,
|
||||
windows.TrusteeValueFromSID(sids.User)}
|
||||
|
||||
adminTrustee := windows.TRUSTEE{nil, windows.NO_MULTIPLE_TRUSTEE,
|
||||
windows.TRUSTEE_IS_SID, windows.TRUSTEE_IS_WELL_KNOWN_GROUP,
|
||||
windows.TrusteeValueFromSID(adminGroupSid)}
|
||||
|
||||
// We declare our access rights via this array of EXPLICIT_ACCESS structures.
|
||||
// We set full access to our user and to Administrators.
|
||||
// We configure the DACL such that any files or directories created within
|
||||
// dirPath will also inherit this DACL.
|
||||
explicitAccess := []windows.EXPLICIT_ACCESS{
|
||||
windows.EXPLICIT_ACCESS{
|
||||
windows.GENERIC_ALL,
|
||||
windows.SET_ACCESS,
|
||||
windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
|
||||
userTrustee,
|
||||
},
|
||||
windows.EXPLICIT_ACCESS{
|
||||
windows.GENERIC_ALL,
|
||||
windows.SET_ACCESS,
|
||||
windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
|
||||
adminTrustee,
|
||||
},
|
||||
}
|
||||
|
||||
dacl, err := windows.ACLFromEntries(explicitAccess, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We now reset the file's owner, primary group, and DACL.
|
||||
// We also must pass PROTECTED_DACL_SECURITY_INFORMATION so that our new ACL
|
||||
// does not inherit any ACL entries from the parent directory.
|
||||
const flags = windows.OWNER_SECURITY_INFORMATION |
|
||||
windows.GROUP_SECURITY_INFORMATION |
|
||||
windows.DACL_SECURITY_INFORMATION |
|
||||
windows.PROTECTED_DACL_SECURITY_INFORMATION
|
||||
return windows.SetNamedSecurityInfo(dirPath, windows.SE_FILE_OBJECT, flags,
|
||||
sids.User, sids.PrimaryGroup, dacl, nil)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
type closeable interface {
|
||||
@@ -29,9 +30,39 @@ func ConnCloseWrite(c net.Conn) error {
|
||||
return c.(closeable).CloseWrite()
|
||||
}
|
||||
|
||||
var processStartTime = time.Now()
|
||||
var tailscaledProcExists = func() bool { return false } // set by safesocket_ps.go
|
||||
|
||||
// tailscaledStillStarting reports whether tailscaled is probably
|
||||
// still starting up. That is, it reports whether the caller should
|
||||
// keep retrying to connect.
|
||||
func tailscaledStillStarting() bool {
|
||||
d := time.Since(processStartTime)
|
||||
if d < 2*time.Second {
|
||||
// Without even checking the process table, assume
|
||||
// that for the first two seconds that tailscaled is
|
||||
// probably still starting. That is, assume they're
|
||||
// running "tailscaled & tailscale up ...." and make
|
||||
// the tailscale client block for a bit for tailscaled
|
||||
// to start accepting on the socket.
|
||||
return true
|
||||
}
|
||||
if d > 5*time.Second {
|
||||
return false
|
||||
}
|
||||
return tailscaledProcExists()
|
||||
}
|
||||
|
||||
// Connect connects to either path (on Unix) or the provided localhost port (on Windows).
|
||||
func Connect(path string, port uint16) (net.Conn, error) {
|
||||
return connect(path, port)
|
||||
for {
|
||||
c, err := connect(path, port)
|
||||
if err != nil && tailscaledStillStarting() {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
}
|
||||
|
||||
// Listen returns a listener either on Unix socket path (on Unix), or
|
||||
|
||||
37
safesocket/safesocket_ps.go
Normal file
37
safesocket/safesocket_ps.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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.
|
||||
|
||||
//go:build linux || windows || darwin || freebsd
|
||||
// +build linux windows darwin freebsd
|
||||
|
||||
package safesocket
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
ps "github.com/mitchellh/go-ps"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tailscaledProcExists = func() bool {
|
||||
procs, err := ps.Processes()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, proc := range procs {
|
||||
name := proc.Executable()
|
||||
const tailscaled = "tailscaled"
|
||||
if len(name) < len(tailscaled) {
|
||||
continue
|
||||
}
|
||||
// Do case insensitive comparison for Windows,
|
||||
// notably, and ignore any ".exe" suffix.
|
||||
if strings.EqualFold(name[:len(tailscaled)], tailscaled) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,8 @@ import (
|
||||
// 20: 2021-06-11: MapResponse.LastSeen used even less (https://github.com/tailscale/tailscale/issues/2107)
|
||||
// 21: 2021-06-15: added MapResponse.DNSConfig.CertDomains
|
||||
// 22: 2021-06-16: added MapResponse.DNSConfig.ExtraRecords
|
||||
const CurrentMapRequestVersion = 22
|
||||
// 23: 2021-08-25: DNSConfig.Routes values may be empty (for ExtraRecords support in 1.14.1+)
|
||||
const CurrentMapRequestVersion = 23
|
||||
|
||||
type StableID string
|
||||
|
||||
@@ -837,12 +838,19 @@ var FilterAllowAll = []FilterRule{
|
||||
type DNSConfig struct {
|
||||
// Resolvers are the DNS resolvers to use, in order of preference.
|
||||
Resolvers []dnstype.Resolver `json:",omitempty"`
|
||||
|
||||
// Routes maps DNS name suffixes to a set of DNS resolvers to
|
||||
// use. It is used to implement "split DNS" and other advanced DNS
|
||||
// routing overlays.
|
||||
// Map keys must be fully-qualified DNS name suffixes, with a
|
||||
// trailing dot but no leading dot.
|
||||
//
|
||||
// Map keys are fully-qualified DNS name suffixes; they may
|
||||
// optionally contain a trailing dot but no leading dot.
|
||||
//
|
||||
// If the value is an empty slice, that means the suffix should still
|
||||
// be handled by Tailscale's built-in resolver (100.100.100.100), such
|
||||
// as for the purpose of handling ExtraRecords.
|
||||
Routes map[string][]dnstype.Resolver `json:",omitempty"`
|
||||
|
||||
// FallbackResolvers is like Resolvers, but is only used if a
|
||||
// split DNS configuration is requested in a configuration that
|
||||
// doesn't work yet without explicit default resolvers.
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"go4.org/mem"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
@@ -118,7 +119,30 @@ func (lt *LogLineTracker) Reset() {
|
||||
|
||||
// Close closes lt. After calling Close, calls to Logf become no-ops.
|
||||
func (lt *LogLineTracker) Close() {
|
||||
|
||||
lt.mu.Lock()
|
||||
defer lt.mu.Unlock()
|
||||
lt.closed = true
|
||||
}
|
||||
|
||||
// MemLogger is a bytes.Buffer with a Logf method for tests that want
|
||||
// to log to a buffer.
|
||||
type MemLogger struct {
|
||||
sync.Mutex
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (ml *MemLogger) Logf(format string, args ...interface{}) {
|
||||
ml.Lock()
|
||||
defer ml.Unlock()
|
||||
fmt.Fprintf(&ml.Buffer, format, args...)
|
||||
if !mem.HasSuffix(mem.B(ml.Buffer.Bytes()), mem.S("\n")) {
|
||||
ml.Buffer.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
func (ml *MemLogger) String() string {
|
||||
ml.Lock()
|
||||
defer ml.Unlock()
|
||||
return ml.Buffer.String()
|
||||
}
|
||||
|
||||
@@ -182,15 +182,18 @@ func TestLongRunningQPS(t *testing.T) {
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
// This will still offer ~500 requests per second,
|
||||
// but won't consume outrageous amount of CPU.
|
||||
start := time.Now()
|
||||
end := start.Add(5 * time.Second)
|
||||
for time.Now().Before(end) {
|
||||
ticker := time.NewTicker(2 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for now := range ticker.C {
|
||||
if now.After(end) {
|
||||
break
|
||||
}
|
||||
wg.Add(1)
|
||||
go f()
|
||||
|
||||
// This will still offer ~500 requests per second, but won't consume
|
||||
// outrageous amount of CPU.
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
}
|
||||
wg.Wait()
|
||||
elapsed := time.Since(start)
|
||||
@@ -201,7 +204,7 @@ func TestLongRunningQPS(t *testing.T) {
|
||||
t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
|
||||
}
|
||||
// We should get very close to the number of requests allowed.
|
||||
if want := int32(0.999 * ideal); numOK < want {
|
||||
if want := int32(0.995 * ideal); numOK < want {
|
||||
t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"inet.af/netaddr"
|
||||
@@ -332,15 +334,32 @@ func TestArrayAllocs(t *testing.T) {
|
||||
if version.IsRace() {
|
||||
t.Skip("skipping test under race detector")
|
||||
}
|
||||
|
||||
// In theory, there should be no allocations. However, escape analysis on
|
||||
// certain architectures fails to detect that certain cases do not escape.
|
||||
// This discrepency currently affects sha256.digest.Sum.
|
||||
// Measure the number of allocations in sha256 to ensure that Hash does
|
||||
// not allocate on top of its usage of sha256.
|
||||
// See https://golang.org/issue/48055.
|
||||
var b []byte
|
||||
h := sha256.New()
|
||||
want := int(testing.AllocsPerRun(1000, func() {
|
||||
b = h.Sum(b[:0])
|
||||
}))
|
||||
switch runtime.GOARCH {
|
||||
case "amd64", "arm64":
|
||||
want = 0 // ensure no allocations on popular architectures
|
||||
}
|
||||
|
||||
type T struct {
|
||||
X [32]byte
|
||||
}
|
||||
x := &T{X: [32]byte{1: 1, 2: 2, 3: 3, 4: 4}}
|
||||
n := int(testing.AllocsPerRun(1000, func() {
|
||||
got := int(testing.AllocsPerRun(1000, func() {
|
||||
sink = Hash(x)
|
||||
}))
|
||||
if n > 0 {
|
||||
t.Errorf("allocs = %v; want 0", n)
|
||||
if got > want {
|
||||
t.Errorf("allocs = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ func (m *darwinRouteMon) skipInterfaceAddrMessage(msg *route.InterfaceAddrMessag
|
||||
if la, ok := addrType(msg.Addrs, unix.RTAX_IFP).(*route.LinkAddr); ok {
|
||||
baseName := strings.TrimRight(la.Name, "0123456789")
|
||||
switch baseName {
|
||||
case "llw", "awdl", "pdp_ip":
|
||||
case "llw", "awdl", "pdp_ip", "ipsec":
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +206,16 @@ func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap {
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, rec := range nm.DNS.ExtraRecords {
|
||||
if rec.Type != "" {
|
||||
continue
|
||||
}
|
||||
ip, err := netaddr.ParseIP(rec.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ret[strings.TrimRight(rec.Name, ".")] = ip
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
@@ -468,19 +478,37 @@ func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Respons
|
||||
return filter.DropSilently
|
||||
}
|
||||
|
||||
func netaddrIPFromNetstackIP(s tcpip.Address) netaddr.IP {
|
||||
switch len(s) {
|
||||
case 4:
|
||||
return netaddr.IPv4(s[0], s[1], s[2], s[3])
|
||||
case 16:
|
||||
var a [16]byte
|
||||
copy(a[:], s)
|
||||
return netaddr.IPFrom16(a)
|
||||
}
|
||||
return netaddr.IP{}
|
||||
}
|
||||
|
||||
func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
reqDetails := r.ID()
|
||||
if debugNetstack {
|
||||
ns.logf("[v2] TCP ForwarderRequest: %s", stringifyTEI(reqDetails))
|
||||
}
|
||||
dialAddr := reqDetails.LocalAddress
|
||||
dialNetAddr, _ := netaddr.FromStdIP(net.IP(dialAddr))
|
||||
isTailscaleIP := tsaddr.IsTailscaleIP(dialNetAddr)
|
||||
clientRemoteIP := netaddrIPFromNetstackIP(reqDetails.RemoteAddress)
|
||||
if !clientRemoteIP.IsValid() {
|
||||
ns.logf("invalid RemoteAddress in TCP ForwarderRequest: %s", stringifyTEI(reqDetails))
|
||||
r.Complete(true)
|
||||
return
|
||||
}
|
||||
|
||||
dialIP := netaddrIPFromNetstackIP(reqDetails.LocalAddress)
|
||||
isTailscaleIP := tsaddr.IsTailscaleIP(dialIP)
|
||||
defer func() {
|
||||
if !isTailscaleIP {
|
||||
// if this is a subnet IP, we added this in before the TCP handshake
|
||||
// so netstack is happy TCP-handshaking as a subnet IP
|
||||
ns.removeSubnetAddress(dialNetAddr)
|
||||
ns.removeSubnetAddress(dialIP)
|
||||
}
|
||||
}()
|
||||
var wq waiter.Queue
|
||||
@@ -490,21 +518,31 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
|
||||
return
|
||||
}
|
||||
r.Complete(false)
|
||||
|
||||
// Asynchronously start the TCP handshake. Note that the
|
||||
// gonet.TCPConn methods c.RemoteAddr() and c.LocalAddr() will
|
||||
// return nil until the handshake actually completes. But we
|
||||
// have the remote address in reqDetails instead, so we don't
|
||||
// use RemoteAddr. The byte copies in both directions in
|
||||
// forwardTCP will block until the TCP handshake is complete.
|
||||
c := gonet.NewTCPConn(&wq, ep)
|
||||
|
||||
if ns.ForwardTCPIn != nil {
|
||||
ns.ForwardTCPIn(c, reqDetails.LocalPort)
|
||||
return
|
||||
}
|
||||
if isTailscaleIP {
|
||||
dialAddr = tcpip.Address(net.ParseIP("127.0.0.1")).To4()
|
||||
dialIP = netaddr.IPv4(127, 0, 0, 1)
|
||||
}
|
||||
ns.forwardTCP(c, &wq, dialAddr, reqDetails.LocalPort)
|
||||
dialAddr := netaddr.IPPortFrom(dialIP, uint16(reqDetails.LocalPort))
|
||||
ns.forwardTCP(c, clientRemoteIP, &wq, dialAddr)
|
||||
}
|
||||
|
||||
func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, dialAddr tcpip.Address, dialPort uint16) {
|
||||
func (ns *Impl) forwardTCP(client *gonet.TCPConn, clientRemoteIP netaddr.IP, wq *waiter.Queue, dialAddr netaddr.IPPort) {
|
||||
defer client.Close()
|
||||
dialAddrStr := net.JoinHostPort(dialAddr.String(), strconv.Itoa(int(dialPort)))
|
||||
dialAddrStr := dialAddr.String()
|
||||
ns.logf("[v2] netstack: forwarding incoming connection to %s", dialAddrStr)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
waitEntry, notifyCh := waiter.NewChannelEntry(nil)
|
||||
@@ -530,7 +568,6 @@ func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, dialAddr tcp
|
||||
defer server.Close()
|
||||
backendLocalAddr := server.LocalAddr().(*net.TCPAddr)
|
||||
backendLocalIPPort, _ := netaddr.FromStdAddr(backendLocalAddr.IP, backendLocalAddr.Port, backendLocalAddr.Zone)
|
||||
clientRemoteIP, _ := netaddr.FromStdIP(client.RemoteAddr().(*net.TCPAddr).IP)
|
||||
ns.e.RegisterIPPortIdentity(backendLocalIPPort, clientRemoteIP)
|
||||
defer ns.e.UnregisterIPPortIdentity(backendLocalIPPort)
|
||||
connClosed := make(chan error, 2)
|
||||
|
||||
@@ -84,6 +84,7 @@ type userspaceEngine struct {
|
||||
wgLogger *wglog.Logger //a wireguard-go logging wrapper
|
||||
reqCh chan struct{}
|
||||
waitCh chan struct{} // chan is closed when first Close call completes; contrast with closing bool
|
||||
magicConnStarted chan struct{} // chan is closed after magicConn.Start
|
||||
timeNow func() mono.Time
|
||||
tundev *tstun.Wrapper
|
||||
wgdev *device.Device
|
||||
@@ -249,13 +250,14 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
closePool.add(tsTUNDev)
|
||||
|
||||
e := &userspaceEngine{
|
||||
timeNow: mono.Now,
|
||||
logf: logf,
|
||||
reqCh: make(chan struct{}, 1),
|
||||
waitCh: make(chan struct{}),
|
||||
tundev: tsTUNDev,
|
||||
router: conf.Router,
|
||||
confListenPort: conf.ListenPort,
|
||||
timeNow: mono.Now,
|
||||
logf: logf,
|
||||
reqCh: make(chan struct{}, 1),
|
||||
waitCh: make(chan struct{}),
|
||||
tundev: tsTUNDev,
|
||||
router: conf.Router,
|
||||
confListenPort: conf.ListenPort,
|
||||
magicConnStarted: make(chan struct{}),
|
||||
}
|
||||
e.isLocalAddr.Store(tsaddr.NewContainsIPFunc(nil))
|
||||
e.isDNSIPOverTailscale.Store(tsaddr.NewContainsIPFunc(nil))
|
||||
@@ -309,6 +311,8 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
closePool.add(e.magicConn)
|
||||
e.magicConn.SetNetworkUp(e.linkMon.InterfaceState().AnyInterfaceUp())
|
||||
|
||||
tsTUNDev.SetDiscoKey(e.magicConn.DiscoPublicKey())
|
||||
|
||||
if conf.RespondToPing {
|
||||
e.tundev.PostFilterIn = echoRespondToAll
|
||||
}
|
||||
@@ -371,7 +375,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
|
||||
// It's a little pointless to apply no-op settings here (they
|
||||
// should already be empty?), but it at least exercises the
|
||||
// router implementation early on the machine.
|
||||
// router implementation early on.
|
||||
e.logf("Clearing router settings...")
|
||||
if err := e.router.Set(nil); err != nil {
|
||||
return nil, err
|
||||
@@ -380,6 +384,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
e.linkMon.Start()
|
||||
e.logf("Starting magicsock...")
|
||||
e.magicConn.Start()
|
||||
close(e.magicConnStarted)
|
||||
|
||||
go e.pollResolver()
|
||||
|
||||
@@ -1092,6 +1097,10 @@ func (e *userspaceEngine) LinkChange(_ bool) {
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) linkChange(changed bool, cur *interfaces.State) {
|
||||
// Issue 2733: wait for e.magicConn to be started; there's two tiny
|
||||
// windows at startup where this callback can be run before Start
|
||||
<-e.magicConnStarted
|
||||
|
||||
up := cur.AnyInterfaceUp()
|
||||
if !up {
|
||||
e.logf("LinkChange: all links down; pausing: %v", cur)
|
||||
|
||||
Reference in New Issue
Block a user