mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-10-21 03:23:08 +00:00
Rework forward NAT with UDP mapping and fragment support
This commit is contained in:
parent
64b86d5f17
commit
d2a4dc29fa
24 changed files with 507 additions and 116 deletions
47
common/kernelports/pool.go
Normal file
47
common/kernelports/pool.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package kernelports
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var ErrExhausted = E.New("kernel ports: no more ports can be reserved")
|
||||
|
||||
func (p *Pool) ranges(protocol uint8) *atomic.Pointer[[]tun.SelectorRange] {
|
||||
if protocol == uint8(header.UDPProtocolNumber) {
|
||||
return &p.udp
|
||||
}
|
||||
return &p.tcp
|
||||
}
|
||||
|
||||
func (p *Pool) PortSelectorRanges(protocol uint8) []tun.SelectorRange {
|
||||
ranges := p.ranges(protocol).Load()
|
||||
if ranges == nil {
|
||||
return nil
|
||||
}
|
||||
return *ranges
|
||||
}
|
||||
|
||||
func (p *Pool) ExpandSelectorRanges(protocol uint8) bool {
|
||||
_, err := p.Acquire(protocol)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (p *Pool) Contains(protocol uint8, port uint16) bool {
|
||||
return slices.ContainsFunc(p.PortSelectorRanges(protocol), func(selectorRange tun.SelectorRange) bool {
|
||||
return port >= selectorRange.Start && port-selectorRange.Start < selectorRange.Count
|
||||
})
|
||||
}
|
||||
|
||||
func (p *Pool) ReleaseSelector(protocol uint8, address netip.AddrPort) {
|
||||
}
|
||||
|
||||
var (
|
||||
_ tun.PortWithSelectorRange = (*Pool)(nil)
|
||||
_ tun.PortWithSelectorReservation = (*Pool)(nil)
|
||||
)
|
||||
17
common/kernelports/pool_darwin.go
Normal file
17
common/kernelports/pool_darwin.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package kernelports
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const (
|
||||
defaultEphemeralFirst = 49152
|
||||
defaultEphemeralLast = 65535
|
||||
)
|
||||
|
||||
func ephemeralRange() (uint16, uint16) {
|
||||
first, firstErr := unix.SysctlUint32("net.inet.ip.portrange.first")
|
||||
last, lastErr := unix.SysctlUint32("net.inet.ip.portrange.last")
|
||||
if firstErr != nil || lastErr != nil || first > last || last > 65535 {
|
||||
return defaultEphemeralFirst, defaultEphemeralLast
|
||||
}
|
||||
return uint16(first), uint16(last)
|
||||
}
|
||||
29
common/kernelports/pool_linux.go
Normal file
29
common/kernelports/pool_linux.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package kernelports
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultEphemeralFirst = 32768
|
||||
defaultEphemeralLast = 60999
|
||||
)
|
||||
|
||||
func ephemeralRange() (uint16, uint16) {
|
||||
content, err := os.ReadFile("/proc/sys/net/ipv4/ip_local_port_range")
|
||||
if err != nil {
|
||||
return defaultEphemeralFirst, defaultEphemeralLast
|
||||
}
|
||||
fields := strings.Fields(string(content))
|
||||
if len(fields) != 2 {
|
||||
return defaultEphemeralFirst, defaultEphemeralLast
|
||||
}
|
||||
first, firstErr := strconv.ParseUint(fields[0], 10, 16)
|
||||
last, lastErr := strconv.ParseUint(fields[1], 10, 16)
|
||||
if firstErr != nil || lastErr != nil || first > last {
|
||||
return defaultEphemeralFirst, defaultEphemeralLast
|
||||
}
|
||||
return uint16(first), uint16(last)
|
||||
}
|
||||
87
common/kernelports/pool_unix.go
Normal file
87
common/kernelports/pool_unix.go
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
//go:build !windows
|
||||
|
||||
package kernelports
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const lowestSelector = 1024
|
||||
|
||||
type Pool struct {
|
||||
tcp atomic.Pointer[[]tun.SelectorRange]
|
||||
udp atomic.Pointer[[]tun.SelectorRange]
|
||||
}
|
||||
|
||||
func New() (*Pool, error) {
|
||||
first, last := ephemeralRange()
|
||||
below := tun.SelectorRange{Start: lowestSelector}
|
||||
if first > lowestSelector {
|
||||
below.Count = first - lowestSelector
|
||||
}
|
||||
above := tun.SelectorRange{Start: last + 1, Count: 65535 - last}
|
||||
selected := below
|
||||
if above.Count > below.Count {
|
||||
selected = above
|
||||
}
|
||||
if selected.Count == 0 {
|
||||
selected = tun.SelectorRange{Start: 49152, Count: 65535 - 49152 + 1}
|
||||
}
|
||||
ranges := []tun.SelectorRange{selected}
|
||||
pool := &Pool{}
|
||||
pool.tcp.Store(&ranges)
|
||||
pool.udp.Store(&ranges)
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func (p *Pool) Acquire(protocol uint8) (tun.SelectorRange, error) {
|
||||
return tun.SelectorRange{}, ErrExhausted
|
||||
}
|
||||
|
||||
func (p *Pool) ReserveSelector(protocol uint8, address netip.AddrPort) bool {
|
||||
var socketType int
|
||||
switch protocol {
|
||||
case uint8(header.TCPProtocolNumber):
|
||||
socketType = unix.SOCK_STREAM
|
||||
case uint8(header.UDPProtocolNumber):
|
||||
socketType = unix.SOCK_DGRAM
|
||||
default:
|
||||
return true
|
||||
}
|
||||
family := unix.AF_INET
|
||||
var sockaddr unix.Sockaddr
|
||||
if address.Addr().Is4() {
|
||||
sockaddr4 := &unix.SockaddrInet4{Port: int(address.Port())}
|
||||
sockaddr4.Addr = address.Addr().As4()
|
||||
sockaddr = sockaddr4
|
||||
} else {
|
||||
family = unix.AF_INET6
|
||||
sockaddr6 := &unix.SockaddrInet6{Port: int(address.Port())}
|
||||
sockaddr6.Addr = address.Addr().As16()
|
||||
sockaddr = sockaddr6
|
||||
}
|
||||
syscall.ForkLock.RLock()
|
||||
fd, err := unix.Socket(family, socketType, 0)
|
||||
if err == nil {
|
||||
unix.CloseOnExec(fd)
|
||||
}
|
||||
syscall.ForkLock.RUnlock()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
err = unix.Bind(fd, sockaddr)
|
||||
return !errors.Is(err, unix.EADDRINUSE)
|
||||
}
|
||||
|
||||
func (p *Pool) Close() error {
|
||||
return nil
|
||||
}
|
||||
7
common/kernelports/pool_unix_other.go
Normal file
7
common/kernelports/pool_unix_other.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
//go:build !windows && !linux && !darwin
|
||||
|
||||
package kernelports
|
||||
|
||||
func ephemeralRange() (uint16, uint16) {
|
||||
return 49152, 65535
|
||||
}
|
||||
121
common/kernelports/pool_windows.go
Normal file
121
common/kernelports/pool_windows.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
package kernelports
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
blockSize uint16 = 1024
|
||||
maxBlocks int = 8
|
||||
)
|
||||
|
||||
// SIO_ACQUIRE_PORT_RESERVATION = _WSAIOW(IOC_VENDOR, 100):
|
||||
// IOC_IN | IOC_VENDOR | 100. Despite the write-only direction code, the
|
||||
// reservation result is written to the WSAIoctl output buffer; using
|
||||
// _WSAIORW instead is rejected with WSAEOPNOTSUPP.
|
||||
const sioAcquirePortReservation uint32 = 0x80000000 | 0x18000000 | 100
|
||||
|
||||
type Pool struct {
|
||||
tcp atomic.Pointer[[]tun.SelectorRange]
|
||||
udp atomic.Pointer[[]tun.SelectorRange]
|
||||
|
||||
access sync.Mutex
|
||||
blocks map[uint8][]windows.Handle
|
||||
}
|
||||
|
||||
func New() (*Pool, error) {
|
||||
pool := &Pool{blocks: make(map[uint8][]windows.Handle)}
|
||||
for _, protocol := range []uint8{uint8(header.TCPProtocolNumber), uint8(header.UDPProtocolNumber)} {
|
||||
_, err := pool.Acquire(protocol)
|
||||
if err != nil {
|
||||
pool.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// A runtime reservation only binds the protocol of the socket it was
|
||||
// acquired on: both ephemeral auto-assignment and explicit binds of that
|
||||
// protocol skip the block, while sockets of the other protocol still receive
|
||||
// ports inside it (observed on Windows 11; the documentation does not say).
|
||||
// Repeated wildcard requests from one process return distinct,
|
||||
// non-contiguous blocks. Closing the socket releases the reservation.
|
||||
func (p *Pool) Acquire(protocol uint8) (tun.SelectorRange, error) {
|
||||
socketType, socketProtocol := windows.SOCK_STREAM, windows.IPPROTO_TCP
|
||||
if protocol == uint8(header.UDPProtocolNumber) {
|
||||
socketType, socketProtocol = windows.SOCK_DGRAM, windows.IPPROTO_UDP
|
||||
} else {
|
||||
protocol = uint8(header.TCPProtocolNumber)
|
||||
}
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
if len(p.blocks[protocol]) >= maxBlocks {
|
||||
return tun.SelectorRange{}, ErrExhausted
|
||||
}
|
||||
socket, err := windows.Socket(windows.AF_INET, socketType, socketProtocol)
|
||||
if err != nil {
|
||||
return tun.SelectorRange{}, E.Cause(err, "create reservation socket")
|
||||
}
|
||||
// INET_PORT_RANGE { USHORT StartPort; USHORT NumberOfPorts; }.
|
||||
// StartPort 0 requests a runtime (wildcard) reservation.
|
||||
var in [4]byte
|
||||
binary.LittleEndian.PutUint16(in[0:2], 0)
|
||||
binary.LittleEndian.PutUint16(in[2:4], blockSize)
|
||||
// INET_PORT_RESERVATION_INSTANCE {
|
||||
// INET_PORT_RESERVATION { USHORT StartPort; USHORT NumberOfPorts; };
|
||||
// INET_PORT_RESERVATION_TOKEN { ULONG64 Token; };
|
||||
// } — the ULONG64 forces 8-byte alignment, so Token sits at offset 8.
|
||||
var out [16]byte
|
||||
var returned uint32
|
||||
err = windows.WSAIoctl(socket, sioAcquirePortReservation,
|
||||
&in[0], uint32(len(in)), &out[0], uint32(len(out)), &returned, nil, 0)
|
||||
if err != nil {
|
||||
windows.Closesocket(socket)
|
||||
return tun.SelectorRange{}, E.Cause(err, "acquire port reservation")
|
||||
}
|
||||
// StartPort is returned in network byte order (as documented for
|
||||
// INET_PORT_RANGE); NumberOfPorts is a plain host-order count.
|
||||
startPort := binary.BigEndian.Uint16(out[0:2])
|
||||
reservedCount := binary.LittleEndian.Uint16(out[2:4])
|
||||
if startPort == 0 || reservedCount < blockSize {
|
||||
windows.Closesocket(socket)
|
||||
return tun.SelectorRange{}, E.New("acquire port reservation: stack returned ", reservedCount, " of ", blockSize, " ports")
|
||||
}
|
||||
p.blocks[protocol] = append(p.blocks[protocol], socket)
|
||||
block := tun.SelectorRange{Start: startPort, Count: blockSize}
|
||||
ranges := p.ranges(protocol)
|
||||
var updated []tun.SelectorRange
|
||||
current := ranges.Load()
|
||||
if current != nil {
|
||||
updated = append(updated, *current...)
|
||||
}
|
||||
updated = append(updated, block)
|
||||
ranges.Store(&updated)
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func (p *Pool) ReserveSelector(protocol uint8, address netip.AddrPort) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Pool) Close() error {
|
||||
p.access.Lock()
|
||||
defer p.access.Unlock()
|
||||
for _, sockets := range p.blocks {
|
||||
for _, socket := range sockets {
|
||||
windows.Closesocket(socket)
|
||||
}
|
||||
}
|
||||
clear(p.blocks)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -14,8 +14,10 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/kernelports"
|
||||
"github.com/sagernet/sing-box/common/windivert"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
|
|
@ -26,8 +28,6 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
bridgeReservedPortCount uint16 = 1024
|
||||
|
||||
bridgeICMPFlowTimeout = time.Minute
|
||||
|
||||
bridgeDivertPriority int16 = 0
|
||||
|
|
@ -123,8 +123,9 @@ type backendWindows struct {
|
|||
|
||||
egress atomic.Pointer[egressState]
|
||||
|
||||
reservation *portReservation
|
||||
reservedStart uint16
|
||||
ports *kernelports.Pool
|
||||
tcpRanges atomic.Pointer[[]tun.SelectorRange]
|
||||
udpRanges atomic.Pointer[[]tun.SelectorRange]
|
||||
|
||||
icmp4, icmp6 *icmpTable
|
||||
|
||||
|
|
@ -153,14 +154,19 @@ func (b *backendWindows) start(scope *adapter.Scope) error {
|
|||
state := b.currentEgressState()
|
||||
b.egress.Store(state)
|
||||
|
||||
err := b.acquireReservations()
|
||||
ports, err := kernelports.New()
|
||||
if err != nil {
|
||||
return err
|
||||
return E.Cause(err, "bridge: reserve ports")
|
||||
}
|
||||
scope.Add(func() error {
|
||||
b.reservation.Close()
|
||||
return nil
|
||||
})
|
||||
b.ports = ports
|
||||
scope.Add(ports.Close)
|
||||
for _, protocol := range []uint8{uint8(header.TCPProtocolNumber), uint8(header.UDPProtocolNumber)} {
|
||||
for _, block := range ports.PortSelectorRanges(protocol) {
|
||||
b.publishRange(protocol, block)
|
||||
}
|
||||
}
|
||||
b.icmp4 = newICMPTable(bridgeICMPFlowTimeout)
|
||||
b.icmp6 = newICMPTable(bridgeICMPFlowTimeout)
|
||||
|
||||
injectHandle, err := windivert.Open(nil, windivert.LayerNetwork, windivert.PriorityHighest, windivert.FlagSendOnly)
|
||||
if err != nil {
|
||||
|
|
@ -201,20 +207,62 @@ func (b *backendWindows) egressLabel() string {
|
|||
return "auto"
|
||||
}
|
||||
|
||||
func (b *backendWindows) acquireReservations() error {
|
||||
reservation, err := acquirePortReservation(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP, bridgeReservedPortCount)
|
||||
if err != nil {
|
||||
return E.Cause(err, "bridge: reserve ports")
|
||||
func (b *backendWindows) selectorRanges(protocol uint8) *atomic.Pointer[[]tun.SelectorRange] {
|
||||
if protocol == uint8(header.UDPProtocolNumber) {
|
||||
return &b.udpRanges
|
||||
}
|
||||
b.reservation = reservation
|
||||
b.reservedStart = reservation.startPort
|
||||
b.icmp4 = newICMPTable(bridgeICMPFlowTimeout)
|
||||
b.icmp6 = newICMPTable(bridgeICMPFlowTimeout)
|
||||
return nil
|
||||
return &b.tcpRanges
|
||||
}
|
||||
|
||||
func (b *backendWindows) PortSelectorRange() (uint16, uint16) {
|
||||
return b.reservedStart, bridgeReservedPortCount
|
||||
func (b *backendWindows) PortSelectorRanges(protocol uint8) []tun.SelectorRange {
|
||||
ranges := b.selectorRanges(protocol).Load()
|
||||
if ranges == nil {
|
||||
return nil
|
||||
}
|
||||
return *ranges
|
||||
}
|
||||
|
||||
func (b *backendWindows) publishRange(protocol uint8, block tun.SelectorRange) {
|
||||
ranges := b.selectorRanges(protocol)
|
||||
var updated []tun.SelectorRange
|
||||
current := ranges.Load()
|
||||
if current != nil {
|
||||
updated = append(updated, *current...)
|
||||
}
|
||||
updated = append(updated, block)
|
||||
ranges.Store(&updated)
|
||||
}
|
||||
|
||||
func (b *backendWindows) ExpandSelectorRanges(protocol uint8) bool {
|
||||
b.egressAccess.Lock()
|
||||
defer b.egressAccess.Unlock()
|
||||
block, err := b.ports.Acquire(protocol)
|
||||
if err != nil {
|
||||
b.logger.Debug(E.Cause(err, "bridge: expand selector range"))
|
||||
return false
|
||||
}
|
||||
state := b.egress.Load()
|
||||
for _, isV6 := range []bool{false, true} {
|
||||
addresses := state.divertAddresses(isV6)
|
||||
if len(addresses) == 0 {
|
||||
continue
|
||||
}
|
||||
err = b.openTransportDiverter(addresses, isV6, protocol, block)
|
||||
if err != nil {
|
||||
b.logger.Warn(E.Cause(err, "bridge: divert expanded selector range"))
|
||||
return false
|
||||
}
|
||||
}
|
||||
b.publishRange(protocol, block)
|
||||
b.logger.Info("bridge: reserved ", block.Count, " more ", transportName(protocol), " ports")
|
||||
return true
|
||||
}
|
||||
|
||||
func transportName(protocol uint8) string {
|
||||
if protocol == uint8(header.UDPProtocolNumber) {
|
||||
return "UDP"
|
||||
}
|
||||
return "TCP"
|
||||
}
|
||||
|
||||
func (b *backendWindows) rebuildDivertersLocked(state *egressState) error {
|
||||
|
|
@ -245,19 +293,37 @@ func (b *backendWindows) closeDivertersLocked() {
|
|||
b.diverters = nil
|
||||
}
|
||||
|
||||
func (b *backendWindows) openTransportDiverter(addresses []netip.Addr, isV6 bool, protocol uint8, block tun.SelectorRange) error {
|
||||
portHigh := uint16(uint32(block.Start) + uint32(block.Count) - 1)
|
||||
var (
|
||||
filter *windivert.Filter
|
||||
err error
|
||||
)
|
||||
if protocol == uint8(header.UDPProtocolNumber) {
|
||||
filter, err = windivert.InboundUDPPortRange(addresses, block.Start, portHigh)
|
||||
} else {
|
||||
filter, err = windivert.InboundTCPPortRange(addresses, block.Start, portHigh)
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause(err, "bridge: build ", transportName(protocol), " divert filter")
|
||||
}
|
||||
return b.openDiverter(filter, divertTransport, isV6)
|
||||
}
|
||||
|
||||
func (b *backendWindows) openFamilyDiverters(addresses []netip.Addr, isV6 bool) error {
|
||||
portHigh := uint16(uint32(b.reservedStart) + uint32(bridgeReservedPortCount) - 1)
|
||||
for _, protocol := range []uint8{uint8(header.TCPProtocolNumber), uint8(header.UDPProtocolNumber)} {
|
||||
for _, block := range b.PortSelectorRanges(protocol) {
|
||||
err := b.openTransportDiverter(addresses, isV6, protocol, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
entries := []struct {
|
||||
what string
|
||||
kind divertKind
|
||||
build func() (*windivert.Filter, error)
|
||||
}{
|
||||
{"TCP", divertTransport, func() (*windivert.Filter, error) {
|
||||
return windivert.InboundTCPPortRange(addresses, b.reservedStart, portHigh)
|
||||
}},
|
||||
{"UDP", divertTransport, func() (*windivert.Filter, error) {
|
||||
return windivert.InboundUDPPortRange(addresses, b.reservedStart, portHigh)
|
||||
}},
|
||||
{"ICMP echo", divertICMPEcho, func() (*windivert.Filter, error) {
|
||||
return windivert.InboundICMPEchoReply(addresses)
|
||||
}},
|
||||
|
|
@ -474,7 +540,7 @@ func (b *backendWindows) embeddedFlowActive(protocol tcpip.TransportProtocolNumb
|
|||
if len(transport) < 4 {
|
||||
return false
|
||||
}
|
||||
return b.portReserved(binary.BigEndian.Uint16(transport[0:2]))
|
||||
return b.selectorReserved(uint8(protocol), binary.BigEndian.Uint16(transport[0:2]))
|
||||
case header.ICMPv4ProtocolNumber:
|
||||
if isV6 || len(transport) < header.ICMPv4MinimumSize {
|
||||
return false
|
||||
|
|
@ -500,8 +566,10 @@ func (b *backendWindows) embeddedFlowActive(protocol tcpip.TransportProtocolNumb
|
|||
}
|
||||
}
|
||||
|
||||
func (b *backendWindows) portReserved(port uint16) bool {
|
||||
return port >= b.reservedStart && uint32(port) < uint32(b.reservedStart)+uint32(bridgeReservedPortCount)
|
||||
func (b *backendWindows) selectorReserved(protocol uint8, port uint16) bool {
|
||||
return slices.ContainsFunc(b.PortSelectorRanges(protocol), func(block tun.SelectorRange) bool {
|
||||
return port >= block.Start && port-block.Start < block.Count
|
||||
})
|
||||
}
|
||||
|
||||
func (b *backendWindows) deliver(packets [][]byte) {
|
||||
|
|
@ -659,7 +727,7 @@ func (b *backendWindows) prepareOutbound(packet []byte, state *egressState) bool
|
|||
if len(info.transport) < 4 {
|
||||
return false
|
||||
}
|
||||
if !b.portReserved(binary.BigEndian.Uint16(info.transport[0:2])) {
|
||||
if !b.selectorReserved(uint8(info.protocol), binary.BigEndian.Uint16(info.transport[0:2])) {
|
||||
b.logger.Debug("bridge: dropping outbound packet with source port outside the reserved block")
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,11 +106,8 @@ func (o *Outbound) PortMTU() uint32 {
|
|||
return o.backend.PortMTU()
|
||||
}
|
||||
|
||||
func (o *Outbound) PortSelectorRange() (uint16, uint16) {
|
||||
if rangedBackend, isRanged := o.backend.(tun.PortWithSelectorRange); isRanged {
|
||||
return rangedBackend.PortSelectorRange()
|
||||
}
|
||||
return 0, 0
|
||||
func (o *Outbound) UpstreamPort() any {
|
||||
return o.backend
|
||||
}
|
||||
|
||||
func (o *Outbound) AttachReturn(returnPath tun.Return) error {
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
//go:build windows && (amd64 || 386)
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// SIO_ACQUIRE_PORT_RESERVATION = _WSAIOW(IOC_VENDOR, 100):
|
||||
// IOC_IN | IOC_VENDOR | 100. Despite the write-only direction code, the
|
||||
// reservation result is written to the WSAIoctl output buffer; using
|
||||
// _WSAIORW instead is rejected with WSAEOPNOTSUPP.
|
||||
const sioAcquirePortReservation uint32 = 0x80000000 | 0x18000000 | 100
|
||||
|
||||
// portReservation holds a runtime port block acquired from the host TCP/IP
|
||||
// stack. Runtime reservation records are protocol- and family-agnostic:
|
||||
// one reservation excludes the block from ephemeral auto-assignment for
|
||||
// TCP and UDP sockets of both address families (and a specific reservation
|
||||
// request for numbers covered by any existing record fails with
|
||||
// WSAEADDRINUSE, whatever its protocol). Explicit binds inside the block
|
||||
// are rejected for the reserving protocol but still allowed for others.
|
||||
// Closing the socket releases the reservation.
|
||||
type portReservation struct {
|
||||
socket windows.Handle
|
||||
startPort uint16
|
||||
}
|
||||
|
||||
func acquirePortReservation(family, socketType, protocol int, count uint16) (*portReservation, error) {
|
||||
socket, err := windows.Socket(family, socketType, protocol)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create reservation socket")
|
||||
}
|
||||
// INET_PORT_RANGE { USHORT StartPort; USHORT NumberOfPorts; }.
|
||||
// StartPort 0 requests a runtime (wildcard) reservation.
|
||||
var in [4]byte
|
||||
binary.LittleEndian.PutUint16(in[0:2], 0)
|
||||
binary.LittleEndian.PutUint16(in[2:4], count)
|
||||
// INET_PORT_RESERVATION_INSTANCE {
|
||||
// INET_PORT_RESERVATION { USHORT StartPort; USHORT NumberOfPorts; };
|
||||
// INET_PORT_RESERVATION_TOKEN { ULONG64 Token; };
|
||||
// } — the ULONG64 forces 8-byte alignment, so Token sits at offset 8.
|
||||
var out [16]byte
|
||||
var returned uint32
|
||||
err = windows.WSAIoctl(socket, sioAcquirePortReservation,
|
||||
&in[0], uint32(len(in)), &out[0], uint32(len(out)), &returned, nil, 0)
|
||||
if err != nil {
|
||||
windows.Closesocket(socket)
|
||||
return nil, E.Cause(err, "acquire port reservation")
|
||||
}
|
||||
// StartPort is returned in network byte order (as documented for
|
||||
// INET_PORT_RANGE); NumberOfPorts is a plain host-order count.
|
||||
startPort := binary.BigEndian.Uint16(out[0:2])
|
||||
reservedCount := binary.LittleEndian.Uint16(out[2:4])
|
||||
if startPort == 0 || reservedCount < count {
|
||||
windows.Closesocket(socket)
|
||||
return nil, E.New("acquire port reservation: stack returned ", reservedCount, " of ", count, " ports")
|
||||
}
|
||||
return &portReservation{
|
||||
socket: socket,
|
||||
startPort: startPort,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *portReservation) Close() {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
windows.Closesocket(r.socket)
|
||||
}
|
||||
|
|
@ -250,6 +250,10 @@ func (c *ClientEndpoint) PortMTU() uint32 {
|
|||
return c.device.PortMTU()
|
||||
}
|
||||
|
||||
func (c *ClientEndpoint) UpstreamPort() any {
|
||||
return c.device
|
||||
}
|
||||
|
||||
func (c *ClientEndpoint) AttachReturn(returnPath tun.Return) error {
|
||||
return c.device.AttachReturn(returnPath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,6 +230,10 @@ func (s *ServerEndpoint) PortMTU() uint32 {
|
|||
return s.device.PortMTU()
|
||||
}
|
||||
|
||||
func (s *ServerEndpoint) UpstreamPort() any {
|
||||
return s.device
|
||||
}
|
||||
|
||||
func (s *ServerEndpoint) AttachReturn(returnPath tun.Return) error {
|
||||
return s.device.AttachReturn(returnPath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -556,6 +556,10 @@ func (e *Endpoint) PortMTU() uint32 {
|
|||
return e.device.PortMTU()
|
||||
}
|
||||
|
||||
func (e *Endpoint) UpstreamPort() any {
|
||||
return e.device
|
||||
}
|
||||
|
||||
func (e *Endpoint) AttachReturn(returnPath tun.Return) error {
|
||||
return e.device.AttachReturn(returnPath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -739,6 +739,10 @@ func (c *ClientEndpoint) PortMTU() uint32 {
|
|||
return c.device.PortMTU()
|
||||
}
|
||||
|
||||
func (c *ClientEndpoint) UpstreamPort() any {
|
||||
return c.device
|
||||
}
|
||||
|
||||
func (c *ClientEndpoint) AttachReturn(returnPath tun.Return) error {
|
||||
return c.device.AttachReturn(returnPath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -655,6 +655,10 @@ func (s *ServerEndpoint) PortMTU() uint32 {
|
|||
return s.device.PortMTU()
|
||||
}
|
||||
|
||||
func (s *ServerEndpoint) UpstreamPort() any {
|
||||
return s.device
|
||||
}
|
||||
|
||||
func (s *ServerEndpoint) AttachReturn(returnPath tun.Return) error {
|
||||
return s.device.AttachReturn(returnPath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,13 @@ func (t *Endpoint) PortMTU() uint32 {
|
|||
return uint32(tsTUN.DefaultTUNMTU())
|
||||
}
|
||||
|
||||
func (t *Endpoint) UpstreamPort() any {
|
||||
if !t.started.Load() {
|
||||
return nil
|
||||
}
|
||||
return t.stack
|
||||
}
|
||||
|
||||
func (t *Endpoint) JudgeFlow(network uint8, source netip.AddrPort, destination netip.AddrPort, firstPacket []byte) tun.FlowVerdict {
|
||||
inet4Address, inet6Address := t.PortAddresses()
|
||||
if destination.Addr() == inet4Address || destination.Addr() == inet6Address {
|
||||
|
|
|
|||
|
|
@ -202,6 +202,10 @@ func (w *Endpoint) PortMTU() uint32 {
|
|||
return w.endpoint.PortMTU()
|
||||
}
|
||||
|
||||
func (w *Endpoint) UpstreamPort() any {
|
||||
return w.endpoint
|
||||
}
|
||||
|
||||
func (w *Endpoint) AttachReturn(returnPath tun.Return) error {
|
||||
return w.endpoint.AttachReturn(returnPath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -547,7 +547,7 @@ func (r *Router) preMatchFlow(ctx context.Context, metadata *adapter.InboundCont
|
|||
metadata.OutboundChain = chain
|
||||
metadataCopy := *metadata
|
||||
result.NewTracker = func() tun.FlowTracker {
|
||||
r.logger.InfoContext(ctx, "pre-match: forward ", metadataCopy.Network, " connection from ", metadataCopy.Source.AddrString(), " to ", metadataCopy.Destination.AddrString(), " via outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
r.logger.InfoContext(ctx, "pre-match: forward ", metadataCopy.Network, " connection from ", metadataCopy.Source, " to ", metadataCopy.Destination, " via outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
flowTrackers := make([]tun.FlowTracker, 0, len(r.trackers)+2)
|
||||
flowTrackers = append(flowTrackers, newFlowLogger(ctx, r.logger, metadataCopy, outbound))
|
||||
flowInterrupter := newFlowInterrupter(chain)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ type Device interface {
|
|||
PortMTU() uint32
|
||||
AttachReturn(returnPath tun.Return) error
|
||||
DetachReturn(returnPath tun.Return) error
|
||||
UpstreamPort() any
|
||||
FrontHeadroom() int
|
||||
NewOutboundQueue(handler func(packetBuffers []*buf.Buffer)) *tun.OutboundQueue
|
||||
Close() error
|
||||
|
|
|
|||
|
|
@ -145,6 +145,10 @@ func (d *stackDevice) PortMTU() uint32 {
|
|||
return d.options.MTU
|
||||
}
|
||||
|
||||
func (d *stackDevice) UpstreamPort() any {
|
||||
return d.stack
|
||||
}
|
||||
|
||||
func (d *stackDevice) NewOutboundQueue(handler func(packetBuffers []*buf.Buffer)) *tun.OutboundQueue {
|
||||
return d.memoryTun.NewOutboundQueue(handler)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/kernelports"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing-tun/gtcpip/header"
|
||||
|
|
@ -29,6 +30,7 @@ type systemDevice struct {
|
|||
options Options
|
||||
dialer N.Dialer
|
||||
device tun.Tun
|
||||
ports *kernelports.Pool
|
||||
inet4Address netip.Addr
|
||||
inet6Address netip.Addr
|
||||
closed bool
|
||||
|
|
@ -58,7 +60,16 @@ func newSystemDevice(options Options) (*systemDevice, error) {
|
|||
func (d *systemDevice) Start() error {
|
||||
d.stateAccess.Lock()
|
||||
defer d.stateAccess.Unlock()
|
||||
return d.startLocked()
|
||||
err := d.startLocked()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ports, err := kernelports.New()
|
||||
if err != nil {
|
||||
d.options.Logger.Warn(E.Cause(err, "reserve selector ports"))
|
||||
}
|
||||
d.ports = ports
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *systemDevice) startLocked() error {
|
||||
|
|
@ -365,15 +376,28 @@ func (d *systemDevice) PortMTU() uint32 {
|
|||
return d.options.MTU
|
||||
}
|
||||
|
||||
func (d *systemDevice) UpstreamPort() any {
|
||||
d.stateAccess.RLock()
|
||||
defer d.stateAccess.RUnlock()
|
||||
if d.ports == nil {
|
||||
return nil
|
||||
}
|
||||
return d.ports
|
||||
}
|
||||
|
||||
func (d *systemDevice) Close() error {
|
||||
d.stateAccess.Lock()
|
||||
defer d.stateAccess.Unlock()
|
||||
d.closed = true
|
||||
if d.device == nil {
|
||||
return nil
|
||||
var err error
|
||||
if d.device != nil {
|
||||
err = d.device.Close()
|
||||
d.device = nil
|
||||
}
|
||||
if d.ports != nil {
|
||||
err = E.Errors(err, d.ports.Close())
|
||||
d.ports = nil
|
||||
}
|
||||
err := d.device.Close()
|
||||
d.device = nil
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type Device interface {
|
|||
SetDevice(device *device.Device, peers []*device.Peer)
|
||||
Inet4Address() netip.Addr
|
||||
Inet6Address() netip.Addr
|
||||
UpstreamPort() any
|
||||
}
|
||||
|
||||
type DeviceOptions struct {
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ func (w *stackDevice) Inet6Address() netip.Addr {
|
|||
return w.inet6Address
|
||||
}
|
||||
|
||||
func (w *stackDevice) UpstreamPort() any {
|
||||
return w.stack
|
||||
}
|
||||
|
||||
func (w *stackDevice) SetDevice(device *device.Device, peers []*device.Peer) {
|
||||
w.router.setPeers(device.AllowedIPs(), peers)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ import (
|
|||
"os"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/kernelports"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
|
@ -25,6 +28,7 @@ type systemDevice struct {
|
|||
options DeviceOptions
|
||||
dialer N.Dialer
|
||||
device tun.Tun
|
||||
ports atomic.Pointer[kernelports.Pool]
|
||||
batchDevice tun.LinuxTUN
|
||||
events chan wgTun.Event
|
||||
closeOnce sync.Once
|
||||
|
|
@ -65,6 +69,14 @@ func (w *systemDevice) Inet6Address() netip.Addr {
|
|||
func (w *systemDevice) SetDevice(device *device.Device, peers []*device.Peer) {
|
||||
}
|
||||
|
||||
func (w *systemDevice) UpstreamPort() any {
|
||||
ports := w.ports.Load()
|
||||
if ports == nil {
|
||||
return nil
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
func (w *systemDevice) Start() error {
|
||||
networkManager := service.FromContext[adapter.NetworkManager](w.options.Context)
|
||||
tunOptions := tun.Options{
|
||||
|
|
@ -100,6 +112,12 @@ func (w *systemDevice) Start() error {
|
|||
tunInterface.Close()
|
||||
return err
|
||||
}
|
||||
ports, err := kernelports.New()
|
||||
if err != nil {
|
||||
w.options.Logger.Warn(E.Cause(err, "reserve selector ports"))
|
||||
} else {
|
||||
w.ports.Store(ports)
|
||||
}
|
||||
w.options.Logger.Info("started at ", w.options.Name)
|
||||
w.device = tunInterface
|
||||
batchTUN, isBatchTUN := tunInterface.(tun.LinuxTUN)
|
||||
|
|
@ -170,6 +188,10 @@ func (w *systemDevice) Close() error {
|
|||
if w.device != nil {
|
||||
err = w.device.Close()
|
||||
}
|
||||
ports := w.ports.Load()
|
||||
if ports != nil {
|
||||
err = E.Errors(err, ports.Close())
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ func (e *Endpoint) PortMTU() uint32 {
|
|||
return e.options.MTU
|
||||
}
|
||||
|
||||
func (e *Endpoint) UpstreamPort() any {
|
||||
return e.tunDevice
|
||||
}
|
||||
|
||||
func (e *Endpoint) WritePackets(packets [][]byte) error {
|
||||
wgDevice := e.device.Load()
|
||||
if wgDevice == nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue