This commit is contained in:
Eliezer Croitoru 2026-09-09 16:27:18 +03:00
parent db9f887958
commit 3a8850b8c2
5 changed files with 152 additions and 6 deletions

Binary file not shown.

Binary file not shown.

View File

@ -457,12 +457,6 @@ 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() {

15
src/winapi_other.go Normal file
View File

@ -0,0 +1,15 @@
//go:build !windows
package main
import (
"fmt"
"net"
"time"
)
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) {}

137
src/winapi_windows.go Normal file
View File

@ -0,0 +1,137 @@
//go:build windows
package main
import (
"fmt"
"net"
"syscall"
"time"
"unsafe"
)
var (
modIphlpapi = syscall.NewLazyDLL("iphlpapi.dll")
procIcmpCreate = modIphlpapi.NewProc("IcmpCreateFile")
procIcmpClose = modIphlpapi.NewProc("IcmpCloseHandle")
procIcmpSend2 = modIphlpapi.NewProc("IcmpSendEcho2")
)
const (
ipSuccess uint32 = 0
ipTTLExpiredTransit uint32 = 11013
ipReqTimedOut uint32 = 11010
)
// mirrors IP_OPTION_INFORMATION from <ipexport.h>
type ipOptInfo struct {
Ttl uint8
Tos uint8
Flags uint8
OptionsSize uint8
OptionsData uintptr // PUCHAR — platform-sized pointer
}
// mirrors ICMP_ECHO_REPLY from <ipexport.h>
type icmpEchoReply struct {
Address uint32 // source IP, bytes in network order inside a LE DWORD
Status uint32
RoundTripTime uint32 // integer ms; we measure our own sub-ms time
DataSize uint16
Reserved uint16
Data uintptr // PVOID — platform-sized pointer
Options ipOptInfo
}
// winAPIProbe sends one ICMP echo with the given TTL via the Windows ICMP
// helper API (iphlpapi.dll) and blocks until a reply arrives or timeout
// elapses. Returns (rtt ms, responder IP, IP status code, error).
func winAPIProbe(targetIP net.IP, ttl int, timeout time.Duration, payload []byte) (float64, string, uint32, error) {
handle, _, e := procIcmpCreate.Call()
if handle == 0 {
return 0, "", 0, fmt.Errorf("IcmpCreateFile: %w", e)
}
defer procIcmpClose.Call(handle) //nolint:errcheck
// Pack the destination IP into a DWORD with the bytes in network order.
// On LE x86/x64: storing [ip[0]..ip[3]] means ip[0] is the LSB of the uint32.
ip4 := targetIP.To4()
dst := uint32(ip4[0]) | uint32(ip4[1])<<8 | uint32(ip4[2])<<16 | uint32(ip4[3])<<24
opts := ipOptInfo{Ttl: uint8(ttl)}
replyBufSz := uintptr(unsafe.Sizeof(icmpEchoReply{})) + uintptr(len(payload)) + 8
replyBuf := make([]byte, replyBufSz)
var dataPtr uintptr
if len(payload) > 0 {
dataPtr = uintptr(unsafe.Pointer(&payload[0]))
}
start := time.Now()
ret, _, _ := procIcmpSend2.Call(
handle,
0, 0, 0, // Event, ApcRoutine, ApcContext — synchronous mode
uintptr(dst),
dataPtr, uintptr(len(payload)),
uintptr(unsafe.Pointer(&opts)),
uintptr(unsafe.Pointer(&replyBuf[0])),
uintptr(replyBufSz),
uintptr(timeout.Milliseconds()),
)
rtt := float64(time.Since(start).Microseconds()) / 1000.0
if ret == 0 {
// Timeout or hard error — treat as timeout.
return 0, "", ipReqTimedOut, nil
}
reply := (*icmpEchoReply)(unsafe.Pointer(&replyBuf[0]))
// Unpack the source IP: Address bytes are in network order inside the DWORD.
fromIP := net.IP{
byte(reply.Address),
byte(reply.Address >> 8),
byte(reply.Address >> 16),
byte(reply.Address >> 24),
}.String()
return rtt, fromIP, reply.Status, nil
}
// sendProbeWinAPI sends one probe for the given TTL using the Windows ICMP
// API. It returns immediately; the blocking IcmpSendEcho2 call runs in its
// own goroutine and records the result directly into the hop.
func (t *Tracer) sendProbeWinAPI(ttl int) {
t.hops[ttl-1].recordSent()
go func() {
payload := make([]byte, t.payloadSize())
copy(payload, "livetrace")
rtt, fromIP, status, err := winAPIProbe(t.targetIP, ttl, t.timeout, payload)
// Discard the result if the tracer was stopped while we were blocked.
select {
case <-t.stopCh:
return
default:
}
if err != nil || status == ipReqTimedOut {
t.hops[ttl-1].recordTimeout()
return
}
reached := status == ipSuccess
t.hops[ttl-1].recordRecv(rtt, fromIP, reached)
if reached {
t.mu.Lock()
if t.maxActive > ttl {
t.maxActive = ttl
}
t.mu.Unlock()
}
}()
}