121 lines
3.1 KiB
Go
121 lines
3.1 KiB
Go
package tun
|
|
|
|
import (
|
|
"net/netip"
|
|
"runtime"
|
|
"sync"
|
|
|
|
"github.com/sagernet/sing-tun/gtcpip/header"
|
|
"github.com/sagernet/sing/contrab/maphash"
|
|
)
|
|
|
|
const (
|
|
natSelectorMin = 49152
|
|
natSelectorMax = 65535
|
|
)
|
|
|
|
type portNAT struct {
|
|
port Port
|
|
hasher maphash.Hasher[flowKey]
|
|
shardMask uint32
|
|
shards []natShard
|
|
selectorStart uint16
|
|
selectorCount uint16
|
|
|
|
counter uint32
|
|
pending [][]byte
|
|
}
|
|
|
|
type natShard struct {
|
|
access sync.RWMutex
|
|
flows map[flowKey]*forwardFlow
|
|
}
|
|
|
|
func newPortNAT(port Port) *portNAT {
|
|
shardCount := 1
|
|
for shardCount < runtime.GOMAXPROCS(0) {
|
|
shardCount <<= 1
|
|
}
|
|
nat := &portNAT{
|
|
port: port,
|
|
hasher: maphash.NewHasher[flowKey](),
|
|
shardMask: uint32(shardCount - 1),
|
|
shards: make([]natShard, shardCount),
|
|
}
|
|
if rangedPort, isRanged := port.(PortWithSelectorRange); isRanged {
|
|
nat.selectorStart, nat.selectorCount = rangedPort.PortSelectorRange()
|
|
}
|
|
for i := range nat.shards {
|
|
nat.shards[i].flows = make(map[flowKey]*forwardFlow)
|
|
}
|
|
return nat
|
|
}
|
|
|
|
func (n *portNAT) shard(key flowKey) *natShard {
|
|
return &n.shards[n.hasher.Hash32(key)&n.shardMask]
|
|
}
|
|
|
|
func (n *portNAT) lookup(key flowKey) *forwardFlow {
|
|
shard := n.shard(key)
|
|
shard.access.RLock()
|
|
flow := shard.flows[key]
|
|
shard.access.RUnlock()
|
|
return flow
|
|
}
|
|
|
|
func (n *portNAT) insert(key flowKey, flow *forwardFlow) {
|
|
shard := n.shard(key)
|
|
shard.access.Lock()
|
|
shard.flows[key] = flow
|
|
shard.access.Unlock()
|
|
}
|
|
|
|
func (n *portNAT) delete(key flowKey) {
|
|
shard := n.shard(key)
|
|
shard.access.Lock()
|
|
delete(shard.flows, key)
|
|
shard.access.Unlock()
|
|
}
|
|
|
|
func (n *portNAT) reverseKeyFor(protocol uint8, portAddress, serverAddress netip.Addr, serverPort, selector uint16) flowKey {
|
|
if protocol == uint8(header.ICMPv4ProtocolNumber) || protocol == uint8(header.ICMPv6ProtocolNumber) {
|
|
return flowKey{
|
|
protocol: protocol,
|
|
source: netip.AddrPortFrom(serverAddress, selector),
|
|
destination: netip.AddrPortFrom(portAddress, selector),
|
|
}
|
|
}
|
|
return flowKey{
|
|
protocol: protocol,
|
|
source: netip.AddrPortFrom(serverAddress, serverPort),
|
|
destination: netip.AddrPortFrom(portAddress, selector),
|
|
}
|
|
}
|
|
|
|
func (n *portNAT) selectorRange(protocol uint8) (uint16, uint32) {
|
|
if n.selectorCount == 0 ||
|
|
protocol == uint8(header.ICMPv4ProtocolNumber) || protocol == uint8(header.ICMPv6ProtocolNumber) {
|
|
return natSelectorMin, natSelectorMax - natSelectorMin + 1
|
|
}
|
|
return n.selectorStart, uint32(n.selectorCount)
|
|
}
|
|
|
|
func (n *portNAT) allocateSelector(protocol uint8, portAddress, serverAddress netip.Addr, serverPort, clientSelector uint16) (uint16, flowKey, bool) {
|
|
rangeStart, rangeCount := n.selectorRange(protocol)
|
|
if clientSelector != 0 &&
|
|
clientSelector >= rangeStart && uint32(clientSelector-rangeStart) < rangeCount {
|
|
key := n.reverseKeyFor(protocol, portAddress, serverAddress, serverPort, clientSelector)
|
|
if n.lookup(key) == nil {
|
|
return clientSelector, key, true
|
|
}
|
|
}
|
|
for range rangeCount {
|
|
n.counter++
|
|
candidate := rangeStart + uint16(n.counter%rangeCount)
|
|
key := n.reverseKeyFor(protocol, portAddress, serverAddress, serverPort, candidate)
|
|
if n.lookup(key) == nil {
|
|
return candidate, key, true
|
|
}
|
|
}
|
|
return 0, flowKey{}, false
|
|
}
|