From 58c4110b57d2f94867a352a37bc492f896eda5f5 Mon Sep 17 00:00:00 2001 From: H1JK Date: Thu, 1 Jan 2026 06:48:36 +0800 Subject: [PATCH] draft: Tun respond ICMP port unreachable when rejecting UDP packets --- adapter/outbound/reject.go | 36 +++++++++++++++++++++++++----------- common/net/sing.go | 3 +++ common/utils/cast.go | 31 +++++++++++++++++++++++++++++++ constant/adapters.go | 4 ++++ constant/rule.go | 10 ++++++++++ go.mod | 2 +- go.sum | 4 ++-- listener/sing/sing.go | 11 +++++++++++ tunnel/tunnel.go | 15 +++++++++++++-- 9 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 common/utils/cast.go diff --git a/adapter/outbound/reject.go b/adapter/outbound/reject.go index 97bcdd5a..af78fa5f 100644 --- a/adapter/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -34,7 +34,10 @@ func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata) if err := r.ResolveUDP(ctx, metadata); err != nil { return nil, err } - return newPacketConn(&nopPacketConn{}, r), nil + if r.drop { + return newPacketConn(dropPacketConn{}, r), nil + } + return newPacketConn(nopPacketConn{}, r), C.ErrResetByRule } func (r *Reject) ResolveUDP(ctx context.Context, metadata *C.Metadata) error { @@ -90,12 +93,10 @@ func NewPass() *Reject { type nopConn struct{} -func (rw nopConn) Read(b []byte) (int, error) { return 0, io.EOF } - -func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { return io.EOF } - -func (rw nopConn) Write(b []byte) (int, error) { return 0, io.EOF } -func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error { return io.EOF } +func (rw nopConn) Read(b []byte) (int, error) { return 0, C.ErrResetByRule } +func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { return C.ErrResetByRule } +func (rw nopConn) Write(b []byte) (int, error) { return 0, C.ErrResetByRule } +func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error { return C.ErrResetByRule } func (rw nopConn) Close() error { return nil } func (rw nopConn) LocalAddr() net.Addr { return nil } func (rw nopConn) RemoteAddr() net.Addr { return nil } @@ -110,11 +111,9 @@ type nopPacketConn struct{} func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil } -func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { - return 0, nil, io.EOF -} +func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, C.ErrResetByRule } func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) { - return nil, nil, nil, io.EOF + return nil, nil, nil, C.ErrResetByRule } func (npc nopPacketConn) Close() error { return nil } func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified } @@ -137,3 +136,18 @@ func (rw dropConn) RemoteAddr() net.Addr { return nil } func (rw dropConn) SetDeadline(time.Time) error { return nil } func (rw dropConn) SetReadDeadline(time.Time) error { return nil } func (rw dropConn) SetWriteDeadline(time.Time) error { return nil } + +type dropPacketConn struct{} + +func (npc dropPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { + return len(b), nil +} +func (npc dropPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF } +func (npc dropPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) { + return nil, nil, nil, io.EOF +} +func (npc dropPacketConn) Close() error { return nil } +func (npc dropPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified } +func (npc dropPacketConn) SetDeadline(time.Time) error { return nil } +func (npc dropPacketConn) SetReadDeadline(time.Time) error { return nil } +func (npc dropPacketConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/common/net/sing.go b/common/net/sing.go index df07bf98..8dadde2f 100644 --- a/common/net/sing.go +++ b/common/net/sing.go @@ -33,6 +33,9 @@ type WriterWithUpstream = network.WriterWithUpstream type WithUpstreamWriter = network.WithUpstreamWriter type WithUpstream = common.WithUpstream +type HandshakeSuccess = network.HandshakeSuccess +type HandshakeFailure = network.HandshakeFailure + var UnwrapReader = network.UnwrapReader var UnwrapWriter = network.UnwrapWriter diff --git a/common/utils/cast.go b/common/utils/cast.go new file mode 100644 index 00000000..6452cdd4 --- /dev/null +++ b/common/utils/cast.go @@ -0,0 +1,31 @@ +package utils + +import ( + "fmt" + "net" +) + +type WithUpstream interface { + Upstream() any +} + +type stdWithUpstreamNetConn interface { + NetConn() net.Conn +} + +func Cast[T any](obj any) (_ T, _ bool) { + if c, ok := obj.(T); ok { + fmt.Printf("Got 1: %T\n", obj) // TODO + return c, true + } + if u, ok := obj.(WithUpstream); ok { + fmt.Printf("Upstream 2: %T\n", obj) // TODO + return Cast[T](u.Upstream()) + } + if u, ok := obj.(stdWithUpstreamNetConn); ok { + fmt.Printf("Std 3: %T\n", obj) // TODO + return Cast[T](u.NetConn()) + } + fmt.Printf("Failed: %T\n", obj) // TODO + return +} diff --git a/constant/adapters.go b/constant/adapters.go index 07ae5de1..bab4ad9a 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -274,6 +274,10 @@ func (s *packetAdapter) Key() string { return s.key } +func (s *packetAdapter) Upstream() any { + return s.UDPPacket +} + func NewPacketAdapter(packet UDPPacket, metadata *Metadata) PacketAdapter { return &packetAdapter{ packet, diff --git a/constant/rule.go b/constant/rule.go index a5941e6b..58a8681f 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -1,5 +1,11 @@ package constant +import ( + "io" + + "github.com/metacubex/sing/common/exceptions" +) + // Rule Type const ( Domain RuleType = iota @@ -129,3 +135,7 @@ type RuleGroup interface { Rule GetRecodeSize() int } + +var ( + ErrResetByRule = exceptions.Cause(io.EOF, "reset by rule") // TODO: replace function from sing +) diff --git a/go.mod b/go.mod index 6f18e9e8..3307ec6f 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/metacubex/sing-shadowsocks v0.2.12 github.com/metacubex/sing-shadowsocks2 v0.2.7 github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 - github.com/metacubex/sing-tun v0.4.11 + github.com/metacubex/sing-tun v0.4.12-0.20251231220427-f11396db2fa1 github.com/metacubex/sing-vmess v0.2.4 github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f github.com/metacubex/smux v0.0.0-20251111013112-03f8d12dafc1 diff --git a/go.sum b/go.sum index 08bd736b..7745b629 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6w github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= -github.com/metacubex/sing-tun v0.4.11 h1:NG5zpvYPbBXf+9GSUmDaGCDwl3hZXV677tbRAw0QtCM= -github.com/metacubex/sing-tun v0.4.11/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= +github.com/metacubex/sing-tun v0.4.12-0.20251231220427-f11396db2fa1 h1:eaQCkCI2STxaGiVK35huV9TjUu3QDo3LiS/LKO5n1Z8= +github.com/metacubex/sing-tun v0.4.12-0.20251231220427-f11396db2fa1/go.mod h1:L/TjQY5JEGy8nvsuYmy/XgMFMCPiF0+AWSFCYfS6r9w= github.com/metacubex/sing-vmess v0.2.4 h1:Tx6AGgCiEf400E/xyDuYyafsel6sGbR8oF7RkAaus6I= github.com/metacubex/sing-vmess v0.2.4/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM= github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= diff --git a/listener/sing/sing.go b/listener/sing/sing.go index 4e229500..c39e781e 100644 --- a/listener/sing/sing.go +++ b/listener/sing/sing.go @@ -296,8 +296,19 @@ func (c *packet) LocalAddr() net.Addr { func (c *packet) Drop() { c.buff.Release() + // always try to report success to ensure that the memory is freed up + if handshake, isHandshake := common.Cast[network.HandshakeSuccess](c); isHandshake { + _ = handshake.HandshakeSuccess() + } } func (c *packet) InAddr() net.Addr { return c.lAddr } + +func (c *packet) Upstream() any { + if c.writer == nil { + return nil + } + return *c.writer +} diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index dbd2bd53..545b6dd1 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -463,9 +463,17 @@ func handleUDPConn(packet C.PacketAdapter) { rawPc, err := retry(ctx, func(ctx context.Context) (C.PacketConn, error) { return proxy.ListenPacketContext(ctx, dialMetadata) }, func(err error) { - logMetadataErr(metadata, rule, proxy, err) + if !errors.Is(err, C.ErrResetByRule) { + logMetadataErr(metadata, rule, proxy, err) + } }) if err != nil { + if errors.Is(err, C.ErrResetByRule) { + logMetadata(metadata, rule, rawPc) + if handshake, isHandshake := utils.Cast[N.HandshakeFailure](packet); isHandshake { + _ = handshake.HandshakeFailure(err) + } + } return nil, nil, err } logMetadata(metadata, rule, rawPc) @@ -490,7 +498,7 @@ func handleUDPConn(packet C.PacketAdapter) { sender.Process(pc, proxy) }() } - sender.Send(packet) // nonblocking + sender.Send(packet) // will not block } func handleTCPConn(connCtx C.ConnContext) { @@ -700,6 +708,9 @@ func shouldStopRetry(err error) bool { if errors.Is(err, resolver.ErrIPv6Disabled) { return true } + if errors.Is(err, C.ErrResetByRule) { + return true + } if errors.Is(err, loopback.ErrReject) { return true }