mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-25 18:11:28 +00:00
Add domain sniffer
This commit is contained in:
parent
2d9203ee74
commit
86a38a1c7e
|
@ -20,7 +20,9 @@ type InboundContext struct {
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
|
SniffEnabled bool
|
||||||
|
SniffOverrideDestination bool
|
||||||
|
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
// ProcessPath string
|
|
||||||
}
|
}
|
||||||
|
|
58
common/sniff/dns.go
Normal file
58
common/sniff/dns.go
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
|
"golang.org/x/net/dns/dnsmessage"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StreamDomainNameQuery(readCtx context.Context, reader io.Reader) (*adapter.InboundContext, error) {
|
||||||
|
var length uint16
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if length > 512 {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(int(length))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
|
||||||
|
readCtx, cancel := context.WithTimeout(readCtx, time.Millisecond*100)
|
||||||
|
err = task.Run(readCtx, func() error {
|
||||||
|
return common.Error(buffer.ReadFullFrom(reader, buffer.FreeLen()))
|
||||||
|
})
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return DomainNameQuery(readCtx, buffer.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func DomainNameQuery(ctx context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||||
|
var parser dnsmessage.Parser
|
||||||
|
_, err := parser.Start(packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
question, err := parser.Question()
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
domain := question.Name.String()
|
||||||
|
if question.Class == dnsmessage.ClassINET && (question.Type == dnsmessage.TypeA || question.Type == dnsmessage.TypeAAAA) && IsDomainName(domain) {
|
||||||
|
return &adapter.InboundContext{Protocol: C.ProtocolDNS, Domain: domain}, nil
|
||||||
|
}
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
6
common/sniff/domain.go
Normal file
6
common/sniff/domain.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package sniff
|
||||||
|
|
||||||
|
import _ "unsafe" // for linkname
|
||||||
|
|
||||||
|
//go:linkname IsDomainName net.isDomainName
|
||||||
|
func IsDomainName(domain string) bool
|
19
common/sniff/http.go
Normal file
19
common/sniff/http.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
std_bufio "bufio"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/protocol/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) {
|
||||||
|
request, err := http.ReadRequest(std_bufio.NewReader(reader))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: request.Host}, nil
|
||||||
|
}
|
148
common/sniff/internal/qtls/qtls.go
Normal file
148
common/sniff/internal/qtls/qtls.go
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
package qtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VersionDraft29 = 0xff00001d
|
||||||
|
Version1 = 0x1
|
||||||
|
Version2 = 0x709a50c4
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
||||||
|
SaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
||||||
|
SaltV2 = []byte{0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HKDFLabelKeyV1 = "quic key"
|
||||||
|
HKDFLabelKeyV2 = "quicv2 key"
|
||||||
|
HKDFLabelIVV1 = "quic iv"
|
||||||
|
HKDFLabelIVV2 = "quicv2 iv"
|
||||||
|
HKDFLabelHeaderProtectionV1 = "quic hp"
|
||||||
|
HKDFLabelHeaderProtectionV2 = "quicv2 hp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AEADAESGCMTLS13(key, nonceMask []byte) cipher.AEAD {
|
||||||
|
if len(nonceMask) != 12 {
|
||||||
|
panic("tls: internal error: wrong nonce length")
|
||||||
|
}
|
||||||
|
aes, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
aead, err := cipher.NewGCM(aes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := &xorNonceAEAD{aead: aead}
|
||||||
|
copy(ret.nonceMask[:], nonceMask)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
type xorNonceAEAD struct {
|
||||||
|
nonceMask [12]byte
|
||||||
|
aead cipher.AEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *xorNonceAEAD) NonceSize() int { return 8 } // 64-bit sequence number
|
||||||
|
func (f *xorNonceAEAD) Overhead() int { return f.aead.Overhead() }
|
||||||
|
func (f *xorNonceAEAD) explicitNonceLen() int { return 0 }
|
||||||
|
|
||||||
|
func (f *xorNonceAEAD) Seal(out, nonce, plaintext, additionalData []byte) []byte {
|
||||||
|
for i, b := range nonce {
|
||||||
|
f.nonceMask[4+i] ^= b
|
||||||
|
}
|
||||||
|
result := f.aead.Seal(out, f.nonceMask[:], plaintext, additionalData)
|
||||||
|
for i, b := range nonce {
|
||||||
|
f.nonceMask[4+i] ^= b
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *xorNonceAEAD) Open(out, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||||
|
for i, b := range nonce {
|
||||||
|
f.nonceMask[4+i] ^= b
|
||||||
|
}
|
||||||
|
result, err := f.aead.Open(out, f.nonceMask[:], ciphertext, additionalData)
|
||||||
|
for i, b := range nonce {
|
||||||
|
f.nonceMask[4+i] ^= b
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func HKDFExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
||||||
|
b := make([]byte, 3, 3+6+len(label)+1+len(context))
|
||||||
|
binary.BigEndian.PutUint16(b, uint16(length))
|
||||||
|
b[2] = uint8(6 + len(label))
|
||||||
|
b = append(b, []byte("tls13 ")...)
|
||||||
|
b = append(b, []byte(label)...)
|
||||||
|
b = b[:3+6+len(label)+1]
|
||||||
|
b[3+6+len(label)] = uint8(len(context))
|
||||||
|
b = append(b, context...)
|
||||||
|
out := make([]byte, length)
|
||||||
|
n, err := hkdf.Expand(hash.New, secret, b).Read(out)
|
||||||
|
if err != nil || n != length {
|
||||||
|
panic("quic: HKDF-Expand-Label invocation failed unexpectedly")
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadUvarint(r io.ByteReader) (uint64, error) {
|
||||||
|
firstByte, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// the first two bits of the first byte encode the length
|
||||||
|
len := 1 << ((firstByte & 0xc0) >> 6)
|
||||||
|
b1 := firstByte & (0xff - 0xc0)
|
||||||
|
if len == 1 {
|
||||||
|
return uint64(b1), nil
|
||||||
|
}
|
||||||
|
b2, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len == 2 {
|
||||||
|
return uint64(b2) + uint64(b1)<<8, nil
|
||||||
|
}
|
||||||
|
b3, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
b4, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if len == 4 {
|
||||||
|
return uint64(b4) + uint64(b3)<<8 + uint64(b2)<<16 + uint64(b1)<<24, nil
|
||||||
|
}
|
||||||
|
b5, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
b6, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
b7, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
b8, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint64(b8) + uint64(b7)<<8 + uint64(b6)<<16 + uint64(b5)<<24 + uint64(b4)<<32 + uint64(b3)<<40 + uint64(b2)<<48 + uint64(b1)<<56, nil
|
||||||
|
}
|
187
common/sniff/quic.go
Normal file
187
common/sniff/quic.go
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"crypto/aes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/sniff/internal/qtls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func QUICClientHello(ctx context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||||
|
reader := bytes.NewReader(packet)
|
||||||
|
|
||||||
|
typeByte, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeByte&0x80 == 0 || typeByte&0x40 == 0 {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
var versionNumber uint32
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &versionNumber)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if versionNumber != qtls.VersionDraft29 && versionNumber != qtls.Version1 && versionNumber != qtls.Version2 {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
if (typeByte&0x30)>>4 == 0x0 {
|
||||||
|
} else if (typeByte&0x30)>>4 != 0x01 {
|
||||||
|
// 0-rtt
|
||||||
|
} else {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
destConnIDLen, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
destConnID := make([]byte, destConnIDLen)
|
||||||
|
_, err = io.ReadFull(reader, destConnID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
srcConnIDLen, err := reader.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyN(io.Discard, reader, int64(srcConnIDLen))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenLen, err := qtls.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyN(io.Discard, reader, int64(tokenLen))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
packetLen, err := qtls.ReadUvarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hdrLen := int(reader.Size()) - reader.Len()
|
||||||
|
if hdrLen != len(packet)-int(packetLen) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyN(io.Discard, reader, 4)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pnBytes := make([]byte, aes.BlockSize)
|
||||||
|
_, err = io.ReadFull(reader, pnBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var salt []byte
|
||||||
|
switch versionNumber {
|
||||||
|
case qtls.Version1:
|
||||||
|
salt = qtls.SaltV1
|
||||||
|
case qtls.Version2:
|
||||||
|
salt = qtls.SaltV2
|
||||||
|
default:
|
||||||
|
salt = qtls.SaltOld
|
||||||
|
}
|
||||||
|
var hkdfHeaderProtectionLabel string
|
||||||
|
switch versionNumber {
|
||||||
|
case qtls.Version2:
|
||||||
|
hkdfHeaderProtectionLabel = qtls.HKDFLabelHeaderProtectionV2
|
||||||
|
default:
|
||||||
|
hkdfHeaderProtectionLabel = qtls.HKDFLabelHeaderProtectionV1
|
||||||
|
}
|
||||||
|
initialSecret := hkdf.Extract(crypto.SHA256.New, destConnID, salt)
|
||||||
|
secret := qtls.HKDFExpandLabel(crypto.SHA256, initialSecret, []byte{}, "client in", crypto.SHA256.Size())
|
||||||
|
hpKey := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, hkdfHeaderProtectionLabel, 16)
|
||||||
|
block, err := aes.NewCipher(hpKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mask := make([]byte, aes.BlockSize)
|
||||||
|
block.Encrypt(mask, pnBytes)
|
||||||
|
newPacket := make([]byte, len(packet))
|
||||||
|
copy(newPacket, packet)
|
||||||
|
newPacket[0] ^= mask[0] & 0xf
|
||||||
|
for i := range newPacket[hdrLen : hdrLen+4] {
|
||||||
|
newPacket[hdrLen+i] ^= mask[i+1]
|
||||||
|
}
|
||||||
|
packetNumberLength := newPacket[0]&0x3 + 1
|
||||||
|
if packetNumberLength != 1 {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
packetNumber := newPacket[hdrLen]
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if packetNumber != 0 {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
extHdrLen := hdrLen + int(packetNumberLength)
|
||||||
|
copy(newPacket[extHdrLen:hdrLen+4], packet[extHdrLen:])
|
||||||
|
data := newPacket[extHdrLen : int(packetLen)+hdrLen]
|
||||||
|
|
||||||
|
var keyLabel string
|
||||||
|
var ivLabel string
|
||||||
|
switch versionNumber {
|
||||||
|
case qtls.Version2:
|
||||||
|
keyLabel = qtls.HKDFLabelKeyV2
|
||||||
|
ivLabel = qtls.HKDFLabelIVV2
|
||||||
|
default:
|
||||||
|
keyLabel = qtls.HKDFLabelKeyV1
|
||||||
|
ivLabel = qtls.HKDFLabelIVV1
|
||||||
|
}
|
||||||
|
|
||||||
|
key := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, keyLabel, 16)
|
||||||
|
iv := qtls.HKDFExpandLabel(crypto.SHA256, secret, []byte{}, ivLabel, 12)
|
||||||
|
cipher := qtls.AEADAESGCMTLS13(key, iv)
|
||||||
|
nonce := make([]byte, int32(cipher.NonceSize()))
|
||||||
|
binary.BigEndian.PutUint64(nonce[len(nonce)-8:], uint64(packetNumber))
|
||||||
|
decrypted, err := cipher.Open(newPacket[extHdrLen:extHdrLen], nonce, data, newPacket[:extHdrLen])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decryptedReader := bytes.NewReader(decrypted)
|
||||||
|
frameType, err := decryptedReader.ReadByte()
|
||||||
|
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))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
metadata.Protocol = C.ProtocolQUIC
|
||||||
|
return metadata, nil
|
||||||
|
}
|
23
common/sniff/quic_test.go
Normal file
23
common/sniff/quic_test.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffQUICv1(t *testing.T) {
|
||||||
|
pkt, err := hex.DecodeString("cc0000000108d2dc7bad02241f5003796e71004215a71bfcb05159416c724be418537389acdd9a4047306283dcb4d7a9cad5cc06322042d204da67a8dbaa328ab476bb428b48fd001501863afd203f8d4ef085629d664f1a734a65969a47e4a63d4e01a21f18c1d90db0c027180906dc135f9ae421bb8617314c8d54c175fef3d3383d310d0916ebcbd6eed9329befbbb109d8fd4af1d2cf9d6adce8e6c1260a7f8256e273e326da0aa7cc148d76e7a08489dc9d52ade89c027cbc3491ada46417c2c04e2ca768e9a7dd6aa00c594e48b678927325da796817693499bb727050cb3baf3d3291a397c3a8d868e8ec7b8f7295e347455c9dadbe2252ae917ac793d958c7fb8a3d2cdb34e3891eb4286f18617556ff7216dd60256aa5b1d11ff4753459fc5f9dedf11d483a26a0835dc6cd50e1c1f54f86e8f1e502821183cd874f6447a74e818bf3445c7795acf4559d1c1fac474911d2ead5c8d23e4aa4f67afb66efe305a30a0b5d825679b31ddc186cbea936535795c7e8c378c87b8c5adc065154d15bae8f85ac8fec2da40c3aa623b682a065440831555011d7647cde44446a0fb4cf5892f2c088ae1920643094be72e3c499fe8d265caf939e8ab607a5b9317917d2a32a812e8a0e6a2f84721bbb5984ffd242838f705d13f4cfb249bc6a5c80d58ac2595edf56648ec3fe21d787573c253a79805252d6d81e26d367d4ff29ef66b5fe8992086af7bada8cad10b82a7c0dc406c5b6d0c5ec3c583e767f759ce08cad6c3c8f91e5a8")
|
||||||
|
require.NoError(t, err)
|
||||||
|
metadata, err := 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) {
|
||||||
|
QUICClientHello(context.Background(), data)
|
||||||
|
})
|
||||||
|
}
|
36
common/sniff/sniff.go
Normal file
36
common/sniff/sniff.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
StreamSniffer = func(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error)
|
||||||
|
PacketSniffer = func(ctx context.Context, packet []byte) (*adapter.InboundContext, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
func PeekStream(ctx context.Context, reader io.Reader, sniffers ...StreamSniffer) (*adapter.InboundContext, error) {
|
||||||
|
for _, sniffer := range sniffers {
|
||||||
|
sniffMetadata, err := sniffer(ctx, reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sniffMetadata, nil
|
||||||
|
}
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func PeekPacket(ctx context.Context, packet []byte, sniffers ...PacketSniffer) (*adapter.InboundContext, error) {
|
||||||
|
for _, sniffer := range sniffers {
|
||||||
|
sniffMetadata, err := sniffer(ctx, packet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sniffMetadata, nil
|
||||||
|
}
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
28
common/sniff/tls.go
Normal file
28
common/sniff/tls.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TLSClientHello(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) {
|
||||||
|
var clientHello *tls.ClientHelloInfo
|
||||||
|
err := tls.Server(bufio.NewReadOnlyConn(reader), &tls.Config{
|
||||||
|
GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||||
|
clientHello = argHello
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
}).HandshakeContext(ctx)
|
||||||
|
if clientHello != nil {
|
||||||
|
return &adapter.InboundContext{Protocol: C.ProtocolTLS, Domain: clientHello.ServerName}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Packet() {
|
||||||
|
}
|
8
constant/protocol.go
Normal file
8
constant/protocol.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProtocolTLS = "tls"
|
||||||
|
ProtocolHTTP = "http"
|
||||||
|
ProtocolQUIC = "quic"
|
||||||
|
ProtocolDNS = "dns"
|
||||||
|
)
|
14
go.mod
14
go.mod
|
@ -6,23 +6,25 @@ require (
|
||||||
github.com/database64128/tfo-go v1.0.4
|
github.com/database64128/tfo-go v1.0.4
|
||||||
github.com/goccy/go-json v0.9.8
|
github.com/goccy/go-json v0.9.8
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/oschwald/geoip2-golang v1.7.0
|
github.com/oschwald/maxminddb-golang v1.9.0
|
||||||
github.com/sagernet/sing v0.0.0-20220705005401-57d12d875b7a
|
github.com/sagernet/sing v0.0.0-20220706042103-9cd9268a7e3a
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
github.com/spf13/cobra v1.5.0
|
github.com/spf13/cobra v1.5.0
|
||||||
github.com/stretchr/testify v1.7.1
|
github.com/stretchr/testify v1.8.0
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||||
github.com/oschwald/maxminddb-golang v1.9.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect
|
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.1.7 // indirect
|
lukechampine.com/blake3 v1.1.7 // indirect
|
||||||
)
|
)
|
||||||
|
|
23
go.sum
23
go.sum
|
@ -11,17 +11,20 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8=
|
|
||||||
github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
|
|
||||||
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
|
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
|
||||||
github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm82Cp5HyvYbt8K3zLY=
|
github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm82Cp5HyvYbt8K3zLY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagernet/sing v0.0.0-20220705005401-57d12d875b7a h1:FhrHCkox9scuTzcT5DDh6flVLFuqU+QSk3VONd41I+o=
|
github.com/sagernet/sing v0.0.0-20220706042103-9cd9268a7e3a h1:QBAfegXTXY1sOZqxKrX3fQVzmvLESBlsiQZbmixSP/U=
|
||||||
github.com/sagernet/sing v0.0.0-20220705005401-57d12d875b7a/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
|
github.com/sagernet/sing v0.0.0-20220706042103-9cd9268a7e3a/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw=
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649/go.mod h1:MuyT+9fEPjvauAv0fSE0a6Q+l0Tv2ZrAafTkYfnxBFw=
|
||||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
|
@ -31,18 +34,24 @@ github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJ
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
|
||||||
|
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8=
|
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8=
|
||||||
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||||
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
||||||
|
|
|
@ -132,6 +132,8 @@ func (a *myInboundAdapter) loopTCPIn() {
|
||||||
ctx := log.ContextWithID(a.ctx)
|
ctx := log.ContextWithID(a.ctx)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
metadata.Inbound = a.tag
|
metadata.Inbound = a.tag
|
||||||
|
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
||||||
|
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
||||||
metadata.Network = C.NetworkTCP
|
metadata.Network = C.NetworkTCP
|
||||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
|
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr())
|
||||||
a.logger.WithContext(ctx).Info("inbound connection from ", metadata.Source)
|
a.logger.WithContext(ctx).Info("inbound connection from ", metadata.Source)
|
||||||
|
@ -161,6 +163,8 @@ func (a *myInboundAdapter) loopUDPIn() {
|
||||||
buffer.Truncate(n)
|
buffer.Truncate(n)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
metadata.Inbound = a.tag
|
metadata.Inbound = a.tag
|
||||||
|
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
||||||
|
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
||||||
metadata.Network = C.NetworkUDP
|
metadata.Network = C.NetworkUDP
|
||||||
metadata.Source = M.SocksaddrFromNetIP(addr)
|
metadata.Source = M.SocksaddrFromNetIP(addr)
|
||||||
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
|
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
|
||||||
|
@ -183,6 +187,8 @@ func (a *myInboundAdapter) loopUDPInThreadSafe() {
|
||||||
buffer.Truncate(n)
|
buffer.Truncate(n)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
metadata.Inbound = a.tag
|
metadata.Inbound = a.tag
|
||||||
|
metadata.SniffEnabled = a.listenOptions.SniffEnabled
|
||||||
|
metadata.SniffOverrideDestination = a.listenOptions.SniffOverrideDestination
|
||||||
metadata.Network = C.NetworkUDP
|
metadata.Network = C.NetworkUDP
|
||||||
metadata.Source = M.SocksaddrFromNetIP(addr)
|
metadata.Source = M.SocksaddrFromNetIP(addr)
|
||||||
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
|
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
|
||||||
|
|
|
@ -77,10 +77,12 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenOptions struct {
|
type ListenOptions struct {
|
||||||
Listen ListenAddress `json:"listen"`
|
Listen ListenAddress `json:"listen"`
|
||||||
Port uint16 `json:"listen_port"`
|
Port uint16 `json:"listen_port"`
|
||||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||||
UDPTimeout int64 `json:"udp_timeout,omitempty"`
|
UDPTimeout int64 `json:"udp_timeout,omitempty"`
|
||||||
|
SniffEnabled bool `json:"sniff,omitempty"`
|
||||||
|
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SimpleInboundOptions struct {
|
type SimpleInboundOptions struct {
|
||||||
|
|
|
@ -12,10 +12,13 @@ import (
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
"github.com/sagernet/sing-box/common/geosite"
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
@ -188,6 +191,29 @@ func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
if metadata.SniffEnabled {
|
||||||
|
_buffer := buf.StackNew()
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
reader := io.TeeReader(conn, buffer)
|
||||||
|
sniffMetadata, err := sniff.PeekStream(ctx, reader, sniff.TLSClientHello, sniff.HTTPHost)
|
||||||
|
if err == nil {
|
||||||
|
metadata.Protocol = sniffMetadata.Protocol
|
||||||
|
metadata.Domain = sniffMetadata.Domain
|
||||||
|
if metadata.SniffOverrideDestination && sniff.IsDomainName(metadata.Domain) {
|
||||||
|
metadata.Destination.Fqdn = metadata.Domain
|
||||||
|
}
|
||||||
|
if metadata.Domain != "" {
|
||||||
|
r.logger.WithContext(ctx).Info("sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
|
||||||
|
} else {
|
||||||
|
r.logger.WithContext(ctx).Info("sniffed protocol: ", metadata.Protocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !buffer.IsEmpty() {
|
||||||
|
conn = bufio.NewCachedConn(conn, buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
detour := r.match(ctx, metadata, r.defaultOutboundForConnection)
|
detour := r.match(ctx, metadata, r.defaultOutboundForConnection)
|
||||||
if !common.Contains(detour.Network(), C.NetworkTCP) {
|
if !common.Contains(detour.Network(), C.NetworkTCP) {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
@ -197,6 +223,31 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
if metadata.SniffEnabled {
|
||||||
|
_buffer := buf.StackNewPacket()
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
_, err := conn.ReadPacket(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sniffMetadata, err := sniff.PeekPacket(ctx, buffer.Bytes(), sniff.QUICClientHello)
|
||||||
|
originDestination := metadata.Destination
|
||||||
|
if err == nil {
|
||||||
|
metadata.Protocol = sniffMetadata.Protocol
|
||||||
|
metadata.Domain = sniffMetadata.Domain
|
||||||
|
if metadata.SniffOverrideDestination && sniff.IsDomainName(metadata.Domain) {
|
||||||
|
metadata.Destination.Fqdn = metadata.Domain
|
||||||
|
}
|
||||||
|
if metadata.Domain != "" {
|
||||||
|
r.logger.WithContext(ctx).Info("sniffed protocol: ", metadata.Protocol, ", domain: ", metadata.Domain)
|
||||||
|
} else {
|
||||||
|
r.logger.WithContext(ctx).Info("sniffed protocol: ", metadata.Protocol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn = bufio.NewCachedPacketConn(conn, buffer, originDestination)
|
||||||
|
}
|
||||||
detour := r.match(ctx, metadata, r.defaultOutboundForPacketConnection)
|
detour := r.match(ctx, metadata, r.defaultOutboundForPacketConnection)
|
||||||
if !common.Contains(detour.Network(), C.NetworkUDP) {
|
if !common.Contains(detour.Network(), C.NetworkUDP) {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
|
Loading…
Reference in a new issue