Expand tsic to offer PingViaDerp

This commit is contained in:
Juan Font 2023-04-13 21:09:09 +00:00
parent a5afe4bd06
commit 17b597ed77
3 changed files with 129 additions and 3 deletions

View File

@ -4,6 +4,7 @@ import (
"net/netip" "net/netip"
"net/url" "net/url"
"github.com/juanfont/headscale/integration/dockertestutil"
"github.com/juanfont/headscale/integration/tsic" "github.com/juanfont/headscale/integration/tsic"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
) )
@ -13,7 +14,7 @@ type TailscaleClient interface {
Hostname() string Hostname() string
Shutdown() error Shutdown() error
Version() string Version() string
Execute(command []string) (string, string, error) Execute(command []string, options ...dockertestutil.ExecuteCommandOption) (string, string, error)
Up(loginServer, authKey string) error Up(loginServer, authKey string) error
UpWithLoginURL(loginServer string) (*url.URL, error) UpWithLoginURL(loginServer string) (*url.URL, error)
Logout() error Logout() error
@ -24,6 +25,7 @@ type TailscaleClient interface {
WaitForLogout() error WaitForLogout() error
WaitForPeers(expected int) error WaitForPeers(expected int) error
Ping(hostnameOrIP string, opts ...tsic.PingOption) error Ping(hostnameOrIP string, opts ...tsic.PingOption) error
PingViaDERP(hostnameOrIP string, opts ...tsic.PingOption) error
Curl(url string, opts ...tsic.CurlOption) (string, error) Curl(url string, opts ...tsic.CurlOption) (string, error)
ID() string ID() string
} }

View File

@ -29,6 +29,7 @@ const (
var ( var (
errTailscalePingFailed = errors.New("ping failed") errTailscalePingFailed = errors.New("ping failed")
errTailscalePingNotDERP = errors.New("ping not via DERP")
errTailscaleNotLoggedIn = errors.New("tailscale not logged in") errTailscaleNotLoggedIn = errors.New("tailscale not logged in")
errTailscaleWrongPeerCount = errors.New("wrong peer count") errTailscaleWrongPeerCount = errors.New("wrong peer count")
errTailscaleCannotUpWithoutAuthkey = errors.New("cannot up without authkey") errTailscaleCannotUpWithoutAuthkey = errors.New("cannot up without authkey")
@ -56,6 +57,7 @@ type TailscaleInContainer struct {
withSSH bool withSSH bool
withTags []string withTags []string
withEntrypoint []string withEntrypoint []string
withExtraHosts []string
workdir 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 // WithDockerEntrypoint allows the docker entrypoint of the container
// to be overridden. This is a dangerous option which can make // to be overridden. This is a dangerous option which can make
// the container not work as intended as a typo might prevent // the container not work as intended as a typo might prevent
@ -169,11 +177,12 @@ func New(
tailscaleOptions := &dockertest.RunOptions{ tailscaleOptions := &dockertest.RunOptions{
Name: hostname, Name: hostname,
Networks: []*dockertest.Network{network}, Networks: []*dockertest.Network{tsic.network},
// Cmd: []string{ // Cmd: []string{
// "tailscaled", "--tun=tsdev", // "tailscaled", "--tun=tsdev",
// }, // },
Entrypoint: tsic.withEntrypoint, Entrypoint: tsic.withEntrypoint,
ExtraHosts: tsic.withExtraHosts,
} }
if tsic.headscaleHostname != "" { if tsic.headscaleHostname != "" {
@ -248,11 +257,13 @@ func (t *TailscaleInContainer) ID() string {
// result of stdout as a string. // result of stdout as a string.
func (t *TailscaleInContainer) Execute( func (t *TailscaleInContainer) Execute(
command []string, command []string,
options ...dockertestutil.ExecuteCommandOption,
) (string, string, error) { ) (string, string, error) {
stdout, stderr, err := dockertestutil.ExecuteCommand( stdout, stderr, err := dockertestutil.ExecuteCommand(
t.container, t.container,
command, command,
[]string{}, []string{},
options...,
) )
if err != nil { if err != nil {
log.Printf("command stderr: %s\n", stderr) log.Printf("command stderr: %s\n", stderr)
@ -477,7 +488,7 @@ func (t *TailscaleInContainer) WaitForPeers(expected int) error {
} }
type ( type (
// PingOption repreent optional settings that can be given // PingOption represent optional settings that can be given
// to ping another host. // to ping another host.
PingOption = func(args *pingArgs) 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. // WithPingTimeout sets the timeout for the ping command.
func WithPingTimeout(timeout time.Duration) PingOption { func WithPingTimeout(timeout time.Duration) PingOption {
return func(args *pingArgs) { 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 ( type (
// CurlOption repreent optional settings that can be given // CurlOption repreent optional settings that can be given
// to curl another host. // to curl another host.

View File

@ -2,6 +2,9 @@ package integration
import ( import (
"testing" "testing"
"time"
"github.com/juanfont/headscale/integration/tsic"
) )
func pingAllHelper(t *testing.T, clients []TailscaleClient, addrs []string) int { 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 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, // pingAllNegativeHelper is intended to have 1 or more nodes timeing out from the ping,
// it counts failures instead of successes. // it counts failures instead of successes.
// func pingAllNegativeHelper(t *testing.T, clients []TailscaleClient, addrs []string) int { // func pingAllNegativeHelper(t *testing.T, clients []TailscaleClient, addrs []string) int {