From 3f1fe814ef0168e95875c647ed1b038d302b8786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Wed, 12 Oct 2022 13:37:06 +0800 Subject: [PATCH] Fix sniff fragmented quic client hello --- common/sniff/quic.go | 91 +++++++++++++++++++++++++++------------ common/sniff/quic_test.go | 9 ++++ common/sniff/sniff.go | 21 ++++----- route/router.go | 8 ++-- 4 files changed, 87 insertions(+), 42 deletions(-) diff --git a/common/sniff/quic.go b/common/sniff/quic.go index 6a7df4e6..c18e09a0 100644 --- a/common/sniff/quic.go +++ b/common/sniff/quic.go @@ -24,8 +24,7 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex if err != nil { return nil, err } - - if typeByte&0x80 == 0 || typeByte&0x40 == 0 { + if typeByte&0x40 == 0 { return nil, E.New("bad type byte") } var versionNumber uint32 @@ -145,9 +144,6 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex default: return nil, E.New("bad packet number length") } - if packetNumber != 0 { - return nil, E.New("bad packet number: ", packetNumber) - } extHdrLen := hdrLen + int(packetNumberLength) copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:]) data := newPacket[extHdrLen : int(packetLen)+hdrLen] @@ -172,37 +168,76 @@ func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContex if err != nil { return nil, err } + var frameType byte + var frameLen uint64 + var fragments []struct { + offset uint64 + length uint64 + payload []byte + } decryptedReader := bytes.NewReader(decrypted) - frameType, err := decryptedReader.ReadByte() - if err != nil { - return nil, err - } - for frameType == 0x0 { - // skip padding + for { frameType, err = decryptedReader.ReadByte() - if err != nil { - return nil, err + if err == io.EOF { + break + } + switch frameType { + case 0x0: + continue + case 0x1: + continue + case 0x6: + var offset uint64 + offset, err = qtls.ReadUvarint(decryptedReader) + if err != nil { + return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err + } + var length uint64 + length, err = qtls.ReadUvarint(decryptedReader) + if err != nil { + return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err + } + index := len(decrypted) - decryptedReader.Len() + fragments = append(fragments, struct { + offset uint64 + length uint64 + payload []byte + }{offset, length, decrypted[index : index+int(length)]}) + frameLen += length + _, err = decryptedReader.Seek(int64(length), io.SeekCurrent) + if err != nil { + return nil, err + } + default: + // ignore unknown frame type } - } - if frameType != 0x6 { - // not crypto frame - return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, nil - } - _, err = qtls.ReadUvarint(decryptedReader) - if err != nil { - return nil, err - } - _, err = qtls.ReadUvarint(decryptedReader) - if err != nil { - return nil, err } tlsHdr := make([]byte, 5) tlsHdr[0] = 0x16 binary.BigEndian.PutUint16(tlsHdr[1:], uint16(0x0303)) - binary.BigEndian.PutUint16(tlsHdr[3:], uint16(decryptedReader.Len())) - metadata, err := TLSClientHello(ctx, io.MultiReader(bytes.NewReader(tlsHdr), decryptedReader)) + binary.BigEndian.PutUint16(tlsHdr[3:], uint16(frameLen)) + var index uint64 + var length int + var readers []io.Reader + readers = append(readers, bytes.NewReader(tlsHdr)) +find: + for { + for _, fragment := range fragments { + if fragment.offset == index { + readers = append(readers, bytes.NewReader(fragment.payload)) + index = fragment.offset + fragment.length + length++ + continue find + } + } + if length == len(fragments) { + break + } + return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, E.New("bad fragments") + } + metadata, err := TLSClientHello(ctx, io.MultiReader(readers...)) if err != nil { - return nil, err + return &adapter.InboundContext{Protocol: C.ProtocolQUIC}, err } metadata.Protocol = C.ProtocolQUIC return metadata, nil diff --git a/common/sniff/quic_test.go b/common/sniff/quic_test.go index 028315fa..80719765 100644 --- a/common/sniff/quic_test.go +++ b/common/sniff/quic_test.go @@ -19,6 +19,15 @@ func TestSniffQUICv1(t *testing.T) { require.Equal(t, metadata.Domain, "cloudflare-quic.com") } +func TestSniffQUICFragment(t *testing.T) { + t.Parallel() + pkt, err := hex.DecodeString("cc00000001082e3d5d1b64040c55000044d0ccea69e773f6631c1d18b04ae9ee75fcfc34ef74fa62533c93534338a86f101a05d70e0697fb483063fa85db1c59ccfbda5c35234931d8524d8aac37eaaad649470a67794cd754b23c98695238b8363452333bc8c4858376b4166e001da2006e35cf98a91e11a56419b2786775284942d0f7163982f7c248867d12dd374957481dbc564013ff785e1916195eef671f725908f761099d992d69231336ba81d9e25fe2fa3a6eff4318a6ccf10176fc841a1b315f7b35c5b292266fc869d76ca533e7d14e86d82db2e22eacd350977e47d2e012d8a5891c5aaf2a0f4c2b2dae897c161e5b68cbb4dee952472bdc1e21504b8f02534ec4366ce3f8bf86efc78e0232778fbd554457567112abdcafcf6d4d8fcf35083c25d9495679614aba21696e338c62b585046cc55ba8c09c844361d889a47c3ea703b4e23545a9ab2c0bb369693a9ddfb5daffa85cf80fdd6ad66738664e5b0a551729b4955cff7255afcb04dee88c2f072c9de7400947a1bd9327ac5d012a33000ada021d4c03d249fb017d6ac9200b2f9436beab8183ddfbe2d8aee31ffb7df9e1cc181c1af80c39a89965d18ed12da8e3ebe2ae1fbe4b348f83ba19e3e3d1c9b22bcf03ab6ad9b30fe180623faa291ebad83bcd71d7b57f2f5e2f3b8e81d24fb70b2f2159239e8f21ffafef2747aba47d97ab4081e603c018b10678cf99cab1fb42156a14486fa435153979d7279fd22cd40af7088bfc7eff41af2f4b3c0c8864d0040d74dff427f7bffdb8c278474ea00311326cf4925471a8cf596cb92119f19e0f789490ba9cb77b98015a987d93e0324cf1a38b55109f00c3e6ddc5180fb107bf468323afec9bb49fd6a86418569789d66cafe3b8253c2aebb3af3782c1c54dd560487d031d28e6a6e23e159581bb1d47efc4da3fe1d169f9ffb0ca9ba61af0a38a92fde5bc5e6ec026e8378a6315a7b95abf1d2da790a391306ce74d0baf8e2ce648ca74c487f2c0a76a28a80cdf5bd34316eb607684fe7e6d9e83824a00e07660d0b90e3cddd61ebf10748263474afa88c300549e64ce2e90560bb1a12dee7e9484f729a8a4ee7c5651adb5194b3b3ae38e501567c7dbf36e7bb37a2c20b74655f47f2d9af18e52e9d4c9c9eee8e63745779b8f0b06f3a09d846ba62eb978ad77c85de1ee2fee3fbb4c2d283c73e1ccba56a4658e48a2665d200f7f9342f8e84c2ba490094a4f94feec89e42d2f654f564c2beb2997bafa1fc2c68ad8e160b63587d49abc31b834878d52acfb05fb73d0e059b206162e3c90b40c4bc08407ffcb3c08431895b691a3fea923f1f3b48db75d3e6b91fd319ffe4d486e0e14bd5c6affc838dee63d9e0b80f169b5e6c02c7321dcb20deb2b8e707b60e345a308d505bbf26a93d8f18b39d62632e9a77cbe48b3b32eb8819d6311a49820d40f5acbf0273c91c36b2269a03e72ee64df3dfb10ddefe73c64ef60870b2b77bd99dea655f5fe791b538a929a14d99f6d69685d72431ea5f0f4b27a044f2f575ab474fcc3857895934de1ca2581798eaef2c17fe5aaf2e6add97fa32997c7026f15c1b1ad0e6043ae506027a7c0242546fdc851cca39a204e56879f2cef838be8ec66e0f2292f8c862e06f810eb9b80c7a467ce6e90155206352c7f82b1173ba3b98d35bb72c259a60db20dd1a43fe6d7aef0265e6eaa5caafd9b64b448ff745a2046acbdb65cf2a5007809808a4828dc99097feedc734c236260c584") + require.NoError(t, err) + metadata, err := sniff.QUICClientHello(context.Background(), pkt) + require.NoError(t, err) + require.Equal(t, metadata.Domain, "cloudflare-quic.com") +} + func FuzzSniffQUIC(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { sniff.QUICClientHello(context.Background(), data) diff --git a/common/sniff/sniff.go b/common/sniff/sniff.go index 5a39d900..8c7a0d3e 100644 --- a/common/sniff/sniff.go +++ b/common/sniff/sniff.go @@ -5,7 +5,6 @@ import ( "context" "io" "net" - "os" "time" "github.com/sagernet/sing-box/adapter" @@ -33,23 +32,25 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout return nil, err } var metadata *adapter.InboundContext + var errors []error for _, sniffer := range sniffers { metadata, err = sniffer(ctx, bytes.NewReader(buffer.Bytes())) - if err != nil { - continue + if metadata != nil { + return metadata, nil } - return metadata, nil + errors = append(errors, err) } - return nil, os.ErrInvalid + return nil, E.Errors(errors...) } func PeekPacket(ctx context.Context, packet []byte, sniffers ...PacketSniffer) (*adapter.InboundContext, error) { + var errors []error for _, sniffer := range sniffers { - sniffMetadata, err := sniffer(ctx, packet) - if err != nil { - continue + metadata, err := sniffer(ctx, packet) + if metadata != nil { + return metadata, nil } - return sniffMetadata, nil + errors = append(errors, err) } - return nil, os.ErrInvalid + return nil, E.Errors(errors...) } diff --git a/route/router.go b/route/router.go index f0814e12..4cc725a7 100644 --- a/route/router.go +++ b/route/router.go @@ -554,8 +554,8 @@ 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) - if err == nil { + sniffMetadata, _ := sniff.PeekStream(ctx, conn, buffer, time.Duration(metadata.InboundOptions.SniffTimeout), sniff.StreamDomainNameQuery, sniff.TLSClientHello, sniff.HTTPHost) + if sniffMetadata != nil { metadata.Protocol = sniffMetadata.Protocol metadata.Domain = sniffMetadata.Domain if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) { @@ -636,8 +636,8 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m buffer.Release() return err } - sniffMetadata, err := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage) - if err == nil { + sniffMetadata, _ := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.DomainNameQuery, sniff.QUICClientHello, sniff.STUNMessage) + if sniffMetadata != nil { metadata.Protocol = sniffMetadata.Protocol metadata.Domain = sniffMetadata.Domain if metadata.InboundOptions.SniffOverrideDestination && M.IsDomainName(metadata.Domain) {