mirror of
https://github.com/SagerNet/sing-tun.git
synced 2026-04-22 16:07:19 +08:00
Add ping proxy implementation
This commit is contained in:
@@ -6,7 +6,7 @@ require (
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/google/btree v1.1.3
|
||||
github.com/sagernet/fswatch v0.1.1
|
||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||
github.com/sagernet/nftables v0.3.0-beta.4
|
||||
github.com/sagernet/sing v0.7.0-beta.1
|
||||
|
||||
@@ -18,8 +18,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
|
||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38=
|
||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
|
||||
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237 h1:SUPFNB+vSP4RBPrSEgNII+HkfqC8hKMpYLodom4o4EU=
|
||||
github.com/sagernet/gvisor v0.0.0-20250822052253-5558536cf237/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func NewICMPDestination(ctx context.Context, logger logger.Logger, dialer net.Dialer, network string, address netip.Addr, routeContext DirectRouteContext) (DirectRouteDestination, error) {
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
|
||||
return NewUnprivilegedICMPDestination(ctx, logger, dialer, network, address, routeContext)
|
||||
} else {
|
||||
destination, err := NewPrivilegedICMPDestination(ctx, logger, dialer, network, address, routeContext)
|
||||
if err != nil {
|
||||
if E.IsMulti(err, os.ErrPermission, unix.EPERM) {
|
||||
return NewUnprivilegedICMPDestination(ctx, logger, dialer, network, address, routeContext)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return destination, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type PrivilegedICMPDestination struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelCauseFunc
|
||||
logger logger.Logger
|
||||
routeContext DirectRouteContext
|
||||
isIPv6 bool
|
||||
localAddr atomic.TypedValue[netip.Addr]
|
||||
rawConn net.Conn
|
||||
}
|
||||
|
||||
func NewPrivilegedICMPDestination(ctx context.Context, logger logger.Logger, dialer net.Dialer, network string, address netip.Addr, routeContext DirectRouteContext) (DirectRouteDestination, error) {
|
||||
var dialNetwork string
|
||||
switch network {
|
||||
case N.NetworkICMPv4:
|
||||
dialNetwork = "ip4:icmp"
|
||||
case N.NetworkICMPv6:
|
||||
dialNetwork = "ip6:icmp"
|
||||
default:
|
||||
return nil, E.New("unsupported network: ", network)
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
rawConn, err := dialer.DialContext(ctx, dialNetwork, address.String())
|
||||
if err != nil {
|
||||
cancel(err)
|
||||
return nil, err
|
||||
}
|
||||
d := &PrivilegedICMPDestination{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
logger: logger,
|
||||
routeContext: routeContext,
|
||||
isIPv6: network == N.NetworkICMPv6,
|
||||
rawConn: rawConn,
|
||||
}
|
||||
go d.loopRead()
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *PrivilegedICMPDestination) loopRead() {
|
||||
for {
|
||||
buffer := buf.NewPacket()
|
||||
_, err := buffer.ReadOnceFrom(d.rawConn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !d.isIPv6 {
|
||||
ipHdr := header.IPv4(buffer.Bytes())
|
||||
ipHdr.SetDestinationAddr(d.localAddr.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(d.localAddr.Load())
|
||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||
Header: icmpHdr,
|
||||
Src: ipHdr.SourceAddress(),
|
||||
Dst: ipHdr.DestinationAddress(),
|
||||
}))
|
||||
}
|
||||
err = d.routeContext.WritePacket(buffer.Bytes())
|
||||
if err != nil {
|
||||
d.logger.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *PrivilegedICMPDestination) WritePacket(packet *buf.Buffer) error {
|
||||
if !d.isIPv6 {
|
||||
ipHdr := header.IPv4(packet.Bytes())
|
||||
d.localAddr.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||
_, err := d.rawConn.Write(icmpHdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
ipHdr := header.IPv6(packet.Bytes())
|
||||
d.localAddr.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||
_, err := d.rawConn.Write(icmpHdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *PrivilegedICMPDestination) Close() error {
|
||||
d.cancel(os.ErrClosed)
|
||||
return d.rawConn.Close()
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//go:build with_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
func (d *PrivilegedICMPDestination) WritePacketBuffer(packetBuffer *stack.PacketBuffer) error {
|
||||
ipHdr := packetBuffer.Network()
|
||||
if !d.isIPv6 {
|
||||
d.localAddr.Store(netip.AddrFrom4(ipHdr.SourceAddress().As4()))
|
||||
} else {
|
||||
d.localAddr.Store(netip.AddrFrom16(ipHdr.SourceAddress().As16()))
|
||||
}
|
||||
packetSlice := packetBuffer.TransportHeader().Slice()
|
||||
packetSlice = append(packetSlice, packetBuffer.Data().AsRange().ToSlice()...)
|
||||
_, err := d.rawConn.Write(packetSlice)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/checksum"
|
||||
"github.com/sagernet/sing-tun/internal/gtcpip/header"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type UnprivilegedICMPDestination struct {
|
||||
ctx context.Context
|
||||
cancel context.CancelCauseFunc
|
||||
logger logger.Logger
|
||||
routeContext DirectRouteContext
|
||||
isIPv6 bool
|
||||
localAddr atomic.TypedValue[netip.Addr]
|
||||
rawConn net.Conn
|
||||
ipHdr bool
|
||||
}
|
||||
|
||||
func NewUnprivilegedICMPDestination(ctx context.Context, logger logger.Logger, dialer net.Dialer, network string, address netip.Addr, routeContext DirectRouteContext) (DirectRouteDestination, error) {
|
||||
var (
|
||||
isIPv6 bool
|
||||
fd int
|
||||
ipHdr bool
|
||||
err error
|
||||
)
|
||||
var dialNetwork string
|
||||
switch network {
|
||||
case N.NetworkICMPv4:
|
||||
dialNetwork = "ip4:icmp"
|
||||
case N.NetworkICMPv6:
|
||||
dialNetwork = "ip6:icmp"
|
||||
isIPv6 = true
|
||||
default:
|
||||
return nil, E.New("unsupported network: ", network)
|
||||
}
|
||||
if !isIPv6 {
|
||||
fd, err = unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP)
|
||||
} else {
|
||||
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name, nameLen := bufio.ToSockaddr(M.SocksaddrFrom(address, 0).AddrPort())
|
||||
err = unixConnect(fd, name, nameLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawConn, err := net.FileConn(os.NewFile(uintptr(fd), "datagram-oriented icmp"))
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
if dialer.Control != nil {
|
||||
var syscallConn syscall.RawConn
|
||||
syscallConn, err = rawConn.(syscall.Conn).SyscallConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = dialer.Control(dialNetwork, address.String(), syscallConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d := &UnprivilegedICMPDestination{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
routeContext: routeContext,
|
||||
isIPv6: network == N.NetworkICMPv6,
|
||||
rawConn: rawConn,
|
||||
ipHdr: ipHdr,
|
||||
}
|
||||
go d.loopRead()
|
||||
return d, nil
|
||||
}
|
||||
|
||||
//go:linkname unixConnect golang.org/x/sys/unix.connect
|
||||
func unixConnect(fd int, addr unsafe.Pointer, addrlen uint32) error
|
||||
|
||||
func (d *UnprivilegedICMPDestination) loopRead() {
|
||||
for {
|
||||
buffer := buf.NewPacket()
|
||||
_, err := buffer.ReadOnceFrom(d.rawConn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if d.ipHdr {
|
||||
if !d.isIPv6 {
|
||||
ipHdr := header.IPv4(buffer.Bytes())
|
||||
ipHdr.SetDestinationAddr(d.localAddr.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(d.localAddr.Load())
|
||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
||||
Header: icmpHdr,
|
||||
Src: ipHdr.SourceAddress(),
|
||||
Dst: ipHdr.DestinationAddress(),
|
||||
}))
|
||||
}
|
||||
err = d.routeContext.WritePacket(buffer.Bytes())
|
||||
if err != nil {
|
||||
d.logger.Error(err)
|
||||
}
|
||||
} else {
|
||||
panic("impl no hdr version for windows and linux")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *UnprivilegedICMPDestination) WritePacket(packet *buf.Buffer) error {
|
||||
if !d.isIPv6 {
|
||||
ipHdr := header.IPv4(packet.Bytes())
|
||||
d.localAddr.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||
_, err := d.rawConn.Write(icmpHdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
ipHdr := header.IPv6(packet.Bytes())
|
||||
d.localAddr.Store(M.AddrFromIP(ipHdr.SourceAddressSlice()))
|
||||
icmpHdr := header.ICMPv6(ipHdr.Payload())
|
||||
_, err := d.rawConn.Write(icmpHdr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *UnprivilegedICMPDestination) Close() error {
|
||||
d.cancel(os.ErrClosed)
|
||||
return d.rawConn.Close()
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//go:build with_gvisor
|
||||
|
||||
package tun
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
func (d *UnprivilegedICMPDestination) WritePacketBuffer(packetBuffer *stack.PacketBuffer) error {
|
||||
ipHdr := packetBuffer.Network()
|
||||
if !d.isIPv6 {
|
||||
d.localAddr.Store(netip.AddrFrom4(ipHdr.SourceAddress().As4()))
|
||||
} else {
|
||||
d.localAddr.Store(netip.AddrFrom16(ipHdr.SourceAddress().As16()))
|
||||
}
|
||||
packetSlice := packetBuffer.TransportHeader().Slice()
|
||||
packetSlice = append(packetSlice, packetBuffer.Data().AsRange().ToSlice()...)
|
||||
_, err := d.rawConn.Write(packetSlice)
|
||||
return err
|
||||
}
|
||||
+2
-2
@@ -36,10 +36,10 @@ func (m *RouteMapping) Lookup(session DirectRouteSession, constructor func() (Di
|
||||
)
|
||||
action, _, ok := m.status.GetAndRefreshOrAdd(session, func() (DirectRouteDestination, bool) {
|
||||
created, err = constructor()
|
||||
return created, err != nil
|
||||
return created, err == nil
|
||||
})
|
||||
if !ok {
|
||||
return created, err
|
||||
return nil, err
|
||||
}
|
||||
return action, nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header/parse"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
@@ -178,7 +179,8 @@ func (w *ICMPBackWriter) WritePacket(p []byte) error {
|
||||
Payload: buffer.MakeWithData(p),
|
||||
})
|
||||
defer packet.DecRef()
|
||||
err = route.WriteHeaderIncludedPacket(packet)
|
||||
parse.IPv4(packet)
|
||||
err = route.WritePacketDirect(packet)
|
||||
if err != nil {
|
||||
return gonet.TranslateNetstackError(err)
|
||||
}
|
||||
@@ -198,7 +200,7 @@ func (w *ICMPBackWriter) WritePacket(p []byte) error {
|
||||
Payload: buffer.MakeWithData(p),
|
||||
})
|
||||
defer packet.DecRef()
|
||||
err = route.WriteHeaderIncludedPacket(packet)
|
||||
err = route.WritePacketDirect(packet)
|
||||
if err != nil {
|
||||
return gonet.TranslateNetstackError(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user