//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 }