1
0
mirror of https://github.com/MetaCubeX/mihomo.git synced 2026-10-10 04:03:11 +08:00

fix: keep connections open after initial sniffing failures

This commit is contained in:
wwqgtxx
2026-07-28 22:11:26 +08:00
parent ea9c544e04
commit 63f15f2d46
2 changed files with 66 additions and 6 deletions
+3 -6
View File
@@ -2,7 +2,6 @@ package sniffer
import (
"errors"
"net"
"net/netip"
"time"
@@ -227,11 +226,9 @@ func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (s
_, err := conn.Peek(1)
_ = conn.SetReadDeadline(time.Time{})
if err != nil {
if _, ok := err.(*net.OpError); ok {
sd.cacheSniffFailed(metadata)
log.Errorln("[Sniffer] [%s] may not have any sent data, Consider adding skip", metadata.DstIP)
_ = conn.Close()
}
// The caller owns failure accounting and the connection lifetime. No
// initial data can be valid for a server-first protocol, so sniffing must
// not close the connection merely because this deadline expired.
log.Debugln("[Sniffer] [%s] the data length not enough, error: %v", metadata.DstIP, err)
return "", SnifferConfig{}, err
}
+63
View File
@@ -90,6 +90,10 @@ type stubSniffer struct {
reply func(data []byte) (string, error)
}
type domainMatcherFunc func(domain string) bool
func (f domainMatcherFunc) MatchDomain(domain string) bool { return f(domain) }
var _ sniffer.Sniffer = (*stubSniffer)(nil)
func (s *stubSniffer) SupportNetwork() C.NetWork { return s.network }
@@ -345,6 +349,65 @@ func TestDispatcherMultipleSniffers(t *testing.T) {
assert.False(t, cached)
})
t.Run("keeps the connection open and caches an initial read failure once", func(t *testing.T) {
waiting := &stubSniffer{reply: func(data []byte) (string, error) {
return "", needAtLeast(len(data) + 1)
}}
sd, err := NewDispatcher(&Config{Enable: true, ParsePureIp: true})
assert.NoError(t, err)
sd.sniffers = []configuredSniffer{{Sniffer: waiting}}
raw := &chunkedConn{
t: t,
readErr: &net.OpError{Op: "read", Net: "tcp", Err: io.ErrNoProgress},
}
t.Cleanup(raw.stopTimer)
metadata := &C.Metadata{
NetWork: C.TCP,
DstIP: netip.MustParseAddr("192.0.2.1"),
DstPort: 80,
}
sniffed := sd.TCPSniff(N.NewBufferedConn(raw), metadata)
assert.False(t, sniffed)
assert.False(t, raw.closed)
assert.Empty(t, waiting.seen)
failures, cached := sd.skipList.Get(metadata.AddrPort())
assert.True(t, cached)
assert.Equal(t, uint8(1), failures)
assert.True(t, raw.deadline.IsZero(), "read deadline was not cleared")
})
t.Run("does not cache a forced initial read failure", func(t *testing.T) {
waiting := &stubSniffer{reply: func(data []byte) (string, error) {
return "", needAtLeast(len(data) + 1)
}}
sd, err := NewDispatcher(&Config{
Enable: true,
ForceDomain: []C.DomainMatcher{domainMatcherFunc(func(string) bool { return true })},
})
assert.NoError(t, err)
sd.sniffers = []configuredSniffer{{Sniffer: waiting}}
raw := &chunkedConn{
t: t,
readErr: &net.OpError{Op: "read", Net: "tcp", Err: io.ErrNoProgress},
}
t.Cleanup(raw.stopTimer)
metadata := &C.Metadata{
NetWork: C.TCP,
Host: "forced.example",
DstIP: netip.MustParseAddr("192.0.2.1"),
DstPort: 80,
}
sniffed := sd.TCPSniff(N.NewBufferedConn(raw), metadata)
assert.False(t, sniffed)
assert.False(t, raw.closed)
_, cached := sd.skipList.Get(metadata.AddrPort())
assert.False(t, cached)
})
t.Run("accepts all-network sniffer", func(t *testing.T) {
allNetwork := &stubSniffer{
network: C.ALLNet,