Skip to content

Commit

Permalink
Expand tsic to offer PingViaDerp
Browse files Browse the repository at this point in the history
  • Loading branch information
juanfont committed Apr 24, 2023
1 parent a5afe4b commit bb07aec
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 3 deletions.
4 changes: 3 additions & 1 deletion integration/tailscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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
Expand All @@ -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
}
80 changes: 78 additions & 2 deletions integration/tsic/tsic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -56,6 +57,7 @@ type TailscaleInContainer struct {
withSSH bool
withTags []string
withEntrypoint []string
withExtraHosts []string
workdir string
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 != "" {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand All @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down
48 changes: 48 additions & 0 deletions integration/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down

0 comments on commit bb07aec

Please sign in to comment.