diff --git a/dialer/icmp/dialer.go b/dialer/icmp/dialer.go index 98a91393..b307522a 100644 --- a/dialer/icmp/dialer.go +++ b/dialer/icmp/dialer.go @@ -14,7 +14,6 @@ import ( icmp_pkg "github.com/go-gost/x/internal/util/icmp" "github.com/go-gost/x/registry" "github.com/quic-go/quic-go" - "golang.org/x/net/icmp" ) func init() { @@ -87,9 +86,9 @@ func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO var pc net.PacketConn if d.ip6 { - pc, err = icmp.ListenPacket("ip6:ipv6-icmp", "") + pc, err = icmp_pkg.ListenPacket("ip6:ipv6-icmp", "") } else { - pc, err = icmp.ListenPacket("ip4:icmp", "") + pc, err = icmp_pkg.ListenPacket("ip4:icmp", "") } if err != nil { return diff --git a/internal/util/icmp/listen_other.go b/internal/util/icmp/listen_other.go new file mode 100644 index 00000000..75720831 --- /dev/null +++ b/internal/util/icmp/listen_other.go @@ -0,0 +1,14 @@ +//go:build !windows + +package icmp + +import ( + "net" + + "golang.org/x/net/icmp" +) + +// ListenPacket listens for ICMP packets on the given network and address. +func ListenPacket(network, address string) (net.PacketConn, error) { + return icmp.ListenPacket(network, address) +} diff --git a/internal/util/icmp/listen_windows.go b/internal/util/icmp/listen_windows.go new file mode 100644 index 00000000..b6fa8356 --- /dev/null +++ b/internal/util/icmp/listen_windows.go @@ -0,0 +1,85 @@ +//go:build windows + +package icmp + +import ( + "context" + "fmt" + "net" + "os" + "syscall" + "unsafe" + + "golang.org/x/net/icmp" +) + +// Windows-specific constants for WSAIoctl. +const ( + SIO_RCVALL = syscall.IOC_IN | syscall.IOC_VENDOR | 1 + + RCVALL_OFF = 0 + RCVALL_ON = 1 + RCVALL_SOCKETLEVELONLY = 2 + RCVALL_IPLEVEL = 3 +) + +// ListenPacket listens for ICMP packets on the given network and address. +// On Windows, when the address is unspecified ("" or "0.0.0.0"/"::"), it uses +// SIO_RCVALL to work around https://github.com/golang/go/issues/38427. +func ListenPacket(network, address string) (net.PacketConn, error) { + if ip := net.ParseIP(address); ip != nil && !ip.IsUnspecified() { + return icmp.ListenPacket(network, address) + } + + dialAddr, dialNetwork := probeAddr(network) + if dialAddr == "" { + return icmp.ListenPacket(network, address) + } + + // Dial an external address to discover the correct local interface. + dialedConn, err := net.Dial(dialNetwork, dialAddr) + if err != nil { + return nil, fmt.Errorf("icmp: dial %s: %w", dialAddr, err) + } + localAddr := dialedConn.LocalAddr() + dialedConn.Close() + + var socketHandle syscall.Handle + cfg := net.ListenConfig{ + Control: func(network, address string, c syscall.RawConn) error { + return c.Control(func(s uintptr) { + socketHandle = syscall.Handle(s) + }) + }, + } + + conn, err := cfg.ListenPacket(context.Background(), network, localAddr.String()) + if err != nil { + return nil, err + } + + // Enable promiscuous mode so the socket receives all ICMP packets, + // including error messages, regardless of the original destination address. + unused := uint32(0) + flag := uint32(RCVALL_IPLEVEL) + size := uint32(unsafe.Sizeof(flag)) + if err := syscall.WSAIoctl(socketHandle, SIO_RCVALL, (*byte)(unsafe.Pointer(&flag)), size, nil, 0, &unused, nil, 0); err != nil { + conn.Close() + return nil, fmt.Errorf("icmp: WSAIoctl(SIO_RCVALL): %w", os.NewSyscallError("WSAIoctl", err)) + } + + return conn, nil +} + +// probeAddr returns the dial network and address to use for discovering +// the local interface. Returns empty strings if the network is not a +// supported ICMP type. +func probeAddr(network string) (dialAddr, dialNetwork string) { + switch network { + case "ip4:icmp": + return "1.1.1.1", "ip4:icmp" + case "ip6:ipv6-icmp": + return "2001:4860:4860::8888", "ip6:ipv6-icmp" + } + return "", "" +} diff --git a/listener/icmp/listener.go b/listener/icmp/listener.go index 2bafb108..5df089d1 100644 --- a/listener/icmp/listener.go +++ b/listener/icmp/listener.go @@ -16,7 +16,6 @@ import ( stats "github.com/go-gost/x/observer/stats/wrapper" "github.com/go-gost/x/registry" "github.com/quic-go/quic-go" - "golang.org/x/net/icmp" ) func init() { @@ -69,9 +68,9 @@ func (l *icmpListener) Init(md md.Metadata) (err error) { var conn net.PacketConn if l.ip6 { - conn, err = icmp.ListenPacket("ip6:ipv6-icmp", addr) + conn, err = icmp_pkg.ListenPacket("ip6:ipv6-icmp", addr) } else { - conn, err = icmp.ListenPacket("ip4:icmp", addr) + conn, err = icmp_pkg.ListenPacket("ip4:icmp", addr) } if err != nil { return