Add ping client
This commit is contained in:
parent
036d61a0aa
commit
8dbb51cfb7
21 changed files with 710 additions and 384 deletions
16
ping/cmsg_unix.go
Normal file
16
ping/cmsg_unix.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//go:build !windows
|
||||
|
||||
package ping
|
||||
|
||||
import (
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
func parseIPv6ControlMessage(cmsg []byte) (*ipv6.ControlMessage, error) {
|
||||
var controlMessage ipv6.ControlMessage
|
||||
err := controlMessage.Parse(cmsg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &controlMessage, nil
|
||||
}
|
||||
46
ping/cmsg_windows.go
Normal file
46
ping/cmsg_windows.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package ping
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/net/ipv6"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
IPV6_HOPLIMIT = 21
|
||||
IPV6_TCLASS = 39
|
||||
IPV6_RECVTCLASS = 40
|
||||
)
|
||||
|
||||
var (
|
||||
alignedSizeofCmsghdr = (sizeofCmsghdr + cmsgAlignTo - 1) & ^(cmsgAlignTo - 1)
|
||||
sizeofCmsghdr = int(unsafe.Sizeof(windows.WSACMSGHDR{}))
|
||||
cmsgAlignTo = int(unsafe.Sizeof(uintptr(0)))
|
||||
)
|
||||
|
||||
func cmsgAlign(n int) int {
|
||||
return (n + cmsgAlignTo - 1) & ^(cmsgAlignTo - 1)
|
||||
}
|
||||
|
||||
func parseIPv6ControlMessage(cmsg []byte) (*ipv6.ControlMessage, error) {
|
||||
var controlMessage ipv6.ControlMessage
|
||||
for len(cmsg) >= sizeofCmsghdr {
|
||||
cmsghdr := (*windows.WSACMSGHDR)(unsafe.Pointer(unsafe.SliceData(cmsg)))
|
||||
msgLen := int(cmsghdr.Len)
|
||||
msgSize := cmsgAlign(msgLen)
|
||||
if msgLen < sizeofCmsghdr || msgSize > len(cmsg) {
|
||||
return nil, fmt.Errorf("invalid control message length %d", cmsghdr.Len)
|
||||
}
|
||||
switch cmsghdr.Type {
|
||||
case IPV6_TCLASS:
|
||||
controlMessage.TrafficClass = int(binary.NativeEndian.Uint32(cmsg[alignedSizeofCmsghdr : alignedSizeofCmsghdr+4]))
|
||||
case IPV6_HOPLIMIT:
|
||||
controlMessage.HopLimit = int(binary.NativeEndian.Uint32(cmsg[alignedSizeofCmsghdr : alignedSizeofCmsghdr+4]))
|
||||
}
|
||||
cmsg = cmsg[msgSize:]
|
||||
}
|
||||
return &controlMessage, nil
|
||||
}
|
||||
75
ping/destination.go
Normal file
75
ping/destination.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package ping
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
var _ tun.DirectRouteDestination = (*Destination)(nil)
|
||||
|
||||
type Destination struct {
|
||||
logger logger.Logger
|
||||
routeContext tun.DirectRouteContext
|
||||
conn *Conn
|
||||
}
|
||||
|
||||
func ConnectDestination(logger logger.Logger, controlFunc control.Func, address netip.Addr, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error) {
|
||||
var (
|
||||
conn *Conn
|
||||
err error
|
||||
)
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "ios", "windows":
|
||||
conn, err = Connect(false, controlFunc, address)
|
||||
default:
|
||||
conn, err = Connect(true, controlFunc, address)
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
conn, err = Connect(false, controlFunc, address)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &Destination{
|
||||
logger: logger,
|
||||
routeContext: routeContext,
|
||||
conn: conn,
|
||||
}
|
||||
go d.loopRead()
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *Destination) loopRead() {
|
||||
for {
|
||||
buffer := buf.NewPacket()
|
||||
err := d.conn.ReadIP(buffer)
|
||||
if err != nil {
|
||||
buffer.Release()
|
||||
if !E.IsClosed(err) {
|
||||
d.logger.Error(E.Cause(err, "receive ICMP echo reply"))
|
||||
}
|
||||
return
|
||||
}
|
||||
err = d.routeContext.WritePacket(buffer.Bytes())
|
||||
if err != nil {
|
||||
d.logger.Error(E.Cause(err, "write ICMP echo reply"))
|
||||
}
|
||||
buffer.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Destination) WritePacket(packet *buf.Buffer) error {
|
||||
return d.conn.WriteIP(packet)
|
||||
}
|
||||
|
||||
func (d *Destination) Close() error {
|
||||
return d.conn.Close()
|
||||
}
|
||||
207
ping/ping.go
Normal file
207
ping/ping.go
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
package ping
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
privileged bool
|
||||
conn net.Conn
|
||||
destination netip.Addr
|
||||
source atomic.TypedValue[netip.Addr]
|
||||
}
|
||||
|
||||
func Connect(privileged bool, controlFunc control.Func, destination netip.Addr) (*Conn, error) {
|
||||
conn, err := connect(privileged, controlFunc, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Conn{
|
||||
privileged: privileged,
|
||||
conn: conn,
|
||||
destination: destination,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Conn) ReadIP(buffer *buf.Buffer) error {
|
||||
if c.destination.Is6() || runtime.GOOS == "linux" && !c.privileged {
|
||||
var readMsg func(b, oob []byte) (n, oobn int, addr netip.Addr, err error)
|
||||
switch conn := c.conn.(type) {
|
||||
case *net.IPConn:
|
||||
readMsg = func(b, oob []byte) (n, oobn int, addr netip.Addr, err error) {
|
||||
var ipAddr *net.IPAddr
|
||||
n, oobn, _, ipAddr, err = conn.ReadMsgIP(b, oob)
|
||||
if ipAddr != nil {
|
||||
addr = M.AddrFromNet(ipAddr)
|
||||
}
|
||||
return
|
||||
}
|
||||
case *net.UDPConn:
|
||||
readMsg = func(b, oob []byte) (n, oobn int, addr netip.Addr, err error) {
|
||||
var udpAddr *net.UDPAddr
|
||||
n, oobn, _, udpAddr, err = conn.ReadMsgUDP(b, oob)
|
||||
if udpAddr != nil {
|
||||
addr = M.AddrFromNet(udpAddr)
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
return E.New("unsupported conn type: ", reflect.TypeOf(c.conn))
|
||||
}
|
||||
if !c.destination.Is6() {
|
||||
oob := ipv4.NewControlMessage(ipv4.FlagTTL)
|
||||
buffer.Advance(header.IPv4MinimumSize)
|
||||
var ttl int
|
||||
// tos int
|
||||
n, oobn, addr, err := readMsg(buffer.FreeBytes(), oob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer.Truncate(n)
|
||||
if oobn > 0 {
|
||||
var controlMessage ipv4.ControlMessage
|
||||
err = controlMessage.Parse(oob[:oobn])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ttl = controlMessage.TTL
|
||||
}
|
||||
ipHdr := header.IPv4(buffer.ExtendHeader(header.IPv4MinimumSize))
|
||||
ipHdr.Encode(&header.IPv4Fields{
|
||||
// TOS: uint8(tos),
|
||||
SrcAddr: addr,
|
||||
DstAddr: c.source.Load(),
|
||||
Protocol: uint8(header.ICMPv4ProtocolNumber),
|
||||
TTL: uint8(ttl),
|
||||
TotalLength: uint16(buffer.Len()),
|
||||
})
|
||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
||||
} else {
|
||||
oob := make([]byte, 1024)
|
||||
buffer.Advance(header.IPv6MinimumSize)
|
||||
var (
|
||||
hopLimit int
|
||||
trafficClass int
|
||||
)
|
||||
n, oobn, addr, err := readMsg(buffer.FreeBytes(), oob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buffer.Truncate(n)
|
||||
if oobn > 0 {
|
||||
var controlMessage *ipv6.ControlMessage
|
||||
controlMessage, err = parseIPv6ControlMessage(oob[:oobn])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hopLimit = controlMessage.HopLimit
|
||||
trafficClass = controlMessage.TrafficClass
|
||||
}
|
||||
icmpHdr := header.ICMPv6(buffer.Bytes())
|
||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||
Header: icmpHdr[:header.ICMPv6DstUnreachableMinimumSize],
|
||||
Src: addr.AsSlice(),
|
||||
Dst: c.source.Load().AsSlice(),
|
||||
}))
|
||||
ipHdr := header.IPv6(buffer.ExtendHeader(header.IPv6MinimumSize))
|
||||
ipHdr.Encode(&header.IPv6Fields{
|
||||
TrafficClass: uint8(trafficClass),
|
||||
PayloadLength: uint16(buffer.Len() - header.IPv6MinimumSize),
|
||||
TransportProtocol: header.ICMPv6ProtocolNumber,
|
||||
HopLimit: uint8(hopLimit),
|
||||
SrcAddr: addr,
|
||||
DstAddr: c.source.Load(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
_, err := buffer.ReadOnceFrom(c.conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !c.destination.Is6() {
|
||||
ipHdr := header.IPv4(buffer.Bytes())
|
||||
ipHdr.SetDestinationAddr(c.source.Load())
|
||||
ipHdr.SetChecksum(0)
|
||||
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
||||
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr[:header.ICMPv4MinimumSize], checksum.Checksum(icmpHdr.Payload(), 0)))
|
||||
} else {
|
||||
ipHdr := header.IPv6(buffer.Bytes())
|
||||
ipHdr.SetDestinationAddr(c.source.Load())
|
||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||
Header: icmpHdr,
|
||||
Src: ipHdr.SourceAddressSlice(),
|
||||
Dst: ipHdr.DestinationAddressSlice(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) ReadICMP(buffer *buf.Buffer) error {
|
||||
_, err := buffer.ReadOnceFrom(c.conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.destination.Is6() || runtime.GOOS == "linux" && !c.privileged {
|
||||
return nil
|
||||
}
|
||||
if !c.destination.Is6() {
|
||||
ipHdr := header.IPv4(buffer.Bytes())
|
||||
buffer.Advance(int(ipHdr.HeaderLength()))
|
||||
} else {
|
||||
ipHdr := header.IPv6(buffer.Bytes())
|
||||
buffer.Advance(buffer.Len() - int(ipHdr.PayloadLength()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) WriteIP(buffer *buf.Buffer) error {
|
||||
defer buffer.Release()
|
||||
if !c.destination.Is6() {
|
||||
ipHdr := header.IPv4(buffer.Bytes())
|
||||
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||
return common.Error(c.conn.Write(ipHdr.Payload()))
|
||||
} else {
|
||||
ipHdr := header.IPv6(buffer.Bytes())
|
||||
c.source.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||
return common.Error(c.conn.Write(ipHdr.Payload()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Conn) WriteICMP(buffer *buf.Buffer) error {
|
||||
defer buffer.Release()
|
||||
return common.Error(c.conn.Write(buffer.Bytes()))
|
||||
}
|
||||
|
||||
func (c *Conn) SetLocalAddr(addr netip.Addr) {
|
||||
c.source.Store(addr)
|
||||
}
|
||||
|
||||
func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
193
ping/ping_test.go
Normal file
193
ping/ping_test.go
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package ping_test
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/rand"
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||
"github.com/sagernet/sing-tun/ping"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
t.Parallel()
|
||||
const addr4 = "127.0.0.1"
|
||||
t.Run("ipv4", func(t *testing.T) {
|
||||
t.Run("unprivileged", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.SkipNow()
|
||||
}
|
||||
t.Run("read-icmp", func(t *testing.T) {
|
||||
testPingIPv4ReadICMP(t, false, addr4)
|
||||
})
|
||||
t.Run("read-ip", func(t *testing.T) {
|
||||
testPingIPv4ReadIP(t, false, addr4)
|
||||
})
|
||||
})
|
||||
t.Run("privileged", func(t *testing.T) {
|
||||
if runtime.GOOS != "windows" && os.Getuid() != 0 {
|
||||
t.SkipNow()
|
||||
}
|
||||
t.Run("read-icmp", func(t *testing.T) {
|
||||
testPingIPv4ReadICMP(t, true, addr4)
|
||||
})
|
||||
t.Run("read-ip", func(t *testing.T) {
|
||||
testPingIPv4ReadIP(t, true, addr4)
|
||||
})
|
||||
})
|
||||
})
|
||||
// const addr6 = "2606:4700:4700::1001"
|
||||
const addr6 = "::1"
|
||||
t.Run("ipv6", func(t *testing.T) {
|
||||
t.Run("unprivileged", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.SkipNow()
|
||||
}
|
||||
t.Run("read-icmp", func(t *testing.T) {
|
||||
testPingIPv6ReadICMP(t, false, addr6)
|
||||
})
|
||||
t.Run("read-ip", func(t *testing.T) {
|
||||
testPingIPv6ReadIP(t, false, addr6)
|
||||
})
|
||||
})
|
||||
t.Run("privileged", func(t *testing.T) {
|
||||
if runtime.GOOS != "windows" && os.Getuid() != 0 {
|
||||
t.SkipNow()
|
||||
}
|
||||
t.Run("read-icmp", func(t *testing.T) {
|
||||
testPingIPv6ReadICMP(t, true, addr6)
|
||||
})
|
||||
t.Run("read-ip", func(t *testing.T) {
|
||||
testPingIPv6ReadIP(t, true, addr6)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testPingIPv4ReadIP(t *testing.T, privileged bool, addr string) {
|
||||
conn, err := ping.Connect(privileged, nil, netip.MustParseAddr(addr))
|
||||
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||
t.SkipNow()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
request := make(header.ICMPv4, header.ICMPv4MinimumSize)
|
||||
request.SetType(header.ICMPv4Echo)
|
||||
request.SetIdent(uint16(rand.Uint32()))
|
||||
request.SetChecksum(header.ICMPv4Checksum(request, 0))
|
||||
|
||||
err = conn.WriteICMP(buf.As(request))
|
||||
require.NoError(t, err)
|
||||
|
||||
conn.SetLocalAddr(netip.MustParseAddr("127.0.0.1"))
|
||||
require.NoError(t, conn.SetReadDeadline(time.Now().Add(3*time.Second)))
|
||||
|
||||
response := buf.NewPacket()
|
||||
err = conn.ReadIP(response)
|
||||
require.NoError(t, err)
|
||||
if runtime.GOOS == "linux" && privileged {
|
||||
response.Reset()
|
||||
err = conn.ReadIP(response)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
ipHdr := header.IPv4(response.Bytes())
|
||||
require.NotZero(t, ipHdr.TTL())
|
||||
icmpHdr := header.ICMPv4(ipHdr.Payload())
|
||||
require.Equal(t, header.ICMPv4EchoReply, icmpHdr.Type())
|
||||
}
|
||||
|
||||
func testPingIPv4ReadICMP(t *testing.T, privileged bool, addr string) {
|
||||
conn, err := ping.Connect(privileged, nil, netip.MustParseAddr(addr))
|
||||
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||
t.SkipNow()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
request := make(header.ICMPv4, header.ICMPv4MinimumSize)
|
||||
request.SetType(header.ICMPv4Echo)
|
||||
request.SetIdent(uint16(rand.Uint32()))
|
||||
request.SetChecksum(header.ICMPv4Checksum(request, 0))
|
||||
|
||||
err = conn.WriteICMP(buf.As(request))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, conn.SetReadDeadline(time.Now().Add(3*time.Second)))
|
||||
|
||||
response := buf.NewPacket()
|
||||
err = conn.ReadICMP(response)
|
||||
require.NoError(t, err)
|
||||
|
||||
if runtime.GOOS == "linux" && privileged {
|
||||
response.Reset()
|
||||
err = conn.ReadICMP(response)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
icmpHdr := header.ICMPv4(response.Bytes())
|
||||
require.Equal(t, header.ICMPv4EchoReply, icmpHdr.Type())
|
||||
}
|
||||
|
||||
func testPingIPv6ReadIP(t *testing.T, privileged bool, addr string) {
|
||||
conn, err := ping.Connect(privileged, nil, netip.MustParseAddr(addr))
|
||||
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||
t.SkipNow()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
request := make(header.ICMPv6, header.ICMPv6MinimumSize)
|
||||
request.SetType(header.ICMPv6EchoRequest)
|
||||
request.SetIdent(uint16(rand.Uint32()))
|
||||
|
||||
err = conn.WriteICMP(buf.As(request))
|
||||
require.NoError(t, err)
|
||||
|
||||
conn.SetLocalAddr(netip.MustParseAddr("::1"))
|
||||
require.NoError(t, conn.SetReadDeadline(time.Now().Add(3*time.Second)))
|
||||
|
||||
response := buf.NewPacket()
|
||||
err = conn.ReadIP(response)
|
||||
require.NoError(t, err)
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" && privileged {
|
||||
response.Reset()
|
||||
err = conn.ReadIP(response)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
ipHdr := header.IPv6(response.Bytes())
|
||||
require.NotZero(t, ipHdr.HopLimit())
|
||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||
require.Equal(t, header.ICMPv6EchoReply, icmpHdr.Type())
|
||||
}
|
||||
|
||||
func testPingIPv6ReadICMP(t *testing.T, privileged bool, addr string) {
|
||||
conn, err := ping.Connect(privileged, nil, netip.MustParseAddr(addr))
|
||||
if runtime.GOOS == "linux" && err != nil && err.Error() == "socket(): permission denied" {
|
||||
t.SkipNow()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
request := make(header.ICMPv6, header.ICMPv6MinimumSize)
|
||||
request.SetType(header.ICMPv6EchoRequest)
|
||||
request.SetIdent(uint16(rand.Uint32()))
|
||||
|
||||
err = conn.WriteICMP(buf.As(request))
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, conn.SetReadDeadline(time.Now().Add(3*time.Second)))
|
||||
|
||||
response := buf.NewPacket()
|
||||
err = conn.ReadICMP(response)
|
||||
require.NoError(t, err)
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" && privileged {
|
||||
response.Reset()
|
||||
err = conn.ReadICMP(response)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
icmpHdr := header.ICMPv6(response.Bytes())
|
||||
require.Equal(t, header.ICMPv6EchoReply, icmpHdr.Type())
|
||||
}
|
||||
86
ping/socket_unix.go
Normal file
86
ping/socket_unix.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
//go:build unix
|
||||
|
||||
package ping
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"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:icmp"
|
||||
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:icmp"
|
||||
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()
|
||||
err = unix.Connect(fd, M.AddrPortToSockaddr(netip.AddrPortFrom(destination, 0)))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "connect()")
|
||||
}
|
||||
|
||||
if destination.Is4() && runtime.GOOS == "linux" {
|
||||
//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()")
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := net.FileConn(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if controlFunc != nil {
|
||||
var syscallConn syscall.RawConn
|
||||
syscallConn, err = conn.(syscall.Conn).SyscallConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = controlFunc(network, destination.String(), syscallConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
38
ping/socket_windows.go
Normal file
38
ping/socket_windows.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package ping
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func connect(privileged bool, controlFunc control.Func, destination netip.Addr) (net.Conn, error) {
|
||||
var dialer net.Dialer
|
||||
dialer.Control = controlFunc
|
||||
if destination.Is6() {
|
||||
dialer.Control = control.Append(dialer.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
err := windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_HOPLIMIT, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = windows.SetsockoptInt(windows.Handle(fd), windows.IPPROTO_IPV6, IPV6_RECVTCLASS, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
var network string
|
||||
if destination.Is4() {
|
||||
network = "ip4:icmp"
|
||||
} else {
|
||||
network = "ip6:ipv6-icmp"
|
||||
}
|
||||
return dialer.Dial(network, destination.String())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue