Compare commits

..

43 Commits

Author SHA1 Message Date
Simeng He
11a1a9096d More logging :( 2021-05-28 16:58:15 -04:00
Simeng He
97967c0a85 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-28 12:52:47 -04:00
Simeng He
8875967f2b Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-28 10:11:29 -04:00
Simeng He
63df9e2a4e Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-27 16:37:35 -04:00
Simeng He
bb3db20e74 2021-05-27 15:04:56 -04:00
Simeng He
1bc78312c3 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-27 12:39:59 -04:00
Simeng He
ba4ee26313 Enough changes for a PR 2021-05-26 14:41:35 -04:00
Simeng He
4005427078 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-26 11:10:54 -04:00
Simeng He
4d00c7ef7e Obnoxious 2021-05-26 11:10:31 -04:00
Simeng He
5d1be01d44 Obnoxious changes 2021-05-26 11:08:40 -04:00
Simeng He
6725596a0a Morelogs 2021-05-25 13:00:32 -04:00
Simeng He
f925d09b83 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-25 12:03:49 -04:00
Simeng He
8cf74ee5e3 Added good integration tests for PingRequest addition 2021-05-21 12:04:04 -04:00
Simeng He
a374db1f8b Starting an integration test 2021-05-21 10:51:16 -04:00
Simeng He
866c2827d6 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-21 10:49:55 -04:00
Simeng He
93b5da680c Added Pingrequest addition, not hard coded added test stubs 2021-05-20 15:27:55 -04:00
Simeng He
e5c813963b Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-19 15:56:05 -04:00
Simeng He
da8b9adc51 Update todos 2021-05-19 15:55:29 -04:00
Simeng He
16b8233459 Logging and testing what happened in disco 2021-05-19 13:53:04 -04:00
Simeng He
88487ee067 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-19 13:52:23 -04:00
Simeng He
be14720df4 More tests added and logging 2021-05-19 10:59:31 -04:00
Simeng He
27773080c1 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-19 10:22:06 -04:00
Simeng He
21dd79ab91 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-18 16:32:12 -04:00
Simeng He
744300ee96 Added allowedIPs and Addresses to testcontrol nodes 2021-05-18 12:19:48 -04:00
Simeng He
7de0421f17 HTTP server added, fixing engine netmap problem 2021-05-18 09:45:04 -04:00
Simeng He
0438c0deda Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-18 09:40:17 -04:00
Simeng He
01be3630b9 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-17 11:17:35 -04:00
Simeng He
d52cf5e99b Added Pinger interface, plumbed down to Direct, Trying some integration tests 2021-05-17 11:17:31 -04:00
Simeng He
a14bf1fdc2 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-16 16:50:36 -04:00
Simeng He
65879efae4 Incredible 2021-05-14 12:31:02 -04:00
Simeng He
031a6fe1db Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-14 10:48:49 -04:00
Simeng He
940e1c7690 Proper url style 2021-05-13 15:00:47 -04:00
Simeng He
49e733773c Put placeholder code for the customping 2021-05-13 13:48:52 -04:00
Simeng He
c56829eff7 Direct tests 2021-05-13 13:05:28 -04:00
Simeng He
73bb80e42b Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-13 09:45:19 -04:00
Simeng He
c8fabf4f41 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-12 10:15:13 -04:00
Simeng He
2455f1035c More Templates temporarily commented out existing ping 2021-05-11 13:08:02 -04:00
Simeng He
c0f692b725 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-11 09:37:33 -04:00
Simeng He
54ac8e8022 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-10 17:33:52 -04:00
Simeng He
84d5c95f65 chunk encoding start 2021-05-10 17:33:36 -04:00
Simeng He
f894fad4f7 Small log additions for my own sake 2021-05-10 12:24:07 -04:00
Simeng He
b40a69e846 Merge branch 'main' of github.com:tailscale/tailscale into simeng-pingtest 2021-05-10 12:23:28 -04:00
Simeng He
e1b16b6b52 Added brad's structs, trying some logging 2021-05-10 09:57:58 -04:00
70 changed files with 1327 additions and 2155 deletions

View File

@@ -1,36 +0,0 @@
name: "integration-vms"
on:
# # NOTE(Xe): uncomment this region when testing the test
# pull_request:
# branches:
# - 'main'
release:
types: [ created ]
schedule:
# At minute 0 past hour 6 and 18
# https://crontab.guru/#00_6,18_*_*_*
- cron: '00 6,18 * * *'
jobs:
experimental-linux-vm-test:
# To set up a new runner, see tstest/integration/vms/runner.nix
runs-on: [ self-hosted, linux, vm_integration_test ]
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Checkout Code
uses: actions/checkout@v1
- name: Download VM Images
run: go test ./tstest/integration/vms -run-vm-tests -run=Download -timeout=60m
env:
XDG_CACHE_HOME: "/var/lib/ghrunner/cache"
- name: Run VM tests
run: go test ./tstest/integration/vms -v -run-vm-tests
env:
TMPDIR: "/tmp"
XDG_CACHE_HOME: "/var/lib/ghrunner/cache"

View File

@@ -11,36 +11,6 @@
set -eu
IFS=".$IFS" read -r major minor patch <VERSION.txt
git_hash=$(git rev-parse HEAD)
if ! git diff-index --quiet HEAD; then
git_hash="${git_hash}-dirty"
fi
base_hash=$(git rev-list --max-count=1 HEAD -- VERSION.txt)
change_count=$(git rev-list --count HEAD "^$base_hash")
short_hash=$(echo "$git_hash" | cut -c1-9)
eval $(./version/version.sh)
if expr "$minor" : "[0-9]*[13579]$" >/dev/null; then
patch="$change_count"
change_suffix=""
elif [ "$change_count" != "0" ]; then
change_suffix="-$change_count"
else
change_suffix=""
fi
long_suffix="$change_suffix-t$short_hash"
SHORT="$major.$minor.$patch"
LONG="${SHORT}$long_suffix"
GIT_HASH="$git_hash"
if [ "$1" = "shellvars" ]; then
cat <<EOF
VERSION_SHORT="$SHORT"
VERSION_LONG="$LONG"
VERSION_GIT_HASH="$GIT_HASH"
EOF
exit 0
fi
exec go build -ldflags "-X tailscale.com/version.Long=${LONG} -X tailscale.com/version.Short=${SHORT} -X tailscale.com/version.GitCommit=${GIT_HASH}" "$@"
exec go build -tags xversion -ldflags "-X tailscale.com/version.Long=${VERSION_LONG} -X tailscale.com/version.Short=${VERSION_SHORT} -X tailscale.com/version.GitCommit=${VERSION_GIT_HASH}" "$@"

View File

@@ -256,25 +256,3 @@ func Logout(ctx context.Context) error {
_, err := send(ctx, "POST", "/localapi/v0/logout", http.StatusNoContent, nil)
return err
}
// SetDNS adds a DNS TXT record for the given domain name, containing
// the provided TXT value. The intended use case is answering
// LetsEncrypt/ACME dns-01 challenges.
//
// The control plane will only permit SetDNS requests with very
// specific names and values. The name should be
// "_acme-challenge." + your node's MagicDNS name. It's expected that
// clients cache the certs from LetsEncrypt (or whichever CA is
// providing them) and only request new ones as needed; the control plane
// rate limits SetDNS requests.
//
// This is a low-level interface; it's expected that most Tailscale
// users use a higher level interface to getting/using TLS
// certificates.
func SetDNS(ctx context.Context, name, value string) error {
v := url.Values{}
v.Set("name", name)
v.Set("value", value)
_, err := send(ctx, "POST", "/localapi/v0/set-dns?"+v.Encode(), 200, nil)
return err
}

View File

@@ -21,9 +21,6 @@ import (
// into a map of filePathOnDisk -> filePathInPackage.
func parseFiles(s string) (map[string]string, error) {
ret := map[string]string{}
if len(s) == 0 {
return ret, nil
}
for _, f := range strings.Split(s, ",") {
fs := strings.Split(f, ":")
if len(fs) != 2 {

View File

@@ -64,6 +64,7 @@ var pingArgs struct {
}
func runPing(ctx context.Context, args []string) error {
fmt.Println("runPing")
c, bc, ctx, cancel := connect(ctx)
defer cancel()

View File

@@ -5,7 +5,6 @@
package cli
import (
"bufio"
"bytes"
"context"
_ "embed"
@@ -16,13 +15,11 @@ import (
"log"
"net/http"
"net/http/cgi"
"os"
"os/exec"
"runtime"
"strings"
"github.com/peterbourgon/ff/v2/ffcli"
"go4.org/mem"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
@@ -85,63 +82,17 @@ func runWeb(ctx context.Context, args []string) error {
return http.ListenAndServe(webArgs.listen, http.HandlerFunc(webHandler))
}
// authorize checks whether the provided user has access to the web UI.
func authorize(name string) error {
func auth() (string, error) {
if distro.Get() == distro.Synology {
return authorizeSynology(name)
}
return nil
}
// authorizeSynology checks whether the provided user has access to the web UI
// by consulting the membership of the "administrators" group.
func authorizeSynology(name string) error {
f, err := os.Open("/etc/group")
if err != nil {
return err
}
defer f.Close()
s := bufio.NewScanner(f)
var agLine string
for s.Scan() {
if !mem.HasPrefix(mem.B(s.Bytes()), mem.S("administrators:")) {
continue
cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("auth: %v: %s", err, out)
}
agLine = s.Text()
break
return string(out), nil
}
if err := s.Err(); err != nil {
return err
}
if agLine == "" {
return fmt.Errorf("admin group not defined")
}
agEntry := strings.Split(agLine, ":")
if len(agEntry) < 4 {
return fmt.Errorf("malformed admin group entry")
}
agMembers := agEntry[3]
for _, m := range strings.Split(agMembers, ",") {
if m == name {
return nil
}
}
return fmt.Errorf("not a member of administrators group")
}
// authenticate returns the name of the user accessing the web UI.
// Note: This is different from a tailscale user, and is typically the local
// user on the node.
func authenticate() (string, error) {
if distro.Get() != distro.Synology {
return "", nil
}
cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("auth: %v: %s", err, out)
}
return strings.TrimSpace(string(out)), nil
return "", nil
}
func synoTokenRedirect(w http.ResponseWriter, r *http.Request) bool {
@@ -247,13 +198,8 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
return
}
user, err := authenticate()
user, err := auth()
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
if err := authorize(user); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}

View File

@@ -55,7 +55,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
W tailscale.com/util/endian from tailscale.com/net/netns
L tailscale.com/util/lineread from tailscale.com/net/interfaces
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli
tailscale.com/wgengine/filter from tailscale.com/types/netmap
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305

View File

@@ -17,10 +17,9 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/klauspost/compress/huff0 from github.com/klauspost/compress/zstd
github.com/klauspost/compress/zstd from tailscale.com/smallzstd
github.com/klauspost/compress/zstd/internal/xxhash from github.com/klauspost/compress/zstd
L 💣 github.com/mdlayher/netlink from tailscale.com/wgengine/monitor+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/mdlayher/netlink+
L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+
L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
W github.com/pkg/errors from github.com/tailscale/certstore
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
@@ -180,7 +179,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
golang.org/x/sync/errgroup from tailscale.com/derp
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from github.com/mdlayher/netlink+
LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+
W golang.org/x/sys/windows from github.com/go-ole/go-ole+
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+
W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled+

View File

@@ -7,6 +7,7 @@ package controlclient
import (
"context"
"fmt"
"log"
"sync"
"time"
@@ -157,6 +158,7 @@ func (c *Auto) Start() {
//
// It should be called whenever there's something new to tell the server.
func (c *Auto) sendNewMapRequest() {
log.Println("sendNewMapRequest breakpoint")
c.mu.Lock()
// If we're not already streaming a netmap, or if we're already stuck
@@ -576,12 +578,9 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM
c.logf("[v1] sendStatus: %s: %v", who, state)
var p *persist.Persist
var loginFin, logoutFin *empty.Message
var fin *empty.Message
if state == StateAuthenticated {
loginFin = new(empty.Message)
}
if state == StateNotAuthenticated {
logoutFin = new(empty.Message)
fin = new(empty.Message)
}
if nm != nil && loggedIn && synced {
pp := c.direct.GetPersist()
@@ -592,13 +591,12 @@ func (c *Auto) sendStatus(who string, err error, url string, nm *netmap.NetworkM
nm = nil
}
new := Status{
LoginFinished: loginFin,
LogoutFinished: logoutFin,
URL: url,
Persist: p,
NetMap: nm,
Hostinfo: hi,
State: state,
LoginFinished: fin,
URL: url,
Persist: p,
NetMap: nm,
Hostinfo: hi,
State: state,
}
if err != nil {
new.Err = err.Error()
@@ -716,9 +714,3 @@ func (c *Auto) TestOnlySetAuthKey(authkey string) {
func (c *Auto) TestOnlyTimeNow() time.Time {
return c.timeNow()
}
// SetDNS sends the SetDNSRequest request to the control plane server,
// requesting a DNS record be created or updated.
func (c *Auto) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) error {
return c.direct.SetDNS(ctx, req)
}

View File

@@ -74,7 +74,4 @@ type Client interface {
// in a separate http request. It has nothing to do with the rest of
// the state machine.
UpdateEndpoints(localPort uint16, endpoints []tailcfg.Endpoint)
// SetDNS sends the SetDNSRequest request to the control plane server,
// requesting a DNS record be created or updated.
SetDNS(context.Context, *tailcfg.SetDNSRequest) error
}

View File

@@ -22,7 +22,7 @@ func fieldsOf(t reflect.Type) (fields []string) {
func TestStatusEqual(t *testing.T) {
// Verify that the Equal method stays in sync with reality
equalHandles := []string{"LoginFinished", "LogoutFinished", "Err", "URL", "NetMap", "State", "Persist", "Hostinfo"}
equalHandles := []string{"LoginFinished", "Err", "URL", "NetMap", "State", "Persist", "Hostinfo"}
if have := fieldsOf(reflect.TypeOf(Status{})); !reflect.DeepEqual(have, equalHandles) {
t.Errorf("Status.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
have, equalHandles)

View File

@@ -81,6 +81,11 @@ type Direct struct {
everEndpoints bool // whether we've ever had non-empty endpoints
localPort uint16 // or zero to mean auto
}
type Pinger interface {
// Ping is a request to start a discovery ping with the peer handling
// the given IP and then call cb with its ping latency & method.
Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult))
}
type Options struct {
Persist persist.Persist // initial persistent data
@@ -96,6 +101,7 @@ type Options struct {
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
DebugFlags []string // debug settings to send to control
LinkMonitor *monitor.Mon // optional link monitor
Pinger Pinger
// KeepSharerAndUserSplit controls whether the client
// understands Node.Sharer. If false, the Sharer is mapped to the User.
@@ -105,18 +111,6 @@ type Options struct {
// forwarding works and should not be double-checked by the
// controlclient package.
SkipIPForwardingCheck bool
// Pinger optionally specifies the Pinger to use to satisfy
// MapResponse.PingRequest queries from the control plane.
// If nil, PingRequest queries are not answered.
Pinger Pinger
}
// Pinger is a subset of the wgengine.Engine interface, containing just the Ping method.
type Pinger interface {
// Ping is a request to start a discovery or TSMP ping with the peer handling
// the given IP and then call cb with its ping latency & method.
Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult))
}
type Decompressor interface {
@@ -568,6 +562,7 @@ func inTest() bool { return flag.Lookup("test.v") != nil }
// maxPolls is how many network maps to download; common values are 1
// or -1 (to keep a long-poll query open to the server).
func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*netmap.NetworkMap)) error {
log.Println("POLLNETMAP BREAKPOINT")
return c.sendMapRequest(ctx, maxPolls, cb)
}
@@ -575,6 +570,7 @@ func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*netmap.N
// but does not fetch anything. It returns an error if the server did not return a
// successful 200 OK response.
func (c *Direct) SendLiteMapUpdate(ctx context.Context) error {
log.Println("SendLiteMapUpdate BREAKPOINT")
return c.sendMapRequest(ctx, 1, nil)
}
@@ -586,6 +582,7 @@ const pollTimeout = 120 * time.Second
// cb nil means to omit peers.
func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netmap.NetworkMap)) error {
c.mu.Lock()
log.Println("sendMapRequest ENDPOINT")
persist := c.persist
serverURL := c.serverURL
serverKey := c.serverKey
@@ -776,6 +773,11 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
}
if pr := resp.PingRequest; pr != nil {
// return err
log.Println("Ping Triggered")
for i := 0; i < 10; i++ {
c.CustomPing(&resp)
}
go answerPing(c.logf, c.httpc, pr)
}
@@ -874,6 +876,8 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
c.expiry = &nm.Expiry
c.mu.Unlock()
// log.Println("MAPRESPONSE: ", resp.Node)
// c.logf("MAPRESPONSE: %v", resp.Node)
cb(nm)
}
if ctx.Err() != nil {
@@ -1227,49 +1231,34 @@ func sleepAsRequested(ctx context.Context, logf logger.Logf, timeoutReset chan<-
}
}
// SetDNS sends the SetDNSRequest request to the control plane server,
// requesting a DNS record be created or updated.
func (c *Direct) SetDNS(ctx context.Context, req *tailcfg.SetDNSRequest) error {
c.mu.Lock()
serverKey := c.serverKey
c.mu.Unlock()
if serverKey.IsZero() {
return errors.New("zero serverKey")
// Run the ping suite from this client to another one
// Send the ping results via http to the adminhttp handlers.
// This is where we hopefully will run the ping suite similar to CLI
func (c *Direct) CustomPing(mr *tailcfg.MapResponse) bool {
log.Printf("Custom Ping Triggered with %d number of peers\n", len(mr.Peers))
log.Println("Ping Request: ", mr.PingRequest)
log.Println("ALOHA")
log.Println("CP PEERLIST : ", mr.Peers, mr.PeersChanged, mr.PeersRemoved, mr.PeerSeenChange)
if len(mr.Peers) > 0 {
log.Println("Peer data: ", mr.Peers[0].ID)
}
machinePrivKey, err := c.getMachinePrivKey()
if err != nil {
return fmt.Errorf("getMachinePrivKey: %w", err)
}
if machinePrivKey.IsZero() {
return errors.New("getMachinePrivKey returned zero key")
ip := mr.PingRequest.TestIP
log.Println("TestIP : ", ip)
start := time.Now()
// Run the ping
var pingRes *ipnstate.PingResult
for i := 1; i <= 10; i++ {
log.Println("Ping attempt ", i)
go c.pinger.Ping(ip, true, func(res *ipnstate.PingResult) {
log.Println("Callback", res, (res.NodeIP))
pingRes = res
})
}
bodyData, err := encode(req, &serverKey, &machinePrivKey)
if err != nil {
return err
}
body := bytes.NewReader(bodyData)
log.Println("PINGRES", pingRes)
duration := time.Since(start)
// Send the data to the handler in api.go admin/api/ping
log.Printf("Ping operation took %f seconds\n", duration.Seconds())
u := fmt.Sprintf("%s/machine/%s/set-dns", c.serverURL, machinePrivKey.Public().HexString())
hreq, err := http.NewRequestWithContext(ctx, "POST", u, body)
if err != nil {
return err
}
res, err := c.httpc.Do(hreq)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
msg, _ := ioutil.ReadAll(res.Body)
return fmt.Errorf("sign-dns response: %v, %.200s", res.Status, strings.TrimSpace(string(msg)))
}
var setDNSRes struct{} // no fields yet
if err := decode(res, &setDNSRes, &serverKey, &machinePrivKey); err != nil {
c.logf("error decoding SetDNSResponse with server key %s and machine key %s: %v", serverKey, machinePrivKey.Public(), err)
return fmt.Errorf("set-dns-response: %v", err)
}
return nil
return len(mr.Peers) > 0
}

View File

@@ -103,3 +103,37 @@ func TestNewHostinfo(t *testing.T) {
}
t.Logf("Got: %s", j)
}
// Currently not working properly
func TestPingFromMapResponse(t *testing.T) {
hi := NewHostinfo()
ni := tailcfg.NetInfo{LinkType: "wired"}
hi.NetInfo = &ni
key, err := wgkey.NewPrivate()
if err != nil {
t.Error(err)
}
opts := Options{
ServerURL: "https://example.com",
Hostinfo: hi,
GetMachinePrivateKey: func() (wgkey.Private, error) {
return key, nil
},
}
c, err := NewDirect(opts)
if c == nil || err != nil {
t.Errorf("Direct not created %w", err)
}
peers := []*tailcfg.Node{
{ID: 1},
{ID: 2},
{ID: 3},
}
pingRequest := tailcfg.PingRequest{URL: "localhost:3040", Log: true, PayloadSize: 10}
mr := &tailcfg.MapResponse{Peers: peers, Domain: "DumbTest", PingRequest: &pingRequest}
if !c.CustomPing(mr) {
t.Errorf("Custom ping failed!\n")
}
t.Log("Successful ping")
}

View File

@@ -64,12 +64,11 @@ func (s State) String() string {
}
type Status struct {
_ structs.Incomparable
LoginFinished *empty.Message // nonempty when login finishes
LogoutFinished *empty.Message // nonempty when logout finishes
Err string
URL string // interactive URL to visit to finish logging in
NetMap *netmap.NetworkMap // server-pushed configuration
_ structs.Incomparable
LoginFinished *empty.Message // nonempty when login finishes
Err string
URL string // interactive URL to visit to finish logging in
NetMap *netmap.NetworkMap // server-pushed configuration
// The internal state should not be exposed outside this
// package, but we have some automated tests elsewhere that need to
@@ -87,7 +86,6 @@ func (s *Status) Equal(s2 *Status) bool {
}
return s != nil && s2 != nil &&
(s.LoginFinished == nil) == (s2.LoginFinished == nil) &&
(s.LogoutFinished == nil) == (s2.LogoutFinished == nil) &&
s.Err == s2.Err &&
s.URL == s2.URL &&
reflect.DeepEqual(s.Persist, s2.Persist) &&

15
go.mod
View File

@@ -5,28 +5,25 @@ go 1.16
require (
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
github.com/coreos/go-iptables v0.6.0
github.com/frankban/quicktest v1.13.0
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3
github.com/gliderlabs/ssh v0.3.2
github.com/go-multierror/multierror v1.0.2
github.com/go-ole/go-ole v1.2.5
github.com/godbus/dbus/v5 v5.0.4
github.com/google/go-cmp v0.5.6
github.com/google/go-cmp v0.5.5
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f
github.com/google/uuid v1.1.2
github.com/goreleaser/nfpm v1.10.3
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190
github.com/jsimonetti/rtnetlink v0.0.0-20210409061457-9561dc9288a7
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/klauspost/compress v1.12.2
github.com/kr/pty v1.1.8
github.com/mdlayher/netlink v1.4.1
github.com/mdlayher/netlink v1.4.0
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697
github.com/miekg/dns v1.1.42
github.com/pborman/getopt v1.1.0
github.com/peterbourgon/ff/v2 v2.0.0
github.com/pkg/sftp v1.13.0
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
@@ -34,14 +31,14 @@ require (
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/net v0.0.0-20210525063256-abc453219eb5
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
golang.org/x/tools v0.1.2
golang.zx2c4.com/wireguard v0.0.0-20210525143454-64cb82f2b3f5
golang.zx2c4.com/wireguard/windows v0.3.15-0.20210525143335-94c0476d63e3
honnef.co/go/tools v0.1.4
inet.af/netaddr v0.0.0-20210602152128-50f8686885e3
inet.af/netaddr v0.0.0-20210523191804-d57edf19c517
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22
inet.af/peercred v0.0.0-20210318190834-4259e17bb763
inet.af/wf v0.0.0-20210516214145-a5343001b756

51
go.sum
View File

@@ -55,8 +55,6 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go v1.38.52 h1:7NKcUyTG/CyDX835kq04DDNe8vXaJhbGW8ThemHb18A=
github.com/aws/aws-sdk-go v1.38.52/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
@@ -233,9 +231,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/goexpect v0.0.0-20210430020637-ab937bf7fd6f h1:7MmqygqdeJtziBUpm4Z9ThROFZUaVGaePMfcDnluf1E=
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f/go.mod h1:n1ej5+FqyEytMt/mugVDZLIiqTMO+vsrgY+kM6ohzN0=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
@@ -311,10 +308,6 @@ github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:x
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48=
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@@ -327,8 +320,8 @@ github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR7
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190 h1:iycCSDo8EKVueI9sfVBBJmtNn9DnXV/K1YWwEJO+uOs=
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
github.com/jsimonetti/rtnetlink v0.0.0-20210409061457-9561dc9288a7 h1:0pS4NUf9WPvydLWHx2VHafjEyfN8vQrAxl/n3Kt2K9c=
github.com/jsimonetti/rtnetlink v0.0.0-20210409061457-9561dc9288a7/go.mod h1:+fPVEwpdpYDhPa086y6yIAwUno3cBJZw15Fds43LDRA=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@@ -348,7 +341,6 @@ github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+O
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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=
@@ -366,7 +358,11 @@ github.com/kyoh86/exportloopref v0.1.8 h1:5Ry/at+eFdkX9Vsdw3qU4YkvGtzuVfzT4X7S77
github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lxn/walk v0.0.0-20201110160827-18ea5e372cdb/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 h1:NVRJ0Uy0SOFcXSKLsS65OmI1sgCCfiDUPj+cwnH7GZw=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20201111105847-2a20daff6a55/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
@@ -402,13 +398,10 @@ github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klX
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
github.com/mdlayher/netlink v1.4.1 h1:I154BCU+mKlIf7BgcAJB2r7QjveNPty6uNY1g9ChVfI=
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697 h1:PBb7ld5cQGfxHF2pKvb/ydtuPwdRaltGI4e0QSCuiNI=
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697/go.mod h1:HtjVsQfsrBm1GDcDTUFn4ZXhftxTwO/hxrvEiRc61U4=
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00 h1:qEtkL8n1DAHpi5/AOgAckwGQUlMe4+jhL/GMt+GKIks=
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.42 h1:gWGe42RGaIqXQZ+r3WUGEKBEtvPHY2SXo4dqixDNxuY=
github.com/miekg/dns v1.1.42/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
@@ -477,8 +470,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/sftp v1.13.0 h1:Riw6pgOKK41foc1I1Uu03CjvbLZDXeGpInycM4shXoI=
github.com/pkg/sftp v1.13.0/go.mod h1:41g+FIPlQUTDCveupEmEA65IoiQFrtgCeDopC4ajGIM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v0.0.0-20201006195004-351e25ade6e3/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
@@ -579,6 +570,8 @@ github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3 h1:fEubocuQkrl
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3/go.mod h1:2P+hpOwd53e7JMX/L4f3VXkv1G+33ES6IWZSrkIeWNs=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBWY6aGilF+IRlQIdmhzLrsEmF6JgN+Ryw=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/tailscale/wireguard-go v0.0.0-20210522003738-46b531feb08a h1:ujoIjR8p8HEVy26RnOe6U5aJwaMYFrIa4cpGGeZF5oc=
github.com/tailscale/wireguard-go v0.0.0-20210522003738-46b531feb08a/go.mod h1:ys4yUmhKncXy1jWP34qUHKipRjl322VVhxoh1Rkfo7c=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8=
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
@@ -647,8 +640,8 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
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-20201112155050-0c6587e931a9/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=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
@@ -716,6 +709,8 @@ golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f h1:Si4U+UcgJzya9kpiEUJKQvjr512OLli+gL4poHrz93U=
golang.org/x/net v0.0.0-20210521195947-fe42d452be8f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5 h1:wjuX4b5yYQnEQHzd+CBcrcC6OVR2J1CN6mUy0oSxIPo=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -770,7 +765,9 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -789,9 +786,10 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210521203332-0cec03c779c1 h1:lCnv+lfrU9FRPGf8NeRuWAAPjNnema5WtBinMgs1fD8=
golang.org/x/sys v0.0.0-20210521203332-0cec03c779c1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea h1:+WiDlPBBaO+h9vPNZi8uJ3k4BkKQB7Iow3aqwHVA5hI=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 h1:C+AwYEtBp/VQwoLntUmQ/yx3MS9vmZaKNdw5eOpoQe8=
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
@@ -801,6 +799,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
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 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
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=
@@ -863,6 +862,8 @@ golang.org/x/tools v0.0.0-20201121010211-780cb80bd7fb/go.mod h1:emZCQorbCU4vsT4f
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=
golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -873,6 +874,10 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
golang.zx2c4.com/wireguard v0.0.0-20210521230051-c27ff9b9f6f7/go.mod h1:a057zjmoc00UN7gVkaJt2sXVK523kMJcogDTEvPIasg=
golang.zx2c4.com/wireguard v0.0.0-20210525143454-64cb82f2b3f5 h1:5D3v3AKu7ktIhDlqZhZ4+YeNKsW+dnc2+zfFAdhwa8M=
golang.zx2c4.com/wireguard v0.0.0-20210525143454-64cb82f2b3f5/go.mod h1:laHzsbfMhGSobUmruXWAyMKKHSqvIcrqZJMyHD+/3O8=
golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9 h1:qowcZ56hhpeoESmWzI4Exhx4Y78TpCyXUJur4/c0CoE=
golang.zx2c4.com/wireguard v0.0.20200321-0.20201111175144-60b3766b89b9/go.mod h1:LMeNfjlcPZTrBC1juwgbQyA4Zy2XVcsrdO/fIJxwyuA=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8 h1:nlXPqGA98n+qcq1pwZ28KjM5EsFQvamKS00A+VUeVjs=
golang.zx2c4.com/wireguard/windows v0.1.2-0.20201113162609-9b85be97fdf8/go.mod h1:psva4yDnAHLuh7lUzOK7J7bLYxNFfo0iKWz+mi9gzkA=
golang.zx2c4.com/wireguard/windows v0.3.15-0.20210525143335-94c0476d63e3 h1:Xw0ZuZcvq981iPGZoLrUXhrK2jOJAw/B6gZxc6g8FsU=
golang.zx2c4.com/wireguard/windows v0.3.15-0.20210525143335-94c0476d63e3/go.mod h1:f/UVhQ6vXZKDodGB3Glgwu9B3djRxR14jIbcuxD8NBw=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -913,6 +918,8 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc=
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -950,8 +957,8 @@ honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzE
honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ=
honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
inet.af/netaddr v0.0.0-20210515010201-ad03edc7c841/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20210602152128-50f8686885e3 h1:RlarOdsmOUCCvy7Xm1JchJIGuQsuKwD/Lo1bjYmfuQI=
inet.af/netaddr v0.0.0-20210602152128-50f8686885e3/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20210523191804-d57edf19c517 h1:gieHAlViNfjNt0m6gKr4aazCMXQobPMOqeyQ1ZN5ekw=
inet.af/netaddr v0.0.0-20210523191804-d57edf19c517/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22 h1:DNtszwGa6w76qlIr+PbPEnlBJdiRV8SaxeigOy0q1gg=
inet.af/netstack v0.0.0-20210317161235-a1bf4e56ef22/go.mod h1:GVx+5OZtbG4TVOW5ilmyRZAZXr1cNwfqUEkTOtWK0PM=
inet.af/peercred v0.0.0-20210318190834-4259e17bb763 h1:gPSJmmVzmdy4kHhlCMx912GdiUz3k/RzJGg0ADqy1dg=

View File

@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
@@ -294,6 +295,7 @@ func (b *LocalBackend) Prefs() *ipn.Prefs {
// Status returns the latest status of the backend and its
// sub-components.
func (b *LocalBackend) Status() *ipnstate.Status {
log.Println("Status ENDPOINT")
sb := new(ipnstate.StatusBuilder)
b.UpdateStatus(sb)
return sb.Status()
@@ -453,13 +455,6 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) {
// Lock b once and do only the things that require locking.
b.mu.Lock()
if st.LogoutFinished != nil {
// Since we're logged out now, our netmap cache is invalid.
// Since st.NetMap==nil means "netmap is unchanged", there is
// no other way to represent this change.
b.setNetMapLocked(nil)
}
prefs := b.prefs
stateKey := b.stateKey
netMap := b.netMap
@@ -657,12 +652,6 @@ func (b *LocalBackend) getNewControlClientFunc() clientGen {
// startIsNoopLocked reports whether a Start call on this LocalBackend
// with the provided Start Options would be a useless no-op.
//
// TODO(apenwarr): we shouldn't need this.
// The state machine is now nearly clean enough where it can accept a new
// connection while in any state, not just Running, and on any platform.
// We'd want to add a few more tests to state_test.go to ensure this continues
// to work as expected.
//
// b.mu must be held.
func (b *LocalBackend) startIsNoopLocked(opts ipn.Options) bool {
// Options has 5 fields; check all of them:
@@ -716,7 +705,6 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
b.send(ipn.Notify{
State: &state,
NetMap: nm,
Prefs: b.prefs,
LoginFinished: new(empty.Message),
})
return nil
@@ -837,6 +825,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
DiscoPublicKey: discoPublic,
DebugFlags: debugFlags,
LinkMonitor: b.e.GetLinkMonitor(),
Pinger: b.e,
// Don't warn about broken Linux IP forwading when
// netstack is being used.
@@ -929,8 +918,8 @@ func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs)
}
}
}
localNets, _ := localNetsB.IPSet()
logNets, _ := logNetsB.IPSet()
localNets := localNetsB.IPSet()
logNets := logNetsB.IPSet()
changed := deephash.UpdateHash(&b.filterHash, haveNetmap, addrs, packetFilter, localNets.Ranges(), logNets.Ranges(), shieldsUp)
if !changed {
@@ -987,8 +976,7 @@ func interfaceRoutes() (ips *netaddr.IPSet, hostIPs []netaddr.IP, err error) {
return nil, nil, err
}
ipSet, _ := b.IPSet()
return ipSet, hostIPs, nil
return b.IPSet(), hostIPs, nil
}
// shrinkDefaultRoute returns an IPSet representing the IPs in route,
@@ -1019,7 +1007,7 @@ func shrinkDefaultRoute(route netaddr.IPPrefix) (*netaddr.IPSet, error) {
for _, pfx := range removeFromDefaultRoute {
b.RemovePrefix(pfx)
}
return b.IPSet()
return b.IPSet(), nil
}
// dnsCIDRsEqual determines whether two CIDR lists are equal
@@ -1888,15 +1876,6 @@ func (b *LocalBackend) initPeerAPIListener() {
b.mu.Lock()
defer b.mu.Unlock()
if b.netMap == nil {
// We're called from authReconfig which checks that
// netMap is non-nil, but if a concurrent Logout,
// ResetForClientDisconnect, or Start happens when its
// mutex was released, the netMap could be
// nil'ed out (Issue 1996). Bail out early here if so.
return
}
if len(b.netMap.Addresses) == len(b.peerAPIListeners) {
allSame := true
for i, pln := range b.peerAPIListeners {
@@ -2350,6 +2329,7 @@ func (b *LocalBackend) LogoutSync(ctx context.Context) error {
func (b *LocalBackend) logout(ctx context.Context, sync bool) error {
b.mu.Lock()
cc := b.cc
b.setNetMapLocked(nil)
b.mu.Unlock()
b.EditPrefs(&ipn.MaskedPrefs{
@@ -2376,6 +2356,10 @@ func (b *LocalBackend) logout(ctx context.Context, sync bool) error {
cc.StartLogout()
}
b.mu.Lock()
b.setNetMapLocked(nil)
b.mu.Unlock()
b.stateMachine()
return err
}
@@ -2577,42 +2561,6 @@ func (b *LocalBackend) FileTargets() ([]*apitype.FileTarget, error) {
return ret, nil
}
// SetDNS adds a DNS record for the given domain name & TXT record
// value.
//
// It's meant for use with dns-01 ACME (LetsEncrypt) challenges.
//
// This is the low-level interface. Other layers will provide more
// friendly options to get HTTPS certs.
func (b *LocalBackend) SetDNS(ctx context.Context, name, value string) error {
req := &tailcfg.SetDNSRequest{
Version: 1,
Type: "TXT",
Name: name,
Value: value,
}
b.mu.Lock()
cc := b.cc
if prefs := b.prefs; prefs != nil {
req.NodeKey = tailcfg.NodeKey(prefs.Persist.PrivateNodeKey.Public())
}
b.mu.Unlock()
if cc == nil {
return errors.New("not connected")
}
if req.NodeKey.IsZero() {
return errors.New("no nodekey")
}
if name == "" {
return errors.New("missing 'name'")
}
if value == "" {
return errors.New("missing 'value'")
}
return cc.SetDNS(ctx, req)
}
func (b *LocalBackend) registerIncomingFile(inf *incomingFile, active bool) {
b.mu.Lock()
defer b.mu.Unlock()

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin,ts_macext ios,ts_macext
// +build darwin,redo ios,redo
package ipnlocal

View File

@@ -140,8 +140,6 @@ func (cc *mockControl) send(err error, url string, loginFinished bool, nm *netma
}
if loginFinished {
s.LoginFinished = &empty.Message{}
} else if url == "" && err == nil && nm == nil {
s.LogoutFinished = &empty.Message{}
}
cc.statusFunc(s)
}
@@ -248,10 +246,6 @@ func (cc *mockControl) UpdateEndpoints(localPort uint16, endpoints []tailcfg.End
cc.called("UpdateEndpoints")
}
func (*mockControl) SetDNS(context.Context, *tailcfg.SetDNSRequest) error {
panic("unexpected SetDNS call")
}
// A very precise test of the sequence of function calls generated by
// ipnlocal.Local into its controlclient instance, and the events it
// produces upstream into the UI.
@@ -554,7 +548,10 @@ func TestStateMachine(t *testing.T) {
c.Assert(nn[0].State, qt.Not(qt.IsNil))
c.Assert(nn[0].LoginFinished, qt.Not(qt.IsNil))
c.Assert(nn[0].NetMap, qt.Not(qt.IsNil))
c.Assert(nn[0].Prefs, qt.Not(qt.IsNil))
// BUG: Prefs should be sent too, or the UI could end up in
// a bad state. (iOS, the only current user of this feature,
// probably wouldn't notice because it happens to not display
// any prefs. Maybe exit nodes will look weird?)
}
// undo the state hack above.
@@ -566,25 +563,24 @@ func TestStateMachine(t *testing.T) {
b.Logout()
{
nn := notifies.drain(2)
c.Assert([]string{"pause", "StartLogout"}, qt.DeepEquals, cc.getCalls())
// BUG: now is not the time to unpause.
c.Assert([]string{"unpause", "StartLogout"}, qt.DeepEquals, cc.getCalls())
c.Assert(nn[0].State, qt.Not(qt.IsNil))
c.Assert(nn[1].Prefs, qt.Not(qt.IsNil))
c.Assert(ipn.Stopped, qt.Equals, *nn[0].State)
c.Assert(ipn.NeedsLogin, qt.Equals, *nn[0].State)
c.Assert(nn[1].Prefs.LoggedOut, qt.IsTrue)
c.Assert(nn[1].Prefs.WantRunning, qt.IsFalse)
c.Assert(ipn.Stopped, qt.Equals, b.State())
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
}
// Let's make the logout succeed.
t.Logf("\n\nLogout (async) - succeed")
notifies.expect(1)
notifies.expect(0)
cc.setAuthBlocked(true)
cc.send(nil, "", false, nil)
{
nn := notifies.drain(1)
c.Assert([]string{"unpause"}, qt.DeepEquals, cc.getCalls())
c.Assert(nn[0].State, qt.Not(qt.IsNil))
c.Assert(ipn.NeedsLogin, qt.Equals, *nn[0].State)
notifies.drain(0)
c.Assert(cc.getCalls(), qt.HasLen, 0)
c.Assert(b.Prefs().LoggedOut, qt.IsTrue)
c.Assert(b.Prefs().WantRunning, qt.IsFalse)
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())

View File

@@ -100,8 +100,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.serveBugReport(w, r)
case "/localapi/v0/file-targets":
h.serveFileTargets(w, r)
case "/localapi/v0/set-dns":
h.serveSetDNS(w, r)
case "/":
io.WriteString(w, "tailscaled\n")
default:
@@ -384,25 +382,6 @@ func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) {
rp.ServeHTTP(w, outReq)
}
func (h *Handler) serveSetDNS(w http.ResponseWriter, r *http.Request) {
if !h.PermitWrite {
http.Error(w, "access denied", http.StatusForbidden)
return
}
if r.Method != "POST" {
http.Error(w, "want POST", 400)
return
}
ctx := r.Context()
err := h.b.SetDNS(ctx, r.FormValue("name"), r.FormValue("value"))
if err != nil {
writeErrorJSON(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(struct{}{})
}
var dialPeerTransportOnce struct {
sync.Once
v *http.Transport

View File

@@ -14,7 +14,6 @@ import (
"math/rand"
"net"
"sync"
"syscall"
"time"
dns "golang.org/x/net/dns/dnsmessage"
@@ -194,14 +193,8 @@ func (f *forwarder) recv(conn *fwdConn) {
return
default:
}
// The 1 extra byte is to detect packet truncation.
out := make([]byte, maxResponseBytes+1)
out := make([]byte, maxResponseBytes)
n := conn.read(out)
var truncated bool
if n > maxResponseBytes {
n = maxResponseBytes
truncated = true
}
if n == 0 {
continue
}
@@ -212,19 +205,6 @@ func (f *forwarder) recv(conn *fwdConn) {
out = out[:n]
txid := getTxID(out)
if truncated {
const dnsFlagTruncated = 0x200
flags := binary.BigEndian.Uint16(out[2:4])
flags |= dnsFlagTruncated
binary.BigEndian.PutUint16(out[2:4], flags)
// TODO(#2067): Remove any incomplete records? RFC 1035 section 6.2
// states that truncation should head drop so that the authority
// section can be preserved if possible. However, the UDP read with
// a too-small buffer has already dropped the end, so that's the
// best we can do.
}
f.mu.Lock()
record, found := f.txMap[txid]
@@ -306,8 +286,6 @@ func (f *forwarder) forward(query packet) error {
}
f.mu.Unlock()
// TODO(#2066): EDNS size clamping
for _, resolver := range resolvers {
f.send(query.bs, resolver)
}
@@ -393,12 +371,6 @@ func (c *fwdConn) send(packet []byte, dst netaddr.IPPort) {
backOff(err)
continue
}
if errors.Is(err, syscall.EHOSTUNREACH) {
// "No route to host." The network stack is fine, but
// can't talk to this destination. Not much we can do
// about that, don't spam logs.
return
}
if networkIsDown(err) {
// Fail.
c.logf("send: network is down")
@@ -450,7 +422,7 @@ func (c *fwdConn) read(out []byte) int {
c.mu.Unlock()
n, _, err := conn.ReadFrom(out)
if err == nil || packetWasTruncated(err) {
if err == nil {
// Success.
return n
}

View File

@@ -23,8 +23,3 @@ func networkIsDown(err error) bool {
func networkIsUnreachable(err error) bool {
return errors.Is(err, networkUnreachable)
}
// packetWasTruncated returns true if err indicates truncation but the RecvFrom
// that generated err was otherwise successful. It always returns false on this
// platform.
func packetWasTruncated(err error) bool { return false }

View File

@@ -8,8 +8,3 @@ package resolver
func networkIsDown(err error) bool { return false }
func networkIsUnreachable(err error) bool { return false }
// packetWasTruncated returns true if err indicates truncation but the RecvFrom
// that generated err was otherwise successful. It always returns false on this
// platform.
func packetWasTruncated(err error) bool { return false }

View File

@@ -5,7 +5,6 @@
package resolver
import (
"errors"
"net"
"os"
@@ -28,16 +27,3 @@ func networkIsUnreachable(err error) bool {
// difference between down and unreachable? Add comments.
return false
}
// packetWasTruncated returns true if err indicates truncation but the RecvFrom
// that generated err was otherwise successful. On Windows, Go's UDP RecvFrom
// calls WSARecvFrom which returns the WSAEMSGSIZE error code when the received
// datagram is larger than the provided buffer. When that happens, both a valid
// size and an error are returned (as per the partial fix for golang/go#14074).
// If the WSAEMSGSIZE error is returned, then we ignore the error to get
// semantics similar to the POSIX operating systems. One caveat is that it
// appears that the source address is not returned when WSAEMSGSIZE occurs, but
// we do not currently look at the source address.
func packetWasTruncated(err error) bool {
return errors.Is(err, windows.WSAEMSGSIZE)
}

View File

@@ -22,10 +22,8 @@ import (
"tailscale.com/wgengine/monitor"
)
// maxResponseBytes is the maximum size of a response from a Resolver. The
// actual buffer size will be one larger than this so that we can detect
// truncation in a platform-agnostic way.
const maxResponseBytes = 4095
// maxResponseBytes is the maximum size of a response from a Resolver.
const maxResponseBytes = 512
// queueSize is the maximal number of DNS requests that can await polling.
// If EnqueueRequest is called when this many requests are already pending,

View File

@@ -66,39 +66,6 @@ func resolveToIP(ipv4, ipv6 netaddr.IP, ns string) dns.HandlerFunc {
}
}
// resolveToTXT returns a handler function which responds to queries of type TXT
// it receives with the strings in txts.
func resolveToTXT(txts []string) dns.HandlerFunc {
return func(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetReply(req)
if len(req.Question) != 1 {
panic("not a single-question request")
}
question := req.Question[0]
if question.Qtype != dns.TypeTXT {
w.WriteMsg(m)
return
}
ans := &dns.TXT{
Hdr: dns.RR_Header{
Name: question.Name,
Rrtype: dns.TypeTXT,
Class: dns.ClassINET,
},
Txt: txts,
}
m.Answer = append(m.Answer, ans)
if err := w.WriteMsg(m); err != nil {
panic(err)
}
}
}
var resolveToNXDOMAIN = dns.HandlerFunc(func(w dns.ResponseWriter, req *dns.Msg) {
m := new(dns.Msg)
m.SetRcode(req, dns.RcodeNameError)

View File

@@ -6,9 +6,7 @@ package resolver
import (
"bytes"
"encoding/hex"
"errors"
"math/rand"
"net"
"testing"
@@ -46,11 +44,9 @@ func dnspacket(domain dnsname.FQDN, tp dns.Type) []byte {
}
type dnsResponse struct {
ip netaddr.IP
txt []string
name dnsname.FQDN
rcode dns.RCode
truncated bool
ip netaddr.IP
name dnsname.FQDN
rcode dns.RCode
}
func unpackResponse(payload []byte) (dnsResponse, error) {
@@ -71,16 +67,6 @@ func unpackResponse(payload []byte) (dnsResponse, error) {
return response, nil
}
response.truncated = h.Truncated
if response.truncated {
// TODO(#2067): Ideally, answer processing should still succeed when
// dealing with a truncated message, but currently when we truncate
// a packet, it's caused by the buffer being too small and usually that
// means the data runs out mid-record. dns.Parser does not like it when
// that happens. We can improve this by trimming off incomplete records.
return response, nil
}
err = parser.SkipAllQuestions()
if err != nil {
return response, err
@@ -104,12 +90,6 @@ func unpackResponse(payload []byte) (dnsResponse, error) {
return response, err
}
response.ip = netaddr.IPv6Raw(res.AAAA)
case dns.TypeTXT:
res, err := parser.TXTResource()
if err != nil {
return response, err
}
response.txt = res.TXT
case dns.TypeNS:
res, err := parser.NSResource()
if err != nil {
@@ -289,32 +269,6 @@ func ipv6Works() bool {
return true
}
func generateTXT(size int, source rand.Source) []string {
const sizePerTXT = 120
if size%2 != 0 {
panic("even lengths only")
}
rng := rand.New(source)
txts := make([]string, 0, size/sizePerTXT+1)
raw := make([]byte, sizePerTXT/2)
rem := size
for ; rem > sizePerTXT; rem -= sizePerTXT {
rng.Read(raw)
txts = append(txts, hex.EncodeToString(raw))
}
if rem > 0 {
rng.Read(raw[:rem/2])
txts = append(txts, hex.EncodeToString(raw[:rem/2]))
}
return txts
}
func TestDelegate(t *testing.T) {
tstest.ResourceCheck(t)
@@ -322,43 +276,13 @@ func TestDelegate(t *testing.T) {
t.Skip("skipping test that requires localhost IPv6")
}
randSource := rand.NewSource(4)
// smallTXT does not require EDNS
smallTXT := generateTXT(300, randSource)
// medTXT and largeTXT are responses that require EDNS but we would like to
// support these sizes of response without truncation because they are
// moderately common.
medTXT := generateTXT(1200, randSource)
largeTXT := generateTXT(4000, randSource)
// xlargeTXT is slightly above the maximum response size that we support,
// so there should be truncation.
xlargeTXT := generateTXT(5000, randSource)
// hugeTXT is significantly larger than any typical MTU and will require
// significant fragmentation. For buffer management reasons, we do not
// intend to handle responses this large, so there should be truncation.
hugeTXT := generateTXT(64000, randSource)
v4server := serveDNS(t, "127.0.0.1:0",
"test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."),
"nxdomain.site.", resolveToNXDOMAIN,
"small.txt.", resolveToTXT(smallTXT),
"med.txt.", resolveToTXT(medTXT),
"large.txt.", resolveToTXT(largeTXT),
"xlarge.txt.", resolveToTXT(xlargeTXT),
"huge.txt.", resolveToTXT(hugeTXT))
"nxdomain.site.", resolveToNXDOMAIN)
defer v4server.Shutdown()
v6server := serveDNS(t, "[::1]:0",
"test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."),
"nxdomain.site.", resolveToNXDOMAIN,
"small.txt.", resolveToTXT(smallTXT),
"med.txt.", resolveToTXT(medTXT),
"large.txt.", resolveToTXT(largeTXT),
"xlarge.txt.", resolveToTXT(xlargeTXT),
"huge.txt.", resolveToTXT(hugeTXT))
"nxdomain.site.", resolveToNXDOMAIN)
defer v6server.Shutdown()
r := New(t.Logf, nil)
@@ -398,31 +322,6 @@ func TestDelegate(t *testing.T) {
dnspacket("nxdomain.site.", dns.TypeA),
dnsResponse{rcode: dns.RCodeNameError},
},
{
"smalltxt",
dnspacket("small.txt.", dns.TypeTXT),
dnsResponse{txt: smallTXT, rcode: dns.RCodeSuccess},
},
{
"medtxt",
dnspacket("med.txt.", dns.TypeTXT),
dnsResponse{txt: medTXT, rcode: dns.RCodeSuccess},
},
{
"largetxt",
dnspacket("large.txt.", dns.TypeTXT),
dnsResponse{txt: largeTXT, rcode: dns.RCodeSuccess},
},
{
"xlargetxt",
dnspacket("xlarge.txt.", dns.TypeTXT),
dnsResponse{rcode: dns.RCodeSuccess, truncated: true},
},
{
"hugetxt",
dnspacket("huge.txt.", dns.TypeTXT),
dnsResponse{rcode: dns.RCodeSuccess, truncated: true},
},
}
for _, tt := range tests {
@@ -446,15 +345,6 @@ func TestDelegate(t *testing.T) {
if response.name != tt.response.name {
t.Errorf("name = %v; want %v", response.name, tt.response.name)
}
if len(response.txt) != len(tt.response.txt) {
t.Errorf("%v txt records, want %v txt records", len(response.txt), len(tt.response.txt))
} else {
for i := range response.txt {
if response.txt[i] != tt.response.txt[i] {
t.Errorf("txt record %v is %s, want %s", i, response.txt[i], tt.response.txt[i])
}
}
}
})
}
}

View File

@@ -2,11 +2,15 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux darwin,!ts_macext
// +build linux,!redo
package interfaces
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
@@ -19,3 +23,64 @@ func TestDefaultRouteInterface(t *testing.T) {
}
t.Logf("got %q", v)
}
// test the specific /proc/net/route path as found on Google Cloud Run instances
func TestGoogleCloudRunDefaultRouteInterface(t *testing.T) {
dir := t.TempDir()
savedProcNetRoutePath := procNetRoutePath
defer func() { procNetRoutePath = savedProcNetRoutePath }()
procNetRoutePath = filepath.Join(dir, "CloudRun")
buf := []byte("Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT\n" +
"eth0\t8008FEA9\t00000000\t0001\t0\t0\t0\t01FFFFFF\t0\t0\t0\n" +
"eth1\t00000000\t00000000\t0001\t0\t0\t0\t00000000\t0\t0\t0\n")
err := ioutil.WriteFile(procNetRoutePath, buf, 0644)
if err != nil {
t.Fatal(err)
}
got, err := DefaultRouteInterface()
if err != nil {
t.Fatal(err)
}
if got != "eth1" {
t.Fatalf("got %s, want eth1", got)
}
}
// we read chunks of /proc/net/route at a time, test that files longer than the chunk
// size can be handled.
func TestExtremelyLongProcNetRoute(t *testing.T) {
dir := t.TempDir()
savedProcNetRoutePath := procNetRoutePath
defer func() { procNetRoutePath = savedProcNetRoutePath }()
procNetRoutePath = filepath.Join(dir, "VeryLong")
f, err := os.Create(procNetRoutePath)
if err != nil {
t.Fatal(err)
}
_, err = f.Write([]byte("Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT\n"))
if err != nil {
t.Fatal(err)
}
for n := 0; n <= 1000; 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 {
t.Fatal(err)
}
}
_, err = f.Write([]byte("tokenring1\t00000000\t00000000\t0001\t0\t0\t0\t00000000\t0\t0\t0\n"))
if err != nil {
t.Fatal(err)
}
got, err := DefaultRouteInterface()
if err != nil {
t.Fatal(err)
}
if got != "tokenring1" {
t.Fatalf("got %q, want tokenring1", got)
}
}

View File

@@ -4,74 +4,7 @@
package interfaces
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
)
// test the specific /proc/net/route path as found on Google Cloud Run instances
func TestGoogleCloudRunDefaultRouteInterface(t *testing.T) {
dir := t.TempDir()
savedProcNetRoutePath := procNetRoutePath
defer func() { procNetRoutePath = savedProcNetRoutePath }()
procNetRoutePath = filepath.Join(dir, "CloudRun")
buf := []byte("Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT\n" +
"eth0\t8008FEA9\t00000000\t0001\t0\t0\t0\t01FFFFFF\t0\t0\t0\n" +
"eth1\t00000000\t00000000\t0001\t0\t0\t0\t00000000\t0\t0\t0\n")
err := ioutil.WriteFile(procNetRoutePath, buf, 0644)
if err != nil {
t.Fatal(err)
}
got, err := DefaultRouteInterface()
if err != nil {
t.Fatal(err)
}
if got != "eth1" {
t.Fatalf("got %s, want eth1", got)
}
}
// we read chunks of /proc/net/route at a time, test that files longer than the chunk
// size can be handled.
func TestExtremelyLongProcNetRoute(t *testing.T) {
dir := t.TempDir()
savedProcNetRoutePath := procNetRoutePath
defer func() { procNetRoutePath = savedProcNetRoutePath }()
procNetRoutePath = filepath.Join(dir, "VeryLong")
f, err := os.Create(procNetRoutePath)
if err != nil {
t.Fatal(err)
}
_, err = f.Write([]byte("Iface\tDestination\tGateway\tFlags\tRefCnt\tUse\tMetric\tMask\tMTU\tWindow\tIRTT\n"))
if err != nil {
t.Fatal(err)
}
for n := 0; n <= 1000; 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 {
t.Fatal(err)
}
}
_, err = f.Write([]byte("tokenring1\t00000000\t00000000\t0001\t0\t0\t0\t00000000\t0\t0\t0\n"))
if err != nil {
t.Fatal(err)
}
got, err := DefaultRouteInterface()
if err != nil {
t.Fatal(err)
}
if got != "tokenring1" {
t.Fatalf("got %q, want tokenring1", got)
}
}
import "testing"
func BenchmarkDefaultRouteInterface(b *testing.B) {
b.ReportAllocs()

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin,!ts_macext
// +build darwin,!redo
package netns

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux,!windows,!darwin darwin,ts_macext
// +build !linux,!windows,!darwin darwin,redo
package netns

View File

@@ -14,6 +14,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"log"
"inet.af/netaddr"
"tailscale.com/net/flowtrack"
@@ -232,6 +233,7 @@ type TSMPPongReply struct {
// AsTSMPPong returns pp as a TSMPPongReply and whether it is one.
// The pong.IPHeader field is not populated.
func (pp *Parsed) AsTSMPPong() (pong TSMPPongReply, ok bool) {
log.Println("TSMPPONG")
if pp.IPProto != ipproto.TSMP {
return
}

View File

@@ -41,9 +41,12 @@ var (
// TailscaleServiceIP returns the listen address of services
// provided by Tailscale itself such as the MagicDNS proxy.
func TailscaleServiceIP() netaddr.IP {
return netaddr.IPv4(100, 100, 100, 100) // "100.100.100.100" for those grepping
serviceIP.Do(func() { mustIP(&serviceIP.v, "100.100.100.100") })
return serviceIP.v
}
var serviceIP onceIP
// IsTailscaleIP reports whether ip is an IP address in a range that
// Tailscale assigns from.
func IsTailscaleIP(ip netaddr.IP) bool {
@@ -123,6 +126,19 @@ type oncePrefix struct {
v netaddr.IPPrefix
}
func mustIP(v *netaddr.IP, ip string) {
var err error
*v, err = netaddr.ParseIP(ip)
if err != nil {
panic(err)
}
}
type onceIP struct {
sync.Once
v netaddr.IP
}
// NewContainsIPFunc returns a func that reports whether ip is in addrs.
//
// It's optimized for the cases of addrs being empty and addrs

View File

@@ -93,11 +93,3 @@ func TestNewContainsIPFunc(t *testing.T) {
t.Fatal("bad")
}
}
var sinkIP netaddr.IP
func BenchmarkTailscaleServiceAddr(b *testing.B) {
for i := 0; i < b.N; i++ {
sinkIP = TailscaleServiceIP()
}
}

View File

@@ -11,7 +11,6 @@ import (
"os"
"os/exec"
"runtime"
"strconv"
"time"
"golang.zx2c4.com/wireguard/tun"
@@ -19,26 +18,20 @@ import (
"tailscale.com/version/distro"
)
// tunMTU is the MTU we set on tailscale's TUN interface. wireguard-go
// defaults to 1420 bytes, which only works if the "outer" MTU is 1500
// bytes. This breaks on DSL connections (typically 1492 MTU) and on
// GCE (1460 MTU?!).
// minimalMTU is the MTU we set on tailscale's TUN
// interface. wireguard-go defaults to 1420 bytes, which only works if
// the "outer" MTU is 1500 bytes. This breaks on DSL connections
// (typically 1492 MTU) and on GCE (1460 MTU?!).
//
// 1280 is the smallest MTU allowed for IPv6, which is a sensible
// "probably works everywhere" setting until we develop proper PMTU
// discovery.
var tunMTU = 1280
func init() {
if mtu, _ := strconv.Atoi(os.Getenv("TS_DEBUG_MTU")); mtu != 0 {
tunMTU = mtu
}
}
const minimalMTU = 1280
// New returns a tun.Device for the requested device name, along with
// the OS-dependent name that was allocated to the device.
func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
dev, err := tun.CreateTUN(tunName, tunMTU)
dev, err := tun.CreateTUN(tunName, minimalMTU)
if err != nil {
return nil, "", err
}

View File

@@ -9,6 +9,7 @@ package tstun
import (
"errors"
"io"
"log"
"os"
"sync"
"sync/atomic"
@@ -276,6 +277,7 @@ func (t *Wrapper) poll() {
var magicDNSIPPort = netaddr.MustParseIPPort("100.100.100.100:0")
func (t *Wrapper) filterOut(p *packet.Parsed) filter.Response {
log.Println("FILTEROUT")
// Fake ICMP echo responses to MagicDNS (100.100.100.100).
if p.IsEchoRequest() && p.Dst == magicDNSIPPort {
header := p.ICMP4Header()
@@ -376,6 +378,7 @@ func (t *Wrapper) Read(buf []byte, offset int) (int, error) {
}
func (t *Wrapper) filterIn(buf []byte) filter.Response {
log.Println("FILTERIN")
p := parsedPacketPool.Get().(*packet.Parsed)
defer parsedPacketPool.Put(p)
p.Decode(buf)
@@ -532,6 +535,7 @@ func (t *Wrapper) InjectInboundCopy(packet []byte) error {
}
func (t *Wrapper) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingRequest) {
log.Println("INJECT OUTBOUND")
pong := packet.TSMPPongReply{
Data: req.Data,
}
@@ -568,8 +572,10 @@ func (t *Wrapper) InjectOutbound(packet []byte) error {
}
select {
case <-t.closed:
log.Println("Closed")
return ErrClosed
case t.outbound <- packet:
log.Println("t.outbound <- packet")
return nil
}
}

View File

@@ -146,8 +146,7 @@ func setfilter(logf logger.Logf, tun *Wrapper) {
}
var sb netaddr.IPSetBuilder
sb.AddPrefix(netaddr.MustParseIPPrefix("1.2.0.0/16"))
ipSet, _ := sb.IPSet()
tun.SetFilter(filter.New(matches, ipSet, ipSet, nil, logf))
tun.SetFilter(filter.New(matches, sb.IPSet(), sb.IPSet(), nil, logf))
}
func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *Wrapper) {

View File

@@ -1,184 +0,0 @@
// 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 deb extracts metadata from Debian packages.
package deb
import (
"archive/tar"
"bufio"
"bytes"
"compress/gzip"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
// Info is the Debian package metadata needed to integrate the package
// into a repository.
type Info struct {
// Version is the version of the package, as reported by dpkg.
Version string
// Arch is the Debian CPU architecture the package is for.
Arch string
// Control is the entire contents of the package's control file,
// with leading and trailing whitespace removed.
Control []byte
// MD5 is the MD5 hash of the package file.
MD5 []byte
// SHA1 is the SHA1 hash of the package file.
SHA1 []byte
// SHA256 is the SHA256 hash of the package file.
SHA256 []byte
}
// ReadFile returns Debian package metadata from the .deb file at path.
func ReadFile(path string) (*Info, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
return Read(f)
}
// Read returns Debian package metadata from the .deb file in r.
func Read(r io.Reader) (*Info, error) {
b := bufio.NewReader(r)
m5, s1, s256 := md5.New(), sha1.New(), sha256.New()
summers := io.MultiWriter(m5, s1, s256)
r = io.TeeReader(b, summers)
t, err := findControlTar(r)
if err != nil {
return nil, fmt.Errorf("searching for control.tar.gz: %w", err)
}
control, err := findControlFile(t)
if err != nil {
return nil, fmt.Errorf("searching for control file in control.tar.gz: %w", err)
}
arch, version, err := findArchAndVersion(control)
if err != nil {
return nil, fmt.Errorf("extracting version and architecture from control file: %w", err)
}
// Exhaust the remainder of r, so that the summers see the entire file.
if _, err := io.Copy(ioutil.Discard, r); err != nil {
return nil, fmt.Errorf("hashing file: %w", err)
}
return &Info{
Version: version,
Arch: arch,
Control: control,
MD5: m5.Sum(nil),
SHA1: s1.Sum(nil),
SHA256: s256.Sum(nil),
}, nil
}
// findControlTar reads r as an `ar` archive, finds a tarball named
// `control.tar.gz` within, and returns a reader for that file.
func findControlTar(r io.Reader) (tarReader io.Reader, err error) {
var magic [8]byte
if _, err := io.ReadFull(r, magic[:]); err != nil {
return nil, fmt.Errorf("reading ar magic: %w", err)
}
if string(magic[:]) != "!<arch>\n" {
return nil, fmt.Errorf("not an ar file (bad magic %q)", magic)
}
for {
var hdr [60]byte
if _, err := io.ReadFull(r, hdr[:]); err != nil {
return nil, fmt.Errorf("reading file header: %w", err)
}
filename := strings.TrimSpace(string(hdr[:16]))
size, err := strconv.ParseInt(strings.TrimSpace(string(hdr[48:58])), 10, 64)
if err != nil {
return nil, fmt.Errorf("reading size of file %q: %w", filename, err)
}
if filename == "control.tar.gz" {
return io.LimitReader(r, size), nil
}
// files in ar are padded out to 2 bytes.
if size%2 == 1 {
size++
}
if _, err := io.CopyN(ioutil.Discard, r, size); err != nil {
return nil, fmt.Errorf("seeking past file %q: %w", filename, err)
}
}
}
// findControlFile reads r as a tar.gz archive, finds a file named
// `control` within, and returns its contents.
func findControlFile(r io.Reader) (control []byte, err error) {
gz, err := gzip.NewReader(r)
if err != nil {
return nil, fmt.Errorf("decompressing control.tar.gz: %w", err)
}
defer gz.Close()
tr := tar.NewReader(gz)
for {
hdr, err := tr.Next()
if err != nil {
if errors.Is(err, io.EOF) {
return nil, errors.New("EOF while looking for control file in control.tar.gz")
}
return nil, fmt.Errorf("reading tar header: %w", err)
}
if filepath.Clean(hdr.Name) != "control" {
continue
}
// Found control file
break
}
bs, err := ioutil.ReadAll(tr)
if err != nil {
return nil, fmt.Errorf("reading control file: %w", err)
}
return bytes.TrimSpace(bs), nil
}
var (
archKey = []byte("Architecture:")
versionKey = []byte("Version:")
)
// findArchAndVersion extracts the architecture and version strings
// from the given control file.
func findArchAndVersion(control []byte) (arch string, version string, err error) {
b := bytes.NewBuffer(control)
for {
l, err := b.ReadBytes('\n')
if err != nil {
return "", "", err
}
if bytes.HasPrefix(l, archKey) {
arch = string(bytes.TrimSpace(l[len(archKey):]))
} else if bytes.HasPrefix(l, versionKey) {
version = string(bytes.TrimSpace(l[len(versionKey):]))
}
if arch != "" && version != "" {
return arch, version, nil
}
}
}

View File

@@ -1,202 +0,0 @@
// 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 deb
import (
"bytes"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/goreleaser/nfpm"
_ "github.com/goreleaser/nfpm/deb"
)
func TestDebInfo(t *testing.T) {
tests := []struct {
name string
in []byte
want *Info
wantErr bool
}{
{
name: "simple",
in: mkTestDeb("1.2.3", "amd64"),
want: &Info{
Version: "1.2.3",
Arch: "amd64",
Control: mkControl(
"Package", "tailscale",
"Version", "1.2.3",
"Section", "net",
"Priority", "extra",
"Architecture", "amd64",
"Installed-Size", "0",
"Description", "test package"),
},
},
{
name: "arm64",
in: mkTestDeb("1.2.3", "arm64"),
want: &Info{
Version: "1.2.3",
Arch: "arm64",
Control: mkControl(
"Package", "tailscale",
"Version", "1.2.3",
"Section", "net",
"Priority", "extra",
"Architecture", "arm64",
"Installed-Size", "0",
"Description", "test package"),
},
},
{
name: "unstable",
in: mkTestDeb("1.7.25", "amd64"),
want: &Info{
Version: "1.7.25",
Arch: "amd64",
Control: mkControl(
"Package", "tailscale",
"Version", "1.7.25",
"Section", "net",
"Priority", "extra",
"Architecture", "amd64",
"Installed-Size", "0",
"Description", "test package"),
},
},
// These truncation tests assume the structure of a .deb
// package, which is as follows:
// magic: 8 bytes
// file header: 60 bytes, before each file blob
//
// The first file in a .deb ar is "debian-binary", which is 4
// bytes long and consists of "2.0\n".
// The second file is control.tar.gz, which is what we care
// about introspecting for metadata.
// The final file is data.tar.gz, which we don't care about.
//
// The first file in control.tar.gz is the "control" file we
// want to read for metadata.
{
name: "truncated_ar_magic",
in: mkTestDeb("1.7.25", "amd64")[:4],
wantErr: true,
},
{
name: "truncated_ar_header",
in: mkTestDeb("1.7.25", "amd64")[:30],
wantErr: true,
},
{
name: "missing_control_tgz",
// Truncate right after the "debian-binary" file, which
// makes the file a valid 1-file archive that's missing
// control.tar.gz.
in: mkTestDeb("1.7.25", "amd64")[:72],
wantErr: true,
},
{
name: "truncated_tgz",
in: mkTestDeb("1.7.25", "amd64")[:172],
wantErr: true,
},
}
for _, test := range tests {
// mkTestDeb returns non-deterministic output due to
// timestamps embedded in the package file, so compute the
// wanted hashes on the fly here.
if test.want != nil {
test.want.MD5 = mkHash(test.in, md5.New)
test.want.SHA1 = mkHash(test.in, sha1.New)
test.want.SHA256 = mkHash(test.in, sha256.New)
}
t.Run(test.name, func(t *testing.T) {
b := bytes.NewBuffer(test.in)
got, err := Read(b)
if err != nil {
if test.wantErr {
t.Logf("got expected error: %v", err)
return
}
t.Fatalf("reading deb info: %v", err)
}
if diff := diff(got, test.want); diff != "" {
t.Fatalf("parsed info diff (-got+want):\n%s", diff)
}
})
}
}
func diff(got, want interface{}) string {
matchField := func(name string) func(p cmp.Path) bool {
return func(p cmp.Path) bool {
if len(p) != 3 {
return false
}
return p[2].String() == "."+name
}
}
toLines := cmp.Transformer("lines", func(b []byte) []string { return strings.Split(string(b), "\n") })
toHex := cmp.Transformer("hex", func(b []byte) string { return hex.EncodeToString(b) })
return cmp.Diff(got, want,
cmp.FilterPath(matchField("Control"), toLines),
cmp.FilterPath(matchField("MD5"), toHex),
cmp.FilterPath(matchField("SHA1"), toHex),
cmp.FilterPath(matchField("SHA256"), toHex))
}
func mkTestDeb(version, arch string) []byte {
info := nfpm.WithDefaults(&nfpm.Info{
Name: "tailscale",
Description: "test package",
Arch: arch,
Platform: "linux",
Version: version,
Section: "net",
Priority: "extra",
})
pkg, err := nfpm.Get("deb")
if err != nil {
panic(fmt.Sprintf("getting deb packager: %v", err))
}
var b bytes.Buffer
if err := pkg.Package(info, &b); err != nil {
panic(fmt.Sprintf("creating deb package: %v", err))
}
return b.Bytes()
}
func mkControl(fs ...string) []byte {
if len(fs)%2 != 0 {
panic("odd number of control file fields")
}
var b bytes.Buffer
for i := 0; i < len(fs); i = i + 2 {
k, v := fs[i], fs[i+1]
fmt.Fprintf(&b, "%s: %s\n", k, v)
}
return bytes.TrimSpace(b.Bytes())
}
func mkHash(b []byte, hasher func() hash.Hash) []byte {
h := hasher()
h.Write(b)
return h.Sum(nil)
}

View File

@@ -11,8 +11,6 @@ import (
"path/filepath"
"runtime"
"sync/atomic"
"tailscale.com/version/distro"
)
// AppSharedDir is a string set by the iOS or Android app on start
@@ -28,15 +26,11 @@ func DefaultTailscaledSocket() string {
if runtime.GOOS == "darwin" {
return "/var/run/tailscaled.socket"
}
if distro.Get() == distro.Synology {
// TODO(maisem): be smarter about this. We can parse /etc/VERSION.
const dsm6Sock = "/var/packages/Tailscale/etc/tailscaled.sock"
const dsm7Sock = "/var/packages/Tailscale/var/tailscaled.sock"
if fi, err := os.Stat(dsm6Sock); err == nil && !fi.IsDir() {
return dsm6Sock
}
if fi, err := os.Stat(dsm7Sock); err == nil && !fi.IsDir() {
return dsm7Sock
if runtime.GOOS == "linux" {
// TODO(crawshaw): does this path change with DSM7?
const synologySock = "/volume1/@appstore/Tailscale/var/tailscaled.sock" // SYNOPKG_PKGDEST in scripts/installer
if fi, err := os.Stat(filepath.Dir(synologySock)); err == nil && fi.IsDir() {
return synologySock
}
}
if fi, err := os.Stat("/var/run"); err == nil && fi.IsDir() {

View File

@@ -749,6 +749,9 @@ type MapRequest struct {
// * "minimize-netmap": have control minimize the netmap, removing
// peers that are unreachable per ACLS.
DebugFlags []string `json:",omitempty"`
// Basic boolean field to determine if a Ping is being intitiated
Ping bool
}
// PortRange represents a range of UDP or TCP port numbers.
@@ -884,6 +887,27 @@ type PingRequest struct {
// Log is whether to log about this ping in the success case.
// For failure cases, the client will log regardless.
Log bool `json:",omitempty"`
Initiator string // admin@email; "system" (for Tailscale)
TestIP netaddr.IP
Types string // empty means all: TSMP+ICMP+disco
StopAfterNDirect int // 1 means stop on 1st direct ping; 4 means 4 direct pings; 0 means do MaxPings and stop
MaxPings int // MaxPings total, direct or DERPed
PayloadSize int // default: 0 extra bytes
}
// According to https://roamresearch.com/#/app/ts-corp/page/4Bn_Famn2
// Client can stream responses back via HTTP
// We will add a struct with the proper fields
type StreamedPingResult struct {
IP netaddr.IP
SeqNum int // somewhat redundant with TxID but for clarity
SentTo NodeID // for exit/subnet relays
TxID string // N hex bytes random
Dir string // "in"/"out"
Type string // ICMP, disco, TSMP, ...
Via string // "direct", "derp-nyc", ...
Seconds float64 // for Dir "in" only
}
type MapResponse struct {
@@ -1174,31 +1198,3 @@ const (
CapabilityFileSharing = "https://tailscale.com/cap/file-sharing"
CapabilityAdmin = "https://tailscale.com/cap/is-admin"
)
// SetDNSRequest is a request to add a DNS record.
//
// This is used for ACME DNS-01 challenges (so people can use
// LetsEncrypt, etc).
//
// The request is encoded to JSON, encrypted with golang.org/x/crypto/nacl/box,
// using the local machine key, and sent to:
// https://login.tailscale.com/machine/<mkey hex>/set-dns
type SetDNSRequest struct {
// Version indicates what level of SetDNSRequest functionality
// the client understands. Currently this type only has
// one version; this field should always be 1 for now.
Version int
// NodeKey is the client's current node key.
NodeKey NodeKey
// Name is the domain name for which to create a record.
Name string
// Type is the DNS record type. For ACME DNS-01 challenges, it
// should be "TXT".
Type string
// Value is the value to add.
Value string
}

View File

@@ -1,100 +0,0 @@
// 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 integration contains Tailscale integration tests.
//
// This package is considered internal and the public API is subject
// to change without notice.
package integration
import (
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
"time"
"tailscale.com/version"
)
// Binaries are the paths to a tailscaled and tailscale binary.
// These can be shared by multiple nodes.
type Binaries struct {
Dir string // temp dir for tailscale & tailscaled
Daemon string // tailscaled
CLI string // tailscale
}
// BuildTestBinaries builds tailscale and tailscaled, failing the test
// if they fail to compile.
func BuildTestBinaries(t testing.TB) *Binaries {
td := t.TempDir()
build(t, td, "tailscale.com/cmd/tailscaled", "tailscale.com/cmd/tailscale")
return &Binaries{
Dir: td,
Daemon: filepath.Join(td, "tailscaled"+exe()),
CLI: filepath.Join(td, "tailscale"+exe()),
}
}
// buildMu limits our use of "go build" to one at a time, so we don't
// fight Go's built-in caching trying to do the same build concurrently.
var buildMu sync.Mutex
func build(t testing.TB, outDir string, targets ...string) {
buildMu.Lock()
defer buildMu.Unlock()
t0 := time.Now()
defer func() { t.Logf("built %s in %v", targets, time.Since(t0).Round(time.Millisecond)) }()
goBin := findGo(t)
cmd := exec.Command(goBin, "install")
if version.IsRace() {
cmd.Args = append(cmd.Args, "-race")
}
cmd.Args = append(cmd.Args, targets...)
cmd.Env = append(os.Environ(), "GOARCH="+runtime.GOARCH, "GOBIN="+outDir)
errOut, err := cmd.CombinedOutput()
if err == nil {
return
}
if strings.Contains(string(errOut), "when GOBIN is set") {
// Fallback slow path for cross-compiled binaries.
for _, target := range targets {
outFile := filepath.Join(outDir, path.Base(target)+exe())
cmd := exec.Command(goBin, "build", "-o", outFile, target)
cmd.Env = append(os.Environ(), "GOARCH="+runtime.GOARCH)
if errOut, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("failed to build %v with %v: %v, %s", target, goBin, err, errOut)
}
}
return
}
t.Fatalf("failed to build %v with %v: %v, %s", targets, goBin, err, errOut)
}
func findGo(t testing.TB) string {
goBin := filepath.Join(runtime.GOROOT(), "bin", "go"+exe())
if fi, err := os.Stat(goBin); err != nil {
if os.IsNotExist(err) {
t.Fatalf("failed to find go at %v", goBin)
}
t.Fatalf("looking for go binary: %v", err)
} else if !fi.Mode().IsRegular() {
t.Fatalf("%v is unexpected %v", goBin, fi.Mode())
}
return goBin
}
func exe() string {
if runtime.GOOS == "windows" {
return ".exe"
}
return ""
}

View File

@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package integration contains Tailscale integration tests.
package integration
import (
@@ -10,7 +11,6 @@ import (
"crypto/tls"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
@@ -20,6 +20,7 @@ import (
"net/http/httptest"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
@@ -42,9 +43,10 @@ import (
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/nettype"
"tailscale.com/version"
)
var verbose = flag.Bool("verbose", false, "verbose debug logs")
// var verbose = flag.Bool("verbose", true, "verbose debug logs")
var mainError atomic.Value // of error
@@ -57,12 +59,13 @@ func TestMain(m *testing.M) {
fmt.Fprintf(os.Stderr, "FAIL: %v\n", err)
os.Exit(1)
}
time.Sleep(5 * time.Second)
os.Exit(0)
}
func TestOneNodeUp_NoAuth(t *testing.T) {
t.Parallel()
bins := BuildTestBinaries(t)
bins := buildTestBinaries(t)
env := newTestEnv(t, bins)
defer env.Close()
@@ -104,7 +107,7 @@ func TestOneNodeUp_NoAuth(t *testing.T) {
func TestOneNodeUp_Auth(t *testing.T) {
t.Parallel()
bins := BuildTestBinaries(t)
bins := buildTestBinaries(t)
env := newTestEnv(t, bins)
defer env.Close()
@@ -151,7 +154,7 @@ func TestOneNodeUp_Auth(t *testing.T) {
func TestTwoNodes(t *testing.T) {
t.Parallel()
bins := BuildTestBinaries(t)
bins := buildTestBinaries(t)
env := newTestEnv(t, bins)
defer env.Close()
@@ -195,7 +198,7 @@ func TestTwoNodes(t *testing.T) {
func TestNodeAddressIPFields(t *testing.T) {
t.Parallel()
bins := BuildTestBinaries(t)
bins := buildTestBinaries(t)
env := newTestEnv(t, bins)
defer env.Close()
@@ -224,57 +227,23 @@ func TestNodeAddressIPFields(t *testing.T) {
d1.MustCleanShutdown(t)
}
func TestAddPingRequest(t *testing.T) {
t.Parallel()
bins := BuildTestBinaries(t)
// testBinaries are the paths to a tailscaled and tailscale binary.
// These can be shared by multiple nodes.
type testBinaries struct {
dir string // temp dir for tailscale & tailscaled
daemon string // tailscaled
cli string // tailscale
}
env := newTestEnv(t, bins)
defer env.Close()
n1 := newTestNode(t, env)
d1 := n1.StartDaemon(t)
defer d1.Kill()
n1.AwaitListening(t)
n1.MustUp()
n1.AwaitRunning(t)
gotPing := make(chan bool, 1)
waitPing := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPing <- true
}))
defer waitPing.Close()
nodes := env.Control.AllNodes()
if len(nodes) != 1 {
t.Fatalf("expected 1 node, got %d nodes", len(nodes))
}
nodeKey := nodes[0].Key
pr := &tailcfg.PingRequest{URL: waitPing.URL, Log: true}
ok := env.Control.AddPingRequest(nodeKey, pr)
if !ok {
t.Fatalf("no node found with NodeKey %v in AddPingRequest", nodeKey)
}
// Wait for PingRequest to come back
waitDuration := 10 * time.Second
pingTimeout := time.NewTimer(waitDuration)
// Ticker sends new PingRequests if the previous did not get through
ticker := time.NewTicker(waitDuration / 5)
select {
case <-gotPing:
pingTimeout.Stop()
ticker.Stop()
case <-ticker.C:
ok := env.Control.AddPingRequest(nodeKey, pr)
if !ok {
t.Fatalf("no node found with NodeKey %v in AddPingRequest", nodeKey)
}
case <-pingTimeout.C:
t.Error("didn't get PingRequest from tailscaled")
// buildTestBinaries builds tailscale and tailscaled, failing the test
// if they fail to compile.
func buildTestBinaries(t testing.TB) *testBinaries {
td := t.TempDir()
build(t, td, "tailscale.com/cmd/tailscaled", "tailscale.com/cmd/tailscale")
return &testBinaries{
dir: td,
daemon: filepath.Join(td, "tailscaled"+exe()),
cli: filepath.Join(td, "tailscale"+exe()),
}
}
@@ -282,7 +251,7 @@ func TestAddPingRequest(t *testing.T) {
// or more nodes.
type testEnv struct {
t testing.TB
Binaries *Binaries
Binaries *testBinaries
LogCatcher *logCatcher
LogCatcherServer *httptest.Server
@@ -300,7 +269,7 @@ type testEnv struct {
// environment.
//
// Call Close to shut everything down.
func newTestEnv(t testing.TB, bins *Binaries) *testEnv {
func newTestEnv(t testing.TB, bins *testBinaries) *testEnv {
if runtime.GOOS == "windows" {
t.Skip("not tested/working on Windows yet")
}
@@ -310,6 +279,9 @@ func newTestEnv(t testing.TB, bins *Binaries) *testEnv {
DERPMap: derpMap,
}
trafficTrap := new(trafficTrap)
log.Println("SERVER ATTACHED")
log.Println(len(control.PingRequestC))
// go func() { control.PingRequestC <- true }()
e := &testEnv{
t: t,
Binaries: bins,
@@ -383,7 +355,7 @@ func (d *Daemon) MustCleanShutdown(t testing.TB) {
// StartDaemon starts the node's tailscaled, failing if it fails to
// start.
func (n *testNode) StartDaemon(t testing.TB) *Daemon {
cmd := exec.Command(n.env.Binaries.Daemon,
cmd := exec.Command(n.env.Binaries.daemon,
"--tun=userspace-networking",
"--state="+n.stateFile,
"--socket="+n.sockFile,
@@ -461,7 +433,7 @@ func (n *testNode) AwaitRunning(t testing.TB) {
// Tailscale returns a command that runs the tailscale CLI with the provided arguments.
// It does not start the process.
func (n *testNode) Tailscale(arg ...string) *exec.Cmd {
cmd := exec.Command(n.env.Binaries.CLI, "--socket="+n.sockFile)
cmd := exec.Command(n.env.Binaries.cli, "--socket="+n.sockFile)
cmd.Args = append(cmd.Args, arg...)
cmd.Dir = n.dir
return cmd
@@ -488,6 +460,63 @@ func (n *testNode) MustStatus(tb testing.TB) *ipnstate.Status {
return st
}
func exe() string {
if runtime.GOOS == "windows" {
return ".exe"
}
return ""
}
func findGo(t testing.TB) string {
goBin := filepath.Join(runtime.GOROOT(), "bin", "go"+exe())
if fi, err := os.Stat(goBin); err != nil {
if os.IsNotExist(err) {
t.Fatalf("failed to find go at %v", goBin)
}
t.Fatalf("looking for go binary: %v", err)
} else if !fi.Mode().IsRegular() {
t.Fatalf("%v is unexpected %v", goBin, fi.Mode())
}
return goBin
}
// buildMu limits our use of "go build" to one at a time, so we don't
// fight Go's built-in caching trying to do the same build concurrently.
var buildMu sync.Mutex
func build(t testing.TB, outDir string, targets ...string) {
buildMu.Lock()
defer buildMu.Unlock()
t0 := time.Now()
defer func() { t.Logf("built %s in %v", targets, time.Since(t0).Round(time.Millisecond)) }()
goBin := findGo(t)
cmd := exec.Command(goBin, "install")
if version.IsRace() {
cmd.Args = append(cmd.Args, "-race")
}
cmd.Args = append(cmd.Args, targets...)
cmd.Env = append(os.Environ(), "GOARCH="+runtime.GOARCH, "GOBIN="+outDir)
errOut, err := cmd.CombinedOutput()
if err == nil {
return
}
if strings.Contains(string(errOut), "when GOBIN is set") {
// Fallback slow path for cross-compiled binaries.
for _, target := range targets {
outFile := filepath.Join(outDir, path.Base(target)+exe())
cmd := exec.Command(goBin, "build", "-o", outFile, target)
cmd.Env = append(os.Environ(), "GOARCH="+runtime.GOARCH)
if errOut, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("failed to build %v with %v: %v, %s", target, goBin, err, errOut)
}
}
return
}
t.Fatalf("failed to build %v with %v: %v, %s", targets, goBin, err, errOut)
}
// logCatcher is a minimal logcatcher for the logtail upload client.
type logCatcher struct {
mu sync.Mutex
@@ -558,7 +587,7 @@ func (lc *logCatcher) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} else {
for _, ent := range jreq {
fmt.Fprintf(&lc.buf, "%s\n", strings.TrimSpace(ent.Text))
if *verbose {
if testing.Verbose() {
fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(ent.Text))
}
}
@@ -657,3 +686,132 @@ func (w *authURLParserWriter) Write(p []byte) (n int, err error) {
}
return n, err
}
type panicOnUseTransport struct{}
func (panicOnUseTransport) RoundTrip(*http.Request) (*http.Response, error) {
panic("unexpected HTTP request")
}
func TestTwoNodePing(t *testing.T) {
// < --->
t.Parallel()
bins := buildTestBinaries(t)
env := newTestEnv(t, bins)
t.Log("Env :", env.ControlServer.URL)
res, err := http.Get(env.ControlServer.URL + "/ping")
t.Log("RESPONSE", res)
if err != nil {
t.Error(err)
}
defer env.Close()
// Create two nodes:
n1 := newTestNode(t, env)
d1 := n1.StartDaemon(t)
defer d1.Kill()
n2 := newTestNode(t, env)
d2 := n2.StartDaemon(t)
defer d2.Kill()
n1.AwaitListening(t)
n2.AwaitListening(t)
n1.MustUp()
n2.MustUp()
n1.AwaitRunning(t)
n2.AwaitRunning(t)
ip1 := n1.AwaitIP(t)
ip2 := n2.AwaitIP(t)
t.Logf("Node IPs : %s, %s\n", ip1, ip2)
if err := tstest.WaitFor(2*time.Second, func() error {
st := n1.MustStatus(t)
t.Log("CURPEER", len(st.Peer))
var peers []*ipnstate.PeerStatus
for _, peer := range st.Peers() {
ps := st.Peer[peer]
if ps.ShareeNode {
continue
}
peers = append(peers, ps)
}
jsonForm, _ := json.MarshalIndent(peers[0], "", " ")
t.Log("PeerStatus", string(jsonForm))
if len(st.Peer) == 0 {
return errors.New("no peers")
}
if len(st.Peer) > 1 {
return fmt.Errorf("got %d peers; want 1", len(st.Peer))
}
peer := st.Peer[st.Peers()[0]]
if peer.ID == st.Self.ID {
return errors.New("peer is self")
}
return nil
}); err != nil {
t.Error(err)
}
d1.MustCleanShutdown(t)
d2.MustCleanShutdown(t)
}
// Tests if our addPingRequest function works
func TestAddPingRequest(t *testing.T) {
}
// Test such that we can simulate the ping instead of hardcoding in the map response
func TestControlSelectivePing(t *testing.T) {
t.Parallel()
bins := buildTestBinaries(t)
env := newTestEnv(t, bins)
log.Println("POSTSTARTUP")
defer env.Close()
// Create two nodes:
n1 := newTestNode(t, env)
d1 := n1.StartDaemon(t)
defer d1.Kill()
n2 := newTestNode(t, env)
d2 := n2.StartDaemon(t)
defer d2.Kill()
n1.AwaitListening(t)
n2.AwaitListening(t)
n1.MustUp()
n2.MustUp()
n1.AwaitRunning(t)
n2.AwaitRunning(t)
// Wait for server to start serveMap
if err := tstest.WaitFor(2*time.Second, func() error {
t.Log("ENOUGHTIME")
env.Control.AddControlPingRequest()
if len(env.Control.PingRequestC) == 0 {
return errors.New("failed to add to PingRequestC")
}
log.Println("CHANNEL LENGTH", len(env.Control.PingRequestC))
return nil
}); err != nil {
t.Error(err)
}
// Wait for a MapResponse
if err := tstest.WaitFor(20*time.Second, func() error {
// Simulate the time needed for MapResponse method call.
time.Sleep(500 * time.Millisecond)
if len(env.Control.PingRequestC) == 1 {
t.Error("Expected PingRequestC to be empty")
}
return nil
}); err != nil {
t.Error(err)
}
d1.MustCleanShutdown(t)
d2.MustCleanShutdown(t)
}

View File

@@ -36,14 +36,16 @@ import (
// Server is a control plane server. Its zero value is ready for use.
// Everything is stored in-memory in one tailnet.
type Server struct {
Logf logger.Logf // nil means to use the log package
DERPMap *tailcfg.DERPMap // nil means to use prod DERP map
RequireAuth bool
BaseURL string // must be set to e.g. "http://127.0.0.1:1234" with no trailing URL
Verbose bool
Logf logger.Logf // nil means to use the log package
DERPMap *tailcfg.DERPMap // nil means to use prod DERP map
RequireAuth bool
BaseURL string // must be set to e.g. "http://127.0.0.1:1234" with no trailing URL
Verbose bool
PingRequestC chan bool
initMuxOnce sync.Once
mux *http.ServeMux
initMuxOnce sync.Once
mux *http.ServeMux
initPRchannelOnce sync.Once
mu sync.Mutex
pubKey wgkey.Key
@@ -54,7 +56,6 @@ type Server struct {
updates map[tailcfg.NodeID]chan updateType
authPath map[string]*AuthPath
nodeKeyAuthed map[tailcfg.NodeKey]bool // key => true once authenticated
pingReqsToAdd map[tailcfg.NodeKey]*tailcfg.PingRequest
}
// NumNodes returns the number of nodes in the testcontrol server.
@@ -68,27 +69,6 @@ func (s *Server) NumNodes() int {
return len(s.nodes)
}
// AddPingRequest sends the ping pr to nodeKeyDst. It reports whether it did so. That is,
// it reports whether nodeKeyDst was connected.
func (s *Server) AddPingRequest(nodeKeyDst tailcfg.NodeKey, pr *tailcfg.PingRequest) bool {
s.mu.Lock()
defer s.mu.Unlock()
if s.pingReqsToAdd == nil {
s.pingReqsToAdd = map[tailcfg.NodeKey]*tailcfg.PingRequest{}
}
// Now send the update to the channel
node := s.nodeLocked(nodeKeyDst)
if node == nil {
return false
}
s.pingReqsToAdd[nodeKeyDst] = pr
nodeID := node.ID
oldUpdatesCh := s.updates[nodeID]
sendUpdate(oldUpdatesCh, updateDebugInjection)
return true
}
type AuthPath struct {
nodeKey tailcfg.NodeKey
@@ -117,14 +97,26 @@ func (s *Server) logf(format string, a ...interface{}) {
}
func (s *Server) initMux() {
log.Println("Mux inited")
s.mux = http.NewServeMux()
s.mux.HandleFunc("/", s.serveUnhandled)
s.mux.HandleFunc("/key", s.serveKey)
s.mux.HandleFunc("/machine/", s.serveMachine)
s.mux.HandleFunc("/ping", s.receivePingInfo)
s.mux.HandleFunc("/mockpingrequest", s.serveMockPing)
}
func (s *Server) initPingRequestC() {
log.Println("Channel created")
s.PingRequestC = make(chan bool, 1)
// s.AddControlPingRequest()
// log.Println("Channel length : ", len(s.PingRequestC))
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println("HTTPSERVE")
s.initMuxOnce.Do(s.initMux)
s.initPRchannelOnce.Do(s.initPingRequestC)
s.mux.ServeHTTP(w, r)
}
@@ -134,6 +126,12 @@ func (s *Server) serveUnhandled(w http.ResponseWriter, r *http.Request) {
go panic(fmt.Sprintf("testcontrol.Server received unhandled request: %s", got.Bytes()))
}
func (s *Server) serveMockPing(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
s.AddControlPingRequest()
io.WriteString(w, "A ControlPingRequest has been queued for our next MapResponse.")
}
func (s *Server) publicKey() wgkey.Key {
pub, _ := s.keyPair()
return pub
@@ -198,13 +196,6 @@ func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) {
func (s *Server) Node(nodeKey tailcfg.NodeKey) *tailcfg.Node {
s.mu.Lock()
defer s.mu.Unlock()
return s.nodeLocked(nodeKey)
}
// nodeLocked returns the node for nodeKey. It's always nil or cloned memory.
//
// s.mu must be held.
func (s *Server) nodeLocked(nodeKey tailcfg.NodeKey) *tailcfg.Node {
return s.nodes[nodeKey].Clone()
}
@@ -220,6 +211,16 @@ func (s *Server) AllNodes() (nodes []*tailcfg.Node) {
return nodes
}
// AddControlPingRequest enqueues a bool to PingRequestC.
// in serveMap this will result to a ControlPingRequest
// added to the next MapResponse sent to the client
func (s *Server) AddControlPingRequest() {
// Redundant check to avoid errors when called multiple times
if len(s.PingRequestC) == 0 {
s.PingRequestC <- true
}
}
func (s *Server) getUser(nodeKey tailcfg.NodeKey) (*tailcfg.User, *tailcfg.Login) {
s.mu.Lock()
defer s.mu.Unlock()
@@ -307,6 +308,7 @@ func (s *Server) CompleteAuth(authPathOrURL string) bool {
}
func (s *Server) serveRegister(w http.ResponseWriter, r *http.Request, mkey tailcfg.MachineKey) {
log.Println("SERVE REGISTER CALLED")
var req tailcfg.RegisterRequest
if err := s.decode(mkey, r.Body, &req); err != nil {
panic(fmt.Sprintf("serveRegister: decode: %v", err))
@@ -402,9 +404,6 @@ const (
// via a lite endpoint update. These ones are never dup-suppressed,
// as the client is expecting an answer regardless.
updateSelfChanged
// updateDebugInjection is an update used for PingRequests
updateDebugInjection
)
func (s *Server) updateLocked(source string, peers []tailcfg.NodeID) {
@@ -413,6 +412,24 @@ func (s *Server) updateLocked(source string, peers []tailcfg.NodeID) {
}
}
// Adds a PingRequest to a MapResponse, we will ping the first peer.
func (s *Server) addPingRequest(res *tailcfg.MapResponse) error {
if len(res.Peers) == 0 {
return errors.New("MapResponse has no peers to ping")
}
if len(res.Peers[0].Addresses) == 0 || len(res.Peers[0].AllowedIPs) == 0 {
return errors.New("peer has no Addresses or no AllowedIPs")
}
targetIP := res.Peers[0].AllowedIPs[0].IP()
res.PingRequest = &tailcfg.PingRequest{URL: s.BaseURL + "/ping", TestIP: targetIP, Types: "tsmp"}
// jsonRes, _ := json.MarshalIndent(res, "", " ")
// log.Println("jsonprint", string(jsonRes))
// log.Println("respeers", res.Peers)
// log.Println("allnodes", s.AllNodes(), res.Node.AllowedIPs)
return nil
}
// sendUpdate sends updateType to dst if dst is non-nil and
// has capacity.
func sendUpdate(dst chan<- updateType, updateType updateType) {
@@ -444,6 +461,7 @@ func (s *Server) UpdateNode(n *tailcfg.Node) (peersToUpdate []tailcfg.NodeID) {
}
func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey tailcfg.MachineKey) {
log.Println("SERVEMAP CALLED")
ctx := r.Context()
req := new(tailcfg.MapRequest)
@@ -496,9 +514,20 @@ func (s *Server) serveMap(w http.ResponseWriter, r *http.Request, mkey tailcfg.M
streaming := req.Stream && !req.ReadOnly
compress := req.Compress != ""
log.Println("CREATED MAPREQ", *req)
log.Println("REQUEST", r)
log.Println("REQBODY", r.Body)
w.WriteHeader(200)
for {
res, err := s.MapResponse(req)
log.Println("LENGTHER", len(s.PingRequestC))
select {
case <-s.PingRequestC:
log.Println("PINGADD", len(s.PingRequestC))
s.addPingRequest(res)
default:
log.Println("NOTEXIST")
}
if err != nil {
// TODO: log
return
@@ -559,6 +588,7 @@ var prodDERPMap = derpmap.Prod()
//
// No updates to s are done here.
func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse, err error) {
log.Println("MAPREQUEST : ", string(JsonPrint(req)))
node := s.Node(req.NodeKey)
if node == nil {
// node key rotated away (once test server supports that)
@@ -587,13 +617,6 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse,
}
res.Node.AllowedIPs = res.Node.Addresses
// Consume the PingRequest while protected by mutex if it exists
s.mu.Lock()
if pr, ok := s.pingReqsToAdd[node.Key]; ok {
res.PingRequest = pr
delete(s.pingReqsToAdd, node.Key)
}
s.mu.Unlock()
return res, nil
}
@@ -744,3 +767,24 @@ func breakSameNodeMapResponseStreams(req *tailcfg.MapRequest) bool {
}
return true
}
// This is where the PUT requests will go
func (s *Server) receivePingInfo(w http.ResponseWriter, r *http.Request) {
if r.Method != "PUT" {
log.Println("Received NON PUT request, should panic if this happens after")
// panic("Only PUT requests are supported currently")
}
w.Header().Set("Content-Type", "text/plain")
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
panic("Failed to read request body")
}
log.Println("Ping Info Received", string(reqBody))
w.WriteHeader(200)
io.WriteString(w, "Ping Streamed Back : "+string(reqBody))
}
func JsonPrint(item interface{}) []byte {
res, _ := json.MarshalIndent(item, "", " ")
return res
}

View File

@@ -1,98 +0,0 @@
# End-to-End VM-based Integration Testing
This test spins up a bunch of common linux distributions and then tries to get
them to connect to a
[`testcontrol`](https://pkg.go.dev/tailscale.com/tstest/integration/testcontrol)
server.
## Running
This test currently only runs on Linux.
This test depends on the following command line tools:
- [qemu](https://www.qemu.org/)
- [cdrkit](https://en.wikipedia.org/wiki/Cdrkit)
- [openssh](https://www.openssh.com/)
This test also requires the following:
- about 10 GB of temporary storage
- about 10 GB of cached VM images
- at least 4 GB of ram for virtual machines
- hardware virtualization support
([KVM](https://www.linux-kvm.org/page/Main_Page)) enabled in the BIOS
- the `kvm` module to be loaded (`modprobe kvm`)
- the user running these tests must have access to `/dev/kvm` (being in the
`kvm` group should suffice)
This optionally requires an AWS profile to be configured at the [default
path](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html).
The S3 bucket is set so that the requester pays. Please keep this in mind when
running these tests on your machine. If you are uncomfortable with the cost from
downloading from S3, you should pass the `-no-s3` flag to disable downloads from
S3. However keep in mind that some distributions do not use stable URLs for each
individual image artifact, so there may be spurious test failures as a result.
If you are using [Nix](https://nixos.org), you can run all of the tests with the
correct command line tools using this command:
```console
$ nix-shell -p openssh -p go -p qemu -p cdrkit --run "go test . --run-vm-tests --v --timeout 30m"
```
Keep the timeout high for the first run, especially if you are not downloading
VM images from S3. The mirrors we pull images from have download rate limits and
will take a while to download.
Because of the hardware requirements of this test, this test will not run
without the `--run-vm-tests` flag set.
## Other Fun Flags
This test's behavior is customized with command line flags.
### Don't Download Images From S3
If you pass the `-no-s3` flag to `go test`, the S3 step will be skipped in favor
of downloading the images directly from upstream sources, which may cause the
test to fail in odd places.
### Distribution Picking
This test runs on a large number of distributions. By default it tries to run
everything, which may or may not be ideal for you. If you only want to test a
subset of distributions, you can use the `--distro-regex` flag to match a subset
of distributions using a [regular expression](https://golang.org/pkg/regexp/)
such as like this:
```console
$ go test -run-vm-tests -distro-regex centos
```
This would run all tests on all versions of CentOS.
```console
$ go test -run-vm-tests -distro-regex '(debian|ubuntu)'
```
This would run all tests on all versions of Debian and Ubuntu.
### Ram Limiting
This test uses a lot of memory. In order to avoid making machines run out of
memory running this test, a semaphore is used to limit how many megabytes of ram
are being used at once. By default this semaphore is set to 4096 MB of ram
(about 4 gigabytes). You can customize this with the `--ram-limit` flag:
```console
$ go test --run-vm-tests --ram-limit 2048
$ go test --run-vm-tests --ram-limit 65536
```
The first example will set the limit to 2048 MB of ram (about 2 gigabytes). The
second example will set the limit to 65536 MB of ram (about 65 gigabytes).
Please be careful with this flag, improper usage of it is known to cause the
Linux out-of-memory killer to engage. Try to keep it within 50-75% of your
machine's available ram (there is some overhead involved with the
virtualization) to be on the safe side.

View File

@@ -1,86 +0,0 @@
// 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.
// +build linux
package vms
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/google/uuid"
)
/*
The images that we use for OpenSUSE Leap 15.1 have an issue that makes the
nocloud backend[1] for cloud-init just not work. As a distro-specific
workaround, we're gonna pretend to be OpenStack.
TODO(Xe): delete once we no longer need to support OpenSUSE Leap 15.1.
[1]: https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html
*/
type openSUSELeap151MetaData struct {
Zone string `json:"availability_zone"` // nova
Hostname string `json:"hostname"` // opensuse-leap-15-1
LaunchIndex string `json:"launch_index"` // 0
Meta openSUSELeap151MetaDataMeta `json:"meta"` // some openstack metadata we don't need to care about
Name string `json:"name"` // opensuse-leap-15-1
UUID string `json:"uuid"` // e9c664cd-b116-433b-aa61-7ff420163dcd
}
type openSUSELeap151MetaDataMeta struct {
Role string `json:"role"` // server
DSMode string `json:"dsmode"` // local
Essential string `json:"essential"` // essential
}
func hackOpenSUSE151UserData(t *testing.T, d Distro, dir string) bool {
if d.name != "opensuse-leap-15-1" {
return false
}
t.Log("doing OpenSUSE Leap 15.1 hack")
osDir := filepath.Join(dir, "openstack", "latest")
err := os.MkdirAll(osDir, 0755)
if err != nil {
t.Fatalf("can't make metadata home: %v", err)
}
metadata, err := json.Marshal(openSUSELeap151MetaData{
Zone: "nova",
Hostname: d.name,
LaunchIndex: "0",
Meta: openSUSELeap151MetaDataMeta{
Role: "server",
DSMode: "local",
Essential: "false",
},
Name: d.name,
UUID: uuid.New().String(),
})
if err != nil {
t.Fatalf("can't encode metadata: %v", err)
}
err = os.WriteFile(filepath.Join(osDir, "meta_data.json"), metadata, 0666)
if err != nil {
t.Fatalf("can't write to meta_data.json: %v", err)
}
data, err := os.ReadFile(filepath.Join(dir, "user-data"))
if err != nil {
t.Fatalf("can't read user_data: %v", err)
}
err = os.WriteFile(filepath.Join(osDir, "user_data"), data, 0666)
if err != nil {
t.Fatalf("can't create output user_data: %v", err)
}
return true
}

View File

@@ -1,30 +0,0 @@
// 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 vms
import "regexp"
type regexValue struct {
r *regexp.Regexp
}
func (r *regexValue) String() string {
if r.r == nil {
return ""
}
return r.r.String()
}
func (r *regexValue) Set(val string) error {
if rex, err := regexp.Compile(val); err != nil {
return err
} else {
r.r = rex
return nil
}
}
func (r regexValue) Unwrap() *regexp.Regexp { return r.r }

View File

@@ -1,22 +0,0 @@
// 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 vms
import (
"flag"
"testing"
)
func TestRegexFlag(t *testing.T) {
var v regexValue
fs := flag.NewFlagSet(t.Name(), flag.PanicOnError)
fs.Var(&v, "regex", "regex to parse")
const want = `.*`
fs.Parse([]string{"-regex", want})
if v.Unwrap().String() != want {
t.Fatalf("got wrong regex: %q, wanted: %q", v.Unwrap().String(), want)
}
}

View File

@@ -1,77 +0,0 @@
# This is a NixOS module to allow a machine to act as an integration test
# runner. This is used for the end-to-end VM test suite.
{ lib, config, pkgs, ... }:
{
# The GitHub Actions self-hosted runner service.
services.github-runner = {
enable = true;
url = "https://github.com/tailscale/tailscale";
replace = true;
extraLabels = [ "vm_integration_test" ];
# Justifications for the packages:
extraPackages = with pkgs; [
# The test suite is written in Go.
go
# This contains genisoimage, which is needed to create cloud-init
# seeds.
cdrkit
# This package is the virtual machine hypervisor we use in tests.
qemu
# This package contains tools like `ssh-keygen`.
openssh
# The C complier so cgo builds work.
gcc
];
# Customize this to include your GitHub username so we can track
# who is running which node.
name = "YOUR-GITHUB-USERNAME-tstest-integration-vms";
# Replace this with the path to the GitHub Actions runner token on
# your disk.
tokenFile = "/run/decrypted/ts-oss-ghaction-token";
};
# A user account so there is a home directory and so they have kvm
# access. Please don't change this account name.
users.users.ghrunner = {
createHome = true;
isSystemUser = true;
extraGroups = [ "kvm" ];
};
# The default github-runner service sets a lot of isolation features
# that attempt to limit the damage that malicious code can use.
# Unfortunately we rely on some "dangerous" features to do these tests,
# so this shim will peel some of them away.
systemd.services.github-runner = {
serviceConfig = {
# We need access to /dev to poke /dev/kvm.
PrivateDevices = lib.mkForce false;
# /dev/kvm is how qemu creates a virtual machine with KVM.
DeviceAllow = lib.mkForce [ "/dev/kvm" ];
# Ensure the service has KVM permissions with the `kvm` group.
ExtraGroups = [ "kvm" ];
# The service runs as a dynamic user by default. This makes it hard
# to persistently store things in /var/lib/ghrunner. This line
# disables the dynamic user feature.
DynamicUser = lib.mkForce false;
# Run this service as our ghrunner user.
User = "ghrunner";
# We need access to /var/lib/ghrunner to store VM images.
ProtectSystem = lib.mkForce null;
};
};
}

View File

@@ -7,14 +7,11 @@
package vms
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
@@ -22,7 +19,6 @@ import (
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"syscall"
@@ -30,36 +26,13 @@ import (
"text/template"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
expect "github.com/google/goexpect"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"golang.org/x/sync/semaphore"
"inet.af/netaddr"
"tailscale.com/net/interfaces"
"tailscale.com/tstest"
"tailscale.com/tstest/integration"
"tailscale.com/tstest/integration/testcontrol"
)
const (
securePassword = "hunter2"
bucketName = "tailscale-integration-vm-images"
)
var (
runVMTests = flag.Bool("run-vm-tests", false, "if set, run expensive VM based integration tests")
noS3 = flag.Bool("no-s3", false, "if set, always download images from the public internet (risks breaking)")
vmRamLimit = flag.Int("ram-limit", 4096, "the maximum number of megabytes of ram that can be used for VMs, must be greater than or equal to 1024")
distroRex = func() *regexValue {
result := &regexValue{r: regexp.MustCompile(`.*`)}
flag.Var(result, "distro-regex", "The regex that matches what distros should be run")
return result
}()
)
var runVMTests = flag.Bool("run-vm-tests", false, "if set, run expensive (10G+ ram) VM based integration tests")
type Distro struct {
name string // amazon-linux
@@ -73,127 +46,16 @@ func (d *Distro) InstallPre() string {
switch d.packageManager {
case "yum":
return ` - [ yum, update, gnupg2 ]
- [ yum, "-y", install, iptables ]`
case "zypper":
return ` - [ zypper, in, "-y", iptables ]`
case "dnf":
return ` - [ dnf, install, "-y", iptables ]`
`
case "apt":
return ` - [ apt-get, update ]
- [ apt-get, "-y", install, curl, "apt-transport-https", gnupg2 ]`
case "apk":
return ` - [ apk, "-U", add, curl, "ca-certificates" ]`
- [ apt-get, "-y", install, curl, "apt-transport-https", gnupg2 ]
`
}
return ""
}
func TestDownloadImages(t *testing.T) {
if !*runVMTests {
t.Skip("not running integration tests (need --run-vm-tests)")
}
for _, d := range distros {
distro := d
t.Run(distro.name, func(t *testing.T) {
if !distroRex.Unwrap().MatchString(distro.name) {
t.Skipf("distro name %q doesn't match regex: %s", distro.name, distroRex)
}
t.Parallel()
fetchDistro(t, distro)
})
}
}
var distros = []Distro{
// NOTE(Xe): If you run into issues getting the autoconfig to work, comment
// out all the other distros and uncomment this one. Connect with a VNC
// client with a command like this:
//
// $ vncviewer :0
//
// On NixOS you can get away with something like this:
//
// $ env NIXPKGS_ALLOW_UNFREE=1 nix-shell -p tigervnc --run 'vncviewer :0'
//
// Login as root with the password root. Then look in
// /var/log/cloud-init-output.log for what you messed up.
// {"alpine-edge", "https://xena.greedo.xeserv.us/pkg/alpine/img/alpine-edge-2021-05-18-cloud-init-within.qcow2", "b3bb15311c0bd3beffa1b554f022b75d3b7309b5fdf76fb146fe7c72b83b16d0", 256, "apk"},
{"amazon-linux", "https://cdn.amazonlinux.com/os-images/2.0.20210427.0/kvm/amzn2-kvm-2.0.20210427.0-x86_64.xfs.gpt.qcow2", "6ef9daef32cec69b2d0088626ec96410cd24afc504d57278bbf2f2ba2b7e529b", 512, "yum"},
{"arch", "https://mirror.pkgbuild.com/images/v20210515.22945/Arch-Linux-x86_64-cloudimg-20210515.22945.qcow2", "e4077f5ba3c5d545478f64834bc4852f9f7a2e05950fce8ecd0df84193162a27", 512, "pacman"},
{"centos-7", "https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-2003.qcow2c", "b7555ecf90b24111f2efbc03c1e80f7b38f1e1fc7e1b15d8fee277d1a4575e87", 512, "yum"},
{"centos-8", "https://cloud.centos.org/centos/8/x86_64/images/CentOS-8-GenericCloud-8.3.2011-20201204.2.x86_64.qcow2", "7ec97062618dc0a7ebf211864abf63629da1f325578868579ee70c495bed3ba0", 768, "dnf"},
{"debian-9", "http://cloud.debian.org/images/cloud/OpenStack/9.13.22-20210531/debian-9.13.22-20210531-openstack-amd64.qcow2", "c36e25f2ab0b5be722180db42ed9928476812f02d053620e1c287f983e9f6f1d", 512, "apt"},
{"debian-10", "https://cdimage.debian.org/images/cloud/buster/20210329-591/debian-10-generic-amd64-20210329-591.qcow2", "70c61956095870c4082103d1a7a1cb5925293f8405fc6cb348588ec97e8611b0", 768, "apt"},
{"fedora-34", "https://download.fedoraproject.org/pub/fedora/linux/releases/34/Cloud/x86_64/images/Fedora-Cloud-Base-34-1.2.x86_64.qcow2", "b9b621b26725ba95442d9a56cbaa054784e0779a9522ec6eafff07c6e6f717ea", 768, "dnf"},
{"opensuse-leap-15-1", "https://download.opensuse.org/repositories/Cloud:/Images:/Leap_15.1/images/openSUSE-Leap-15.1-OpenStack.x86_64.qcow2", "40bc72b8ee143364fc401f2c9c9a11ecb7341a29fa84c6f7bf42fc94acf19a02", 512, "zypper"},
{"opensuse-leap-15-2", "https://download.opensuse.org/repositories/Cloud:/Images:/Leap_15.2/images/openSUSE-Leap-15.2-OpenStack.x86_64.qcow2", "4df9cee9281d1f57d20f79dc65d76e255592b904760e73c0dd44ac753a54330f", 512, "zypper"},
{"opensuse-leap-15-3", "http://mirror.its.dal.ca/opensuse/distribution/leap/15.3/appliances/openSUSE-Leap-15.3-JeOS.x86_64-OpenStack-Cloud.qcow2", "22e0392e4d0becb523d1bc5f709366140b7ee20d6faf26de3d0f9046d1ee15d5", 512, "zypper"},
{"opensuse-tumbleweed", "https://download.opensuse.org/tumbleweed/appliances/openSUSE-Tumbleweed-JeOS.x86_64-OpenStack-Cloud.qcow2", "79e610bba3ed116556608f031c06e4b9260e3be2b193ce1727914ba213afac3f", 512, "zypper"},
{"ubuntu-16-04", "https://cloud-images.ubuntu.com/xenial/20210429/xenial-server-cloudimg-amd64-disk1.img", "50a21bc067c05e0c73bf5d8727ab61152340d93073b3dc32eff18b626f7d813b", 512, "apt"},
{"ubuntu-18-04", "https://cloud-images.ubuntu.com/bionic/20210526/bionic-server-cloudimg-amd64.img", "389ffd5d36bbc7a11bf384fd217cda9388ccae20e5b0cb7d4516733623c96022", 512, "apt"},
{"ubuntu-20-04", "https://cloud-images.ubuntu.com/focal/20210603/focal-server-cloudimg-amd64.img", "1c0969323b058ba8b91fec245527069c2f0502fc119b9138b213b6bfebd965cb", 512, "apt"},
{"ubuntu-20-10", "https://cloud-images.ubuntu.com/groovy/20210604/groovy-server-cloudimg-amd64.img", "2196df5f153faf96443e5502bfdbcaa0baaefbaec614348fec344a241855b0ef", 512, "apt"},
{"ubuntu-21-04", "https://cloud-images.ubuntu.com/hirsute/20210603/hirsute-server-cloudimg-amd64.img", "bf07f36fc99ff521d3426e7d257e28f0c81feebc9780b0c4f4e25ae594ff4d3b", 512, "apt"},
}
// fetchFromS3 fetches a distribution image from Amazon S3 or reports whether
// it is unable to. It can fail to fetch from S3 if there is either no AWS
// configuration (in ~/.aws/credentials) or if the `-no-s3` flag is passed. In
// that case the test will fall back to downloading distribution images from the
// public internet.
//
// Like fetching from HTTP, the test will fail if an error is encountered during
// the downloading process.
//
// This function writes the distribution image to fout. It is always closed. Do
// not expect fout to remain writable.
func fetchFromS3(t *testing.T, fout *os.File, d Distro) bool {
t.Helper()
if *noS3 {
t.Log("you asked to not use S3, not using S3")
return false
}
sess, err := session.NewSession(&aws.Config{
Region: aws.String("us-east-1"),
})
if err != nil {
t.Logf("can't make AWS session: %v", err)
return false
}
dler := s3manager.NewDownloader(sess, func(d *s3manager.Downloader) {
d.PartSize = 64 * 1024 * 1024 // 64MB per part
})
t.Logf("fetching s3://%s/%s", bucketName, d.sha256sum)
_, err = dler.Download(fout, &s3.GetObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String(d.sha256sum),
})
if err != nil {
fout.Close()
t.Fatalf("can't get s3://%s/%s: %v", bucketName, d.sha256sum, err)
}
err = fout.Close()
if err != nil {
t.Fatalf("can't close fout: %v", err)
}
return true
}
// fetchDistro fetches a distribution from the internet if it doesn't already exist locally. It
// also validates the sha256 sum from a known good hash.
func fetchDistro(t *testing.T, resultDistro Distro) {
@@ -208,93 +70,70 @@ func fetchDistro(t *testing.T, resultDistro Distro) {
qcowPath := filepath.Join(cdir, "qcow2", resultDistro.sha256sum)
_, err = os.Stat(qcowPath)
if err == nil {
hash := checkCachedImageHash(t, resultDistro, cdir)
if hash != resultDistro.sha256sum {
t.Logf("hash for %s (%s) doesn't match expected %s, re-downloading", resultDistro.name, qcowPath, resultDistro.sha256sum)
err = errors.New("some fake non-nil error to force a redownload")
if err := os.Remove(qcowPath); err != nil {
t.Fatalf("can't delete wrong cached image: %v", err)
}
}
}
if err != nil {
t.Logf("downloading distro image %s to %s", resultDistro.url, qcowPath)
fout, err := os.Create(qcowPath)
if err != nil {
t.Fatal(err)
}
if !fetchFromS3(t, fout, resultDistro) {
resp, err := http.Get(resultDistro.url)
if err != nil {
t.Fatalf("can't fetch qcow2 for %s (%s): %v", resultDistro.name, resultDistro.url, err)
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
t.Fatalf("%s replied %s", resultDistro.url, resp.Status)
}
_, err = io.Copy(fout, resp.Body)
resp.Body.Close()
if err != nil {
t.Fatalf("download of %s failed: %v", resultDistro.url, err)
}
hash := checkCachedImageHash(t, resultDistro, cdir)
if hash != resultDistro.sha256sum {
t.Fatalf("hash mismatch, want: %s, got: %s", resultDistro.sha256sum, hash)
}
resp, err := http.Get(resultDistro.url)
if err != nil {
t.Fatalf("can't fetch qcow2 for %s (%s): %v", resultDistro.name, resultDistro.url, err)
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
t.Fatalf("%s replied %s", resultDistro.url, resp.Status)
}
_, err = io.Copy(fout, resp.Body)
resp.Body.Close()
if err != nil {
t.Fatalf("download of %s failed: %v", resultDistro.url, err)
}
err = fout.Close()
if err != nil {
t.Fatalf("can't close fout: %v", err)
}
fin, err := os.Open(qcowPath)
if err != nil {
t.Fatal(err)
}
hasher := sha256.New()
if _, err := io.Copy(hasher, fin); err != nil {
t.Fatal(err)
}
hash := hex.EncodeToString(hasher.Sum(nil))
if hash != resultDistro.sha256sum {
t.Logf("got: %q", hash)
t.Logf("want: %q", resultDistro.sha256sum)
t.Fatal("hash mismatch, someone is doing something nasty")
}
t.Logf("hash check passed (%s)", resultDistro.sha256sum)
}
}
func checkCachedImageHash(t *testing.T, d Distro, cacheDir string) (gotHash string) {
t.Helper()
qcowPath := filepath.Join(cacheDir, "qcow2", d.sha256sum)
fin, err := os.Open(qcowPath)
if err != nil {
t.Fatal(err)
}
hasher := sha256.New()
if _, err := io.Copy(hasher, fin); err != nil {
t.Fatal(err)
}
hash := hex.EncodeToString(hasher.Sum(nil))
if hash != d.sha256sum {
t.Fatalf("hash mismatch, got: %q, want: %q", hash, d.sha256sum)
}
gotHash = hash
return
}
// run runs a command or fails the test.
func run(t *testing.T, dir, prog string, args ...string) {
t.Helper()
t.Logf("running: %s %s", prog, strings.Join(args, " "))
tstest.FixLogs(t)
cmd := exec.Command(prog, args...)
cmd.Stdout = log.Writer()
cmd.Stderr = log.Writer()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = dir
if err := cmd.Run(); err != nil {
t.Fatal(err)
}
}
// mkLayeredQcow makes a layered qcow image that allows us to keep the upstream
// VM images pristine and only do our changes on an overlay.
// mkLayeredQcow makes a layered qcow image that allows us to keep the upstream VM images
// pristine and only do our changes on an overlay.
func mkLayeredQcow(t *testing.T, tdir string, d Distro) {
t.Helper()
@@ -311,13 +150,7 @@ func mkLayeredQcow(t *testing.T, tdir string, d Distro) {
)
}
var (
metaDataTempl = template.Must(template.New("meta-data.yaml").Parse(metaDataTemplate))
userDataTempl = template.Must(template.New("user-data.yaml").Parse(userDataTemplate))
)
// mkSeed makes the cloud-init seed ISO that is used to configure a VM with
// tailscale.
// mkSeed makes the cloud-init seed ISO that is used to configure a VM with tailscale.
func mkSeed(t *testing.T, d Distro, sshKey, hostURL, tdir string, port int) {
t.Helper()
@@ -331,7 +164,7 @@ func mkSeed(t *testing.T, d Distro, sshKey, hostURL, tdir string, port int) {
t.Fatal(err)
}
err = metaDataTempl.Execute(fout, struct {
err = template.Must(template.New("meta-data.yaml").Parse(metaDataTemplate)).Execute(fout, struct {
ID string
Hostname string
}{
@@ -355,20 +188,18 @@ func mkSeed(t *testing.T, d Distro, sshKey, hostURL, tdir string, port int) {
t.Fatal(err)
}
err = userDataTempl.Execute(fout, struct {
err = template.Must(template.New("user-data.yaml").Parse(userDataTemplate)).Execute(fout, struct {
SSHKey string
HostURL string
Hostname string
Port int
InstallPre string
Password string
}{
SSHKey: strings.TrimSpace(sshKey),
HostURL: hostURL,
Hostname: d.name,
Port: port,
InstallPre: d.InstallPre(),
Password: securePassword,
})
if err != nil {
t.Fatal(err)
@@ -380,23 +211,17 @@ func mkSeed(t *testing.T, d Distro, sshKey, hostURL, tdir string, port int) {
}
}
args := []string{
run(t, tdir, "genisoimage",
"-output", filepath.Join(dir, "seed.iso"),
"-volid", "cidata", "-joliet", "-rock",
filepath.Join(dir, "meta-data"),
filepath.Join(dir, "user-data"),
}
if hackOpenSUSE151UserData(t, d, dir) {
args = append(args, filepath.Join(dir, "openstack"))
}
run(t, tdir, "genisoimage", args...)
)
}
// mkVM makes a KVM-accelerated virtual machine and prepares it for introduction
// to the testcontrol server. The function it returns is for killing the virtual
// machine when it is time for it to die.
// mkVM makes a KVM-accelerated virtual machine and prepares it for introduction to the
// testcontrol server. The function it returns is for killing the virtual machine when it
// is time for it to die.
func mkVM(t *testing.T, n int, d Distro, sshKey, hostURL, tdir string) func() {
t.Helper()
@@ -404,8 +229,9 @@ func mkVM(t *testing.T, n int, d Distro, sshKey, hostURL, tdir string) func() {
if err != nil {
t.Fatalf("can't find cache dir: %v", err)
}
cdir = filepath.Join(cdir, "tailscale", "vm-test")
cdir = filepath.Join(cdir, "within", "mkvm")
os.MkdirAll(filepath.Join(cdir, "qcow2"), 0755)
os.MkdirAll(filepath.Join(cdir, "seed"), 0755)
port := 23100 + n
@@ -424,16 +250,12 @@ func mkVM(t *testing.T, n int, d Distro, sshKey, hostURL, tdir string) func() {
"-drive", driveArg,
"-cdrom", filepath.Join(tdir, d.name, "seed", "seed.iso"),
"-vnc", fmt.Sprintf(":%d", n),
"-smbios", "type=1,serial=ds=nocloud;h=" + d.name,
}
t.Logf("running: qemu-system-x86_64 %s", strings.Join(args, " "))
cmd := exec.Command("qemu-system-x86_64", args...)
cmd.Stdout = log.Writer()
cmd.Stderr = log.Writer()
err = cmd.Start()
if err != nil {
t.Fatal(err)
}
@@ -449,41 +271,43 @@ func mkVM(t *testing.T, n int, d Distro, sshKey, hostURL, tdir string) func() {
if err != nil {
t.Errorf("can't kill %s (%d): %v", d.name, cmd.Process.Pid, err)
}
cmd.Wait()
}
}
// ipMapping maps a hostname, SSH port and SSH IP together
type ipMapping struct {
name string
port int
ip string
}
// TestVMIntegrationEndToEnd creates a virtual machine with qemu, installs
// tailscale on it and then ensures that it connects to the network
// successfully.
// TestVMIntegrationEndToEnd creates a virtual machine with mkvm(1X), installs tailscale on it and then ensures that it connects to the network successfully.
func TestVMIntegrationEndToEnd(t *testing.T) {
if !*runVMTests {
t.Skip("not running integration tests (need --run-vm-tests)")
t.Skip("not running integration tests (need -run-vm-tests)")
}
os.Setenv("CGO_ENABLED", "0")
if _, err := exec.LookPath("qemu-system-x86_64"); err != nil {
t.Logf("hint: nix-shell -p go -p qemu -p cdrkit --run 'go test --v --timeout=60m --run-vm-tests'")
t.Logf("hint: nix-shell -p go -p qemu -p cdrkit --run 'go test -v -timeout=60m -run-vm-tests'")
t.Fatalf("missing dependency: %v", err)
}
if _, err := exec.LookPath("genisoimage"); err != nil {
t.Logf("hint: nix-shell -p go -p qemu -p cdrkit --run 'go test --v --timeout=60m --run-vm-tests'")
t.Logf("hint: nix-shell -p go -p qemu -p cdrkit --run 'go test -v -timeout=60m -run-vm-tests'")
t.Fatalf("missing dependency: %v", err)
}
dir := t.TempDir()
distros := []Distro{
{"amazon-linux", "https://cdn.amazonlinux.com/os-images/2.0.20210427.0/kvm/amzn2-kvm-2.0.20210427.0-x86_64.xfs.gpt.qcow2", "6ef9daef32cec69b2d0088626ec96410cd24afc504d57278bbf2f2ba2b7e529b", 512, "yum"},
{"centos-7", "https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2", "1db30c9c272fb37b00111b93dcebff16c278384755bdbe158559e9c240b73b80", 512, "yum"},
{"centos-8", "https://cloud.centos.org/centos/8/x86_64/images/CentOS-8-GenericCloud-8.3.2011-20201204.2.x86_64.qcow2", "7ec97062618dc0a7ebf211864abf63629da1f325578868579ee70c495bed3ba0", 768, "dnf"},
{"debian-9", "https://cdimage.debian.org/cdimage/openstack/9.13.21-20210511/debian-9.13.21-20210511-openstack-amd64.qcow2", "0667a08e2d947b331aee068db4bbf3a703e03edaf5afa52e23d534adff44b62a", 512, "apt"},
{"debian-10", "https://cdimage.debian.org/images/cloud/buster/20210329-591/debian-10-generic-amd64-20210329-591.qcow2", "70c61956095870c4082103d1a7a1cb5925293f8405fc6cb348588ec97e8611b0", 768, "apt"},
{"fedora-34", "https://download.fedoraproject.org/pub/fedora/linux/releases/34/Cloud/x86_64/images/Fedora-Cloud-Base-34-1.2.x86_64.qcow2", "b9b621b26725ba95442d9a56cbaa054784e0779a9522ec6eafff07c6e6f717ea", 768, "dnf"},
{"opensuse-leap-15.1", "https://download.opensuse.org/repositories/Cloud:/Images:/Leap_15.1/images/openSUSE-Leap-15.1-OpenStack.x86_64.qcow2", "3203e256dab5981ca3301408574b63bc522a69972fbe9850b65b54ff44a96e0a", 512, "zypper"},
{"opensuse-leap-15.2", "https://download.opensuse.org/repositories/Cloud:/Images:/Leap_15.2/images/openSUSE-Leap-15.2-OpenStack.x86_64.qcow2", "4df9cee9281d1f57d20f79dc65d76e255592b904760e73c0dd44ac753a54330f", 512, "zypper"},
{"opensuse-tumbleweed", "https://download.opensuse.org/tumbleweed/appliances/openSUSE-Tumbleweed-JeOS.x86_64-OpenStack-Cloud.qcow2", "ba3ecd281045b5019f0fb11378329a644a41870b77631ea647b128cd07eb804b", 512, "zypper"},
{"ubuntu-16-04", "https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img", "50a21bc067c05e0c73bf5d8727ab61152340d93073b3dc32eff18b626f7d813b", 512, "apt"},
{"ubuntu-18-04", "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img", "08396cf95c18534a2e3f88289bd92d18eee76f0e75813636b3ab9f1e603816d7", 512, "apt"},
{"ubuntu-20-04", "https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img", "513158b22ff0f08d0a078d8d60293bcddffdb17094a7809c76c52aba415ecc54", 512, "apt"},
{"ubuntu-20-10", "https://cloud-images.ubuntu.com/groovy/current/groovy-server-cloudimg-amd64.img", "e470df72fce4fb8d0ee4ef8af8eed740ee3bf51290515eb42e5c747725e98b6d", 512, "apt"},
{"ubuntu-21-04", "https://cloud-images.ubuntu.com/hirsute/current/hirsute-server-cloudimg-amd64.img", "7fab8eda0bcf6f8f6e63845ccf1e29de4706e3359c82d3888835093020fe6f05", 512, "apt"},
}
rex := distroRex.Unwrap()
dir := t.TempDir()
ln, err := net.Listen("tcp", deriveBindhost(t)+":0")
if err != nil {
@@ -496,7 +320,7 @@ func TestVMIntegrationEndToEnd(t *testing.T) {
var (
ipMu sync.Mutex
ipMap = map[string]ipMapping{}
ipMap = map[string]string{} // SSH port => IP address
)
mux := http.NewServeMux()
@@ -512,12 +336,7 @@ func TestVMIntegrationEndToEnd(t *testing.T) {
name := path.Base(r.URL.Path)
host, _, _ := net.SplitHostPort(r.RemoteAddr)
port, err := strconv.Atoi(name)
if err != nil {
log.Panicf("bad port: %v", port)
}
distro := r.UserAgent()
ipMap[distro] = ipMapping{distro, port, host}
ipMap[name] = host
t.Logf("%s: %v", name, host)
})
@@ -543,224 +362,133 @@ func TestVMIntegrationEndToEnd(t *testing.T) {
loginServer := fmt.Sprintf("http://%s", ln.Addr())
t.Logf("loginServer: %s", loginServer)
tstest.FixLogs(t)
defer tstest.UnfixLogs(t)
cancels := make(chan func(), len(distros))
ramsem := semaphore.NewWeighted(int64(*vmRamLimit))
bins := integration.BuildTestBinaries(t)
t.Run("do", func(t *testing.T) {
t.Run("mkvm", func(t *testing.T) {
for n, distro := range distros {
n, distro := n, distro
if rex.MatchString(distro.name) {
t.Logf("%s matches %s", distro.name, rex)
} else {
continue
}
t.Run(distro.name, func(t *testing.T) {
ctx, done := context.WithCancel(context.Background())
defer done()
t.Parallel()
err := ramsem.Acquire(ctx, int64(distro.mem))
if err != nil {
t.Fatalf("can't acquire ram semaphore: %v", err)
}
defer ramsem.Release(int64(distro.mem))
cancel := mkVM(t, n, distro, string(pubkey), loginServer, dir)
defer cancel()
var ipm ipMapping
t.Run("wait-for-start", func(t *testing.T) {
waiter := time.NewTicker(time.Second)
defer waiter.Stop()
var ok bool
for {
<-waiter.C
ipMu.Lock()
if ipm, ok = ipMap[distro.name]; ok {
ipMu.Unlock()
break
}
ipMu.Unlock()
}
})
testDistro(t, loginServer, signer, ipm, bins)
cancels <- cancel
})
}
})
}
func testDistro(t *testing.T, loginServer string, signer ssh.Signer, ipm ipMapping, bins *integration.Binaries) {
t.Helper()
port := ipm.port
hostport := fmt.Sprintf("127.0.0.1:%d", port)
ccfg := &ssh.ClientConfig{
User: "root",
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer), ssh.Password(securePassword)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
close(cancels)
for cancel := range cancels {
//lint:ignore SA9001 They do actually get ran
defer cancel()
// NOTE(Xe): This deadline loop helps to make things a bit faster, centos
// sometimes is slow at starting its sshd and will sometimes randomly kill
// SSH sessions on transition to multi-user.target. I don't know why they
// don't use socket activation.
const maxRetries = 5
var working bool
for i := 0; i < maxRetries; i++ {
cli, err := ssh.Dial("tcp", hostport, ccfg)
if err == nil {
working = true
cli.Close()
if len(cancels) == 0 {
t.Log("all VMs started")
break
}
time.Sleep(10 * time.Second)
}
if !working {
t.Fatalf("can't connect to %s, tried %d times", hostport, maxRetries)
}
t.Run("wait-for-vms", func(t *testing.T) {
t.Log("waiting for VMs to register")
waiter := time.NewTicker(time.Second)
defer waiter.Stop()
n := 0
for {
<-waiter.C
ipMu.Lock()
if len(ipMap) == len(distros) {
ipMu.Unlock()
break
} else {
if n%30 == 0 {
t.Logf("ipMap: %d", len(ipMap))
t.Logf("distros: %d", len(distros))
}
}
n++
ipMu.Unlock()
}
})
t.Logf("about to ssh into 127.0.0.1:%d", port)
cli, err := ssh.Dial("tcp", hostport, ccfg)
if err != nil {
t.Fatal(err)
}
copyBinaries(t, cli, bins)
ipMu.Lock()
defer ipMu.Unlock()
t.Run("join-net", func(t *testing.T) {
for port := range ipMap {
port := port
t.Run(port, func(t *testing.T) {
config := &ssh.ClientConfig{
User: "ts",
Auth: []ssh.AuthMethod{ssh.PublicKeys(signer), ssh.Password("hunter2")},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
timeout := 5 * time.Minute
cli, err := ssh.Dial("tcp", fmt.Sprintf("127.0.0.1:%s", port), config)
if err != nil {
t.Fatalf("can't dial 127.0.0.1:%s: %v", port, err)
}
defer cli.Close()
e, _, err := expect.SpawnSSH(cli, timeout, expect.Verbose(true), expect.VerboseWriter(log.Writer()))
if err != nil {
t.Fatalf("%d: can't register a shell session: %v", port, err)
}
defer e.Close()
t.Parallel()
t.Logf("about to ssh into 127.0.0.1:%s", port)
timeout := 5 * time.Minute
t.Log("opened session")
e, _, err := expect.SpawnSSH(cli, timeout, expect.Verbose(true), expect.VerboseWriter(os.Stdout))
if err != nil {
t.Fatalf("%s: can't register a shell session: %v", port, err)
}
defer e.Close()
_, _, err = e.Expect(regexp.MustCompile(`(\#)`), timeout)
if err != nil {
t.Fatalf("%d: can't get a shell: %v", port, err)
}
t.Logf("got shell for %d", port)
err = e.Send("systemctl start tailscaled.service\n")
if err != nil {
t.Fatalf("can't send command to start tailscaled: %v", err)
}
_, _, err = e.Expect(regexp.MustCompile(`(\#)`), timeout)
if err != nil {
t.Fatalf("%d: can't get a shell: %v", port, err)
}
err = e.Send(fmt.Sprintf("sudo tailscale up --login-server %s\n", loginServer))
if err != nil {
t.Fatalf("%d: can't send tailscale up command: %v", port, err)
}
_, _, err = e.Expect(regexp.MustCompile(`Success.`), timeout)
if err != nil {
t.Fatalf("not successful: %v", err)
}
}
_, _, err = e.Expect(regexp.MustCompile(`(\$|\>)`), timeout)
if err != nil {
t.Fatalf("%s: can't get a shell: %v", port, err)
}
t.Logf("got shell for %s", port)
err = e.Send(fmt.Sprintf("sudo tailscale up --login-server %s\n", loginServer))
if err != nil {
t.Fatalf("%s: can't send tailscale up command: %v", port, err)
}
_, _, err = e.Expect(regexp.MustCompile(`Success.`), timeout)
if err != nil {
t.Fatalf("can't extract URL: %v", err)
}
})
}
})
func copyBinaries(t *testing.T, conn *ssh.Client, bins *integration.Binaries) {
cli, err := sftp.NewClient(conn)
if err != nil {
t.Fatalf("can't connect over sftp to copy binaries: %v", err)
}
mkdir(t, cli, "/usr/bin")
mkdir(t, cli, "/usr/sbin")
mkdir(t, cli, "/etc/systemd/system")
mkdir(t, cli, "/etc/default")
copyFile(t, cli, bins.Daemon, "/usr/sbin/tailscaled")
copyFile(t, cli, bins.CLI, "/usr/bin/tailscale")
// TODO(Xe): revisit this life decision, hopefully before this assumption
// breaks the test.
copyFile(t, cli, "../../../cmd/tailscaled/tailscaled.defaults", "/etc/default/tailscaled")
copyFile(t, cli, "../../../cmd/tailscaled/tailscaled.service", "/etc/systemd/system/tailscaled.service")
t.Log("tailscale installed!")
}
func mkdir(t *testing.T, cli *sftp.Client, name string) {
t.Helper()
err := cli.MkdirAll(name)
if err != nil {
t.Fatalf("can't make %s: %v", name, err)
}
}
func copyFile(t *testing.T, cli *sftp.Client, localSrc, remoteDest string) {
t.Helper()
fin, err := os.Open(localSrc)
if err != nil {
t.Fatalf("can't open: %v", err)
}
defer fin.Close()
fi, err := fin.Stat()
if err != nil {
t.Fatalf("can't stat: %v", err)
}
fout, err := cli.Create(remoteDest)
if err != nil {
t.Fatalf("can't create output file: %v", err)
}
err = fout.Chmod(fi.Mode())
if err != nil {
fout.Close()
t.Fatalf("can't chmod fout: %v", err)
}
n, err := io.Copy(fout, fin)
if err != nil {
fout.Close()
t.Fatalf("copy failed: %v", err)
}
if fi.Size() != n {
t.Fatalf("incorrect number of bytes copied: wanted: %d, got: %d", fi.Size(), n)
}
err = fout.Close()
if err != nil {
t.Fatalf("can't close fout on remote host: %v", err)
if numNodes := cs.NumNodes(); numNodes != len(ipMap) {
t.Errorf("wanted %d nodes, got: %d", len(ipMap), numNodes)
}
}
func deriveBindhost(t *testing.T) string {
t.Helper()
ifName, err := interfaces.DefaultRouteInterface()
ifaces, err := net.Interfaces()
if err != nil {
t.Fatal(err)
}
var ret string
err = interfaces.ForeachInterfaceAddress(func(i interfaces.Interface, prefix netaddr.IPPrefix) {
if ret != "" || i.Name != ifName {
return
rex := regexp.MustCompile(`^(eth|enp|wlp|wlan)`)
for _, iface := range ifaces {
t.Logf("found interface %s: %d", iface.Name, iface.Flags&net.FlagUp)
if (iface.Flags & net.FlagUp) == 0 {
continue
}
if rex.MatchString(iface.Name) {
addrs, err := iface.Addrs()
if err != nil {
t.Fatalf("can't get address for %s: %v", iface.Name, err)
}
for _, addr := range addrs {
return netaddr.MustParseIPPrefix(addr.String()).IP().String()
}
}
ret = prefix.IP().String()
})
if ret != "" {
return ret
}
if err != nil {
t.Fatal(err)
}
t.Fatal("can't find a bindhost")
return "unreachable"
return "invalid"
}
func TestDeriveBindhost(t *testing.T) {
@@ -781,15 +509,12 @@ cloud_final_modules:
- [scripts-user, once-per-instance]
users:
- name: root
ssh-authorized-keys:
- {{.SSHKey}}
- name: ts
plain_text_passwd: {{.Password}}
groups: [ wheel ]
sudo: [ "ALL=(ALL) NOPASSWD:ALL" ]
shell: /bin/sh
ssh-authorized-keys:
- name: ts
plain_text_passwd: hunter2
groups: [ wheel ]
sudo: [ "ALL=(ALL) NOPASSWD:ALL" ]
shell: /bin/sh
ssh-authorized-keys:
- {{.SSHKey}}
write_files:
@@ -801,5 +526,7 @@ write_files:
runcmd:
{{.InstallPre}}
- [ "sh", "-c", "curl https://raw.githubusercontent.com/tailscale/tailscale/Xe/test-install-script-libvirtd/scripts/installer.sh | sh" ]
- [ systemctl, enable, --now, tailscaled.service ]
- [ curl, "{{.HostURL}}/myip/{{.Port}}", "-H", "User-Agent: {{.Hostname}}" ]
`

View File

@@ -89,3 +89,64 @@ func (fn JSONHandlerFunc) ServeHTTPReturn(w http.ResponseWriter, r *http.Request
w.Write(b)
return err
}
// TODO() Set this function such that chunk encoding works
// Currently the same thing with chunking headers set.
func (fn JSONHandlerFunc) ServeHTTPChunkEncodingReturn(w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Connection", "Keep-Alive")
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("X-Content-Type-Options", "nosniff")
var resp *response
status, data, err := fn(r)
if err != nil {
if werr, ok := err.(HTTPError); ok {
resp = &response{
Status: "error",
Error: werr.Msg,
Data: data,
}
// Unwrap the HTTPError here because we are communicating with
// the client in this handler. We don't want the wrapping
// ReturnHandler to do it too.
err = werr.Err
if werr.Msg != "" {
err = fmt.Errorf("%s: %w", werr.Msg, err)
}
// take status from the HTTPError to encourage error handling in one location
if status != 0 && status != werr.Code {
err = fmt.Errorf("[unexpected] non-zero status that does not match HTTPError status, status: %d, HTTPError.code: %d: %w", status, werr.Code, err)
}
status = werr.Code
} else {
status = http.StatusInternalServerError
resp = &response{
Status: "error",
Error: "internal server error",
}
}
} else if status == 0 {
status = http.StatusInternalServerError
resp = &response{
Status: "error",
Error: "internal server error",
}
} else if err == nil {
resp = &response{
Status: "success",
Data: data,
}
}
b, jerr := json.Marshal(resp)
if jerr != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"status":"error","error":"json marshal error"}`))
if err != nil {
return fmt.Errorf("%w, and then we could not respond: %v", err, jerr)
}
return jerr
}
w.WriteHeader(status)
w.Write(b)
return err
}

View File

@@ -21,48 +21,46 @@ const (
type FQDN string
func ToFQDN(s string) (FQDN, error) {
if isValidFQDN(s) {
return FQDN(s), nil
}
if len(s) == 0 || s == "." {
return FQDN("."), nil
}
if s[len(s)-1] == '.' {
s = s[:len(s)-1]
}
if s[0] == '.' {
s = s[1:]
}
raw := s
totalLen := len(s)
if s[len(s)-1] == '.' {
s = s[:len(s)-1]
} else {
totalLen += 1 // account for missing dot
}
if totalLen > maxNameLength {
if len(s) > maxNameLength {
return "", fmt.Errorf("%q is too long to be a DNS name", s)
}
st := 0
for i := 0; i < len(s); i++ {
if s[i] != '.' {
continue
fs := strings.Split(s, ".")
for _, f := range fs {
if !validLabel(f) {
return "", fmt.Errorf("%q is not a valid DNS label", f)
}
label := s[st:i]
// You might be tempted to do further validation of the
// contents of labels here, based on the hostname rules in RFC
// 1123. However, DNS labels are not always subject to
// hostname rules. In general, they can contain any non-zero
// byte sequence, even though in practice a more restricted
// set is used.
//
// See https://github.com/tailscale/tailscale/issues/2024 for more.
if len(label) == 0 || len(label) > maxLabelLength {
return "", fmt.Errorf("%q is not a valid DNS label", label)
}
st = i + 1
}
if raw[len(raw)-1] != '.' {
raw = raw + "."
return FQDN(s + "."), nil
}
func validLabel(s string) bool {
if len(s) == 0 || len(s) > maxLabelLength {
return false
}
return FQDN(raw), nil
if !isalphanum(s[0]) || !isalphanum(s[len(s)-1]) {
return false
}
for i := 1; i < len(s)-1; i++ {
if !isalphanum(s[i]) && s[i] != '-' {
return false
}
}
return true
}
// WithTrailingDot returns f as a string, with a trailing dot.
@@ -94,6 +92,51 @@ func (f FQDN) Contains(other FQDN) bool {
return strings.HasSuffix(other.WithTrailingDot(), cmp)
}
// isValidFQDN reports whether s is already a valid FQDN, without
// allocating.
func isValidFQDN(s string) bool {
if len(s) == 0 {
return false
}
if len(s) > maxNameLength {
return false
}
// DNS root name.
if s == "." {
return true
}
// Missing trailing dot.
if s[len(s)-1] != '.' {
return false
}
// Leading dots not allowed.
if s[0] == '.' {
return false
}
st := 0
for i := 0; i < len(s); i++ {
if s[i] != '.' {
continue
}
label := s[st:i]
if len(label) == 0 || len(label) > maxLabelLength {
return false
}
if !isalphanum(label[0]) || !isalphanum(label[len(label)-1]) {
return false
}
for j := 1; j < len(label)-1; j++ {
if !isalphanum(label[j]) && label[j] != '-' {
return false
}
}
st = i + 1
}
return true
}
// SanitizeLabel takes a string intended to be a DNS name label
// and turns it into a valid name label according to RFC 1035.
func SanitizeLabel(label string) string {

View File

@@ -24,7 +24,6 @@ func TestFQDN(t *testing.T) {
{".foo.com", "foo.com.", false, 2},
{"com", "com.", false, 1},
{"www.tailscale.com", "www.tailscale.com.", false, 3},
{"_ssh._tcp.tailscale.com", "_ssh._tcp.tailscale.com.", false, 4},
{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com", "", true, 0},
{strings.Repeat("aaaaa.", 60) + "com", "", true, 0},
{"foo..com", "", true, 0},
@@ -185,24 +184,3 @@ func TestTrimSuffix(t *testing.T) {
}
}
}
var sinkFQDN FQDN
func BenchmarkToFQDN(b *testing.B) {
tests := []string{
"www.tailscale.com.",
"www.tailscale.com",
".www.tailscale.com",
"_ssh._tcp.www.tailscale.com.",
"_ssh._tcp.www.tailscale.com",
}
for _, test := range tests {
b.Run(test, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
sinkFQDN, _ = ToFQDN(test)
}
})
}
}

9
version/GENERATE.go Normal file
View File

@@ -0,0 +1,9 @@
// Copyright (c) 2020 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.
// Placeholder that indicates this directory is a valid go package,
// but that redo must 'redo all' in this directory before it can
// be imported.
package version

2
version/all.do Normal file
View File

@@ -0,0 +1,2 @@
redo-ifchange ver.go version.xcconfig version.h

1
version/clean.do Normal file
View File

@@ -0,0 +1 @@
rm -f *~ .*~ describe.txt long.txt short.txt version.xcconfig ver.go version.h version version-info.sh

102
version/mkversion_test.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright (c) 2020 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 version
import (
"fmt"
"os/exec"
"runtime"
"strconv"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
)
func mkversion(t *testing.T, gitHash, otherHash string, major, minor, patch, changeCount int) (string, bool) {
t.Helper()
bs, err := exec.Command("./version.sh", gitHash, otherHash, strconv.Itoa(major), strconv.Itoa(minor), strconv.Itoa(patch), strconv.Itoa(changeCount)).CombinedOutput()
out := strings.TrimSpace(string(bs))
if err != nil {
return out, false
}
return out, true
}
func TestMkversion(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("skip test on Windows, because there is no shell to execute mkversion.sh.")
}
tests := []struct {
gitHash, otherHash string
major, minor, patch, changeCount int
want string
}{
{"abcdef", "", 0, 98, 0, 0, `
VERSION_SHORT="0.98.0"
VERSION_LONG="0.98.0-tabcdef"
VERSION_GIT_HASH="abcdef"
VERSION_EXTRA_HASH=""
VERSION_XCODE="100.98.0"
VERSION_WINRES="0,98,0,0"`},
{"abcdef", "", 0, 98, 1, 0, `
VERSION_SHORT="0.98.1"
VERSION_LONG="0.98.1-tabcdef"
VERSION_GIT_HASH="abcdef"
VERSION_EXTRA_HASH=""
VERSION_XCODE="100.98.1"
VERSION_WINRES="0,98,1,0"`},
{"abcdef", "", 1, 1, 0, 37, `
VERSION_SHORT="1.1.1037"
VERSION_LONG="1.1.1037-tabcdef"
VERSION_GIT_HASH="abcdef"
VERSION_EXTRA_HASH=""
VERSION_XCODE="101.1.1037"
VERSION_WINRES="1,1,1037,0"`},
{"abcdef", "", 1, 2, 9, 0, `
VERSION_SHORT="1.2.9"
VERSION_LONG="1.2.9-tabcdef"
VERSION_GIT_HASH="abcdef"
VERSION_EXTRA_HASH=""
VERSION_XCODE="101.2.9"
VERSION_WINRES="1,2,9,0"`},
{"abcdef", "", 1, 15, 0, 129, `
VERSION_SHORT="1.15.129"
VERSION_LONG="1.15.129-tabcdef"
VERSION_GIT_HASH="abcdef"
VERSION_EXTRA_HASH=""
VERSION_XCODE="101.15.129"
VERSION_WINRES="1,15,129,0"`},
{"abcdef", "", 1, 2, 0, 17, `
VERSION_SHORT="1.2.0"
VERSION_LONG="1.2.0-17-tabcdef"
VERSION_GIT_HASH="abcdef"
VERSION_EXTRA_HASH=""
VERSION_XCODE="101.2.0"
VERSION_WINRES="1,2,0,0"`},
{"abcdef", "defghi", 1, 15, 0, 129, `
VERSION_SHORT="1.15.129"
VERSION_LONG="1.15.129-tabcdef-gdefghi"
VERSION_GIT_HASH="abcdef"
VERSION_EXTRA_HASH="defghi"
VERSION_XCODE="101.15.129"
VERSION_WINRES="1,15,129,0"`},
{"abcdef", "", 0, 99, 5, 0, ""}, // unstable, patch number not allowed
{"abcdef", "", 0, 99, 5, 123, ""}, // unstable, patch number not allowed
}
for _, test := range tests {
want := strings.ReplaceAll(strings.TrimSpace(test.want), " ", "")
got, ok := mkversion(t, test.gitHash, test.otherHash, test.major, test.minor, test.patch, test.changeCount)
invoc := fmt.Sprintf("version.sh %s %s %d %d %d %d", test.gitHash, test.otherHash, test.major, test.minor, test.patch, test.changeCount)
if want == "" && ok {
t.Errorf("%s ok=true, want false", invoc)
continue
}
if diff := cmp.Diff(got, want); want != "" && diff != "" {
t.Errorf("%s wrong output (-got+want):\n%s", invoc, diff)
}
}
}

9
version/ver.go.do Normal file
View File

@@ -0,0 +1,9 @@
redo-ifchange version-info.sh ver.go.in
. ./version-info.sh
sed -e "s/{LONGVER}/$VERSION_LONG/g" \
-e "s/{SHORTVER}/$VERSION_SHORT/g" \
-e "s/{GITCOMMIT}/$VERSION_GIT_HASH/g" \
-e "s/{EXTRAGITCOMMIT}/$VERSION_EXTRA_HASH/g" \
<ver.go.in >$3

14
version/ver.go.in Normal file
View File

@@ -0,0 +1,14 @@
// Copyright (c) 2020 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.
// +build redo
package version
const Long = "{LONGVER}"
const Short = "{SHORTVER}"
const LONG = Long
const SHORT = Short
const GitCommit = "{GITCOMMIT}"
const ExtraGitCommit = "{EXTRAGITCOMMIT}"

View File

@@ -0,0 +1,3 @@
./version.sh ../.. >$3
redo-always
redo-stamp <$3

View File

@@ -2,30 +2,31 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !redo,!xversion
// Package version provides the version that the binary was built at.
package version
// Long is a full version number for this build, of the form
// "x.y.z-commithash", or "date.yyyymmdd" if no actual version was
// provided.
var Long = "date.20210603"
const Long = "date.20210505"
// Short is a short version number for this build, of the form
// "x.y.z", or "date.yyyymmdd" if no actual version was provided.
var Short = ""
const Short = Long
func init() {
if Short == "" {
// If it hasn't been link-stamped with -X:
Short = Long
}
}
// LONG is a deprecated alias for Long. Don't use it.
const LONG = Long
// SHORT is a deprecated alias for Short. Don't use it.
const SHORT = Short
// GitCommit, if non-empty, is the git commit of the
// github.com/tailscale/tailscale repository at which Tailscale was
// built. Its format is the one returned by `git describe --always
// --exclude "*" --dirty --abbrev=200`.
var GitCommit = ""
const GitCommit = ""
// ExtraGitCommit, if non-empty, is the git commit of a "supplemental"
// repository at which Tailscale was built. Its format is the same as
@@ -37,4 +38,4 @@ var GitCommit = ""
// Android OSS repository). Together, GitCommit and ExtraGitCommit
// exactly describe what repositories and commits were used in a
// build.
var ExtraGitCommit = ""
const ExtraGitCommit = ""

9
version/version.h.do Normal file
View File

@@ -0,0 +1,9 @@
redo-ifchange version-info.sh
. ./version-info.sh
cat >$3 <<EOF
#define TAILSCALE_VERSION_LONG "$VERSION_LONG"
#define TAILSCALE_VERSION_SHORT "$VERSION_SHORT"
#define TAILSCALE_VERSION_WIN_RES $VERSION_WINRES
EOF

130
version/version.sh Executable file
View File

@@ -0,0 +1,130 @@
#!/bin/sh
set -eu
# Return the commitid of the given ref in the given repo dir. If the worktree
# or index is dirty, also appends -dirty.
#
# $ git_hash_dirty ../.. HEAD
# 1be01ddc6e430ca3aa9beea3587d16750efb3241-dirty
git_hash_dirty() {
(
cd "$1"
x=$(git rev-parse HEAD)
if ! git diff-index --quiet HEAD; then
x="$x-dirty"
fi
echo "$x"
)
}
case $# in
0|1)
# extra_hash_or_dir is either:
# - a git commitid
# or
# - the path to a git repo from which to calculate the real hash.
#
# It gets embedded as an additional commit hash in built
# binaries, to help us locate the exact set of tools and code
# that were used.
extra_hash_or_dir="${1:-}"
if [ -z "$extra_hash_or_dir" ]; then
# Nothing, empty extra hash is fine.
extra_hash=""
elif [ -e "$extra_hash_or_dir/.git" ]; then
extra_hash=$(git_hash_dirty "$extra_hash_or_dir" HEAD)
elif ! expr "$extra_hash_or_dir" : "^[0-9a-f]*$"; then
echo "Invalid extra hash '$extra_hash_or_dir', must be a git commit or path to a git repo" >&2
exit 1
else
extra_hash="$extra_hash_or_dir"
fi
# Load the base version and optional corresponding git hash
# from the VERSION file. If there is no git hash in the file,
# we use the hash of the last change to the VERSION file.
version_file="$(dirname $0)/../VERSION.txt"
IFS=".$IFS" read -r major minor patch base_git_hash <"$version_file"
if [ -z "$base_git_hash" ]; then
base_git_hash=$(git rev-list --max-count=1 HEAD -- "$version_file")
fi
git_hash=$(git_hash_dirty . HEAD)
# The number of extra commits between the release base to git_hash.
change_count=$(git rev-list --count HEAD "^$base_git_hash")
;;
6)
# Test mode: rather than run git commands and whatnot, take in
# all the version pieces as arguments.
git_hash=$1
extra_hash=$2
major=$3
minor=$4
patch=$5
change_count=$6
;;
*)
echo "Usage: $0 [extra-git-commitid-or-dir]"
exit 1
esac
# Shortened versions of git hashes, so that they fit neatly into an
# "elongated" but still human-readable version number.
short_git_hash=$(echo "$git_hash" | cut -c1-9)
short_extra_hash=$(echo "$extra_hash" | cut -c1-9)
# Convert major/minor/patch/change_count into an adjusted
# major/minor/patch. This block is where all our policies on
# versioning are.
if expr "$minor" : "[0-9]*[13579]$" >/dev/null; then
# Odd minor numbers are unstable builds.
if [ "$patch" != "0" ]; then
# This is a fatal error, because a non-zero patch number
# indicates that we created an unstable VERSION.txt in violation
# of our versioning policy, and we want to blow up loudly to
# get that fixed.
echo "Unstable release $major.$minor.$patch has a non-zero patch number, which is not allowed" >&2
exit 1
fi
patch="$change_count"
change_suffix=""
elif [ "$change_count" != "0" ]; then
# Even minor numbers are stable builds, but stable builds are
# supposed to have a zero change count. Therefore, we're currently
# describing a commit that's on a release branch, but hasn't been
# tagged as a patch release yet.
#
# We used to change the version number to 0.0.0 in that case, but that
# caused some features to get disabled due to the low version number.
# Instead, add yet another suffix to the version number, with a change
# count.
change_suffix="-$change_count"
else
# Even minor number with no extra changes.
change_suffix=""
fi
# Hack for 1.1: add 1000 to the patch number. We switched from using
# the proprietary repo's change_count over to using the OSS repo's
# change_count, and this was necessary to avoid a backwards jump in
# release numbers.
if [ "$major.$minor" = "1.1" ]; then
patch="$((patch + 1000))"
fi
# At this point, the version number correctly reflects our
# policies. All that remains is to output the various vars that other
# code can use to embed version data.
if [ -z "$extra_hash" ]; then
long_version_suffix="$change_suffix-t$short_git_hash"
else
long_version_suffix="$change_suffix-t$short_git_hash-g$short_extra_hash"
fi
cat <<EOF
VERSION_SHORT="$major.$minor.$patch"
VERSION_LONG="$major.$minor.$patch$long_version_suffix"
VERSION_GIT_HASH="$git_hash"
VERSION_EXTRA_HASH="$extra_hash"
VERSION_XCODE="$((major + 100)).$minor.$patch"
VERSION_WINRES="$major,$minor,$patch,0"
EOF

View File

@@ -0,0 +1,14 @@
redo-ifchange version-info.sh
. ./version-info.sh
# CFBundleShortVersionString: the "short name" used in the App Store.
# eg. 0.92.98
echo "VERSION_NAME = $VERSION_SHORT"
# CFBundleVersion: the build number. Needs to be 3 numeric sections
# that increment for each release according to SemVer rules.
#
# We start counting at 100 because we submitted using raw build
# numbers before, and Apple doesn't let you start over. e.g. 0.98.3
# -> 100.98.3
echo "VERSION_ID = $VERSION_XCODE"

17
version/xversion.go Normal file
View File

@@ -0,0 +1,17 @@
// Copyright (c) 2020 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.
// +build !redo,xversion
package version
// Replaced at build time with the Go linker flag -X. See
// ../build_dist.sh for example usage, and version.go for field
// documentation.
var Long string = "<not set>"
var Short string = "<not set>"
var LONG = Long
var SHORT = Short
var GitCommit = ""
var ExtraGitCommit = ""

View File

@@ -130,8 +130,7 @@ func NewAllowAllForTest(logf logger.Logf) *Filter {
var sb netaddr.IPSetBuilder
sb.AddPrefix(any4)
sb.AddPrefix(any6)
ipSet, _ := sb.IPSet()
return New(ms, ipSet, ipSet, nil, logf)
return New(ms, sb.IPSet(), sb.IPSet(), nil, logf)
}
// NewAllowNone returns a packet filter that rejects everything.

View File

@@ -54,10 +54,7 @@ func newFilter(logf logger.Logf) *Filter {
var logB netaddr.IPSetBuilder
logB.Complement()
localNetsSet, _ := localNets.IPSet()
logBSet, _ := logB.IPSet()
return New(matches, localNetsSet, logBSet, nil, logf)
return New(matches, localNets.IPSet(), logB.IPSet(), nil, logf)
}
func TestFilter(t *testing.T) {
@@ -427,7 +424,7 @@ func TestLoggingPrivacy(t *testing.T) {
logB.AddPrefix(netaddr.MustParseIPPrefix("100.64.0.0/10"))
logB.AddPrefix(tsaddr.TailscaleULARange())
f := newFilter(logf)
f.logIPs, _ = logB.IPSet()
f.logIPs = logB.IPSet()
var (
ts4 = netaddr.IPPortFrom(tsaddr.CGNATRange().IP().Next(), 1234)

View File

@@ -15,6 +15,7 @@ import (
"errors"
"fmt"
"hash/fnv"
"log"
"math"
"math/rand"
"net"
@@ -824,6 +825,7 @@ func (c *Conn) LastRecvActivityOfDisco(dk tailcfg.DiscoKey) time.Time {
// Ping handles a "tailscale ping" CLI query.
func (c *Conn) Ping(peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
log.Println("CLIPING")
c.mu.Lock()
defer c.mu.Unlock()
if c.privateKey.IsZero() {
@@ -2212,6 +2214,7 @@ func nodesEqual(x, y []*tailcfg.Node) bool {
// conditionally sent to SetDERPMap instead.
func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
c.mu.Lock()
log.Println("NETMAP being set")
defer c.mu.Unlock()
if c.netMap != nil && nodesEqual(c.netMap.Peers, nm.Peers) {
@@ -2869,6 +2872,26 @@ func (c *RebindingUDPConn) closeLocked() error {
return c.pconn.Close()
}
func (c *RebindingUDPConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) {
for {
c.mu.Lock()
pconn := c.pconn
c.mu.Unlock()
n, err := pconn.WriteTo(b, addr)
if err != nil {
c.mu.Lock()
pconn2 := c.pconn
c.mu.Unlock()
if pconn != pconn2 {
continue
}
}
return n, err
}
}
func (c *RebindingUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
for {
c.mu.Lock()
@@ -3370,6 +3393,7 @@ func (de *discoEndpoint) send(b []byte) error {
now := time.Now()
de.mu.Lock()
log.Println("Discosend")
udpAddr, derpAddr := de.addrForSendLocked(now)
if udpAddr.IsZero() || now.After(de.trustBestAddrUntil) {
de.sendPingsLocked(now, true)
@@ -3428,7 +3452,9 @@ func (de *discoEndpoint) removeSentPingLocked(txid stun.TxID, sp sentPing) {
// The caller (startPingLocked) should've already been recorded the ping in
// sentPing and set up the timer.
func (de *discoEndpoint) sendDiscoPing(ep netaddr.IPPort, txid stun.TxID, logLevel discoLogLevel) {
log.Println("sendDiscoPing")
sent, _ := de.sendDiscoMessage(ep, &disco.Ping{TxID: [12]byte(txid)}, logLevel)
log.Println(sent)
if !sent {
de.forgetPing(txid)
}
@@ -3607,6 +3633,7 @@ func (de *discoEndpoint) noteConnectivityChange() {
// It should be called with the Conn.mu held.
func (de *discoEndpoint) handlePongConnLocked(m *disco.Pong, src netaddr.IPPort) {
de.mu.Lock()
log.Println("Disco Reached")
defer de.mu.Unlock()
isDerp := src.IP() == derpMagicIPAddr

View File

@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"io"
"log"
"os"
"reflect"
"runtime"
@@ -307,6 +308,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
e.wgLogger = wglog.NewLogger(logf)
e.tundev.OnTSMPPongReceived = func(pong packet.TSMPPongReply) {
log.Println("PONGReceived")
e.mu.Lock()
defer e.mu.Unlock()
cb := e.pongCallback[pong.Data]
@@ -369,6 +371,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
// echoRespondToAll is an inbound post-filter responding to all echo requests.
func echoRespondToAll(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
log.Println("ECHO respond to all")
if p.IsEchoRequest() {
header := p.ICMP4Header()
header.ToResponse()
@@ -1087,6 +1090,7 @@ func (e *userspaceEngine) SetDERPMap(dm *tailcfg.DERPMap) {
}
func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) {
log.Println("SETNETMAP")
e.magicConn.SetNetworkMap(nm)
e.mu.Lock()
e.netMap = nm
@@ -1123,15 +1127,18 @@ func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {
}
func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.PingResult)) {
log.Println("Userspace Ping Called")
res := &ipnstate.PingResult{IP: ip.String()}
peer, err := e.peerForIP(ip)
if err != nil {
log.Println(err)
e.logf("ping(%v): %v", ip, err)
res.Err = err.Error()
cb(res)
return
}
if peer == nil {
log.Println("No peer moment")
e.logf("ping(%v): no matching peer", ip)
res.Err = "no matching peer"
cb(res)
@@ -1139,6 +1146,7 @@ func (e *userspaceEngine) Ping(ip netaddr.IP, useTSMP bool, cb func(*ipnstate.Pi
}
pingType := "disco"
if useTSMP {
log.Println("TSMPSELECTED")
pingType = "TSMP"
}
e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key.ShortString(), peer.ComputedName)
@@ -1167,8 +1175,10 @@ func (e *userspaceEngine) mySelfIPMatchingFamily(dst netaddr.IP) (src netaddr.IP
}
func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
log.Println("TSMPcheck")
srcIP, err := e.mySelfIPMatchingFamily(ip)
if err != nil {
log.Println("TSMPcheckerror")
res.Err = err.Error()
cb(res)
return
@@ -1190,12 +1200,17 @@ func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *i
var data [8]byte
crand.Read(data[:])
log.Println("CRAND CHECKlog")
fmt.Println("CRAND CHECKfmt")
expireTimer := time.AfterFunc(10*time.Second, func() {
log.Println("CHECKEXPIRE")
e.setTSMPPongCallback(data, nil)
})
log.Println("TIMECHECK")
t0 := time.Now()
e.setTSMPPongCallback(data, func(pong packet.TSMPPongReply) {
log.Println("ping cb called")
expireTimer.Stop()
d := time.Since(t0)
res.LatencySeconds = d.Seconds()
@@ -1208,22 +1223,32 @@ func (e *userspaceEngine) sendTSMPPing(ip netaddr.IP, peer *tailcfg.Node, res *i
var tsmpPayload [9]byte
tsmpPayload[0] = byte(packet.TSMPTypePing)
copy(tsmpPayload[1:], data[:])
log.Println("PAYLOADCHECK")
tsmpPing := packet.Generate(iph, tsmpPayload[:])
log.Println("BEFOREPACKET", tsmpPing)
log.Println("PACKETGEN", *res, res.LatencySeconds)
e.tundev.InjectOutbound(tsmpPing)
log.Println("TUNDEVINJECT")
}
func (e *userspaceEngine) setTSMPPongCallback(data [8]byte, cb func(packet.TSMPPongReply)) {
log.Println("Ponger2nolock", data)
e.mu.Lock()
log.Println("Ponger2", e.pongCallback == nil, cb == nil)
defer e.mu.Unlock()
if e.pongCallback == nil {
log.Println("pongCallback nil")
e.pongCallback = map[[8]byte]func(packet.TSMPPongReply){}
}
if cb == nil {
log.Println("DELETEoccur")
delete(e.pongCallback, data)
} else {
log.Println("Callbackset")
e.pongCallback[data] = cb
}
log.Println("PONGCALLBACKMAP", data, e.pongCallback)
}
func (e *userspaceEngine) RegisterIPPortIdentity(ipport netaddr.IPPort, tsIP netaddr.IP) {
@@ -1291,12 +1316,20 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error)
// Check for exact matches before looking for subnet matches.
var bestInNMPrefix netaddr.IPPrefix
var bestInNM *tailcfg.Node
log.Println("Scan starting : ", len(nm.Peers), nm.Addresses)
for _, p := range nm.Peers {
log.Println("peerp", p.Addresses, p.AllowedIPs, p.ID)
for _, a := range p.Addresses {
log.Println("paddr", a)
if a.IP() == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) {
log.Println("Foundp")
return p, nil
} else {
// log.Println("Failure : ", a.IP(), a.IsSingleIP(), tsaddr.IsTailscaleIP(ip))
}
}
log.Println("ALLOW : ", p.AllowedIPs)
bestInNM = p
for _, cidr := range p.AllowedIPs {
if !cidr.Contains(ip) {
continue
@@ -1310,6 +1343,7 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error)
e.wgLock.Lock()
defer e.wgLock.Unlock()
log.Println("Scanpoint2")
// TODO(bradfitz): this is O(n peers). Add ART to netaddr?
var best netaddr.IPPrefix
@@ -1334,10 +1368,13 @@ func (e *userspaceEngine) peerForIP(ip netaddr.IP) (n *tailcfg.Node, err error)
}
}
}
log.Println("Scanpoint3")
if bestInNM == nil {
log.Println("Scanpoint4")
return nil, nil
}
if bestInNMPrefix.Bits() == 0 {
log.Println("Scanpoint5")
return nil, errors.New("exit node found but not enabled")
}
return nil, fmt.Errorf("node %q found, but not using its %v route", bestInNM.ComputedNameWithHost, bestInNMPrefix)

View File

@@ -6,6 +6,7 @@
package wglog
import (
"encoding/base64"
"fmt"
"strings"
"sync"
@@ -120,20 +121,17 @@ func (x *Logger) SetPeers(peers []wgcfg.Peer) {
// wireguardGoString prints p in the same format used by wireguard-go.
func wireguardGoString(k wgkey.Key) string {
src := k
b64 := func(input byte) byte {
return input + 'A' + byte(((25-int(input))>>8)&6) - byte(((51-int(input))>>8)&75) - byte(((61-int(input))>>8)&15) + byte(((62-int(input))>>8)&3)
}
b := []byte("peer(____…____)")
const first = len("peer(")
const second = len("peer(____…")
b[first+0] = b64((src[0] >> 2) & 63)
b[first+1] = b64(((src[0] << 4) | (src[1] >> 4)) & 63)
b[first+2] = b64(((src[1] << 2) | (src[2] >> 6)) & 63)
b[first+3] = b64(src[2] & 63)
b[second+0] = b64(src[29] & 63)
b[second+1] = b64((src[30] >> 2) & 63)
b[second+2] = b64(((src[30] << 4) | (src[31] >> 4)) & 63)
b[second+3] = b64((src[31] << 2) & 63)
return string(b)
const prefix = "peer("
b := make([]byte, len(prefix)+44)
copy(b, prefix)
r := b[len(prefix):]
base64.StdEncoding.Encode(r, k[:])
r = r[4:]
copy(r, "…")
r = r[len("…"):]
copy(r, b[len(prefix)+39:len(prefix)+43])
r = r[4:]
r[0] = ')'
r = r[1:]
return string(b[:len(b)-len(r)])
}