From bb07aec82c02cf6c570561e3ee7d6257f9615df4 Mon Sep 17 00:00:00 2001 From: Juan Font Date: Thu, 13 Apr 2023 21:09:09 +0000 Subject: [PATCH] Expand tsic to offer PingViaDerp --- integration/tailscale.go | 4 +- integration/tsic/tsic.go | 80 +++++++++++++++++++++++++++++++++++++++- integration/utils.go | 48 ++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 3 deletions(-) diff --git a/integration/tailscale.go b/integration/tailscale.go index fbb1d3e5..0ae52eae 100644 --- a/integration/tailscale.go +++ b/integration/tailscale.go @@ -4,6 +4,7 @@ import ( "net/netip" "net/url" + "github.com/juanfont/headscale/integration/dockertestutil" "github.com/juanfont/headscale/integration/tsic" "tailscale.com/ipn/ipnstate" ) @@ -13,7 +14,7 @@ type TailscaleClient interface { Hostname() string Shutdown() error Version() string - Execute(command []string) (string, string, error) + Execute(command []string, options ...dockertestutil.ExecuteCommandOption) (string, string, error) Up(loginServer, authKey string) error UpWithLoginURL(loginServer string) (*url.URL, error) Logout() error @@ -24,6 +25,7 @@ type TailscaleClient interface { WaitForLogout() error WaitForPeers(expected int) error Ping(hostnameOrIP string, opts ...tsic.PingOption) error + PingViaDERP(hostnameOrIP string, opts ...tsic.PingOption) error Curl(url string, opts ...tsic.CurlOption) (string, error) ID() string } diff --git a/integration/tsic/tsic.go b/integration/tsic/tsic.go index efb9ef95..e5a469d9 100644 --- a/integration/tsic/tsic.go +++ b/integration/tsic/tsic.go @@ -29,6 +29,7 @@ const ( var ( errTailscalePingFailed = errors.New("ping failed") + errTailscalePingNotDERP = errors.New("ping not via DERP") errTailscaleNotLoggedIn = errors.New("tailscale not logged in") errTailscaleWrongPeerCount = errors.New("wrong peer count") errTailscaleCannotUpWithoutAuthkey = errors.New("cannot up without authkey") @@ -56,6 +57,7 @@ type TailscaleInContainer struct { withSSH bool withTags []string withEntrypoint []string + withExtraHosts []string workdir string } @@ -124,6 +126,12 @@ func WithDockerWorkdir(dir string) Option { } } +func WithExtraHosts(hosts []string) Option { + return func(tsic *TailscaleInContainer) { + tsic.withExtraHosts = hosts + } +} + // WithDockerEntrypoint allows the docker entrypoint of the container // to be overridden. This is a dangerous option which can make // the container not work as intended as a typo might prevent @@ -169,11 +177,12 @@ func New( tailscaleOptions := &dockertest.RunOptions{ Name: hostname, - Networks: []*dockertest.Network{network}, + Networks: []*dockertest.Network{tsic.network}, // Cmd: []string{ // "tailscaled", "--tun=tsdev", // }, Entrypoint: tsic.withEntrypoint, + ExtraHosts: tsic.withExtraHosts, } if tsic.headscaleHostname != "" { @@ -248,11 +257,13 @@ func (t *TailscaleInContainer) ID() string { // result of stdout as a string. func (t *TailscaleInContainer) Execute( command []string, + options ...dockertestutil.ExecuteCommandOption, ) (string, string, error) { stdout, stderr, err := dockertestutil.ExecuteCommand( t.container, command, []string{}, + options..., ) if err != nil { log.Printf("command stderr: %s\n", stderr) @@ -477,7 +488,7 @@ func (t *TailscaleInContainer) WaitForPeers(expected int) error { } type ( - // PingOption repreent optional settings that can be given + // PingOption represent optional settings that can be given // to ping another host. PingOption = func(args *pingArgs) @@ -488,6 +499,15 @@ type ( } ) +type ( + DERPPingOption = func(args *derpPingArgs) + + derpPingArgs struct { + timeout time.Duration + count int + } +) + // WithPingTimeout sets the timeout for the ping command. func WithPingTimeout(timeout time.Duration) PingOption { return func(args *pingArgs) { @@ -555,6 +575,62 @@ func (t *TailscaleInContainer) Ping(hostnameOrIP string, opts ...PingOption) err }) } +// PingViaDERP executes the Tailscale ping command and pings a hostname +// or IP via the DERP network (i.e., not a direct connection). It accepts a series of DERPPingOption. +// TODO(kradalby): Make multiping, go routine magic. +func (t *TailscaleInContainer) PingViaDERP(hostnameOrIP string, opts ...PingOption) error { + args := pingArgs{ + timeout: time.Second, + count: defaultPingCount, + } + + for _, opt := range opts { + opt(&args) + } + + command := []string{ + "tailscale", "ping", + fmt.Sprintf("--timeout=%s", args.timeout), + fmt.Sprintf("--c=%d", args.count), + "--until-direct=false", + } + + command = append(command, hostnameOrIP) + + return t.pool.Retry(func() error { + result, _, err := t.Execute( + command, + dockertestutil.ExecuteCommandTimeout( + time.Duration(int64(args.timeout)*int64(args.count)), + ), + ) + if err != nil { + fmt.Printf( + "failed to run ping command from %s to %s, err: %s", + t.Hostname(), + hostnameOrIP, + err, + ) + + return err + } + + if strings.Contains(result, "is local") { + return nil + } + + if !strings.Contains(result, "pong") { + return backoff.Permanent(errTailscalePingFailed) + } + + if !strings.Contains(result, "via DERP") { + return backoff.Permanent(errTailscalePingNotDERP) + } + + return nil + }) +} + type ( // CurlOption repreent optional settings that can be given // to curl another host. diff --git a/integration/utils.go b/integration/utils.go index ae6d578f..43860b1f 100644 --- a/integration/utils.go +++ b/integration/utils.go @@ -2,6 +2,9 @@ package integration import ( "testing" + "time" + + "github.com/juanfont/headscale/integration/tsic" ) func pingAllHelper(t *testing.T, clients []TailscaleClient, addrs []string) int { @@ -22,6 +25,51 @@ func pingAllHelper(t *testing.T, clients []TailscaleClient, addrs []string) int return success } +func pingDerpAllHelper(t *testing.T, clients []TailscaleClient, addrs []string) int { + t.Helper() + success := 0 + + for _, client := range clients { + for _, addr := range addrs { + if isSelfClient(client, addr) { + continue + } + + err := client.PingViaDERP( + addr, + tsic.WithPingTimeout(2*time.Second), + tsic.WithPingCount(10), + ) + if err != nil { + t.Errorf("failed to ping %s from %s: %s", addr, client.Hostname(), err) + } else { + success++ + } + } + } + + return success +} + +func isSelfClient(client TailscaleClient, addr string) bool { + if addr == client.Hostname() { + return true + } + + ips, err := client.IPs() + if err != nil { + return false + } + + for _, ip := range ips { + if ip.String() == addr { + return true + } + } + + return false +} + // pingAllNegativeHelper is intended to have 1 or more nodes timeing out from the ping, // it counts failures instead of successes. // func pingAllNegativeHelper(t *testing.T, clients []TailscaleClient, addrs []string) int {