1
0
mirror of https://github.com/juanfont/headscale.git synced 2025-06-01 01:15:56 +02:00

parse and validate traceroute

Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
Kristoffer Dalby 2025-03-14 16:23:37 +01:00
parent a30afb1121
commit 36cbd4c2d6
No known key found for this signature in database
3 changed files with 155 additions and 5 deletions

View File

@ -1141,6 +1141,139 @@ func TestSubnetRouterMultiNetwork(t *testing.T) {
for _, peerKey := range status.Peers() { for _, peerKey := range status.Peers() {
peerStatus := status.Peer[peerKey] peerStatus := status.Peer[peerKey]
// TestSubnetRouterMultiNetworkExitNode
func TestSubnetRouterMultiNetworkExitNode(t *testing.T) {
IntegrationSkip(t)
t.Parallel()
spec := ScenarioSpec{
NodesPerUser: 1,
Users: []string{"user1", "user2"},
Networks: map[string][]string{
"usernet1": {"user1"},
"usernet2": {"user2"},
},
ExtraService: map[string][]extraServiceFunc{
"usernet1": {Webservice},
},
}
scenario, err := NewScenario(spec)
require.NoErrorf(t, err, "failed to create scenario: %s", err)
// defer scenario.ShutdownAssertNoPanics(t)
err = scenario.CreateHeadscaleEnv([]tsic.Option{},
hsic.WithTestName("clienableroute"),
hsic.WithEmbeddedDERPServerOnly(),
hsic.WithTLS(),
)
assertNoErrHeadscaleEnv(t, err)
allClients, err := scenario.ListTailscaleClients()
assertNoErrListClients(t, err)
err = scenario.WaitForTailscaleSync()
assertNoErrSync(t, err)
headscale, err := scenario.Headscale()
assertNoErrGetHeadscale(t, err)
assert.NotNil(t, headscale)
var user1c, user2c TailscaleClient
for _, c := range allClients {
s := c.MustStatus()
if s.User[s.Self.UserID].LoginName == "user1@test.no" {
user1c = c
}
if s.User[s.Self.UserID].LoginName == "user2@test.no" {
user2c = c
}
}
require.NotNil(t, user1c)
require.NotNil(t, user2c)
// Advertise the exit nodes for the dockersubnet of user1
command := []string{
"tailscale",
"set",
"--advertise-exit-node",
}
_, _, err = user1c.Execute(command)
require.NoErrorf(t, err, "failed to advertise route: %s", err)
nodes, err := headscale.ListNodes()
require.NoError(t, err)
assert.Len(t, nodes, 2)
assertNodeRouteCount(t, nodes[0], 2, 0, 0)
// Verify that no routes has been sent to the client,
// they are not yet enabled.
status, err := user1c.Status()
require.NoError(t, err)
for _, peerKey := range status.Peers() {
peerStatus := status.Peer[peerKey]
assert.Nil(t, peerStatus.PrimaryRoutes)
requirePeerSubnetRoutes(t, peerStatus, nil)
}
// Enable route
_, err = headscale.ApproveRoutes(
nodes[0].Id,
[]netip.Prefix{tsaddr.AllIPv4()},
)
require.NoError(t, err)
time.Sleep(5 * time.Second)
nodes, err = headscale.ListNodes()
require.NoError(t, err)
assert.Len(t, nodes, 2)
assertNodeRouteCount(t, nodes[0], 2, 2, 2)
// Verify that the routes have been sent to the client.
status, err = user2c.Status()
require.NoError(t, err)
for _, peerKey := range status.Peers() {
peerStatus := status.Peer[peerKey]
requirePeerSubnetRoutes(t, peerStatus, []netip.Prefix{tsaddr.AllIPv4(), tsaddr.AllIPv6()})
}
// Tell user2c to use user1c as an exit node.
command = []string{
"tailscale",
"set",
"--exit-node",
user1c.Hostname(),
}
_, _, err = user2c.Execute(command)
require.NoErrorf(t, err, "failed to advertise route: %s", err)
usernet1, err := scenario.Network("usernet1")
require.NoError(t, err)
services, err := scenario.Services("usernet1")
require.NoError(t, err)
require.Len(t, services, 1)
web := services[0]
webip := netip.MustParseAddr(web.GetIPInNetwork(usernet1))
// We cant mess to much with ip forwarding in containers so
// we settle for a simple ping here.
// Direct is false since we use internal DERP which means we
// cant discover a direct path between docker networks.
err = user2c.Ping(webip.String(),
tsic.WithPingUntilDirect(false),
tsic.WithPingCount(1),
)
require.NoError(t, err)
}
assert.Nil(t, peerStatus.PrimaryRoutes) assert.Nil(t, peerStatus.PrimaryRoutes)
assertPeerSubnetRoutes(t, peerStatus, []netip.Prefix{*pref}) assertPeerSubnetRoutes(t, peerStatus, []netip.Prefix{*pref})
} }
@ -1163,7 +1296,18 @@ func TestSubnetRouterMultiNetwork(t *testing.T) {
assert.Len(t, result, 13) assert.Len(t, result, 13)
tr, err := user2c.Traceroute(webip) tr, err := user2c.Traceroute(webip)
assert.Contains(t, tr, user1c.MustIPv4().String()) require.NoError(t, err)
assertTracerouteViaIP(t, tr, user1c.MustIPv4())
}
func assertTracerouteViaIP(t *testing.T, tr util.Traceroute, ip netip.Addr) {
t.Helper()
require.NotNil(t, tr)
require.True(t, tr.Success)
require.NoError(t, tr.Err)
require.NotEmpty(t, tr.Route)
require.Equal(t, tr.Route[0].IP, ip)
} }
// requirePeerSubnetRoutes asserts that the peer has the expected subnet routes. // requirePeerSubnetRoutes asserts that the peer has the expected subnet routes.

View File

@ -5,6 +5,7 @@ import (
"net/netip" "net/netip"
"net/url" "net/url"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/integration/dockertestutil" "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"
@ -41,7 +42,7 @@ type TailscaleClient interface {
WaitForPeers(expected int) error WaitForPeers(expected int) error
Ping(hostnameOrIP string, opts ...tsic.PingOption) error Ping(hostnameOrIP string, opts ...tsic.PingOption) error
Curl(url string, opts ...tsic.CurlOption) (string, error) Curl(url string, opts ...tsic.CurlOption) (string, error)
Traceroute(netip.Addr) (string, error) Traceroute(netip.Addr) (util.Traceroute, error)
ID() string ID() string
ReadFile(path string) ([]byte, error) ReadFile(path string) ([]byte, error)

View File

@ -1130,14 +1130,19 @@ func (t *TailscaleInContainer) Curl(url string, opts ...CurlOption) (string, err
return result, nil return result, nil
} }
func (t *TailscaleInContainer) Traceroute(ip netip.Addr) (string, error) { func (t *TailscaleInContainer) Traceroute(ip netip.Addr) (util.Traceroute, error) {
command := []string{ command := []string{
"traceroute", "traceroute",
ip.String(), ip.String(),
} }
var result string var result util.Traceroute
result, _, err := t.Execute(command) stdout, stderr, err := t.Execute(command)
if err != nil {
return result, err
}
result, err = util.ParseTraceroute(stdout + stderr)
if err != nil { if err != nil {
return result, err return result, err
} }