mirror of
https://github.com/SagerNet/sing-tun.git
synced 2025-10-05 16:47:27 +08:00
155 lines
4.0 KiB
Go
155 lines
4.0 KiB
Go
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()
|
|
}
|