Files
Archive/sing-box/common/tlsspoof/raw_darwin.go
T
2026-04-17 21:15:10 +02:00

197 lines
6.2 KiB
Go

package tlsspoof
import (
"encoding/binary"
"net"
"net/netip"
"strconv"
"strings"
"sync"
"syscall"
"github.com/sagernet/sing-tun/gtcpip/header"
E "github.com/sagernet/sing/common/exceptions"
"golang.org/x/sys/unix"
)
const PlatformSupported = true
// Offsets into xinpcb_n within each net.inet.tcp.pcblist_n record, identical
// to the values used by common/process/searcher_darwin_shared.go.
const (
darwinXinpgenSize = 24
darwinXsocketOffset = 104
darwinXinpcbForeignPort = 16
darwinXinpcbLocalPort = 18
darwinXinpcbVFlag = 44
darwinXinpcbForeignAddr = 48
darwinXinpcbLocalAddr = 64
darwinXinpcbIPv4Offset = 12
darwinTCPExtraSize = 208
darwinXtcpcbSndNxtOffset = 56
darwinXtcpcbRcvNxtOffset = 80
)
// darwinStructSize returns the size of xinpcb_n for the running Darwin kernel.
// Darwin 22 (macOS 13 Ventura) grew the struct from 384 to 408 bytes; there is
// no ABI-stable way to read it, so we key off the kernel version.
var darwinStructSize = sync.OnceValues(func() (int, error) {
value, err := syscall.Sysctl("kern.osrelease")
if err != nil {
return 0, E.Cause(err, "sysctl kern.osrelease")
}
major, _, ok := strings.Cut(value, ".")
if !ok {
return 0, E.New("unexpected kern.osrelease format: ", value)
}
n, err := strconv.ParseInt(major, 10, 64)
if err != nil {
return 0, E.Cause(err, "parse kern.osrelease major version: ", value)
}
if n >= 22 {
return 408, nil
}
return 384, nil
})
type darwinSpoofer struct {
method Method
src netip.AddrPort
dst netip.AddrPort
rawFD int
rawSockAddr unix.Sockaddr
sendNext uint32
receiveNext uint32
}
func newRawSpoofer(conn net.Conn, method Method) (Spoofer, error) {
_, src, dst, err := tcpEndpoints(conn)
if err != nil {
return nil, err
}
fd, sockaddr, err := openDarwinRawSocket(src, dst)
if err != nil {
return nil, err
}
sendNext, receiveNext, err := readDarwinTCPSequence(src, dst)
if err != nil {
unix.Close(fd)
return nil, err
}
return &darwinSpoofer{
method: method,
src: src,
dst: dst,
rawFD: fd,
rawSockAddr: sockaddr,
sendNext: sendNext,
receiveNext: receiveNext,
}, nil
}
// readDarwinTCPSequence scans net.inet.tcp.pcblist_n for the PCB that matches
// src -> dst and returns (snd_nxt, rcv_nxt). These live in xtcpcb_n at the end
// of each record; see darwin-xnu bsd/netinet/in_pcblist.c:get_pcblist_n.
func readDarwinTCPSequence(src, dst netip.AddrPort) (uint32, uint32, error) {
buffer, err := unix.SysctlRaw("net.inet.tcp.pcblist_n")
if err != nil {
return 0, 0, E.Cause(err, "sysctl net.inet.tcp.pcblist_n")
}
structSize, err := darwinStructSize()
if err != nil {
return 0, 0, err
}
itemSize := structSize + darwinTCPExtraSize
for i := darwinXinpgenSize; i+itemSize <= len(buffer); i += itemSize {
inpcb := buffer[i : i+darwinXsocketOffset]
xtcpcb := buffer[i+structSize : i+itemSize]
localPort := binary.BigEndian.Uint16(inpcb[darwinXinpcbLocalPort : darwinXinpcbLocalPort+2])
remotePort := binary.BigEndian.Uint16(inpcb[darwinXinpcbForeignPort : darwinXinpcbForeignPort+2])
if localPort != src.Port() || remotePort != dst.Port() {
continue
}
versionFlag := inpcb[darwinXinpcbVFlag]
var localAddr, remoteAddr netip.Addr
switch {
case versionFlag&0x1 != 0:
localAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset : darwinXinpcbLocalAddr+darwinXinpcbIPv4Offset+4]))
remoteAddr = netip.AddrFrom4([4]byte(inpcb[darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset : darwinXinpcbForeignAddr+darwinXinpcbIPv4Offset+4]))
case versionFlag&0x2 != 0:
localAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbLocalAddr : darwinXinpcbLocalAddr+16]))
remoteAddr = netip.AddrFrom16([16]byte(inpcb[darwinXinpcbForeignAddr : darwinXinpcbForeignAddr+16]))
default:
continue
}
if localAddr.Unmap() != src.Addr() || remoteAddr.Unmap() != dst.Addr() {
continue
}
sendNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbSndNxtOffset : darwinXtcpcbSndNxtOffset+4])
receiveNext := binary.NativeEndian.Uint32(xtcpcb[darwinXtcpcbRcvNxtOffset : darwinXtcpcbRcvNxtOffset+4])
return sendNext, receiveNext, nil
}
return 0, 0, E.New("tls_spoof: connection ", src, "->", dst, " not found in pcblist_n")
}
func openDarwinRawSocket(src, dst netip.AddrPort) (int, unix.Sockaddr, error) {
if dst.Addr().Is4() {
return openIPv4RawSocket(dst)
}
// macOS does not accept IPV6_HDRINCL on AF_INET6 SOCK_RAW IPPROTO_TCP
// sockets, so the kernel builds the IPv6 header itself. Bind to the real
// connection's source address so in6_selectsrc returns it, and rely on
// in6p_cksum defaulting to -1 so the user-supplied TCP checksum is
// preserved (including deliberately corrupted ones).
fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_RAW, unix.IPPROTO_TCP)
if err != nil {
return -1, nil, E.Cause(err, "open AF_INET6 SOCK_RAW")
}
err = unix.Bind(fd, &unix.SockaddrInet6{Addr: src.Addr().As16()})
if err != nil {
unix.Close(fd)
return -1, nil, E.Cause(err, "bind AF_INET6 SOCK_RAW")
}
sockaddr := &unix.SockaddrInet6{Port: int(dst.Port()), Addr: dst.Addr().As16()}
return fd, sockaddr, nil
}
func (s *darwinSpoofer) Inject(payload []byte) error {
if !s.src.Addr().Is4() {
segment, err := buildSpoofTCPSegment(s.method, s.src, s.dst, s.sendNext, s.receiveNext, payload)
if err != nil {
return err
}
err = unix.Sendto(s.rawFD, segment, 0, s.rawSockAddr)
if err != nil {
return E.Cause(err, "sendto raw socket")
}
return nil
}
frame, err := buildSpoofFrame(s.method, s.src, s.dst, s.sendNext, s.receiveNext, payload)
if err != nil {
return err
}
// Darwin inherits the historical BSD quirk: with IP_HDRINCL the kernel
// expects ip_len and ip_off in host byte order, not network byte order.
// Apple's rip_output swaps them back before transmission.
ip := header.IPv4(frame)
ip.SetTotalLengthDarwinRaw(ip.TotalLength())
ip.SetFlagsFragmentOffsetDarwinRaw(ip.Flags(), ip.FragmentOffset())
err = unix.Sendto(s.rawFD, frame, 0, s.rawSockAddr)
if err != nil {
return E.Cause(err, "sendto raw socket")
}
return nil
}
func (s *darwinSpoofer) Close() error {
if s.rawFD < 0 {
return nil
}
err := unix.Close(s.rawFD)
s.rawFD = -1
return err
}