fix(icmp): ICMP tunnel not working on Windows due to wildcard bind (#98)
On Windows, icmp.ListenPacket("ip4:icmp", "0.0.0.0") does not reliably
deliver ICMP packets (golang/go#38427). Add a platform-specific
ListenPacket helper that binds to a discovered interface address and
enables SIO_RCVALL via WSAIoctl for promiscuous receive.
Fixes go-gost/x#36
This commit is contained in:
@@ -14,7 +14,6 @@ import (
|
|||||||
icmp_pkg "github.com/go-gost/x/internal/util/icmp"
|
icmp_pkg "github.com/go-gost/x/internal/util/icmp"
|
||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"golang.org/x/net/icmp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -87,9 +86,9 @@ func (d *icmpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialO
|
|||||||
|
|
||||||
var pc net.PacketConn
|
var pc net.PacketConn
|
||||||
if d.ip6 {
|
if d.ip6 {
|
||||||
pc, err = icmp.ListenPacket("ip6:ipv6-icmp", "")
|
pc, err = icmp_pkg.ListenPacket("ip6:ipv6-icmp", "")
|
||||||
} else {
|
} else {
|
||||||
pc, err = icmp.ListenPacket("ip4:icmp", "")
|
pc, err = icmp_pkg.ListenPacket("ip4:icmp", "")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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 "", ""
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
stats "github.com/go-gost/x/observer/stats/wrapper"
|
stats "github.com/go-gost/x/observer/stats/wrapper"
|
||||||
"github.com/go-gost/x/registry"
|
"github.com/go-gost/x/registry"
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"golang.org/x/net/icmp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -69,9 +68,9 @@ func (l *icmpListener) Init(md md.Metadata) (err error) {
|
|||||||
|
|
||||||
var conn net.PacketConn
|
var conn net.PacketConn
|
||||||
if l.ip6 {
|
if l.ip6 {
|
||||||
conn, err = icmp.ListenPacket("ip6:ipv6-icmp", addr)
|
conn, err = icmp_pkg.ListenPacket("ip6:ipv6-icmp", addr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = icmp.ListenPacket("ip4:icmp", addr)
|
conn, err = icmp_pkg.ListenPacket("ip4:icmp", addr)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user