119 lines
3.2 KiB
Go
119 lines
3.2 KiB
Go
//go:build unix
|
|
|
|
package ping
|
|
|
|
import (
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"runtime"
|
|
"syscall"
|
|
|
|
"github.com/sagernet/sing/common/bufio"
|
|
"github.com/sagernet/sing/common/control"
|
|
E "github.com/sagernet/sing/common/exceptions"
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
func connect(privileged bool, controlFunc control.Func, destination netip.Addr) (net.Conn, error) {
|
|
var (
|
|
network string
|
|
fd int
|
|
err error
|
|
)
|
|
if destination.Is4() {
|
|
network = "ip4" // like std's netFD.ctrlNetwork
|
|
if !privileged {
|
|
fd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP)
|
|
} else {
|
|
fd, err = unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_ICMP)
|
|
}
|
|
} else {
|
|
network = "ip6" // like std's netFD.ctrlNetwork
|
|
if !privileged {
|
|
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6)
|
|
} else {
|
|
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_ICMPV6)
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, E.Cause(err, "socket()")
|
|
}
|
|
file := os.NewFile(uintptr(fd), "datagram-oriented icmp")
|
|
defer file.Close()
|
|
if controlFunc != nil {
|
|
var syscallConn syscall.RawConn
|
|
syscallConn, err = file.SyscallConn()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = controlFunc(network, destination.String(), syscallConn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if destination.Is4() && (runtime.GOOS == "linux" || runtime.GOOS == "android") {
|
|
//err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVTOS, 1)
|
|
//if err != nil {
|
|
// return nil, err
|
|
//}
|
|
err = unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_RECVTTL, 1)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "setsockopt()")
|
|
}
|
|
}
|
|
if destination.Is6() {
|
|
err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_RECVHOPLIMIT, 1)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "setsockopt()")
|
|
}
|
|
err = unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_RECVTCLASS, 1)
|
|
if err != nil {
|
|
return nil, E.Cause(err, "setsockopt()")
|
|
}
|
|
}
|
|
|
|
var bindAddress netip.Addr
|
|
if !destination.Is6() {
|
|
bindAddress = netip.AddrFrom4([4]byte{})
|
|
} else {
|
|
bindAddress = netip.AddrFrom16([16]byte{})
|
|
}
|
|
|
|
err = unix.Bind(fd, M.AddrPortToSockaddr(netip.AddrPortFrom(bindAddress, 0)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
useUnconnected := (runtime.GOOS == "darwin" && !privileged) ||
|
|
((runtime.GOOS == "linux" || runtime.GOOS == "android") && privileged)
|
|
if useUnconnected {
|
|
// A connected ICMP socket only receives messages whose source is the connected peer,
|
|
// so the Time Exceeded replies that transit routers send for traceroute never reach it.
|
|
// Additionally, on macOS NetworkExtension, writing to a connected socket returns EPIPE.
|
|
var packetConn net.PacketConn
|
|
packetConn, err = net.FilePacketConn(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var writeTarget net.Addr
|
|
if privileged {
|
|
writeTarget = M.SocksaddrFrom(destination, 0).IPAddr()
|
|
} else {
|
|
writeTarget = M.SocksaddrFrom(destination, 0).UDPAddr()
|
|
}
|
|
return bufio.NewBindPacketConn(packetConn, writeTarget), nil
|
|
}
|
|
err = unix.Connect(fd, M.AddrPortToSockaddr(netip.AddrPortFrom(destination, 0)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var conn net.Conn
|
|
conn, err = net.FileConn(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return conn, nil
|
|
}
|