Add flow dispatcher

This commit is contained in:
世界 2026-07-06 11:49:18 +08:00
parent 47bdde06c3
commit ed63adda33
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
27 changed files with 2469 additions and 963 deletions

194
ping/port.go Normal file
View file

@ -0,0 +1,194 @@
package ping
import (
"context"
"net/netip"
"slices"
"sync"
"time"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/gtcpip/header"
"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"
)
const defaultFlowTimeout = time.Minute
type Port struct {
ctx context.Context
logger logger.ContextLogger
controlFunc func(destination netip.Addr) control.Func
timeout time.Duration
returnAccess sync.Mutex
returnPaths []tun.Return
flowAccess sync.Mutex
flows map[flowKey]*Destination
lastSweep time.Time
}
type flowKey struct {
source netip.Addr
destination netip.Addr
identifier uint16
}
func NewPort(ctx context.Context, logger logger.ContextLogger, controlFunc func(destination netip.Addr) control.Func, timeout time.Duration) *Port {
if timeout <= 0 {
timeout = defaultFlowTimeout
}
return &Port{
ctx: ctx,
logger: logger,
controlFunc: controlFunc,
timeout: timeout,
flows: make(map[flowKey]*Destination),
}
}
func (p *Port) PortAddresses() (netip.Addr, netip.Addr) {
return netip.IPv4Unspecified(), netip.IPv6Unspecified()
}
func (p *Port) PortMTU() uint32 {
return 0
}
func (p *Port) AttachReturn(returnPath tun.Return) error {
p.returnAccess.Lock()
defer p.returnAccess.Unlock()
if slices.Contains(p.returnPaths, returnPath) {
return nil
}
p.returnPaths = append(p.returnPaths[:len(p.returnPaths):len(p.returnPaths)], returnPath)
return nil
}
func (p *Port) DetachReturn(returnPath tun.Return) error {
p.returnAccess.Lock()
defer p.returnAccess.Unlock()
returnPaths := make([]tun.Return, 0, len(p.returnPaths))
for _, existing := range p.returnPaths {
if existing != returnPath {
returnPaths = append(returnPaths, existing)
}
}
p.returnPaths = returnPaths
return nil
}
func (p *Port) WritePackets(packets [][]byte) error {
var errs []error
for _, packet := range packets {
err := p.writePacket(packet)
if err != nil {
errs = append(errs, err)
}
}
return E.Errors(errs...)
}
func (p *Port) writePacket(packet []byte) error {
var (
source netip.Addr
destination netip.Addr
identifier uint16
)
switch header.IPVersion(packet) {
case header.IPv4Version:
ipHdr := header.IPv4(packet)
if !ipHdr.IsValid(len(packet)) || ipHdr.TransportProtocol() != header.ICMPv4ProtocolNumber || ipHdr.PayloadLength() < header.ICMPv4MinimumSize {
return nil
}
icmpHdr := header.ICMPv4(ipHdr.Payload())
if icmpHdr.Type() != header.ICMPv4Echo || icmpHdr.Code() != 0 {
return nil
}
source = ipHdr.SourceAddr()
destination = ipHdr.DestinationAddr()
identifier = icmpHdr.Ident()
case header.IPv6Version:
ipHdr := header.IPv6(packet)
if !ipHdr.IsValid(len(packet)) || ipHdr.TransportProtocol() != header.ICMPv6ProtocolNumber || ipHdr.PayloadLength() < header.ICMPv6MinimumSize {
return nil
}
icmpHdr := header.ICMPv6(ipHdr.Payload())
if icmpHdr.Type() != header.ICMPv6EchoRequest || icmpHdr.Code() != 0 {
return nil
}
source = ipHdr.SourceAddr()
destination = ipHdr.DestinationAddr()
identifier = icmpHdr.Ident()
default:
return nil
}
flow, err := p.flowFor(source, destination, identifier)
if err != nil {
return E.Cause(err, "connect ICMP flow to ", destination)
}
return flow.WritePacket(buf.As(packet))
}
func (p *Port) flowFor(source netip.Addr, destination netip.Addr, identifier uint16) (*Destination, error) {
key := flowKey{source: source, destination: destination, identifier: identifier}
p.flowAccess.Lock()
defer p.flowAccess.Unlock()
now := time.Now()
if now.Sub(p.lastSweep) >= p.timeout {
p.lastSweep = now
for oldKey, oldFlow := range p.flows {
if oldFlow.IsClosed() {
delete(p.flows, oldKey)
}
}
}
flow, loaded := p.flows[key]
if loaded && !flow.IsClosed() {
return flow, nil
}
var controlFunc control.Func
if p.controlFunc != nil {
controlFunc = p.controlFunc(destination)
}
flow, err := ConnectDestination(p.ctx, p.logger, controlFunc, destination, portWriter{p}, p.timeout)
if err != nil {
return nil, err
}
p.flows[key] = flow
return flow, nil
}
type portWriter struct {
port *Port
}
func (w portWriter) WritePacket(packet []byte) error {
w.port.returnAccess.Lock()
returnPaths := w.port.returnPaths
w.port.returnAccess.Unlock()
for _, returnPath := range returnPaths {
headroom := returnPath.ReturnHeadroom()
buffer := make([]byte, headroom+len(packet))
copy(buffer[headroom:], packet)
unconsumed := returnPath.ReturnPackets([][]byte{buffer})
if len(unconsumed) == 0 {
return nil
}
}
return nil
}
func (p *Port) Close() error {
p.flowAccess.Lock()
defer p.flowAccess.Unlock()
var errs []error
for key, flow := range p.flows {
errs = append(errs, flow.Close())
delete(p.flows, key)
}
return E.Errors(errs...)
}