82 lines
1.7 KiB
Go
82 lines
1.7 KiB
Go
//go:build windows
|
|
|
|
package diagnostic
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type NetworkCollectorImpl struct{}
|
|
|
|
func (tracer *NetworkCollectorImpl) Collect(ctx context.Context, options TraceOptions) ([]*Hop, string, error) {
|
|
ipversion := "-4"
|
|
if !options.useV4 {
|
|
ipversion = "-6"
|
|
}
|
|
|
|
args := []string{
|
|
ipversion,
|
|
"-w",
|
|
strconv.FormatInt(int64(options.timeout.Seconds()), 10),
|
|
"-h",
|
|
strconv.FormatUint(options.ttl, 10),
|
|
// Do not resolve host names (can add 30+ seconds to run time)
|
|
"-d",
|
|
options.address,
|
|
}
|
|
command := exec.CommandContext(ctx, "tracert.exe", args...)
|
|
|
|
return decodeNetworkOutputToFile(command, DecodeLine)
|
|
}
|
|
|
|
func DecodeLine(text string) (*Hop, error) {
|
|
const requestTimedOut = "Request timed out."
|
|
|
|
fields := strings.Fields(text)
|
|
parts := []string{}
|
|
filter := func(s string) bool { return s != "*" && s != "ms" }
|
|
|
|
for _, field := range fields {
|
|
if filter(field) {
|
|
parts = append(parts, field)
|
|
}
|
|
}
|
|
|
|
index, err := strconv.ParseUint(parts[0], 10, 8)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("couldn't parse index from timeout hop: %w", err)
|
|
}
|
|
|
|
domain := ""
|
|
rtts := []time.Duration{}
|
|
|
|
for _, part := range parts[1:] {
|
|
|
|
rtt, err := strconv.ParseFloat(strings.TrimLeft(part, "<"), 64)
|
|
|
|
if err != nil {
|
|
domain += part + " "
|
|
} else {
|
|
rtts = append(rtts, time.Duration(rtt*MicrosecondsFactor))
|
|
}
|
|
}
|
|
|
|
domain, _ = strings.CutSuffix(domain, " ")
|
|
// If the domain is equal to "Request timed out." then we build a
|
|
// timeout hop.
|
|
if domain == requestTimedOut {
|
|
return NewTimeoutHop(uint8(index)), nil
|
|
}
|
|
|
|
if domain == "" {
|
|
return nil, ErrEmptyDomain
|
|
}
|
|
|
|
return NewHop(uint8(index), domain, rtts), nil
|
|
}
|