From 683bda983805f1fda08e6e96d5cf0c4d6cf0b179 Mon Sep 17 00:00:00 2001 From: PuerNya Date: Thu, 31 Aug 2023 12:08:43 +0800 Subject: [PATCH] Add bittorrent protocol sniffer support --- common/sniff/bittorrent.go | 86 ++++++++++++++++++++++++++++ constant/protocol.go | 11 ++-- docs/configuration/route/sniff.md | 15 ++--- docs/configuration/route/sniff.zh.md | 15 ++--- route/router.go | 10 ++-- 5 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 common/sniff/bittorrent.go diff --git a/common/sniff/bittorrent.go b/common/sniff/bittorrent.go new file mode 100644 index 00000000..78ae56d6 --- /dev/null +++ b/common/sniff/bittorrent.go @@ -0,0 +1,86 @@ +package sniff + +import ( + "bytes" + "context" + "encoding/binary" + "io" + "io/ioutil" + "math" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" +) + +func BittorrentTCPMessage(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) { + packet, err := ioutil.ReadAll(reader) + if err != nil { + return nil, os.ErrInvalid + } + if len(packet) < 20 { + return nil, os.ErrInvalid + } + if packet[0] != 19 || string(packet[1:20]) != "BitTorrent protocol" { + return nil, os.ErrInvalid + } + return &adapter.InboundContext{Protocol: C.ProtocolBittorrent}, nil +} + +func BittorrentUDPMessage(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { + pLen := len(packet) + if pLen < 20 { + return nil, os.ErrInvalid + } + + buffer := bytes.NewReader(packet) + + var typeAndVersion uint8 + + if binary.Read(buffer, binary.BigEndian, &typeAndVersion) != nil { + return nil, os.ErrInvalid + } else if packet[0]>>4&0xF > 4 || packet[0]&0xF != 1 { + return nil, os.ErrInvalid + } + + var extension uint8 + + if binary.Read(buffer, binary.BigEndian, &extension) != nil { + return nil, os.ErrInvalid + } else if extension != 0 && extension != 1 { + return nil, os.ErrInvalid + } + + for extension != 0 { + if extension != 1 { + return nil, os.ErrInvalid + } + if binary.Read(buffer, binary.BigEndian, &extension) != nil { + return nil, os.ErrInvalid + } + + var length uint8 + + if err := binary.Read(buffer, binary.BigEndian, &length); err != nil { + return nil, os.ErrInvalid + } + if int32(pLen) >= int32(length) { + return nil, os.ErrInvalid + } + } + + if int32(pLen) >= int32(2) { + return nil, os.ErrInvalid + } + + var timestamp uint32 + + if err := binary.Read(buffer, binary.BigEndian, ×tamp); err != nil { + return nil, os.ErrInvalid + } + if math.Abs(float64(time.Now().UnixMicro()-int64(timestamp))) > float64(24*time.Hour) { + return nil, os.ErrInvalid + } + return &adapter.InboundContext{Protocol: C.ProtocolBittorrent}, nil +} diff --git a/constant/protocol.go b/constant/protocol.go index 810c79ec..f2c9bce6 100644 --- a/constant/protocol.go +++ b/constant/protocol.go @@ -1,9 +1,10 @@ package constant const ( - ProtocolTLS = "tls" - ProtocolHTTP = "http" - ProtocolQUIC = "quic" - ProtocolDNS = "dns" - ProtocolSTUN = "stun" + ProtocolTLS = "tls" + ProtocolHTTP = "http" + ProtocolQUIC = "quic" + ProtocolDNS = "dns" + ProtocolSTUN = "stun" + ProtocolBittorrent = "bittorrent" ) diff --git a/docs/configuration/route/sniff.md b/docs/configuration/route/sniff.md index 2ba2c251..094869ac 100644 --- a/docs/configuration/route/sniff.md +++ b/docs/configuration/route/sniff.md @@ -2,10 +2,11 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c #### Supported Protocols -| Network | Protocol | Domain Name | -|:-------:|:--------:|:-----------:| -| TCP | HTTP | Host | -| TCP | TLS | Server Name | -| UDP | QUIC | Server Name | -| UDP | STUN | / | -| TCP/UDP | DNS | / | \ No newline at end of file +| Network | Protocol | Domain Name | +|:-------:|:----------:|:-----------:| +| TCP | HTTP | Host | +| TCP | TLS | Server Name | +| UDP | QUIC | Server Name | +| UDP | STUN | / | +| TCP/UDP | DNS | / | +| TCP/UDP | Bittorrent | / | \ No newline at end of file diff --git a/docs/configuration/route/sniff.zh.md b/docs/configuration/route/sniff.zh.md index c3cdcc4e..89c0ffd5 100644 --- a/docs/configuration/route/sniff.zh.md +++ b/docs/configuration/route/sniff.zh.md @@ -2,10 +2,11 @@ #### 支持的协议 -| 网络 | 协议 | 域名 | -|:-------:|:----:|:-----------:| -| TCP | HTTP | Host | -| TCP | TLS | Server Name | -| UDP | QUIC | Server Name | -| UDP | STUN | / | -| TCP/UDP | DNS | / | \ No newline at end of file +| 网络 | 协议 | 域名 | +|:-------:|:----------:|:-----------:| +| TCP | HTTP | Host | +| TCP | TLS | Server Name | +| UDP | QUIC | Server Name | +| UDP | STUN | / | +| TCP/UDP | DNS | / | +| TCP/UDP | Bittorrent | / | \ No newline at end of file diff --git a/route/router.go b/route/router.go index ff9224ac..1bbcc64a 100644 --- a/route/router.go +++ b/route/router.go @@ -26,9 +26,9 @@ import ( "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/transport/fakeip" - "github.com/sagernet/sing-dns" - "github.com/sagernet/sing-tun" - "github.com/sagernet/sing-vmess" + dns "github.com/sagernet/sing-dns" + tun "github.com/sagernet/sing-tun" + vmess "github.com/sagernet/sing-vmess" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" @@ -652,7 +652,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad if metadata.InboundOptions.SniffEnabled { buffer := buf.NewPacket() buffer.FullReset() - sniffMetadata, err := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost) + sniffMetadata, err := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost, sniff.BittorrentTCPMessage) if sniffMetadata != nil { metadata.Protocol = sniffMetadata.Protocol metadata.Domain = sniffMetadata.Domain @@ -771,7 +771,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m metadata.Destination = destination } if metadata.InboundOptions.SniffEnabled { - sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage) + sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage, sniff.BittorrentUDPMessage) if sniffMetadata != nil { metadata.Protocol = sniffMetadata.Protocol metadata.Domain = sniffMetadata.Domain