mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-19 13:48:20 +02:00
util: harden parsing of traceroute
Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
parent
9f1e718a74
commit
10efdf0714
@ -90,15 +90,19 @@ func ParseTraceroute(output string) (Traceroute, error) {
|
||||
return Traceroute{}, errors.New("empty traceroute output")
|
||||
}
|
||||
|
||||
// Parse the header line
|
||||
headerRegex := regexp.MustCompile(`traceroute to ([^ ]+) \(([^)]+)\)`)
|
||||
// Parse the header line - handle both 'traceroute' and 'tracert' (Windows)
|
||||
headerRegex := regexp.MustCompile(`(?i)(?:traceroute|tracing route) to ([^ ]+) (?:\[([^\]]+)\]|\(([^)]+)\))`)
|
||||
headerMatches := headerRegex.FindStringSubmatch(lines[0])
|
||||
if len(headerMatches) != 3 {
|
||||
if len(headerMatches) < 2 {
|
||||
return Traceroute{}, fmt.Errorf("parsing traceroute header: %s", lines[0])
|
||||
}
|
||||
|
||||
hostname := headerMatches[1]
|
||||
// IP can be in either capture group 2 or 3 depending on format
|
||||
ipStr := headerMatches[2]
|
||||
if ipStr == "" {
|
||||
ipStr = headerMatches[3]
|
||||
}
|
||||
ip, err := netip.ParseAddr(ipStr)
|
||||
if err != nil {
|
||||
return Traceroute{}, fmt.Errorf("parsing IP address %s: %w", ipStr, err)
|
||||
@ -111,44 +115,112 @@ func ParseTraceroute(output string) (Traceroute, error) {
|
||||
Success: false,
|
||||
}
|
||||
|
||||
// Parse each hop line
|
||||
hopRegex := regexp.MustCompile(`^\s*(\d+)\s+(?:([^ ]+) \(([^)]+)\)|(\*))(?:\s+(\d+\.\d+) ms)?(?:\s+(\d+\.\d+) ms)?(?:\s+(\d+\.\d+) ms)?`)
|
||||
// More flexible regex that handles various traceroute output formats
|
||||
// Main pattern handles: "hostname (IP)", "hostname [IP]", "IP only", "* * *"
|
||||
hopRegex := regexp.MustCompile(`^\s*(\d+)\s+(.*)$`)
|
||||
// Patterns for parsing the hop details
|
||||
hostIPRegex := regexp.MustCompile(`^([^ ]+) \(([^)]+)\)`)
|
||||
hostIPBracketRegex := regexp.MustCompile(`^([^ ]+) \[([^\]]+)\]`)
|
||||
// Pattern for latencies with flexible spacing and optional '<'
|
||||
latencyRegex := regexp.MustCompile(`(<?\d+(?:\.\d+)?)\s*ms\b`)
|
||||
|
||||
for i := 1; i < len(lines); i++ {
|
||||
matches := hopRegex.FindStringSubmatch(lines[i])
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
matches := hopRegex.FindStringSubmatch(line)
|
||||
if len(matches) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
hop, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return Traceroute{}, fmt.Errorf("parsing hop number: %w", err)
|
||||
// Skip lines that don't start with a hop number
|
||||
continue
|
||||
}
|
||||
|
||||
remainder := strings.TrimSpace(matches[2])
|
||||
var hopHostname string
|
||||
var hopIP netip.Addr
|
||||
var latencies []time.Duration
|
||||
|
||||
// Handle hostname and IP
|
||||
if matches[2] != "" && matches[3] != "" {
|
||||
hopHostname = matches[2]
|
||||
hopIP, err = netip.ParseAddr(matches[3])
|
||||
if err != nil {
|
||||
return Traceroute{}, fmt.Errorf("parsing hop IP address %s: %w", matches[3], err)
|
||||
// Check for Windows tracert format which has latencies before hostname
|
||||
// Format: " 1 <1 ms <1 ms <1 ms router.local [192.168.1.1]"
|
||||
latencyFirst := false
|
||||
if strings.Contains(remainder, " ms ") && !strings.HasPrefix(remainder, "*") {
|
||||
// Check if latencies appear before any hostname/IP
|
||||
firstSpace := strings.Index(remainder, " ")
|
||||
if firstSpace > 0 {
|
||||
firstPart := remainder[:firstSpace]
|
||||
if _, err := strconv.ParseFloat(strings.TrimPrefix(firstPart, "<"), 64); err == nil {
|
||||
latencyFirst = true
|
||||
}
|
||||
}
|
||||
} else if matches[4] == "*" {
|
||||
hopHostname = "*"
|
||||
// No IP for timeouts
|
||||
}
|
||||
|
||||
// Parse latencies
|
||||
for j := 5; j <= 7; j++ {
|
||||
if j < len(matches) && matches[j] != "" {
|
||||
ms, err := strconv.ParseFloat(matches[j], 64)
|
||||
if err != nil {
|
||||
return Traceroute{}, fmt.Errorf("parsing latency: %w", err)
|
||||
if latencyFirst {
|
||||
// Windows format: extract latencies first
|
||||
for {
|
||||
latMatch := latencyRegex.FindStringSubmatchIndex(remainder)
|
||||
if latMatch == nil || latMatch[0] > 0 {
|
||||
break
|
||||
}
|
||||
// Extract and remove the latency from the beginning
|
||||
latStr := strings.TrimPrefix(remainder[latMatch[2]:latMatch[3]], "<")
|
||||
ms, err := strconv.ParseFloat(latStr, 64)
|
||||
if err == nil {
|
||||
// Round to nearest microsecond to avoid floating point precision issues
|
||||
duration := time.Duration(ms * float64(time.Millisecond))
|
||||
latencies = append(latencies, duration.Round(time.Microsecond))
|
||||
}
|
||||
remainder = strings.TrimSpace(remainder[latMatch[1]:])
|
||||
}
|
||||
}
|
||||
|
||||
// Now parse hostname/IP from remainder
|
||||
if strings.HasPrefix(remainder, "*") {
|
||||
// Timeout hop
|
||||
hopHostname = "*"
|
||||
// Skip any remaining asterisks
|
||||
remainder = strings.TrimLeft(remainder, "* ")
|
||||
} else if hostMatch := hostIPRegex.FindStringSubmatch(remainder); len(hostMatch) >= 3 {
|
||||
// Format: hostname (IP)
|
||||
hopHostname = hostMatch[1]
|
||||
hopIP, _ = netip.ParseAddr(hostMatch[2])
|
||||
remainder = strings.TrimSpace(remainder[len(hostMatch[0]):])
|
||||
} else if hostMatch := hostIPBracketRegex.FindStringSubmatch(remainder); len(hostMatch) >= 3 {
|
||||
// Format: hostname [IP] (Windows)
|
||||
hopHostname = hostMatch[1]
|
||||
hopIP, _ = netip.ParseAddr(hostMatch[2])
|
||||
remainder = strings.TrimSpace(remainder[len(hostMatch[0]):])
|
||||
} else {
|
||||
// Try to parse as IP only or hostname only
|
||||
parts := strings.Fields(remainder)
|
||||
if len(parts) > 0 {
|
||||
hopHostname = parts[0]
|
||||
if ip, err := netip.ParseAddr(parts[0]); err == nil {
|
||||
hopIP = ip
|
||||
}
|
||||
remainder = strings.TrimSpace(strings.Join(parts[1:], " "))
|
||||
}
|
||||
}
|
||||
|
||||
// Extract latencies from the remaining part (if not already done)
|
||||
if !latencyFirst {
|
||||
latencyMatches := latencyRegex.FindAllStringSubmatch(remainder, -1)
|
||||
for _, match := range latencyMatches {
|
||||
if len(match) > 1 {
|
||||
// Remove '<' prefix if present (e.g., "<1 ms")
|
||||
latStr := strings.TrimPrefix(match[1], "<")
|
||||
ms, err := strconv.ParseFloat(latStr, 64)
|
||||
if err == nil {
|
||||
// Round to nearest microsecond to avoid floating point precision issues
|
||||
duration := time.Duration(ms * float64(time.Millisecond))
|
||||
latencies = append(latencies, duration.Round(time.Microsecond))
|
||||
}
|
||||
}
|
||||
latencies = append(latencies, time.Duration(ms*float64(time.Millisecond)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,6 +335,431 @@ func TestParseTraceroute(t *testing.T) {
|
||||
want: Traceroute{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "windows tracert format",
|
||||
input: `Tracing route to google.com [8.8.8.8]
|
||||
over a maximum of 30 hops:
|
||||
|
||||
1 <1 ms <1 ms <1 ms router.local [192.168.1.1]
|
||||
2 5 ms 4 ms 5 ms 10.0.0.1
|
||||
3 * * * Request timed out.
|
||||
4 20 ms 19 ms 21 ms 8.8.8.8`,
|
||||
want: Traceroute{
|
||||
Hostname: "google.com",
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "router.local",
|
||||
IP: netip.MustParseAddr("192.168.1.1"),
|
||||
Latencies: []time.Duration{
|
||||
1 * time.Millisecond,
|
||||
1 * time.Millisecond,
|
||||
1 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 2,
|
||||
Hostname: "10.0.0.1",
|
||||
IP: netip.MustParseAddr("10.0.0.1"),
|
||||
Latencies: []time.Duration{
|
||||
5 * time.Millisecond,
|
||||
4 * time.Millisecond,
|
||||
5 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 3,
|
||||
Hostname: "*",
|
||||
},
|
||||
{
|
||||
Hop: 4,
|
||||
Hostname: "8.8.8.8",
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
Latencies: []time.Duration{
|
||||
20 * time.Millisecond,
|
||||
19 * time.Millisecond,
|
||||
21 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "mixed latency formats",
|
||||
input: `traceroute to 192.168.1.1 (192.168.1.1), 30 hops max, 60 byte packets
|
||||
1 gateway (192.168.1.1) 0.5 ms * 0.4 ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "192.168.1.1",
|
||||
IP: netip.MustParseAddr("192.168.1.1"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "gateway",
|
||||
IP: netip.MustParseAddr("192.168.1.1"),
|
||||
Latencies: []time.Duration{
|
||||
500 * time.Microsecond,
|
||||
400 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "only one latency value",
|
||||
input: `traceroute to 10.0.0.1 (10.0.0.1), 30 hops max, 60 byte packets
|
||||
1 10.0.0.1 (10.0.0.1) 1.5 ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "10.0.0.1",
|
||||
IP: netip.MustParseAddr("10.0.0.1"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "10.0.0.1",
|
||||
IP: netip.MustParseAddr("10.0.0.1"),
|
||||
Latencies: []time.Duration{
|
||||
1500 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "backward compatibility - original format with 3 latencies",
|
||||
input: `traceroute to 172.24.0.3 (172.24.0.3), 30 hops max, 46 byte packets
|
||||
1 ts-head-hk0urr.headscale.net (100.64.0.1) 1.135 ms 0.922 ms 0.619 ms
|
||||
2 172.24.0.3 (172.24.0.3) 0.593 ms 0.549 ms 0.522 ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "172.24.0.3",
|
||||
IP: netip.MustParseAddr("172.24.0.3"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "ts-head-hk0urr.headscale.net",
|
||||
IP: netip.MustParseAddr("100.64.0.1"),
|
||||
Latencies: []time.Duration{
|
||||
1135 * time.Microsecond,
|
||||
922 * time.Microsecond,
|
||||
619 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 2,
|
||||
Hostname: "172.24.0.3",
|
||||
IP: netip.MustParseAddr("172.24.0.3"),
|
||||
Latencies: []time.Duration{
|
||||
593 * time.Microsecond,
|
||||
549 * time.Microsecond,
|
||||
522 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "two latencies only - common on packet loss",
|
||||
input: `traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
|
||||
1 gateway (192.168.1.1) 1.2 ms 1.1 ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "8.8.8.8",
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "gateway",
|
||||
IP: netip.MustParseAddr("192.168.1.1"),
|
||||
Latencies: []time.Duration{
|
||||
1200 * time.Microsecond,
|
||||
1100 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: false,
|
||||
Err: errors.New("traceroute did not reach target"),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "hostname without parentheses - some traceroute versions",
|
||||
input: `traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
|
||||
1 192.168.1.1 1.2 ms 1.1 ms 1.0 ms
|
||||
2 8.8.8.8 20.1 ms 19.9 ms 20.2 ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "8.8.8.8",
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "192.168.1.1",
|
||||
IP: netip.MustParseAddr("192.168.1.1"),
|
||||
Latencies: []time.Duration{
|
||||
1200 * time.Microsecond,
|
||||
1100 * time.Microsecond,
|
||||
1000 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 2,
|
||||
Hostname: "8.8.8.8",
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
Latencies: []time.Duration{
|
||||
20100 * time.Microsecond,
|
||||
19900 * time.Microsecond,
|
||||
20200 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ipv6 traceroute",
|
||||
input: `traceroute to 2001:4860:4860::8888 (2001:4860:4860::8888), 30 hops max, 80 byte packets
|
||||
1 2001:db8::1 (2001:db8::1) 1.123 ms 1.045 ms 0.987 ms
|
||||
2 2001:4860:4860::8888 (2001:4860:4860::8888) 15.234 ms 14.876 ms 15.123 ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "2001:4860:4860::8888",
|
||||
IP: netip.MustParseAddr("2001:4860:4860::8888"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "2001:db8::1",
|
||||
IP: netip.MustParseAddr("2001:db8::1"),
|
||||
Latencies: []time.Duration{
|
||||
1123 * time.Microsecond,
|
||||
1045 * time.Microsecond,
|
||||
987 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 2,
|
||||
Hostname: "2001:4860:4860::8888",
|
||||
IP: netip.MustParseAddr("2001:4860:4860::8888"),
|
||||
Latencies: []time.Duration{
|
||||
15234 * time.Microsecond,
|
||||
14876 * time.Microsecond,
|
||||
15123 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "macos traceroute with extra spacing",
|
||||
input: `traceroute to google.com (8.8.8.8), 64 hops max, 52 byte packets
|
||||
1 router.home (192.168.1.1) 2.345 ms 1.234 ms 1.567 ms
|
||||
2 * * *
|
||||
3 isp-gw.net (10.1.1.1) 15.234 ms 14.567 ms 15.890 ms
|
||||
4 google.com (8.8.8.8) 20.123 ms 19.456 ms 20.789 ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "google.com",
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "router.home",
|
||||
IP: netip.MustParseAddr("192.168.1.1"),
|
||||
Latencies: []time.Duration{
|
||||
2345 * time.Microsecond,
|
||||
1234 * time.Microsecond,
|
||||
1567 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 2,
|
||||
Hostname: "*",
|
||||
},
|
||||
{
|
||||
Hop: 3,
|
||||
Hostname: "isp-gw.net",
|
||||
IP: netip.MustParseAddr("10.1.1.1"),
|
||||
Latencies: []time.Duration{
|
||||
15234 * time.Microsecond,
|
||||
14567 * time.Microsecond,
|
||||
15890 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 4,
|
||||
Hostname: "google.com",
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
Latencies: []time.Duration{
|
||||
20123 * time.Microsecond,
|
||||
19456 * time.Microsecond,
|
||||
20789 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "busybox traceroute minimal format",
|
||||
input: `traceroute to 10.0.0.1 (10.0.0.1), 30 hops max, 38 byte packets
|
||||
1 10.0.0.1 (10.0.0.1) 1.234 ms 1.123 ms 1.456 ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "10.0.0.1",
|
||||
IP: netip.MustParseAddr("10.0.0.1"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "10.0.0.1",
|
||||
IP: netip.MustParseAddr("10.0.0.1"),
|
||||
Latencies: []time.Duration{
|
||||
1234 * time.Microsecond,
|
||||
1123 * time.Microsecond,
|
||||
1456 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "linux traceroute with dns failure fallback to IP",
|
||||
input: `traceroute to example.com (93.184.216.34), 30 hops max, 60 byte packets
|
||||
1 192.168.1.1 (192.168.1.1) 1.234 ms 1.123 ms 1.098 ms
|
||||
2 10.0.0.1 (10.0.0.1) 5.678 ms 5.432 ms 5.321 ms
|
||||
3 93.184.216.34 (93.184.216.34) 20.123 ms 19.876 ms 20.234 ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "example.com",
|
||||
IP: netip.MustParseAddr("93.184.216.34"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "192.168.1.1",
|
||||
IP: netip.MustParseAddr("192.168.1.1"),
|
||||
Latencies: []time.Duration{
|
||||
1234 * time.Microsecond,
|
||||
1123 * time.Microsecond,
|
||||
1098 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 2,
|
||||
Hostname: "10.0.0.1",
|
||||
IP: netip.MustParseAddr("10.0.0.1"),
|
||||
Latencies: []time.Duration{
|
||||
5678 * time.Microsecond,
|
||||
5432 * time.Microsecond,
|
||||
5321 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 3,
|
||||
Hostname: "93.184.216.34",
|
||||
IP: netip.MustParseAddr("93.184.216.34"),
|
||||
Latencies: []time.Duration{
|
||||
20123 * time.Microsecond,
|
||||
19876 * time.Microsecond,
|
||||
20234 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "alpine linux traceroute with ms variations",
|
||||
input: `traceroute to 1.1.1.1 (1.1.1.1), 30 hops max, 46 byte packets
|
||||
1 gateway (192.168.0.1) 0.456ms 0.389ms 0.412ms
|
||||
2 1.1.1.1 (1.1.1.1) 8.234ms 7.987ms 8.123ms`,
|
||||
want: Traceroute{
|
||||
Hostname: "1.1.1.1",
|
||||
IP: netip.MustParseAddr("1.1.1.1"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "gateway",
|
||||
IP: netip.MustParseAddr("192.168.0.1"),
|
||||
Latencies: []time.Duration{
|
||||
456 * time.Microsecond,
|
||||
389 * time.Microsecond,
|
||||
412 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 2,
|
||||
Hostname: "1.1.1.1",
|
||||
IP: netip.MustParseAddr("1.1.1.1"),
|
||||
Latencies: []time.Duration{
|
||||
8234 * time.Microsecond,
|
||||
7987 * time.Microsecond,
|
||||
8123 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "mixed asterisk and latency values",
|
||||
input: `traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
|
||||
1 gateway (192.168.1.1) * 1.234 ms 1.123 ms
|
||||
2 10.0.0.1 (10.0.0.1) 5.678 ms * 5.432 ms
|
||||
3 8.8.8.8 (8.8.8.8) 20.123 ms 19.876 ms *`,
|
||||
want: Traceroute{
|
||||
Hostname: "8.8.8.8",
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
Route: []TraceroutePath{
|
||||
{
|
||||
Hop: 1,
|
||||
Hostname: "gateway",
|
||||
IP: netip.MustParseAddr("192.168.1.1"),
|
||||
Latencies: []time.Duration{
|
||||
1234 * time.Microsecond,
|
||||
1123 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 2,
|
||||
Hostname: "10.0.0.1",
|
||||
IP: netip.MustParseAddr("10.0.0.1"),
|
||||
Latencies: []time.Duration{
|
||||
5678 * time.Microsecond,
|
||||
5432 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
{
|
||||
Hop: 3,
|
||||
Hostname: "8.8.8.8",
|
||||
IP: netip.MustParseAddr("8.8.8.8"),
|
||||
Latencies: []time.Duration{
|
||||
20123 * time.Microsecond,
|
||||
19876 * time.Microsecond,
|
||||
},
|
||||
},
|
||||
},
|
||||
Success: true,
|
||||
Err: nil,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
Loading…
Reference in New Issue
Block a user