diff --git a/Dockerfile b/Dockerfile index 53ab1f8..b4e8176 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,12 +6,9 @@ COPY src/ . RUN go mod init ngtech.co.il/network/livtrace/v2 && go mod tidy -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ - go build -ldflags="-s -w" -o /out/livetrace-linux-amd64 . +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /out/livetrace-linux-amd64 . -RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \ - go build -ldflags="-s -w" -o /out/livetrace-windows-amd64.exe . +RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o /out/livetrace-windows-amd64.exe . -# Stage 2: Export target FROM scratch AS bin COPY --from=builder /out/ / diff --git a/dist/livetrace-linux-amd64 b/dist/livetrace-linux-amd64 index cdc11bf..d646c6c 100755 Binary files a/dist/livetrace-linux-amd64 and b/dist/livetrace-linux-amd64 differ diff --git a/dist/livetrace-windows-amd64.exe b/dist/livetrace-windows-amd64.exe index 44482be..925a1bc 100755 Binary files a/dist/livetrace-windows-amd64.exe and b/dist/livetrace-windows-amd64.exe differ diff --git a/src/main.go b/src/main.go index 2f57829..5d2ef5a 100644 --- a/src/main.go +++ b/src/main.go @@ -295,12 +295,16 @@ func NewTracer( func (t *Tracer) Done() <-chan struct{} { return t.stopCh } func (t *Tracer) Start() error { - var err error + if t.proto == "winapi" { + // Windows ICMP API manages its own sockets per probe. + // No raw socket, no receive loop, no cleanup loop needed. + go t.probeLoop() + return nil + } - // ICMP socket — always required (receiving ICMP errors, and sending in ICMP mode). + var err error t.icmpConn, err = icmp.ListenPacket("ip4:icmp", "0.0.0.0") if err != nil { - // Fallback: unprivileged ICMP dgram (Linux ≥5.x with ping_group_range). t.icmpConn, err = icmp.ListenPacket("udp4", "0.0.0.0:0") if err != nil { hint := "try: sudo ./livetrace" @@ -312,7 +316,6 @@ func (t *Tracer) Start() error { } t.icmpPC = t.icmpConn.IPv4PacketConn() - // UDP sending socket — only needed in UDP mode. if t.proto == "udp" { t.udpSend, err = net.ListenPacket("udp4", ":0") if err != nil { @@ -334,7 +337,9 @@ func (t *Tracer) Stop() { default: close(t.stopCh) } - _ = t.icmpConn.Close() + if t.icmpConn != nil { + _ = t.icmpConn.Close() + } if t.udpSend != nil { _ = t.udpSend.Close() } @@ -386,9 +391,12 @@ func (t *Tracer) makeUDPBytes() []byte { // ─── Sending ────────────────────────────────────────────────────────────────── func (t *Tracer) sendProbe(ttl int) { - if t.proto == "udp" { + switch t.proto { + case "udp": t.sendProbeUDP(ttl) - } else { + case "winapi": + t.sendProbeWinAPI(ttl) + default: t.sendProbeICMP(ttl) } } @@ -449,6 +457,12 @@ func (t *Tracer) sendProbeUDP(ttl int) { } } +func winAPIProbe(_ net.IP, _ int, _ time.Duration, _ []byte) (float64, string, uint32, error) { + return 0, "", 0, fmt.Errorf("winapi probe not supported on this platform") +} + +func (t *Tracer) sendProbeWinAPI(_ int) {} + // ─── Probe loop ─────────────────────────────────────────────────────────────── func (t *Tracer) probeLoop() { @@ -898,7 +912,7 @@ func main() { maxHops := flag.Int("m", 30, "maximum hops") intervalMS := flag.Int("i", 1000, "probe interval in milliseconds") timeoutMS := flag.Int("t", 3000, "probe timeout in milliseconds") - proto := flag.String("proto", "icmp", "probe protocol: icmp or udp") + proto := flag.String("proto", "icmp", "probe protocol: icmp, udp, or winapi (Windows ICMP API, Windows only)") port := flag.Int("port", 33434, "base destination port (UDP mode only)") size := flag.Int("size", defPktSize, "total IP packet size in bytes (min 28); use 1500 for MTU test") noDNS := flag.Bool("n", false, "disable reverse DNS (show numeric IPs only)") @@ -930,10 +944,15 @@ Examples: } p := strings.ToLower(*proto) - if p != "icmp" && p != "udp" { - fmt.Fprintln(os.Stderr, "Error: -proto must be icmp or udp") - os.Exit(1) - } + +if p != "icmp" && p != "udp" && p != "winapi" { + fmt.Fprintln(os.Stderr, "Error: -proto must be icmp, udp, or winapi") + os.Exit(1) +} +if p == "winapi" && runtime.GOOS != "windows" { + fmt.Fprintln(os.Stderr, "Error: -proto winapi is only available on Windows") + os.Exit(1) +} tracer, err := NewTracer( flag.Arg(0),