diff --git a/common/sniff/ssh.go b/common/sniff/ssh.go new file mode 100644 index 00000000..194d0bda --- /dev/null +++ b/common/sniff/ssh.go @@ -0,0 +1,26 @@ +package sniff + +import ( + "bufio" + "context" + "io" + "os" + "strings" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" +) + +func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader) error { + scanner := bufio.NewScanner(reader) + if !scanner.Scan() { + return os.ErrInvalid + } + fistLine := scanner.Text() + if !strings.HasPrefix(fistLine, "SSH-2.0-") { + return os.ErrInvalid + } + metadata.Protocol = C.ProtocolSSH + metadata.Client = fistLine[8:] + return nil +} diff --git a/common/sniff/ssh_test.go b/common/sniff/ssh_test.go new file mode 100644 index 00000000..55babb23 --- /dev/null +++ b/common/sniff/ssh_test.go @@ -0,0 +1,24 @@ +package sniff_test + +import ( + "bytes" + "context" + "encoding/hex" + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/sniff" + C "github.com/sagernet/sing-box/constant" + "github.com/stretchr/testify/require" + "testing" +) + +func TestSniffSSH(t *testing.T) { + t.Parallel() + + pkt, err := hex.DecodeString("5353482d322e302d64726f70626561720d0a000001a40a1492892570d1223aef61b0d647972c8bd30000009f637572766532353531392d7368613235362c637572766532353531392d736861323536406c69627373682e6f72672c6469666669652d68656c6c6d616e2d67726f757031342d7368613235362c6469666669652d68656c6c6d616e2d67726f757031342d736861312c6b6578677565737332406d6174742e7563632e61736e2e61752c6b65782d7374726963742d732d763030406f70656e7373682e636f6d000000207373682d656432353531392c7273612d736861322d3235362c7373682d7273610000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d6374720000003363686163686132302d706f6c7931333035406f70656e7373682e636f6d2c6165733132382d6374722c6165733235362d63747200000017686d61632d736861312c686d61632d736861322d32353600000017686d61632d736861312c686d61632d736861322d323536000000046e6f6e65000000046e6f6e65000000000000000000000000002aa6ed090585b7d635b6") + require.NoError(t, err) + var metadata adapter.InboundContext + err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt)) + require.NoError(t, err) + require.Equal(t, C.ProtocolSSH, metadata.Protocol) + require.Equal(t, "dropbear", metadata.Client) +} diff --git a/constant/protocol.go b/constant/protocol.go index 8b1854d4..f867f428 100644 --- a/constant/protocol.go +++ b/constant/protocol.go @@ -8,6 +8,7 @@ const ( ProtocolSTUN = "stun" ProtocolBitTorrent = "bittorrent" ProtocolDTLS = "dtls" + ProtocolSSH = "ssh" ) const ( diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index ab96e75a..70e2acc8 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -7,14 +7,15 @@ icon: material/new-box :material-plus: QUIC client type detect support for QUIC :material-plus: Chromium support for QUIC :material-plus: BitTorrent support - :material-plus: DTLS support + :material-plus: DTLS support + :material-plus: SSH support If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed. #### Supported Protocols | Network | Protocol | Domain Name | Client | -|:-------:|:------------:|:-----------:|:----------------:| +| :-----: | :----------: | :---------: | :--------------: | | TCP | `http` | Host | / | | TCP | `tls` | Server Name | / | | UDP | `quic` | Server Name | QUIC Client Type | @@ -22,9 +23,10 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c | TCP/UDP | `dns` | / | / | | TCP/UDP | `bittorrent` | / | / | | UDP | `dtls` | / | / | +| TCP | `ssh` | / | SSH Client Name | | QUIC Client | Type | -|:------------------------:|:----------:| +| :----------------------: | :--------: | | Chromium/Cronet | `chrimium` | | Safari/Apple Network API | `safari` | | Firefox / uquic firefox | `firefox` | diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index 1ebc6d38..7421fd07 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -7,24 +7,26 @@ icon: material/new-box :material-plus: QUIC 的 客户端类型探测支持 :material-plus: QUIC 的 Chromium 支持 :material-plus: BitTorrent 支持 - :material-plus: DTLS 支持 + :material-plus: DTLS 支持 + :material-plus: SSH 支持 如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。 #### 支持的协议 -| 网络 | 协议 | 域名 | 客户端 | -|:-------:|:------------:|:-----------:|:----------:| -| TCP | `http` | Host | / | -| TCP | `tls` | Server Name | / | +| 网络 | 协议 | 域名 | 客户端 | +| :-----: | :----------: | :---------: | :-------------: | +| TCP | `http` | Host | / | +| TCP | `tls` | Server Name | / | | UDP | `quic` | Server Name | QUIC 客户端类型 | -| UDP | `stun` | / | / | -| TCP/UDP | `dns` | / | / | -| TCP/UDP | `bittorrent` | / | / | -| UDP | `dtls` | / | / | +| UDP | `stun` | / | / | +| TCP/UDP | `dns` | / | / | +| TCP/UDP | `bittorrent` | / | / | +| UDP | `dtls` | / | / | +| TCP | `SSH` | / | SSH 客户端名称 | -| QUIC 客户端 | 类型 | -|:------------------------:|:----------:| +| QUIC 客户端 | 类型 | +| :----------------------: | :--------: | | Chromium/Cronet | `chrimium` | | Safari/Apple Network API | `safari` | | Firefox / uquic firefox | `firefox` | diff --git a/route/router.go b/route/router.go index 4653e0bc..742f5bd2 100644 --- a/route/router.go +++ b/route/router.go @@ -860,9 +860,10 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), - sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost, + sniff.StreamDomainNameQuery, + sniff.SSH, sniff.BitTorrent, ) if err == nil {