package integration import ( "fmt" "strings" "testing" "time" "github.com/juanfont/headscale/integration/hsic" "github.com/juanfont/headscale/integration/tsic" "github.com/rs/zerolog/log" ) func TestPingAllByIP(t *testing.T) { IntegrationSkip(t) t.Parallel() scenario, err := NewScenario() if err != nil { t.Errorf("failed to create scenario: %s", err) } spec := map[string]int{ "namespace1": len(TailscaleVersions), "namespace2": len(TailscaleVersions), } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("pingallbyip")) if err != nil { t.Errorf("failed to create headscale environment: %s", err) } allClients, err := scenario.ListTailscaleClients() if err != nil { t.Errorf("failed to get clients: %s", err) } allIps, err := scenario.ListTailscaleClientsIPs() if err != nil { t.Errorf("failed to get clients: %s", err) } err = scenario.WaitForTailscaleSync() if err != nil { t.Errorf("failed wait for tailscale clients to be in sync: %s", err) } success := 0 for _, client := range allClients { for _, ip := range allIps { err := client.Ping(ip.String()) if err != nil { t.Errorf("failed to ping %s from %s: %s", ip, client.Hostname(), err) } else { success++ } } } t.Logf("%d successful pings out of %d", success, len(allClients)*len(allIps)) err = scenario.Shutdown() if err != nil { t.Errorf("failed to tear down scenario: %s", err) } } func TestPingAllByHostname(t *testing.T) { IntegrationSkip(t) t.Parallel() scenario, err := NewScenario() if err != nil { t.Errorf("failed to create scenario: %s", err) } spec := map[string]int{ // Omit 1.16.2 (-1) because it does not have the FQDN field "namespace3": len(TailscaleVersions) - 1, "namespace4": len(TailscaleVersions) - 1, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("pingallbyname")) if err != nil { t.Errorf("failed to create headscale environment: %s", err) } allClients, err := scenario.ListTailscaleClients() if err != nil { t.Errorf("failed to get clients: %s", err) } err = scenario.WaitForTailscaleSync() if err != nil { t.Errorf("failed wait for tailscale clients to be in sync: %s", err) } allHostnames, err := scenario.ListTailscaleClientsFQDNs() if err != nil { t.Errorf("failed to get FQDNs: %s", err) } success := 0 for _, client := range allClients { for _, hostname := range allHostnames { err := client.Ping(hostname) if err != nil { t.Errorf("failed to ping %s from %s: %s", hostname, client.Hostname(), err) } else { success++ } } } t.Logf("%d successful pings out of %d", success, len(allClients)*len(allClients)) err = scenario.Shutdown() if err != nil { t.Errorf("failed to tear down scenario: %s", err) } } func TestTaildrop(t *testing.T) { IntegrationSkip(t) t.Parallel() retry := func(times int, sleepInverval time.Duration, doWork func() error) error { var err error for attempts := 0; attempts < times; attempts++ { err = doWork() if err == nil { return nil } time.Sleep(sleepInverval) } return err } scenario, err := NewScenario() if err != nil { t.Errorf("failed to create scenario: %s", err) } spec := map[string]int{ // Omit 1.16.2 (-1) because it does not have the FQDN field "taildrop": len(TailscaleVersions) - 1, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("taildrop")) if err != nil { t.Errorf("failed to create headscale environment: %s", err) } allClients, err := scenario.ListTailscaleClients() if err != nil { t.Errorf("failed to get clients: %s", err) } err = scenario.WaitForTailscaleSync() if err != nil { t.Errorf("failed wait for tailscale clients to be in sync: %s", err) } // This will essentially fetch and cache all the FQDNs _, err = scenario.ListTailscaleClientsFQDNs() if err != nil { t.Errorf("failed to get FQDNs: %s", err) } for _, client := range allClients { command := []string{"touch", fmt.Sprintf("/tmp/file_from_%s", client.Hostname())} if _, _, err := client.Execute(command); err != nil { t.Errorf("failed to create taildrop file on %s, err: %s", client.Hostname(), err) } for _, peer := range allClients { if client.Hostname() == peer.Hostname() { continue } // It is safe to ignore this error as we handled it when caching it peerFQDN, _ := peer.FQDN() t.Run(fmt.Sprintf("%s-%s", client.Hostname(), peer.Hostname()), func(t *testing.T) { command := []string{ "tailscale", "file", "cp", fmt.Sprintf("/tmp/file_from_%s", client.Hostname()), fmt.Sprintf("%s:", peerFQDN), } err := retry(10, 1*time.Second, func() error { t.Logf( "Sending file from %s to %s\n", client.Hostname(), peer.Hostname(), ) _, _, err := client.Execute(command) return err }) if err != nil { t.Errorf( "failed to send taildrop file on %s, err: %s", client.Hostname(), err, ) } }) } } for _, client := range allClients { command := []string{ "tailscale", "file", "get", "/tmp/", } if _, _, err := client.Execute(command); err != nil { t.Errorf("failed to get taildrop file on %s, err: %s", client.Hostname(), err) } for _, peer := range allClients { if client.Hostname() == peer.Hostname() { continue } t.Run(fmt.Sprintf("%s-%s", client.Hostname(), peer.Hostname()), func(t *testing.T) { command := []string{ "ls", fmt.Sprintf("/tmp/file_from_%s", peer.Hostname()), } log.Printf( "Checking file in %s from %s\n", client.Hostname(), peer.Hostname(), ) result, _, err := client.Execute(command) if err != nil { t.Errorf("failed to execute command to ls taildrop: %s", err) } log.Printf("Result for %s: %s\n", peer.Hostname(), result) if fmt.Sprintf("/tmp/file_from_%s\n", peer.Hostname()) != result { t.Errorf( "taildrop result is not correct %s, wanted %s", result, fmt.Sprintf("/tmp/file_from_%s\n", peer.Hostname()), ) } }) } } err = scenario.Shutdown() if err != nil { t.Errorf("failed to tear down scenario: %s", err) } } func TestResolveMagicDNS(t *testing.T) { IntegrationSkip(t) t.Parallel() scenario, err := NewScenario() if err != nil { t.Errorf("failed to create scenario: %s", err) } spec := map[string]int{ // Omit 1.16.2 (-1) because it does not have the FQDN field "magicdns1": len(TailscaleVersions) - 1, "magicdns2": len(TailscaleVersions) - 1, } err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{}, hsic.WithTestName("magicdns")) if err != nil { t.Errorf("failed to create headscale environment: %s", err) } allClients, err := scenario.ListTailscaleClients() if err != nil { t.Errorf("failed to get clients: %s", err) } err = scenario.WaitForTailscaleSync() if err != nil { t.Errorf("failed wait for tailscale clients to be in sync: %s", err) } // Poor mans cache _, err = scenario.ListTailscaleClientsFQDNs() if err != nil { t.Errorf("failed to get FQDNs: %s", err) } _, err = scenario.ListTailscaleClientsIPs() if err != nil { t.Errorf("failed to get IPs: %s", err) } for _, client := range allClients { for _, peer := range allClients { // It is safe to ignore this error as we handled it when caching it peerFQDN, _ := peer.FQDN() command := []string{ "tailscale", "ip", peerFQDN, } result, _, err := client.Execute(command) if err != nil { t.Errorf( "failed to execute resolve/ip command %s from %s: %s", peerFQDN, client.Hostname(), err, ) } ips, err := peer.IPs() if err != nil { t.Errorf( "failed to get ips for %s: %s", peer.Hostname(), err, ) } for _, ip := range ips { if !strings.Contains(result, ip.String()) { t.Errorf("ip %s is not found in \n%s\n", ip.String(), result) } } } } err = scenario.Shutdown() if err != nil { t.Errorf("failed to tear down scenario: %s", err) } }