Upstream acceptLoop treats any Accept error as terminal and silently returns, leaving the stack alive but every new TCP SYN NAT-rewritten onto a dead port (instant RST) until a full restart. When the listener fd is closed out from under the stack (a stray close on a reused fd number from another runtime in the same process), all new TCP dies forever while UDP/QUIC/DNS keep working. - System.Close() now marks a deliberate shutdown first; acceptLoop still exits quietly on it. - Any other Accept error is logged (the errno names the killer path), the listener is recreated on the same address, the forwarder port is republished atomically, and the loop keeps serving. - If the rebind fails, the loop logs an error and gives up - no worse than upstream. - acceptRecoveries counter is kept as telemetry. tcpPort/tcpPort6 become atomic (written by the heal path, read from the tunLoop dispatch/NAT path); listener replacement is serialized against Close() with a mutex.
1035 lines
31 KiB
Go
1035 lines
31 KiB
Go
package tun
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"slices"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/sagernet/sing-tun/gtcpip"
|
|
"github.com/sagernet/sing-tun/gtcpip/checksum"
|
|
"github.com/sagernet/sing-tun/gtcpip/header"
|
|
"github.com/sagernet/sing/common"
|
|
"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"
|
|
M "github.com/sagernet/sing/common/metadata"
|
|
N "github.com/sagernet/sing/common/network"
|
|
)
|
|
|
|
var ErrIncludeAllNetworks = E.New("`system` and `mixed` stack are not available when `includeAllNetworks` is enabled. See https://github.com/SagerNet/sing-tun/issues/25")
|
|
|
|
type System struct {
|
|
ctx context.Context
|
|
tun Tun
|
|
tunName string
|
|
netNs string
|
|
mtu int
|
|
handler Handler
|
|
logger logger.Logger
|
|
inet4Prefixes []netip.Prefix
|
|
inet6Prefixes []netip.Prefix
|
|
inet4Address netip.Addr
|
|
inet4NextAddress netip.Addr
|
|
inet6Address netip.Addr
|
|
inet6NextAddress netip.Addr
|
|
broadcastAddr netip.Addr
|
|
inet4LoopbackAddress []netip.Addr
|
|
inet6LoopbackAddress []netip.Addr
|
|
udpTimeout time.Duration
|
|
icmpTimeout time.Duration
|
|
tcpListener net.Listener
|
|
tcpListener6 net.Listener
|
|
// lx/040: ports are written by acceptLoop on self-heal relisten and read
|
|
// concurrently from the tunLoop path (dispatch filter + NAT rewrite) —
|
|
// they must be atomic. listenAccess serializes listener replacement
|
|
// against Close(); closing marks a deliberate shutdown so acceptLoop can
|
|
// tell it apart from the listener dying out from under the stack.
|
|
tcpPort atomic.Uint32
|
|
tcpPort6 atomic.Uint32
|
|
closing atomic.Bool
|
|
listenAccess sync.Mutex
|
|
acceptRecoveries atomic.Uint32
|
|
tcpNat *TCPNat
|
|
udpNat *UDPNat
|
|
udpNATOptions UDPNatOptions
|
|
dispatcher *ForwardDispatcher
|
|
bindInterface bool
|
|
interfaceFinder control.InterfaceFinder
|
|
frontHeadroom int
|
|
txChecksumOffload bool
|
|
multiPendingPackets bool
|
|
}
|
|
|
|
type Session struct {
|
|
SourceAddress netip.Addr
|
|
DestinationAddress netip.Addr
|
|
SourcePort uint16
|
|
DestinationPort uint16
|
|
}
|
|
|
|
func NewSystem(options StackOptions) (Stack, error) {
|
|
stack := &System{
|
|
ctx: options.Context,
|
|
tun: options.Tun,
|
|
tunName: options.TunOptions.Name,
|
|
netNs: options.TunOptions.NetNs,
|
|
mtu: int(options.TunOptions.MTU),
|
|
inet4LoopbackAddress: options.TunOptions.Inet4LoopbackAddress,
|
|
inet6LoopbackAddress: options.TunOptions.Inet6LoopbackAddress,
|
|
udpTimeout: options.UDPTimeout,
|
|
icmpTimeout: options.ICMPTimeout,
|
|
handler: options.Handler,
|
|
logger: options.Logger,
|
|
inet4Prefixes: options.TunOptions.Inet4Address,
|
|
inet6Prefixes: options.TunOptions.Inet6Address,
|
|
broadcastAddr: BroadcastAddr(options.TunOptions.Inet4Address),
|
|
udpNATOptions: UDPNatOptions{
|
|
Timeout: options.UDPTimeout,
|
|
Mapping: options.UDPMapping,
|
|
Filtering: options.UDPFiltering,
|
|
MaxSize: options.UDPNATMax,
|
|
InterfaceFinder: options.InterfaceFinder,
|
|
ExcludeInterface: []string{options.TunOptions.Name},
|
|
},
|
|
bindInterface: options.ForwarderBindInterface,
|
|
interfaceFinder: options.InterfaceFinder,
|
|
multiPendingPackets: options.TunOptions.EXP_MultiPendingPackets,
|
|
}
|
|
if len(options.TunOptions.Inet4Address) > 0 {
|
|
if !HasNextAddress(options.TunOptions.Inet4Address[0], 1) {
|
|
return nil, E.New("need one more IPv4 address in first prefix for system stack")
|
|
}
|
|
stack.inet4Address = options.TunOptions.Inet4Address[0].Addr()
|
|
stack.inet4NextAddress = stack.inet4Address.Next()
|
|
}
|
|
if len(options.TunOptions.Inet6Address) > 0 {
|
|
if !HasNextAddress(options.TunOptions.Inet6Address[0], 1) {
|
|
return nil, E.New("need one more IPv6 address in first prefix for system stack")
|
|
}
|
|
stack.inet6Address = options.TunOptions.Inet6Address[0].Addr()
|
|
stack.inet6NextAddress = stack.inet6Address.Next()
|
|
}
|
|
if !stack.inet4NextAddress.IsValid() && !stack.inet6NextAddress.IsValid() {
|
|
return nil, E.New("missing interface address")
|
|
}
|
|
return stack, nil
|
|
}
|
|
|
|
func (s *System) ResetNetwork() {
|
|
if s.tcpNat != nil {
|
|
s.tcpNat.Purge()
|
|
}
|
|
if s.udpNat != nil {
|
|
s.udpNat.Purge()
|
|
}
|
|
s.dispatcher.ResetNetwork()
|
|
}
|
|
|
|
func (s *System) Close() error {
|
|
// lx/040: mark the deliberate shutdown BEFORE closing the listeners so
|
|
// acceptLoop exits quietly instead of treating it as a foreign kill.
|
|
s.closing.Store(true)
|
|
s.dispatcher.Close()
|
|
if s.udpNat != nil {
|
|
s.udpNat.Close()
|
|
}
|
|
s.listenAccess.Lock()
|
|
defer s.listenAccess.Unlock()
|
|
return common.Close(
|
|
s.tcpListener,
|
|
s.tcpListener6,
|
|
)
|
|
}
|
|
|
|
func (s *System) Start() error {
|
|
err := s.start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go s.tunLoop()
|
|
return nil
|
|
}
|
|
|
|
// lx/040: TCP forwarder bind, shared by start() and the acceptLoop self-heal
|
|
// relisten path. isIPv6 selects the address family; the bind-to-interface
|
|
// Control and the EADDRNOTAVAIL retry loop match the original start() code.
|
|
func (s *System) listenTCP(isIPv6 bool) (net.Listener, error) {
|
|
var listener net.ListenConfig
|
|
if s.bindInterface {
|
|
listener.Control = control.Append(listener.Control, func(network, address string, conn syscall.RawConn) error {
|
|
bindErr := control.BindToInterface0(s.interfaceFinder, conn, network, address, s.tunName, -1, true)
|
|
if bindErr != nil {
|
|
s.logger.Warn("bind forwarder to interface: ", bindErr)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
network := "tcp4"
|
|
address := s.inet4Address
|
|
if isIPv6 {
|
|
network = "tcp6"
|
|
address = s.inet6Address
|
|
}
|
|
var (
|
|
tcpListener net.Listener
|
|
err error
|
|
)
|
|
for range 3 {
|
|
tcpListener, err = listenNetworkNamespace(s.ctx, s.netNs, listener, network, net.JoinHostPort(address.String(), "0"))
|
|
if !retryableListenError(err) {
|
|
break
|
|
}
|
|
time.Sleep(time.Second)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return tcpListener, nil
|
|
}
|
|
|
|
func (s *System) start() error {
|
|
_ = fixWindowsFirewall()
|
|
var tcpListener net.Listener
|
|
var err error
|
|
if s.inet4NextAddress.IsValid() {
|
|
tcpListener, err = s.listenTCP(false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.tcpListener = tcpListener
|
|
s.tcpPort.Store(uint32(M.SocksaddrFromNet(tcpListener.Addr()).Port))
|
|
go s.acceptLoop(tcpListener, false)
|
|
}
|
|
if s.inet6NextAddress.IsValid() {
|
|
tcpListener, err = s.listenTCP(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.tcpListener6 = tcpListener
|
|
s.tcpPort6.Store(uint32(M.SocksaddrFromNet(tcpListener.Addr()).Port))
|
|
go s.acceptLoop(tcpListener, true)
|
|
}
|
|
s.tcpNat = NewNat(s.ctx, s.udpTimeout)
|
|
udpNATOptions := s.udpNATOptions
|
|
udpNATOptions.Handler = s.handler
|
|
udpNATOptions.Prepare = s.preparePacketConnection
|
|
s.udpNat = NewUDPNat(udpNATOptions)
|
|
err = s.udpNat.Start()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if linuxTUN, isLinuxTUN := s.tun.(LinuxTUN); isLinuxTUN {
|
|
s.frontHeadroom = linuxTUN.FrontHeadroom()
|
|
s.txChecksumOffload = linuxTUN.TXChecksumOffload()
|
|
}
|
|
if s.handler != nil {
|
|
s.dispatcher = NewForwardDispatcher(s.handler, newSystemWriteback(s.tun, s.frontHeadroom), s.logger, s.udpTimeout, s.icmpTimeout)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *System) tunLoop() {
|
|
if winTun, isWinTun := s.tun.(WinTun); isWinTun {
|
|
s.wintunLoop(winTun)
|
|
return
|
|
}
|
|
if linuxTUN, isLinuxTUN := s.tun.(LinuxTUN); isLinuxTUN {
|
|
batchSize := linuxTUN.BatchSize()
|
|
if batchSize > 1 {
|
|
s.batchLoopLinux(linuxTUN, batchSize)
|
|
return
|
|
}
|
|
}
|
|
if darwinTUN, isDarwinTUN := s.tun.(DarwinTUN); isDarwinTUN && s.multiPendingPackets {
|
|
s.batchLoopDarwin(darwinTUN)
|
|
return
|
|
}
|
|
packetBuffer := make([]byte, s.mtu+PacketOffset)
|
|
for {
|
|
n, err := s.tun.Read(packetBuffer)
|
|
if err != nil {
|
|
if E.IsClosed(err) {
|
|
return
|
|
}
|
|
s.logger.Error(E.Cause(err, "read packet"))
|
|
}
|
|
if n < header.IPv4MinimumSize {
|
|
continue
|
|
}
|
|
rawPacket := packetBuffer[:n]
|
|
packet := packetBuffer[PacketOffset:n]
|
|
if s.processPacket(packet) {
|
|
_, err = s.tun.Write(rawPacket)
|
|
if err != nil {
|
|
s.logger.Trace(E.Cause(err, "write packet"))
|
|
}
|
|
}
|
|
s.dispatcher.Flush()
|
|
}
|
|
}
|
|
|
|
func (s *System) wintunLoop(winTun WinTun) {
|
|
for {
|
|
packet, release, err := winTun.ReadPacket()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if len(packet) < header.IPv4MinimumSize {
|
|
release()
|
|
continue
|
|
}
|
|
if s.processPacket(packet) {
|
|
_, err = winTun.Write(packet)
|
|
if err != nil {
|
|
s.logger.Trace(E.Cause(err, "write packet"))
|
|
}
|
|
}
|
|
s.dispatcher.Flush()
|
|
release()
|
|
}
|
|
}
|
|
|
|
func (s *System) batchLoopLinux(linuxTUN LinuxTUN, batchSize int) {
|
|
packetBuffers := make([][]byte, batchSize)
|
|
writeBuffers := make([][]byte, 0, batchSize)
|
|
packetSizes := make([]int, batchSize)
|
|
for i := range packetBuffers {
|
|
packetBuffers[i] = make([]byte, s.mtu+s.frontHeadroom)
|
|
}
|
|
for {
|
|
n, err := linuxTUN.BatchRead(packetBuffers, s.frontHeadroom, packetSizes)
|
|
if err != nil {
|
|
if E.IsClosed(err) {
|
|
return
|
|
}
|
|
s.logger.Error(E.Cause(err, "batch read packet"))
|
|
}
|
|
if n == 0 {
|
|
continue
|
|
}
|
|
for i := range n {
|
|
packetSize := packetSizes[i]
|
|
if packetSize < header.IPv4MinimumSize {
|
|
continue
|
|
}
|
|
packetBuffer := packetBuffers[i]
|
|
packet := packetBuffer[s.frontHeadroom : s.frontHeadroom+packetSize]
|
|
if s.processPacket(packet) {
|
|
writeBuffers = append(writeBuffers, packetBuffer[:s.frontHeadroom+packetSize])
|
|
}
|
|
}
|
|
if len(writeBuffers) > 0 {
|
|
_, err = linuxTUN.BatchWrite(writeBuffers, s.frontHeadroom)
|
|
if err != nil {
|
|
s.logger.Trace(E.Cause(err, "batch write packet"))
|
|
}
|
|
writeBuffers = writeBuffers[:0]
|
|
}
|
|
s.dispatcher.Flush()
|
|
}
|
|
}
|
|
|
|
func (s *System) batchLoopDarwin(darwinTUN DarwinTUN) {
|
|
var writeBuffers []*buf.Buffer
|
|
var releaseBuffers []*buf.Buffer
|
|
for {
|
|
buffers, err := darwinTUN.BatchRead()
|
|
if err != nil {
|
|
if E.IsClosed(err) || errors.Is(err, syscall.EBADF) {
|
|
return
|
|
}
|
|
s.logger.Error(E.Cause(err, "batch read packet"))
|
|
}
|
|
if len(buffers) == 0 {
|
|
continue
|
|
}
|
|
writeBuffers = writeBuffers[:0]
|
|
releaseBuffers = releaseBuffers[:0]
|
|
for _, buffer := range buffers {
|
|
packetSize := buffer.Len()
|
|
if packetSize < header.IPv4MinimumSize {
|
|
buffer.Release()
|
|
continue
|
|
}
|
|
if s.processPacket(buffer.Bytes()) {
|
|
writeBuffers = append(writeBuffers, buffer)
|
|
} else {
|
|
releaseBuffers = append(releaseBuffers, buffer)
|
|
}
|
|
}
|
|
if len(writeBuffers) > 0 {
|
|
err = darwinTUN.BatchWrite(writeBuffers)
|
|
if err != nil {
|
|
s.logger.Trace(E.Cause(err, "batch write packet"))
|
|
}
|
|
buf.ReleaseMulti(writeBuffers)
|
|
}
|
|
s.dispatcher.Flush()
|
|
buf.ReleaseMulti(releaseBuffers)
|
|
}
|
|
}
|
|
|
|
func (s *System) processPacket(packet []byte) bool {
|
|
var (
|
|
writeBack bool
|
|
err error
|
|
)
|
|
switch ipVersion := header.IPVersion(packet); ipVersion {
|
|
case header.IPv4Version:
|
|
writeBack, err = s.processIPv4(packet)
|
|
case header.IPv6Version:
|
|
writeBack, err = s.processIPv6(packet)
|
|
default:
|
|
err = E.New("ip: unknown version: ", ipVersion)
|
|
}
|
|
if err != nil {
|
|
s.logger.Trace(err)
|
|
return false
|
|
}
|
|
return writeBack
|
|
}
|
|
|
|
func (s *System) acceptLoop(listener net.Listener, isIPv6 bool) {
|
|
for {
|
|
conn, err := listener.Accept()
|
|
if err != nil {
|
|
// lx/040 (SPECS/TASKS/040): upstream silently returns on ANY Accept
|
|
// error, leaving the stack alive but every new TCP SYN NAT-rewritten
|
|
// onto a dead port (instant RST) until a VPN restart — the LxBox §047
|
|
// "browser dead, QUIC alive" failure. A deliberate System.Close is the
|
|
// only quiet exit; anything else means the listener died out from
|
|
// under us (e.g. a foreign close on a reused fd number from the
|
|
// Java side of the shared Android process) — log it (the errno names
|
|
// the killer) and recreate the listener.
|
|
if s.closing.Load() {
|
|
return
|
|
}
|
|
newListener, healErr := s.healListener(listener, isIPv6, err)
|
|
if healErr != nil {
|
|
s.logger.Error("system stack: tcp", ipVersionSuffix(isIPv6), " accept loop died: ", err, "; relisten failed: ", healErr)
|
|
return
|
|
}
|
|
listener = newListener
|
|
continue
|
|
}
|
|
connPort := M.SocksaddrFromNet(conn.RemoteAddr()).Port
|
|
session := s.tcpNat.LookupBack(connPort)
|
|
if session == nil {
|
|
s.logger.Trace(E.New("unknown session with port ", connPort))
|
|
continue
|
|
}
|
|
go s.handler.NewConnectionEx(s.ctx, conn, M.SocksaddrFromNetIP(session.Source), M.SocksaddrFromNetIP(session.Destination), nil)
|
|
}
|
|
}
|
|
|
|
// lx/040: recreate a TCP forwarder listener that died out from under the
|
|
// stack. Returns the replacement listener after publishing it (listener field
|
|
// + atomic port) under listenAccess, or an error if the stack is closing or
|
|
// the bind failed.
|
|
func (s *System) healListener(dead net.Listener, isIPv6 bool, cause error) (net.Listener, error) {
|
|
port := &s.tcpPort
|
|
if isIPv6 {
|
|
port = &s.tcpPort6
|
|
}
|
|
oldPort := port.Load()
|
|
s.logger.Warn("system stack: tcp", ipVersionSuffix(isIPv6), " listener (port ", oldPort, ") accept failed: ", cause, " — recreating listener")
|
|
_ = dead.Close() // release netpoll state; harmless if already closed
|
|
newListener, err := s.listenTCP(isIPv6)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.listenAccess.Lock()
|
|
defer s.listenAccess.Unlock()
|
|
if s.closing.Load() {
|
|
_ = newListener.Close()
|
|
return nil, net.ErrClosed
|
|
}
|
|
if isIPv6 {
|
|
s.tcpListener6 = newListener
|
|
} else {
|
|
s.tcpListener = newListener
|
|
}
|
|
newPort := uint32(M.SocksaddrFromNet(newListener.Addr()).Port)
|
|
port.Store(newPort)
|
|
recoveries := s.acceptRecoveries.Add(1)
|
|
s.logger.Warn("system stack: tcp", ipVersionSuffix(isIPv6), " listener recreated (port ", oldPort, " → ", newPort, ", recoveries: ", recoveries, ")")
|
|
return newListener, nil
|
|
}
|
|
|
|
func ipVersionSuffix(isIPv6 bool) string {
|
|
if isIPv6 {
|
|
return "6"
|
|
}
|
|
return "4"
|
|
}
|
|
|
|
func (s *System) dispatchIPv4(ipHdr header.IPv4, destination netip.Addr) bool {
|
|
switch ipHdr.TransportProtocol() {
|
|
case header.TCPProtocolNumber:
|
|
if slices.Contains(s.inet4LoopbackAddress, destination) {
|
|
return false
|
|
}
|
|
if ipHdr.SourceAddr() == s.inet4Address &&
|
|
ipHdr.FragmentOffset() == 0 &&
|
|
len(ipHdr.Payload()) >= header.TCPMinimumSize &&
|
|
header.TCP(ipHdr.Payload()).SourcePort() == uint16(s.tcpPort.Load()) {
|
|
return false
|
|
}
|
|
case header.ICMPv4ProtocolNumber:
|
|
if destination == s.inet4Address {
|
|
return false
|
|
}
|
|
}
|
|
return s.dispatcher.Dispatch(ipHdr)
|
|
}
|
|
|
|
func (s *System) dispatchIPv6(ipHdr header.IPv6, destination netip.Addr) bool {
|
|
switch ipHdr.TransportProtocol() {
|
|
case header.TCPProtocolNumber:
|
|
if slices.Contains(s.inet6LoopbackAddress, destination) {
|
|
return false
|
|
}
|
|
if ipHdr.SourceAddr() == s.inet6Address &&
|
|
len(ipHdr.Payload()) >= header.TCPMinimumSize &&
|
|
header.TCP(ipHdr.Payload()).SourcePort() == uint16(s.tcpPort6.Load()) {
|
|
return false
|
|
}
|
|
case header.ICMPv6ProtocolNumber:
|
|
if destination == s.inet6Address {
|
|
return false
|
|
}
|
|
}
|
|
return s.dispatcher.Dispatch(ipHdr)
|
|
}
|
|
|
|
func (s *System) processIPv4(ipHdr header.IPv4) (writeBack bool, err error) {
|
|
destination := ipHdr.DestinationAddr()
|
|
if destination == s.broadcastAddr || !destination.IsGlobalUnicast() {
|
|
return
|
|
}
|
|
if s.dispatchIPv4(ipHdr, destination) {
|
|
return false, nil
|
|
}
|
|
writeBack = true
|
|
switch ipHdr.TransportProtocol() {
|
|
case header.TCPProtocolNumber:
|
|
writeBack, err = s.processIPv4TCP(ipHdr, ipHdr.Payload())
|
|
case header.UDPProtocolNumber:
|
|
writeBack = false
|
|
err = s.processIPv4UDP(ipHdr, ipHdr.Payload())
|
|
case header.ICMPv4ProtocolNumber:
|
|
writeBack, err = s.processIPv4ICMP(ipHdr, ipHdr.Payload())
|
|
}
|
|
if err != nil {
|
|
writeBack = false
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *System) processIPv6(ipHdr header.IPv6) (writeBack bool, err error) {
|
|
destination := ipHdr.DestinationAddr()
|
|
if !destination.IsGlobalUnicast() {
|
|
return
|
|
}
|
|
if s.dispatchIPv6(ipHdr, destination) {
|
|
return false, nil
|
|
}
|
|
writeBack = true
|
|
switch ipHdr.TransportProtocol() {
|
|
case header.TCPProtocolNumber:
|
|
writeBack, err = s.processIPv6TCP(ipHdr, ipHdr.Payload())
|
|
case header.UDPProtocolNumber:
|
|
writeBack = false
|
|
err = s.processIPv6UDP(ipHdr, ipHdr.Payload())
|
|
case header.ICMPv6ProtocolNumber:
|
|
writeBack, err = s.processIPv6ICMP(ipHdr, ipHdr.Payload())
|
|
}
|
|
if err != nil {
|
|
writeBack = false
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *System) processIPv4TCP(ipHdr header.IPv4, tcpHdr header.TCP) (bool, error) {
|
|
source := netip.AddrPortFrom(ipHdr.SourceAddr(), tcpHdr.SourcePort())
|
|
destination := netip.AddrPortFrom(ipHdr.DestinationAddr(), tcpHdr.DestinationPort())
|
|
if !destination.Addr().IsGlobalUnicast() {
|
|
return false, nil
|
|
} else if source.Addr() == s.inet4Address && source.Port() == uint16(s.tcpPort.Load()) {
|
|
session := s.tcpNat.LookupBack(destination.Port())
|
|
if session == nil {
|
|
return false, E.New("ipv4: tcp: session not found: ", destination.Port())
|
|
}
|
|
rewriteIPv4TCP(ipHdr, tcpHdr, s.txChecksumOffload,
|
|
session.Destination.Addr(), session.Destination.Port(), true,
|
|
session.Source.Addr(), session.Source.Port(), true)
|
|
} else {
|
|
var loopback bool
|
|
for _, inet4LoopbackAddress := range s.inet4LoopbackAddress {
|
|
if destination.Addr() == inet4LoopbackAddress {
|
|
rewriteIPv4TCP(ipHdr, tcpHdr, s.txChecksumOffload,
|
|
inet4LoopbackAddress, 0, false,
|
|
source.Addr(), 0, false)
|
|
loopback = true
|
|
break
|
|
}
|
|
}
|
|
if !loopback {
|
|
natPort := s.tcpNat.Lookup(source, destination)
|
|
if natPort == 0 {
|
|
return false, E.New("ipv4: tcp: NAT port space exhausted")
|
|
}
|
|
rewriteIPv4TCP(ipHdr, tcpHdr, s.txChecksumOffload,
|
|
s.inet4NextAddress, natPort, true,
|
|
s.inet4Address, uint16(s.tcpPort.Load()), true)
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (s *System) processIPv6TCP(ipHdr header.IPv6, tcpHdr header.TCP) (bool, error) {
|
|
source := netip.AddrPortFrom(ipHdr.SourceAddr(), tcpHdr.SourcePort())
|
|
destination := netip.AddrPortFrom(ipHdr.DestinationAddr(), tcpHdr.DestinationPort())
|
|
if !destination.Addr().IsGlobalUnicast() {
|
|
return false, nil
|
|
} else if source.Addr() == s.inet6Address && source.Port() == uint16(s.tcpPort6.Load()) {
|
|
session := s.tcpNat.LookupBack(destination.Port())
|
|
if session == nil {
|
|
return false, E.New("ipv6: tcp: session not found: ", destination.Port())
|
|
}
|
|
rewriteIPv6TCP(ipHdr, tcpHdr, s.txChecksumOffload,
|
|
session.Destination.Addr(), session.Destination.Port(), true,
|
|
session.Source.Addr(), session.Source.Port(), true)
|
|
} else {
|
|
var loopback bool
|
|
for _, inet6LoopbackAddress := range s.inet6LoopbackAddress {
|
|
if destination.Addr() == inet6LoopbackAddress {
|
|
rewriteIPv6TCP(ipHdr, tcpHdr, s.txChecksumOffload,
|
|
inet6LoopbackAddress, 0, false,
|
|
source.Addr(), 0, false)
|
|
loopback = true
|
|
break
|
|
}
|
|
}
|
|
if !loopback {
|
|
natPort := s.tcpNat.Lookup(source, destination)
|
|
if natPort == 0 {
|
|
return false, E.New("ipv6: tcp: NAT port space exhausted")
|
|
}
|
|
rewriteIPv6TCP(ipHdr, tcpHdr, s.txChecksumOffload,
|
|
s.inet6NextAddress, natPort, true,
|
|
s.inet6Address, uint16(s.tcpPort6.Load()), true)
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func rewriteIPv4TCP(ipHdr header.IPv4, tcpHdr header.TCP, txChecksumOffload bool,
|
|
newSource netip.Addr, newSourcePort uint16, rewriteSourcePort bool,
|
|
newDestination netip.Addr, newDestinationPort uint16, rewriteDestinationPort bool,
|
|
) {
|
|
oldSource := ipHdr.SourceAddress()
|
|
oldDestination := ipHdr.DestinationAddress()
|
|
newSourceAddr := tcpip.AddrFrom4(newSource.As4())
|
|
newDestinationAddr := tcpip.AddrFrom4(newDestination.As4())
|
|
if newSourceAddr != oldSource {
|
|
ipHdr.SetSourceAddressWithChecksumUpdate(newSourceAddr)
|
|
if !txChecksumOffload {
|
|
tcpHdr.UpdateChecksumPseudoHeaderAddress(oldSource, newSourceAddr, true)
|
|
}
|
|
}
|
|
if newDestinationAddr != oldDestination {
|
|
ipHdr.SetDestinationAddressWithChecksumUpdate(newDestinationAddr)
|
|
if !txChecksumOffload {
|
|
tcpHdr.UpdateChecksumPseudoHeaderAddress(oldDestination, newDestinationAddr, true)
|
|
}
|
|
}
|
|
if txChecksumOffload {
|
|
if rewriteSourcePort {
|
|
tcpHdr.SetSourcePort(newSourcePort)
|
|
}
|
|
if rewriteDestinationPort {
|
|
tcpHdr.SetDestinationPort(newDestinationPort)
|
|
}
|
|
tcpHdr.SetChecksum(0)
|
|
} else {
|
|
if rewriteSourcePort {
|
|
tcpHdr.SetSourcePortWithChecksumUpdate(newSourcePort)
|
|
}
|
|
if rewriteDestinationPort {
|
|
tcpHdr.SetDestinationPortWithChecksumUpdate(newDestinationPort)
|
|
}
|
|
}
|
|
}
|
|
|
|
func rewriteIPv6TCP(ipHdr header.IPv6, tcpHdr header.TCP, txChecksumOffload bool,
|
|
newSource netip.Addr, newSourcePort uint16, rewriteSourcePort bool,
|
|
newDestination netip.Addr, newDestinationPort uint16, rewriteDestinationPort bool,
|
|
) {
|
|
oldSource := ipHdr.SourceAddress()
|
|
oldDestination := ipHdr.DestinationAddress()
|
|
newSourceAddr := tcpip.AddrFrom16(newSource.As16())
|
|
newDestinationAddr := tcpip.AddrFrom16(newDestination.As16())
|
|
if newSourceAddr != oldSource {
|
|
ipHdr.SetSourceAddress(newSourceAddr)
|
|
if !txChecksumOffload {
|
|
tcpHdr.UpdateChecksumPseudoHeaderAddress(oldSource, newSourceAddr, true)
|
|
}
|
|
}
|
|
if newDestinationAddr != oldDestination {
|
|
ipHdr.SetDestinationAddress(newDestinationAddr)
|
|
if !txChecksumOffload {
|
|
tcpHdr.UpdateChecksumPseudoHeaderAddress(oldDestination, newDestinationAddr, true)
|
|
}
|
|
}
|
|
if txChecksumOffload {
|
|
if rewriteSourcePort {
|
|
tcpHdr.SetSourcePort(newSourcePort)
|
|
}
|
|
if rewriteDestinationPort {
|
|
tcpHdr.SetDestinationPort(newDestinationPort)
|
|
}
|
|
tcpHdr.SetChecksum(0)
|
|
} else {
|
|
if rewriteSourcePort {
|
|
tcpHdr.SetSourcePortWithChecksumUpdate(newSourcePort)
|
|
}
|
|
if rewriteDestinationPort {
|
|
tcpHdr.SetDestinationPortWithChecksumUpdate(newDestinationPort)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *System) processIPv4UDP(ipHdr header.IPv4, udpHdr header.UDP) error {
|
|
if ipHdr.Flags()&header.IPv4FlagMoreFragments != 0 {
|
|
return E.New("ipv4: fragment dropped")
|
|
}
|
|
if ipHdr.FragmentOffset() != 0 {
|
|
return E.New("ipv4: udp: fragment dropped")
|
|
}
|
|
source := M.SocksaddrFrom(ipHdr.SourceAddr(), udpHdr.SourcePort())
|
|
destination := M.SocksaddrFrom(ipHdr.DestinationAddr(), udpHdr.DestinationPort())
|
|
if !destination.Addr.IsGlobalUnicast() {
|
|
return nil
|
|
}
|
|
s.udpNat.NewPacket([][]byte{udpHdr.Payload()}, source, destination, ipHdr)
|
|
return nil
|
|
}
|
|
|
|
func (s *System) processIPv6UDP(ipHdr header.IPv6, udpHdr header.UDP) error {
|
|
source := M.SocksaddrFrom(ipHdr.SourceAddr(), udpHdr.SourcePort())
|
|
destination := M.SocksaddrFrom(ipHdr.DestinationAddr(), udpHdr.DestinationPort())
|
|
if !destination.Addr.IsGlobalUnicast() {
|
|
return nil
|
|
}
|
|
s.udpNat.NewPacket([][]byte{udpHdr.Payload()}, source, destination, ipHdr)
|
|
return nil
|
|
}
|
|
|
|
func (s *System) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) {
|
|
var writer N.PacketWriter
|
|
if source.IsIPv4() {
|
|
packet := userData.(header.IPv4)
|
|
headerLen := packet.HeaderLength() + header.UDPMinimumSize
|
|
headerCopy := make([]byte, headerLen)
|
|
copy(headerCopy, packet[:headerLen])
|
|
writer = &systemUDPPacketWriter4{
|
|
s.tun,
|
|
s.frontHeadroom + PacketOffset,
|
|
headerCopy,
|
|
source.AddrPort(),
|
|
s.txChecksumOffload,
|
|
}
|
|
} else {
|
|
packet := userData.(header.IPv6)
|
|
headerLen := len(packet) - int(packet.PayloadLength()) + header.UDPMinimumSize
|
|
headerCopy := make([]byte, headerLen)
|
|
copy(headerCopy, packet[:headerLen])
|
|
writer = &systemUDPPacketWriter6{
|
|
s.tun,
|
|
s.frontHeadroom + PacketOffset,
|
|
headerCopy,
|
|
source.AddrPort(),
|
|
s.txChecksumOffload,
|
|
}
|
|
}
|
|
return true, s.ctx, writer, nil
|
|
}
|
|
|
|
func (s *System) processIPv4ICMP(ipHdr header.IPv4, icmpHdr header.ICMPv4) (bool, error) {
|
|
if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != 0 {
|
|
return false, nil
|
|
}
|
|
icmpHdr.SetType(header.ICMPv4EchoReply)
|
|
sourceAddress := ipHdr.SourceAddr()
|
|
ipHdr.SetSourceAddr(ipHdr.DestinationAddr())
|
|
ipHdr.SetDestinationAddr(sourceAddress)
|
|
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, 0))
|
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
|
return true, nil
|
|
}
|
|
|
|
func (s *System) processIPv6ICMP(ipHdr header.IPv6, icmpHdr header.ICMPv6) (bool, error) {
|
|
if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != 0 {
|
|
return false, nil
|
|
}
|
|
icmpHdr.SetType(header.ICMPv6EchoReply)
|
|
sourceAddress := ipHdr.SourceAddr()
|
|
ipHdr.SetSourceAddr(ipHdr.DestinationAddr())
|
|
ipHdr.SetDestinationAddr(sourceAddress)
|
|
icmpHdr.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
|
|
Header: icmpHdr,
|
|
Src: ipHdr.SourceAddressSlice(),
|
|
Dst: ipHdr.DestinationAddressSlice(),
|
|
}))
|
|
return true, nil
|
|
}
|
|
|
|
type systemUDPPacketWriter4 struct {
|
|
tun Tun
|
|
frontHeadroom int
|
|
header []byte
|
|
source netip.AddrPort
|
|
txChecksumOffload bool
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter4) FrontHeadroom() int {
|
|
return w.frontHeadroom + len(w.header)
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter4) preparePacket(buffer *buf.Buffer, destination M.Socksaddr) *buf.Buffer {
|
|
payloadLen := buffer.Len()
|
|
buffer = (N.ReadWaitOptions{FrontHeadroom: w.FrontHeadroom()}).Copy(buffer)
|
|
copy(buffer.ExtendHeader(len(w.header)), w.header)
|
|
ipHdr := header.IPv4(buffer.Bytes())
|
|
ipHdr.SetTotalLength(uint16(buffer.Len()))
|
|
ipHdr.SetDestinationAddress(ipHdr.SourceAddress())
|
|
ipHdr.SetSourceAddr(destination.Addr)
|
|
udpHdr := header.UDP(ipHdr.Payload())
|
|
udpHdr.SetDestinationPort(udpHdr.SourcePort())
|
|
udpHdr.SetSourcePort(destination.Port)
|
|
udpHdr.SetLength(uint16(payloadLen + header.UDPMinimumSize))
|
|
if !w.txChecksumOffload {
|
|
udpHdr.SetChecksum(^checksum.Checksum(udpHdr.Payload(), udpHdr.CalculateChecksum(
|
|
header.PseudoHeaderChecksum(header.UDPProtocolNumber, ipHdr.SourceAddressSlice(), ipHdr.DestinationAddressSlice(), ipHdr.PayloadLength()),
|
|
)))
|
|
} else {
|
|
udpHdr.SetChecksum(0)
|
|
}
|
|
ipHdr.SetChecksum(^ipHdr.CalculateChecksum())
|
|
return buffer
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter4) prepareWritePacket(buffer *buf.Buffer, destination M.Socksaddr) *buf.Buffer {
|
|
buffer = w.preparePacket(buffer, destination)
|
|
if PacketOffset > 0 {
|
|
PacketFillHeader(buffer.ExtendHeader(PacketOffset), header.IPv4Version)
|
|
}
|
|
if remainingHeadroom := w.frontHeadroom - PacketOffset; remainingHeadroom > 0 {
|
|
buffer.Advance(-remainingHeadroom)
|
|
}
|
|
return buffer
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter4) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
buffer = w.prepareWritePacket(buffer, destination)
|
|
defer buffer.Release()
|
|
return common.Error(w.tun.Write(buffer.Bytes()))
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter4) CreatePacketBatchWriter() (N.PacketBatchWriter, bool) {
|
|
switch w.tun.(type) {
|
|
case LinuxTUN, DarwinTUN:
|
|
return w, true
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter4) WritePacketBatch(buffers []*buf.Buffer, destinations []M.Socksaddr) error {
|
|
if len(buffers) == 0 || len(buffers) != len(destinations) {
|
|
buf.ReleaseMulti(buffers)
|
|
return os.ErrInvalid
|
|
}
|
|
defer func() {
|
|
buf.ReleaseMulti(buffers)
|
|
}()
|
|
switch tunInterface := w.tun.(type) {
|
|
case LinuxTUN:
|
|
packets := make([][]byte, len(buffers))
|
|
for index, buffer := range buffers {
|
|
buffer = w.preparePacket(buffer, destinations[index])
|
|
buffer.Advance(-w.frontHeadroom)
|
|
buffers[index] = buffer
|
|
packets[index] = buffer.Bytes()
|
|
}
|
|
return common.Error(tunInterface.BatchWrite(packets, w.frontHeadroom))
|
|
case DarwinTUN:
|
|
for index, buffer := range buffers {
|
|
buffers[index] = w.preparePacket(buffer, destinations[index])
|
|
}
|
|
return tunInterface.BatchWrite(buffers)
|
|
default:
|
|
return os.ErrInvalid
|
|
}
|
|
}
|
|
|
|
type systemUDPPacketWriter6 struct {
|
|
tun Tun
|
|
frontHeadroom int
|
|
header []byte
|
|
source netip.AddrPort
|
|
txChecksumOffload bool
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter6) FrontHeadroom() int {
|
|
return w.frontHeadroom + len(w.header)
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter6) preparePacket(buffer *buf.Buffer, destination M.Socksaddr) *buf.Buffer {
|
|
payloadLen := buffer.Len()
|
|
buffer = (N.ReadWaitOptions{FrontHeadroom: w.FrontHeadroom()}).Copy(buffer)
|
|
copy(buffer.ExtendHeader(len(w.header)), w.header)
|
|
ipHdr := header.IPv6(buffer.Bytes())
|
|
udpLen := uint16(header.UDPMinimumSize + payloadLen)
|
|
ipHdr.SetPayloadLength(udpLen)
|
|
ipHdr.SetDestinationAddress(ipHdr.SourceAddress())
|
|
ipHdr.SetSourceAddr(destination.Addr)
|
|
udpHdr := header.UDP(ipHdr.Payload())
|
|
udpHdr.SetDestinationPort(udpHdr.SourcePort())
|
|
udpHdr.SetSourcePort(destination.Port)
|
|
udpHdr.SetLength(udpLen)
|
|
if !w.txChecksumOffload {
|
|
udpHdr.SetChecksum(^checksum.Checksum(udpHdr.Payload(), udpHdr.CalculateChecksum(
|
|
header.PseudoHeaderChecksum(header.UDPProtocolNumber, ipHdr.SourceAddressSlice(), ipHdr.DestinationAddressSlice(), ipHdr.PayloadLength()),
|
|
)))
|
|
} else {
|
|
udpHdr.SetChecksum(0)
|
|
}
|
|
return buffer
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter6) prepareWritePacket(buffer *buf.Buffer, destination M.Socksaddr) *buf.Buffer {
|
|
buffer = w.preparePacket(buffer, destination)
|
|
if PacketOffset > 0 {
|
|
PacketFillHeader(buffer.ExtendHeader(PacketOffset), header.IPv6Version)
|
|
}
|
|
if remainingHeadroom := w.frontHeadroom - PacketOffset; remainingHeadroom > 0 {
|
|
buffer.Advance(-remainingHeadroom)
|
|
}
|
|
return buffer
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter6) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
|
buffer = w.prepareWritePacket(buffer, destination)
|
|
defer buffer.Release()
|
|
return common.Error(w.tun.Write(buffer.Bytes()))
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter6) CreatePacketBatchWriter() (N.PacketBatchWriter, bool) {
|
|
switch w.tun.(type) {
|
|
case LinuxTUN, DarwinTUN:
|
|
return w, true
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
func (w *systemUDPPacketWriter6) WritePacketBatch(buffers []*buf.Buffer, destinations []M.Socksaddr) error {
|
|
if len(buffers) == 0 || len(buffers) != len(destinations) {
|
|
buf.ReleaseMulti(buffers)
|
|
return os.ErrInvalid
|
|
}
|
|
defer func() {
|
|
buf.ReleaseMulti(buffers)
|
|
}()
|
|
switch tunInterface := w.tun.(type) {
|
|
case LinuxTUN:
|
|
packets := make([][]byte, len(buffers))
|
|
for index, buffer := range buffers {
|
|
buffer = w.preparePacket(buffer, destinations[index])
|
|
buffer.Advance(-w.frontHeadroom)
|
|
buffers[index] = buffer
|
|
packets[index] = buffer.Bytes()
|
|
}
|
|
return common.Error(tunInterface.BatchWrite(packets, w.frontHeadroom))
|
|
case DarwinTUN:
|
|
for index, buffer := range buffers {
|
|
buffers[index] = w.preparePacket(buffer, destinations[index])
|
|
}
|
|
return tunInterface.BatchWrite(buffers)
|
|
default:
|
|
return os.ErrInvalid
|
|
}
|
|
}
|
|
|
|
func newSystemWriteback(tunInterface Tun, frontHeadroom int) ForwardWriteback {
|
|
if linuxTUN, isLinuxTUN := tunInterface.(LinuxTUN); isLinuxTUN {
|
|
return &systemWritebackLinux{linuxTUN: linuxTUN, frontHeadroom: frontHeadroom}
|
|
}
|
|
if darwinTUN, isDarwinTUN := tunInterface.(DarwinTUN); isDarwinTUN {
|
|
return &systemWritebackDarwin{darwinTUN: darwinTUN, frontHeadroom: frontHeadroom}
|
|
}
|
|
return &systemWriteback{tun: tunInterface, frontHeadroom: frontHeadroom}
|
|
}
|
|
|
|
type systemWritebackLinux struct {
|
|
linuxTUN LinuxTUN
|
|
frontHeadroom int
|
|
}
|
|
|
|
func (w *systemWritebackLinux) ReturnHeadroom() int {
|
|
return w.frontHeadroom + PacketOffset
|
|
}
|
|
|
|
func (w *systemWritebackLinux) WriteReturnPackets(packets [][]byte) error {
|
|
return common.Error(w.linuxTUN.BatchWrite(packets, w.frontHeadroom))
|
|
}
|
|
|
|
type systemWritebackDarwin struct {
|
|
darwinTUN DarwinTUN
|
|
frontHeadroom int
|
|
}
|
|
|
|
func (w *systemWritebackDarwin) ReturnHeadroom() int {
|
|
return w.frontHeadroom + PacketOffset
|
|
}
|
|
|
|
func (w *systemWritebackDarwin) WriteReturnPackets(packets [][]byte) error {
|
|
buffers := make([]*buf.Buffer, 0, len(packets))
|
|
for _, packet := range packets {
|
|
buffers = append(buffers, buf.As(packet[PacketOffset:]))
|
|
}
|
|
return w.darwinTUN.BatchWrite(buffers)
|
|
}
|
|
|
|
type systemWriteback struct {
|
|
tun Tun
|
|
frontHeadroom int
|
|
}
|
|
|
|
func (w *systemWriteback) ReturnHeadroom() int {
|
|
return w.frontHeadroom + PacketOffset
|
|
}
|
|
|
|
func (w *systemWriteback) WriteReturnPackets(packets [][]byte) error {
|
|
var writeErrors []error
|
|
for _, packet := range packets {
|
|
if PacketOffset > 0 {
|
|
PacketFillHeader(packet, header.IPVersion(packet[PacketOffset:]))
|
|
}
|
|
_, err := w.tun.Write(packet)
|
|
if err != nil {
|
|
writeErrors = append(writeErrors, err)
|
|
}
|
|
}
|
|
return E.Errors(writeErrors...)
|
|
}
|