Improve sniffer

This commit is contained in:
世界 2025-04-04 16:21:50 +08:00
parent 3adc10a797
commit af17eaa537
No known key found for this signature in database
GPG Key ID: CD109927C34A63C4
12 changed files with 65 additions and 50 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
) )
const ( const (
@ -23,7 +24,7 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
var first byte var first byte
err := binary.Read(reader, binary.BigEndian, &first) err := binary.Read(reader, binary.BigEndian, &first)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if first != 19 { if first != 19 {
@ -33,7 +34,7 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
var protocol [19]byte var protocol [19]byte
_, err = reader.Read(protocol[:]) _, err = reader.Read(protocol[:])
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if string(protocol[:]) != "BitTorrent protocol" { if string(protocol[:]) != "BitTorrent protocol" {
return os.ErrInvalid return os.ErrInvalid

View File

@ -5,13 +5,11 @@ import (
"encoding/binary" "encoding/binary"
"io" "io"
"os" "os"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/task" E "github.com/sagernet/sing/common/exceptions"
mDNS "github.com/miekg/dns" mDNS "github.com/miekg/dns"
) )
@ -20,22 +18,16 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
var length uint16 var length uint16
err := binary.Read(reader, binary.BigEndian, &length) err := binary.Read(reader, binary.BigEndian, &length)
if err != nil { if err != nil {
return os.ErrInvalid return E.Cause1(ErrNeedMoreData, err)
} }
if length == 0 { if length == 0 {
return os.ErrInvalid return os.ErrInvalid
} }
buffer := buf.NewSize(int(length)) buffer := buf.NewSize(int(length))
defer buffer.Release() defer buffer.Release()
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100) _, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
var readTask task.Group
readTask.Append0(func(ctx context.Context) error {
return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen()))
})
err = readTask.Run(readCtx)
cancel()
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
return DomainNameQuery(readCtx, metadata, buffer.Bytes()) return DomainNameQuery(readCtx, metadata, buffer.Bytes())
} }

View File

@ -3,10 +3,12 @@ package sniff
import ( import (
std_bufio "bufio" std_bufio "bufio"
"context" "context"
"errors"
"io" "io"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/http" "github.com/sagernet/sing/protocol/http"
) )
@ -14,8 +16,12 @@ import (
func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
request, err := http.ReadRequest(std_bufio.NewReader(reader)) request, err := http.ReadRequest(std_bufio.NewReader(reader))
if err != nil { if err != nil {
if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err return err
} }
}
metadata.Protocol = C.ProtocolHTTP metadata.Protocol = C.ProtocolHTTP
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString() metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
return nil return nil

View File

@ -20,8 +20,6 @@ import (
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
) )
var ErrClientHelloFragmented = E.New("need more packet for chromium QUIC connection")
func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error { func QUICClientHello(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
reader := bytes.NewReader(packet) reader := bytes.NewReader(packet)
typeByte, err := reader.ReadByte() typeByte, err := reader.ReadByte()
@ -308,7 +306,7 @@ find:
metadata.Protocol = C.ProtocolQUIC metadata.Protocol = C.ProtocolQUIC
metadata.Client = C.ClientChromium metadata.Client = C.ClientChromium
metadata.SniffContext = fragments metadata.SniffContext = fragments
return ErrClientHelloFragmented return E.Cause1(ErrNeedMoreData, err)
} }
metadata.Domain = fingerprint.ServerName metadata.Domain = fingerprint.ServerName
for metadata.Client == "" { for metadata.Client == "" {

View File

@ -20,11 +20,11 @@ func TestSniffQUICChromeNew(t *testing.T) {
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC) require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium) require.Equal(t, metadata.Client, C.ClientChromium)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894") pkt, err = hex.DecodeString("cc0000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea44894b626c685cd5d5c965f7e97b3a1bdc520b75813e747f37a3ae83ad38b9ca2acb0de4fc9424839a50c8fb815a62b498609fbbc59145698860e0509cc08a04d1b119daef844ba2f09c16e2665e5cc0b47624b71f7b950c54fd56b4a1fbb826cba44eeeee3949ced8f5de60d4c81b19ee59f75aa1abb33f22c6b13c27095eb1e99cff01fdc93e6e88da2622ee18c08a79f508befd7e33e99bca60e64bef9a47b764384bd93823daeeb6fcb4d7cfbc4ab53eff59b3636f6dcaaf229b5a94941b5712807166b9bd5e82cb4a9708a71451c4cd6f6e33fb2fe40c8c70dd51a30b37ff9c5e35783debde0093fde19ce074b4887b3c90980b107b9c0f32cf61a66f37c251b789abc4d27fc421207966846c8cc7faa42d9af6ad355a6bc94cb78223b612be8b3e2a4df61fee83a674a0ceb8b7c3a29b97102cda22fecdf6a4628e5b612bc17eab64d6f75feedd0b106c0419e484e66725759964cb5935ac5125e5ae920cd280bd40df57c1d7ae1845700bd4eb7b7ab12bc0850950bfe6e69edd6ac1daa5db2c2b07484327196e561c513462d72872dc6771c39f6b60d46a1f2c92343b7338450a0ef8e39f97fa70652b3a12cd04043698951627aaaa82cc95e76df92021d30e8014c984f12eea0143de8b17e5e4a36ec07bf4814251b391f168a59ef75afcd2319249aaba930f06bb7a11b9491e6f71b3d5774a6503a965e94edd0a67737282fc9cb0271779ff14151b7aa9267bb8f7d643185512515aeea513c0c98bfae782381a3317064195d8825cf8b25c17cdab5fced02612a3f2870e40df57e6ca3f08228a2b04e8de1425eb4b970118f9bbdc212223ff86a5d6b648cdf2366722f21de4b14a1014879eadb69215cdb1aa2a9f4f310ecfe3116214fe3ab0a23f4775a0a54b48d7dfd8f7283ed687b3ac7e1a7e42a0bdc3478aba8651c03e1e9cc9df17d106b8130afe854269b0103b7a696f452721887b19d8181830073c9f10684c65f96d3a6c6efbae044eec03d6399e001fa44d54635dc72f9b8ea6b87d0f452cad1e1e32273e2b47c40f2730235adcae8523b8282f86b8cf1ab63ae54aaa06130df3bbf6ecac7d7d1d43d2a87aea837267ff8ccfaa4b7e47b7ded909e6603d0b928a304f8915c839153598adc4178eb48bc0e98ad7793d7980275e1e491ba4847a4a04ae30fe7f5cc7d4b6f4f63a525e9964d72245860ca76a668a4654adb6619f16e9db79131e5675b93cafb96c92f1da8464d4fef2a22e7f9db695965fe2cc27ea30974629c8fe17cfa2f860179e1eb9faaa88a91ec9ce6da28c1a2894c3b932b5e1c807146718cc77ca13c61eaae00c7c99e019f599772064b198c5c2c5e863336367673630b417ac845ddb7c93b0856317e5d64bab208c5730abc2c63536784fbeaaec139dffc917e775715f1e42164ddef5138d4d163609ab3fbdcab968f8738385c0e7e34ff3cf7771a1dc5ba25a8850fdf96dabafa21f9065f307457ce9af4b7a73450c9d20a3b46fa8d3a1163d22bd01a7d17f0ec274181bf9640fa941427694bfeb1346089f7a851efe0fbb7a2041fa6bb6541ccbad77dd3e1a97999fc05f1fef070e7b5c4b385b8b2a8cc32483fdeba6a373970de2fa4139ba18e5916f949aab0aab2894")
require.NoError(t, err) require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f") pkt, err = hex.DecodeString("c20000000108e241a0c601413b4f004046006d8f15dae9999edf39d58df6762822b9a2ab996d7f6a10044338af3b51b1814bc4ac0fa5a87c34c6ae604af8cabc5957c5240174deefc8e378719ffdab2ae4e15bf4514bea4489e2ff30c43a5f63beb2e4501ce7754085bcbe838003a0b4bccb53863c0766df7eac073c2bdc170772b157997945acdc2ab2e84750cc9aa0ffa0fdc023da7fc565a14f87f7c563dbc9183dd226aab79957d263f66e64b85a1b15a24516bd2c7c04eea4fa0a34ef9849c21585db2e4adb7c05e265c4f38d8ffe4cbed0f3b0e68f3693bf1f726c3fb135b8e32a5d22931d7c55fc2ff4b9a354933ab14544df3cdaf3e3217dfb8d7feb3465dc34df6320ea486f12e5b2d609aaa5f4515c20c86fc440f8087be0ee3d339835746ae2573c2afdee6bb6ef7e9eb541feae9209391b2902cfb0bdaccd9da8d290714638b7da588d4a656ca6eabba78b7363922d6037cf060b161a42019d4feb4156459103cffdeefd0e63114af2b0e0c39e70ebc7fecb8dd1ebb8d60b2137f509bb7dcef5f1d3e06ab1d391466652d57440a410fb4f58a6ce1fb62feb453241f64e110709f59a3d9ebdac94f811337d0e4a80fd6b56b2a70cd6eebbf98e1661291da6bf5beb8b8afc376dfd20eb76afe709e8e8f28e0ef82105954e346546ad25973df43f4acddbec0ffd9b215f62abebebf71305b5ea993560316f69430bf5afe50420340622f802b5830f3bcebffff04980c75a59d28902879e5d51a4fb21062a4ae13c42297075b21d54ee04303879c1157e7470c1451673c98a2f3921f2f3e8f6acfe85b01caaca66b59e5ebffbfe68e5e9ab17e9a1b857eb409df91cb76767fc1814fd3c522a9b117edd0b02526e469cb4afb291a4dcc74c79b47ec6e7ce558c597129366f83ec306b11d2598c705fd4ee9ee99df6b7039bef13b08fc6f26853ad213829d24f895747d45a47414f931c583fb6c3e4f6c27d0c2b81a5f3cee390ec6314e1fec637e8d28b675e97caafdfbf8c25d34a635083a7553d219dd80dbb39087d74c6ad6192ca6f48a3ff8d47db41b2a492c63fcd780012780931dae0a325f9dcbd772d09a700f132c4bc1d9809b25b9751b694eb72a8ba4db7208d2b1bab63e1845208e4f841ea30218a559db98751589716b6d059ca673378f5fe7c7d8a1c82e14a561c47313bbcc278412ba86ffb2b87ec308eab9df696f5b4b54f8e361731bf232820a02a35fda7e5d4bf01b8f005ad299a055116e7b23c181f15a66442cf6032ca477bccc55b79d424eb4f245847bd81a581dc369dd20b1a4892733bde3c38e492c0039f69f2b947a4dc251a49ee7ccc0f36b3b75a555fa1d126db75f94dab60f52f6b15a877a0c380b59f82d35c570bc5f8051e9ef87db51f52383d47b50829b7f9e947ccc67aa280566aa48b4a85c1c7eca6f542789d8abcc050f1aa3cc221b6859656a21454aa21c7bfb9d12115f61c3ed46263ade68a8d3679fa62a659a5da7817406bd16618fccf33ed208ada1b03584e8b485d3cb6ed80a0774e60b6cd55aff64169ea998cf8235997049515abac58e0169ca07fb1c8c4c8b2803ba9d27b44c045d0a1cac86e5e188195c68001f53eb44851b6d821fc01ccbb41e27f38e6ddd66540c2d62ed6e0d551e22c0f26b60078c74a6302a1ed3d9e8fc0861257a63f6ac4e759fd54bff088becd28e30944a6c15db4fc8ae6244346869add946d9d92c430d737e042fa18b28a8ed64d1e8987ad9061cdc1335f")
require.NoError(t, err) require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
@ -40,7 +40,7 @@ func TestSniffQUICChromium(t *testing.T) {
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)
require.Equal(t, metadata.Protocol, C.ProtocolQUIC) require.Equal(t, metadata.Protocol, C.ProtocolQUIC)
require.Equal(t, metadata.Client, C.ClientChromium) require.Equal(t, metadata.Client, C.ClientChromium)
require.ErrorIs(t, err, sniff.ErrClientHelloFragmented) require.ErrorIs(t, err, sniff.ErrNeedMoreData)
pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28") pkt, err = hex.DecodeString("c90000000108f40d654cc09b27f5000044d073eb38807026d4088455e650e7ccf750d01a72f15f9bfc8ff40d223499db1a485cff14dbd45b9be118172834dc35dca3cf62f61a1266f40b92faf3d28d67a466cfdca678ddced15cd606d31959cf441828467857b226d1a241847c82c57312cefe68ba5042d929919bcd4403b39e5699fe87dda05df1b3801e048edee792458e9b1a9b1d4039df05847bcee3be567494b5876e3bd4c3220fe9dfdb2c07d77410f907f744251ef15536cc03b267d3668d5b75bc1ad2fe735cd3bb73519dd9f1625a49e17ad27bdeccf706c83b5ea339a0a05dd0072f4a8f162bd29926b4997f05613c6e4b0270b0c02805ca0543f27c1ff8505a5750bdd33529ee73c491050a10c6903f53c1121dbe0380e84c007c8df74a1b02443ed80ba7766aef5549e618d4fd249844ee28565142005369869299e8c3035ecef3d799f6cada8549e75b4ce4cbf4c85ef071fd7ff067b1ca9b5968dc41d13d011f6d7843823bac97acb1eb8ee45883f0f254b5f9bd4c763b67e2d8c70a7618a0ef0de304cf597a485126e09f8b2fd795b394c0b4bc4cd2634c2057970da2c798c5e8af7aed4f76f5e25d04e3f8c9c5a5b150d17e0d4c74229898c69b8dc7b8bcc9d359eb441de75c68fbdebec62fb669dcccfb1aad03e3fa073adb2ccf7bb14cbaf99e307d2c903ee71a8f028102eb510caee7e7397512086a78d1f95635c7d06845b5a708652dc4e5cd61245aae5b3c05b84815d84d367bce9b9e3f6d6b90701ac3679233c14d5ce2a1eff26469c966266dc6284bdb95c9c6158934c413a872ce22101e4163e3293d236b301592ca4ccacc1fd4c37066e79c2d9857c8a2560dcf0b33b19163c4240c471b19907476e7e25c65f7eb37276594a0f6b4c33c340cc3284178f17ac5e34dbe7509db890e4ddfd0540fbf9deb32a0101d24fe58b26c5f81c627db9d6ae59d7a111a3d5d1f6109f4eec0d0234e6d73c73a44f50999462724b51ce0fd8283535d70d9e83872c79c59897407a0736741011ae5c64862eb0712f9e7b07aa1d5418ca3fde8626257c6fe418f3c5479055bb2b0ab4c25f649923fc2a41c79aaa7d0f3af6d8b8cf06f61f0230d09bbb60bb49b9e49cc5973748a6cf7ffdee7804d424f9423c63e7ff22f4bd24e4867636ef9fe8dd37f59941a8a47c27765caa8e875a30b62834f17c569227e5e6ed15d58e05d36e76332befad065a2cd4079e66d5af189b0337624c89b1560c3b1b0befd5c1f20e6de8e3d664b3ac06b3d154b488983e14aa93266f5f8b621d2a9bb7ccce509eb26e025c9c45f7cccc09ce85b3103af0c93ce9822f82ecb168ca3177829afb2ea0da2c380e7b1728add55a5d42632e2290363d4cbe432b67e13691648e1acfab22cf0d551eee857709b428bb78e27a45aff6eca301c02e4d13cf36cc2494fdd1aef8dede6e18febd79dca4c6964d09b91c25a08f0947c76ab5104de9404459c2edf5f4adb9dfd771be83656f77fbbafb1ad3281717066010be8778952495383c9f2cf0a38527228c662a35171c5981731f1af09bab842fe6c3162ad4152a4221f560eb6f9bea66b294ffbd3643da2fe34096da13c246505452540177a2a0a1a69106e5cfc279a4890fc3be2952f26be245f930e6c2d9e7e26ee960481e72b99594a1185b46b94b6436d00ba6c70ffe135d43907c92c6f1c09fb9453f103730714f5700fa4347f9715c774cb04a7218dacc66d9c2fade18b14e684aa7fc9ebda0a28")
require.NoError(t, err) require.NoError(t, err)
err = sniff.QUICClientHello(context.Background(), &metadata, pkt) err = sniff.QUICClientHello(context.Background(), &metadata, pkt)

View File

@ -8,6 +8,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
) )
@ -15,7 +16,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktVersion uint8 var tpktVersion uint8
err := binary.Read(reader, binary.BigEndian, &tpktVersion) err := binary.Read(reader, binary.BigEndian, &tpktVersion)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if tpktVersion != 0x03 { if tpktVersion != 0x03 {
return os.ErrInvalid return os.ErrInvalid
@ -24,7 +25,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktReserved uint8 var tpktReserved uint8
err = binary.Read(reader, binary.BigEndian, &tpktReserved) err = binary.Read(reader, binary.BigEndian, &tpktReserved)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if tpktReserved != 0x00 { if tpktReserved != 0x00 {
return os.ErrInvalid return os.ErrInvalid
@ -33,7 +34,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var tpktLength uint16 var tpktLength uint16
err = binary.Read(reader, binary.BigEndian, &tpktLength) err = binary.Read(reader, binary.BigEndian, &tpktLength)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if tpktLength != 19 { if tpktLength != 19 {
@ -43,7 +44,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var cotpLength uint8 var cotpLength uint8
err = binary.Read(reader, binary.BigEndian, &cotpLength) err = binary.Read(reader, binary.BigEndian, &cotpLength)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if cotpLength != 14 { if cotpLength != 14 {
@ -53,7 +54,7 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var cotpTpduType uint8 var cotpTpduType uint8
err = binary.Read(reader, binary.BigEndian, &cotpTpduType) err = binary.Read(reader, binary.BigEndian, &cotpTpduType)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if cotpTpduType != 0xE0 { if cotpTpduType != 0xE0 {
return os.ErrInvalid return os.ErrInvalid
@ -61,13 +62,13 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
err = rw.SkipN(reader, 5) err = rw.SkipN(reader, 5)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
var rdpType uint8 var rdpType uint8
err = binary.Read(reader, binary.BigEndian, &rdpType) err = binary.Read(reader, binary.BigEndian, &rdpType)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if rdpType != 0x01 { if rdpType != 0x01 {
return os.ErrInvalid return os.ErrInvalid
@ -75,12 +76,12 @@ func RDP(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
var rdpFlags uint8 var rdpFlags uint8
err = binary.Read(reader, binary.BigEndian, &rdpFlags) err = binary.Read(reader, binary.BigEndian, &rdpFlags)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
var rdpLength uint8 var rdpLength uint8
err = binary.Read(reader, binary.BigEndian, &rdpLength) err = binary.Read(reader, binary.BigEndian, &rdpLength)
if err != nil { if err != nil {
return err return E.Cause1(ErrNeedMoreData, err)
} }
if rdpLength != 8 { if rdpLength != 8 {
return os.ErrInvalid return os.ErrInvalid

View File

@ -3,6 +3,7 @@ package sniff
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"io" "io"
"net" "net"
"time" "time"
@ -19,6 +20,8 @@ type (
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
) )
var ErrNeedMoreData = E.New("need more data")
func Skip(metadata *adapter.InboundContext) bool { func Skip(metadata *adapter.InboundContext) bool {
// skip server first protocols // skip server first protocols
switch metadata.Destination.Port { switch metadata.Destination.Port {
@ -40,7 +43,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
timeout = C.ReadPayloadTimeout timeout = C.ReadPayloadTimeout
} }
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
var errors []error var sniffError error
for i := 0; ; i++ { for i := 0; ; i++ {
err := conn.SetReadDeadline(deadline) err := conn.SetReadDeadline(deadline)
if err != nil { if err != nil {
@ -54,7 +57,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
} }
return E.Cause(err, "read payload") return E.Cause(err, "read payload")
} }
errors = nil sniffError = nil
for _, sniffer := range sniffers { for _, sniffer := range sniffers {
reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader { reader := io.MultiReader(common.Map(append(buffers, buffer), func(it *buf.Buffer) io.Reader {
return bytes.NewReader(it.Bytes()) return bytes.NewReader(it.Bytes())
@ -63,20 +66,23 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
if err == nil { if err == nil {
return nil return nil
} }
errors = append(errors, err) sniffError = E.Errors(sniffError, err)
}
if !errors.Is(err, ErrNeedMoreData) {
break
} }
} }
return E.Errors(errors...) return sniffError
} }
func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error { func PeekPacket(ctx context.Context, metadata *adapter.InboundContext, packet []byte, sniffers ...PacketSniffer) error {
var errors []error var sniffError []error
for _, sniffer := range sniffers { for _, sniffer := range sniffers {
err := sniffer(ctx, metadata, packet) err := sniffer(ctx, metadata, packet)
if err == nil { if err == nil {
return nil return nil
} }
errors = append(errors, err) sniffError = append(sniffError, err)
} }
return E.Errors(errors...) return E.Errors(sniffError...)
} }

View File

@ -5,22 +5,26 @@ import (
"context" "context"
"io" "io"
"os" "os"
"strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
) )
func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
scanner := bufio.NewScanner(reader) const sshPrefix = "SSH-2.0-"
if !scanner.Scan() { bReader := bufio.NewReader(reader)
prefix, err := bReader.Peek(len(sshPrefix))
if err != nil {
return E.Cause1(ErrNeedMoreData, err)
} else if string(prefix) != sshPrefix {
return os.ErrInvalid return os.ErrInvalid
} }
fistLine := scanner.Text() fistLine, _, err := bReader.ReadLine()
if !strings.HasPrefix(fistLine, "SSH-2.0-") { if err != nil {
return os.ErrInvalid return err
} }
metadata.Protocol = C.ProtocolSSH metadata.Protocol = C.ProtocolSSH
metadata.Client = fistLine[8:] metadata.Client = string(fistLine)[8:]
return nil return nil
} }

View File

@ -3,11 +3,13 @@ package sniff
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"errors"
"io" "io"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
) )
func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error { func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reader io.Reader) error {
@ -23,5 +25,9 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
metadata.Domain = clientHello.ServerName metadata.Domain = clientHello.ServerName
return nil return nil
} }
if errors.Is(err, io.ErrUnexpectedEOF) {
return E.Cause1(ErrNeedMoreData, err)
} else {
return err return err
}
} }

2
go.mod
View File

@ -26,7 +26,7 @@ require (
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
github.com/sagernet/quic-go v0.49.0-beta.1 github.com/sagernet/quic-go v0.49.0-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.5 github.com/sagernet/sing v0.6.6-0.20250406082302-d3673bff4af8
github.com/sagernet/sing-dns v0.4.1 github.com/sagernet/sing-dns v0.4.1
github.com/sagernet/sing-mux v0.3.1 github.com/sagernet/sing-mux v0.3.1
github.com/sagernet/sing-quic v0.4.1 github.com/sagernet/sing-quic v0.4.1

4
go.sum
View File

@ -119,8 +119,8 @@ github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8W
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.5 h1:TBKTK6Ms0/MNTZm+cTC2hhKunE42XrNIdsxcYtWqeUU= github.com/sagernet/sing v0.6.6-0.20250406082302-d3673bff4af8 h1:1jHChanwnGF5DJZ5pR/RkVf69VyjQxfDVfOMJx7bPyI=
github.com/sagernet/sing v0.6.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.6-0.20250406082302-d3673bff4af8/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk= github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk=
github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI= github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI=

View File

@ -549,7 +549,7 @@ func (r *Router) actionSniff(
sniffBuffer.Release() sniffBuffer.Release()
} }
} else if inputPacketConn != nil { } else if inputPacketConn != nil {
if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrClientHelloFragmented) { if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError) r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError)
return return
} }
@ -618,7 +618,8 @@ func (r *Router) actionSniff(
} }
packetBuffers = append(packetBuffers, packetBuffer) packetBuffers = append(packetBuffers, packetBuffer)
metadata.PacketSniffError = err metadata.PacketSniffError = err
if errors.Is(err, sniff.ErrClientHelloFragmented) { if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
continue continue
} }