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...) }