Update On Thu Mar 13 19:35:42 CET 2025

This commit is contained in:
github-action[bot]
2025-03-13 19:35:42 +01:00
parent eca238082b
commit 20f9665606
149 changed files with 7237 additions and 6183 deletions

1
.github/update.log vendored
View File

@@ -940,3 +940,4 @@ Update On Sun Mar 9 19:28:11 CET 2025
Update On Mon Mar 10 19:34:25 CET 2025 Update On Mon Mar 10 19:34:25 CET 2025
Update On Tue Mar 11 19:37:59 CET 2025 Update On Tue Mar 11 19:37:59 CET 2025
Update On Wed Mar 12 19:36:26 CET 2025 Update On Wed Mar 12 19:36:26 CET 2025
Update On Thu Mar 13 19:35:33 CET 2025

View File

@@ -13,6 +13,7 @@ import (
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
gost "github.com/metacubex/mihomo/transport/gost-plugin"
"github.com/metacubex/mihomo/transport/restls" "github.com/metacubex/mihomo/transport/restls"
obfs "github.com/metacubex/mihomo/transport/simple-obfs" obfs "github.com/metacubex/mihomo/transport/simple-obfs"
shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls" shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls"
@@ -34,6 +35,7 @@ type ShadowSocks struct {
obfsMode string obfsMode string
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
gostOption *gost.Option
shadowTLSOption *shadowtls.ShadowTLSOption shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restlsC.Config restlsConfig *restlsC.Config
} }
@@ -71,6 +73,17 @@ type v2rayObfsOption struct {
V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"` V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"`
} }
type gostObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
}
type shadowTLSOption struct { type shadowTLSOption struct {
Password string `obfs:"password"` Password string `obfs:"password"`
Host string `obfs:"host"` Host string `obfs:"host"`
@@ -97,7 +110,13 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port) c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket": case "websocket":
var err error var err error
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption) if ss.v2rayOption != nil {
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
} else if ss.gostOption != nil {
c, err = gost.NewGostWebsocket(ctx, c, ss.gostOption)
} else {
return nil, fmt.Errorf("plugin options is required")
}
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
@@ -240,6 +259,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
} }
var v2rayOption *v2rayObfs.Option var v2rayOption *v2rayObfs.Option
var gostOption *gost.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restlsC.Config var restlsConfig *restlsC.Config
@@ -281,6 +301,28 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.Fingerprint = opts.Fingerprint v2rayOption.Fingerprint = opts.Fingerprint
} }
} else if option.Plugin == "gost-plugin" {
opts := gostObfsOption{Host: "bing.com", Mux: true}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize gost-plugin error: %w", addr, err)
}
if opts.Mode != "websocket" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
}
obfsMode = opts.Mode
gostOption = &gost.Option{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
Mux: opts.Mux,
}
if opts.TLS {
gostOption.TLS = true
gostOption.SkipCertVerify = opts.SkipCertVerify
gostOption.Fingerprint = opts.Fingerprint
}
} else if option.Plugin == shadowtls.Mode { } else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode obfsMode = shadowtls.Mode
opt := &shadowTLSOption{ opt := &shadowTLSOption{
@@ -336,6 +378,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
option: &option, option: &option,
obfsMode: obfsMode, obfsMode: obfsMode,
v2rayOption: v2rayOption, v2rayOption: v2rayOption,
gostOption: gostOption,
obfsOption: obfsOption, obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt, shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig, restlsConfig: restlsConfig,

View File

@@ -60,28 +60,35 @@ func (sd *Dispatcher) forceSniff(metadata *C.Metadata) bool {
return false return false
} }
func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter) bool { // UDPSniff is called when a UDP NAT is created and passed the first initialization packet.
// It may return a wrapped packetSender if the sniffer process needs to wait for multiple packets.
// This function must be non-blocking, and any blocking operations should be done in the wrapped packetSender.
func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter, packetSender C.PacketSender) C.PacketSender {
metadata := packet.Metadata() metadata := packet.Metadata()
if sd.shouldOverride(metadata) { if sd.shouldOverride(metadata) {
for sniffer, config := range sd.sniffers { for current, config := range sd.sniffers {
if sniffer.SupportNetwork() == C.UDP || sniffer.SupportNetwork() == C.ALLNet { if current.SupportNetwork() == C.UDP || current.SupportNetwork() == C.ALLNet {
inWhitelist := sniffer.SupportPort(metadata.DstPort) inWhitelist := current.SupportPort(metadata.DstPort)
overrideDest := config.OverrideDest overrideDest := config.OverrideDest
if inWhitelist { if inWhitelist {
host, err := sniffer.SniffData(packet.Data()) if wrapable, ok := current.(sniffer.MultiPacketSniffer); ok {
return wrapable.WrapperSender(packetSender, overrideDest)
}
host, err := current.SniffData(packet.Data())
if err != nil { if err != nil {
continue continue
} }
sd.replaceDomain(metadata, host, overrideDest) replaceDomain(metadata, host, overrideDest)
return true return packetSender
} }
} }
} }
} }
return false return packetSender
} }
// TCPSniff returns true if the connection is sniffed to have a domain // TCPSniff returns true if the connection is sniffed to have a domain
@@ -130,13 +137,13 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
sd.skipList.Delete(dst) sd.skipList.Delete(dst)
sd.replaceDomain(metadata, host, overrideDest) replaceDomain(metadata, host, overrideDest)
return true return true
} }
return false return false
} }
func (sd *Dispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) { func replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
metadata.SniffHost = host metadata.SniffHost = host
if overrideDest { if overrideDest {
log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]", log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]",

View File

@@ -7,10 +7,14 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"sync"
"time"
"github.com/metacubex/mihomo/common/buf" "github.com/metacubex/mihomo/common/buf"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/constant"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/sniffer"
"github.com/metacubex/quic-go/quicvarint" "github.com/metacubex/quic-go/quicvarint"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
@@ -21,6 +25,12 @@ import (
const ( const (
versionDraft29 uint32 = 0xff00001d versionDraft29 uint32 = 0xff00001d
version1 uint32 = 0x1 version1 uint32 = 0x1
quicPacketTypeInitial = 0x00
quicPacketType0RTT = 0x01
// Timeout before quic sniffer all packets
quicWaitConn = time.Second * 3
) )
var ( var (
@@ -30,6 +40,9 @@ var (
errNotQuicInitial = errors.New("not QUIC initial packet") errNotQuicInitial = errors.New("not QUIC initial packet")
) )
var _ sniffer.Sniffer = (*QuicSniffer)(nil)
var _ sniffer.MultiPacketSniffer = (*QuicSniffer)(nil)
type QuicSniffer struct { type QuicSniffer struct {
*BaseSniffer *BaseSniffer
} }
@@ -44,67 +57,156 @@ func NewQuicSniffer(snifferConfig SnifferConfig) (*QuicSniffer, error) {
}, nil }, nil
} }
func (quic QuicSniffer) Protocol() string { func (sniffer *QuicSniffer) Protocol() string {
return "quic" return "quic"
} }
func (quic QuicSniffer) SupportNetwork() C.NetWork { func (sniffer *QuicSniffer) SupportNetwork() C.NetWork {
return C.UDP return C.UDP
} }
func (quic QuicSniffer) SniffData(b []byte) (string, error) { func (sniffer *QuicSniffer) SniffData(b []byte) (string, error) {
return "", ErrorUnsupportedSniffer
}
func (sniffer *QuicSniffer) WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender {
return &quicPacketSender{
sender: packetSender,
buffer: make([]quicDataBlock, 0),
chClose: make(chan struct{}),
override: override,
}
}
type quicDataBlock struct {
offset uint64
length uint64
data []byte
}
var _ constant.PacketSender = (*quicPacketSender)(nil)
type quicPacketSender struct {
lock sync.RWMutex
buffer []quicDataBlock
sender constant.PacketSender
result string
override bool
chClose chan struct{}
closed bool
}
// Send will send PacketAdapter nonblocking
// the implement must call UDPPacket.Drop() inside Send
func (q *quicPacketSender) Send(current constant.PacketAdapter) {
defer q.sender.Send(current)
q.lock.RLock()
if q.closed {
q.lock.RUnlock()
return
}
q.lock.RUnlock()
err := q.readQuicData(current.Data())
if err != nil {
q.close()
return
}
}
// Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy
func (q *quicPacketSender) Process(conn constant.PacketConn, proxy constant.WriteBackProxy) {
q.sender.Process(conn, proxy)
}
// ResolveUDP wait sniffer recv all fragments and update the domain
func (q *quicPacketSender) ResolveUDP(data *constant.Metadata) error {
select {
case <-q.chClose:
q.lock.RLock()
replaceDomain(data, q.result, q.override)
q.lock.RUnlock()
break
case <-time.After(quicWaitConn):
q.close()
}
return q.sender.ResolveUDP(data)
}
// Close stop the Process loop
func (q *quicPacketSender) Close() {
q.sender.Close()
q.close()
}
func (q *quicPacketSender) close() {
q.lock.Lock()
if !q.closed {
close(q.chClose)
q.closed = true
}
q.lock.Unlock()
}
func (q *quicPacketSender) readQuicData(b []byte) error {
buffer := buf.As(b) buffer := buf.As(b)
typeByte, err := buffer.ReadByte() typeByte, err := buffer.ReadByte()
if err != nil { if err != nil {
return "", errNotQuic return errNotQuic
} }
isLongHeader := typeByte&0x80 > 0 isLongHeader := typeByte&0x80 > 0
if !isLongHeader || typeByte&0x40 == 0 { if !isLongHeader || typeByte&0x40 == 0 {
return "", errNotQuicInitial return errNotQuicInitial
} }
vb, err := buffer.ReadBytes(4) vb, err := buffer.ReadBytes(4)
if err != nil { if err != nil {
return "", errNotQuic return errNotQuic
} }
versionNumber := binary.BigEndian.Uint32(vb) versionNumber := binary.BigEndian.Uint32(vb)
if versionNumber != 0 && typeByte&0x40 == 0 { if versionNumber != 0 && typeByte&0x40 == 0 {
return "", errNotQuic return errNotQuic
} else if versionNumber != versionDraft29 && versionNumber != version1 { } else if versionNumber != versionDraft29 && versionNumber != version1 {
return "", errNotQuic return errNotQuic
} }
if (typeByte&0x30)>>4 != 0x0 { connIdLen, err := buffer.ReadByte()
return "", errNotQuicInitial if err != nil || connIdLen == 0 {
return errNotQuic
}
destConnID := make([]byte, int(connIdLen))
if _, err := io.ReadFull(buffer, destConnID); err != nil {
return errNotQuic
} }
var destConnID []byte packetType := (typeByte & 0x30) >> 4
if l, err := buffer.ReadByte(); err != nil { if packetType != quicPacketTypeInitial {
return "", errNotQuic return nil
} else if destConnID, err = buffer.ReadBytes(int(l)); err != nil {
return "", errNotQuic
} }
if l, err := buffer.ReadByte(); err != nil { if l, err := buffer.ReadByte(); err != nil {
return "", errNotQuic return errNotQuic
} else if _, err := buffer.ReadBytes(int(l)); err != nil { } else if _, err := buffer.ReadBytes(int(l)); err != nil {
return "", errNotQuic return errNotQuic
} }
tokenLen, err := quicvarint.Read(buffer) tokenLen, err := quicvarint.Read(buffer)
if err != nil || tokenLen > uint64(len(b)) { if err != nil || tokenLen > uint64(len(b)) {
return "", errNotQuic return errNotQuic
} }
if _, err = buffer.ReadBytes(int(tokenLen)); err != nil { if _, err = buffer.ReadBytes(int(tokenLen)); err != nil {
return "", errNotQuic return errNotQuic
} }
packetLen, err := quicvarint.Read(buffer) packetLen, err := quicvarint.Read(buffer)
if err != nil { if err != nil {
return "", errNotQuic return errNotQuic
} }
hdrLen := len(b) - buffer.Len() hdrLen := len(b) - buffer.Len()
@@ -120,7 +222,7 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
hpKey := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic hp", 16) hpKey := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic hp", 16)
block, err := aes.NewCipher(hpKey) block, err := aes.NewCipher(hpKey)
if err != nil { if err != nil {
return "", err return err
} }
cache := buf.NewPacket() cache := buf.NewPacket()
@@ -130,6 +232,7 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16]) block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16])
firstByte := b[0] firstByte := b[0]
// Encrypt/decrypt first byte. // Encrypt/decrypt first byte.
if isLongHeader { if isLongHeader {
// Long header: 4 bits masked // Long header: 4 bits masked
// High 4 bits are not protected. // High 4 bits are not protected.
@@ -153,8 +256,8 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
packetNumber[i] ^= mask[1+i] packetNumber[i] ^= mask[1+i]
} }
if packetNumber[0] != 0 && packetNumber[0] != 1 { if int(packetLen)+hdrLen > len(b) || extHdrLen > len(b) {
return "", errNotQuicInitial return errNotQuic
} }
data := b[extHdrLen : int(packetLen)+hdrLen] data := b[extHdrLen : int(packetLen)+hdrLen]
@@ -163,12 +266,13 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12) iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
aesCipher, err := aes.NewCipher(key) aesCipher, err := aes.NewCipher(key)
if err != nil { if err != nil {
return "", err return err
} }
aead, err := cipher.NewGCM(aesCipher) aead, err := cipher.NewGCM(aesCipher)
if err != nil { if err != nil {
return "", err return err
} }
// We only decrypt once, so we do not need to XOR it back. // We only decrypt once, so we do not need to XOR it back.
// https://github.com/quic-go/qtls-go1-20/blob/e132a0e6cb45e20ac0b705454849a11d09ba5a54/cipher_suites.go#L496 // https://github.com/quic-go/qtls-go1-20/blob/e132a0e6cb45e20ac0b705454849a11d09ba5a54/cipher_suites.go#L496
for i, b := range packetNumber { for i, b := range packetNumber {
@@ -177,12 +281,11 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
dst := cache.Extend(len(data)) dst := cache.Extend(len(data))
decrypted, err := aead.Open(dst[:0], iv, data, extHdr) decrypted, err := aead.Open(dst[:0], iv, data, extHdr)
if err != nil { if err != nil {
return "", err return err
} }
buffer = buf.As(decrypted) buffer = buf.As(decrypted)
cryptoLen := uint(0)
cryptoData := cache.Extend(buffer.Len())
for i := 0; !buffer.IsEmpty(); i++ { for i := 0; !buffer.IsEmpty(); i++ {
frameType := byte(0x0) // Default to PADDING frame frameType := byte(0x0) // Default to PADDING frame
for frameType == 0x0 && !buffer.IsEmpty() { for frameType == 0x0 && !buffer.IsEmpty() {
@@ -193,79 +296,141 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
case 0x01: // PING frame case 0x01: // PING frame
case 0x02, 0x03: // ACK frame case 0x02, 0x03: // ACK frame
if _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged if _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
ackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count ackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count
if err != nil { if err != nil {
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range if _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
} }
if frameType == 0x03 { if frameType == 0x03 {
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count if _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
} }
case 0x06: // CRYPTO frame, we will use this frame case 0x06: // CRYPTO frame, we will use this frame
offset, err := quicvarint.Read(buffer) // Field: Offset offset, err := quicvarint.Read(buffer) // Field: Offset
if err != nil { if err != nil {
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
length, err := quicvarint.Read(buffer) // Field: Length length, err := quicvarint.Read(buffer) // Field: Length
if err != nil || length > uint64(buffer.Len()) { if err != nil || length > uint64(buffer.Len()) {
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if cryptoLen < uint(offset+length) {
cryptoLen = uint(offset + length) q.lock.RLock()
if q.buffer == nil {
q.lock.RUnlock()
// sniffDone() was called, return the connection
return nil
} }
if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data q.lock.RUnlock()
return "", io.ErrUnexpectedEOF
data = make([]byte, length)
if _, err := buffer.Read(data); err != nil { // Field: Crypto Data
return io.ErrUnexpectedEOF
} }
q.lock.Lock()
q.buffer = append(q.buffer, quicDataBlock{
offset: offset,
length: length,
data: data,
})
q.lock.Unlock()
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type if _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
length, err := quicvarint.Read(buffer) // Field: Reason Phrase Length length, err := quicvarint.Read(buffer) // Field: Reason Phrase Length
if err != nil { if err != nil {
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err := buffer.ReadBytes(int(length)); err != nil { // Field: Reason Phrase if _, err := buffer.ReadBytes(int(length)); err != nil { // Field: Reason Phrase
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
default: default:
// Only above frame types are permitted in initial packet. // Only above frame types are permitted in initial packet.
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8 // See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
return "", errNotQuicInitial return errNotQuicInitial
} }
} }
domain, err := ReadClientHello(cryptoData[:cryptoLen]) _ = q.tryAssemble()
if err != nil {
return "", err return nil
}
func (q *quicPacketSender) tryAssemble() error {
q.lock.RLock()
if q.buffer == nil {
q.lock.RUnlock()
return nil
} }
return *domain, nil var frameLen uint64
for _, fragment := range q.buffer {
frameLen += fragment.length
}
buffer := buf.NewSize(int(frameLen))
var index uint64
var length int
loop:
for {
for _, fragment := range q.buffer {
if fragment.offset == index {
if _, err := buffer.Write(fragment.data); err != nil {
return err
}
index = fragment.offset + fragment.length
length++
continue loop
}
}
break
}
domain, err := ReadClientHello(buffer.Bytes())
if err != nil {
q.lock.RUnlock()
return err
}
q.lock.RUnlock()
q.lock.Lock()
q.result = *domain
q.lock.Unlock()
q.close()
return err
} }
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte { func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {

View File

@@ -3,35 +3,184 @@ package sniffer
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"github.com/stretchr/testify/assert" "net"
"net/netip"
"testing" "testing"
"github.com/metacubex/mihomo/constant"
"github.com/stretchr/testify/assert"
) )
type fakeSender struct {
resultCh chan *constant.Metadata
}
var _ constant.PacketSender = (*fakeSender)(nil)
func (e *fakeSender) Send(packet constant.PacketAdapter) {
// Ensure that the wrapper's Send can correctly handle the situation where the packet is directly discarded.
packet.Drop()
}
func (e *fakeSender) Process(constant.PacketConn, constant.WriteBackProxy) {
panic("not implemented")
}
func (e *fakeSender) ResolveUDP(metadata *constant.Metadata) error {
e.resultCh <- metadata
return nil
}
func (e *fakeSender) Close() {
panic("not implemented")
}
type fakeUDPPacket struct {
data []byte
data2 []byte // backup
}
func (s *fakeUDPPacket) InAddr() net.Addr {
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
}
func (s *fakeUDPPacket) LocalAddr() net.Addr {
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
}
func (s *fakeUDPPacket) Data() []byte {
return s.data
}
func (s *fakeUDPPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
return 0, net.ErrClosed
}
func (s *fakeUDPPacket) Drop() {
for i := range s.data {
if s.data[i] != s.data2[i] { // ensure input data not changed
panic("data has been changed!")
}
s.data[i] = 0 // forcing data to become illegal
}
s.data = nil
}
var _ constant.UDPPacket = (*fakeUDPPacket)(nil)
func asPacket(data string) constant.PacketAdapter {
pktData, _ := hex.DecodeString(data)
meta := &constant.Metadata{}
pkt := &fakeUDPPacket{data: pktData, data2: bytes.Clone(pktData)}
pktAdp := constant.NewPacketAdapter(pkt, meta)
return pktAdp
}
func testQuicSniffer(data []string, async bool) (string, error) {
q, err := NewQuicSniffer(SnifferConfig{})
if err != nil {
return "", err
}
resultCh := make(chan *constant.Metadata, 1)
emptySender := &fakeSender{resultCh: resultCh}
sender := q.WrapperSender(emptySender, true)
go func() {
meta := constant.Metadata{}
err = sender.ResolveUDP(&meta)
if err != nil {
panic(err)
}
}()
for _, d := range data {
if async {
go sender.Send(asPacket(d))
} else {
sender.Send(asPacket(d))
}
}
meta := <-resultCh
return meta.SniffHost, nil
}
func TestQuicHeaders(t *testing.T) { func TestQuicHeaders(t *testing.T) {
cases := []struct { cases := []struct {
input string input []string
domain string domain string
}{ }{
//Normal domain quic sniff
{ {
input: "cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b", input: []string{"cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b"},
domain: "www.google.com", domain: "www.google.com",
}, },
{ {
input: "c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738", input: []string{"c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738"},
domain: "cloudflare-dns.com", domain: "cloudflare-dns.com",
}, },
// Fragmented quic sniff
{
input: []string{
"c70000000108afb466a232f7f9f2000044d0168a15a021477ecb9731ed77784d42301462e2d59b0395adc1fa6b569d428583f100860d6b6ae29b6c1b8c0f9c0d9081475ff801f34a9e0677adf685f02b1169fe86c683fb51934915ff43921a73b98fb0b734406f8dd90ce6060d75e923b0d3c738291b421bf16de27ed4785d727ce589f5d0957c413c81d6ee75052e3ab50fe53f1abbb24a138a52e1412683992ad769e65ed301a736914843543e2a3e11eb395726d4fcc9283f8607b38685069f63d05ab8bf38aa24d4073a1e68fa1b6087cec44d7fa628342e9d88a0d20b381014cdd1a07b9d913a3bbcad0cfbddd0560617cf26054138075eb86e06db1e68781541587302e6dda86cae779f9848fcefcc33626f8953bfe4dc293d23e74c87020e79e9ffd58ee345382bd4d1d6e5a3389b0a977124708d05e3c305545857041734dc7092901ab54604b3750b3139dd3b8f2bd94cda89d85be3756fda6f0cfb6f66af3d2e36a7808ff7bce271a0272f8dbc88193ede31613433985cd35c7bd9b627d434e7b2e94b38402b8f1b5619a903572dcf4c2b864c6ee66657c9ec81e03fbe765037f83b2229171888ba08651fc78a1b50c7cc52f6dfe8273723e08932b1a16a6b717a80b5520cf3f40e46f9d9c350eaa914bf99dd4ab700cfdae21437daf695916d4f3121235e4913e0657d8cdcf4afd8f2c7ef977a2dfe49f46fef46c8fa6932e745311d4a6eb3124d5e0a204b9e3227e86a55e662f7002d4f4a72cba8c77c3adc3eff076dfb9195cf68455cecbbfc9b5444d9c4a4775bba68d57ff52edac6ce6ff4efbf6466579bf68308f2ba9a59b2c09506064091a86af621e9dae52366a90599db0d64a23944bc48966b6d3ab8e20f4afb5b0e94370d26a89a9c4207b454554e58ac74f62ffb3eb2686eaa596b9610322a5ce8eeb42f2ead1c71b11b51bc4f1800eb549a2bb529ca4a0d165ae461e45b556b2365e9459d531489d59d0dfa544a76c5c00b0a01270741d4061a331c32fd6cd0e68bbce49137b852e215c9db52f3e430416d8979520e5270be324f3d93132358c0eac35a4618ea7aad997dbbd8e99d4ea577271b935e3fe928f90abd94593806d272a565a414686b8e56c28e34b77671de6a696b09414380bc658c69a309d3225ba8493e9076dac776c845ce11a7ccd6cae58fba5434014250f3e211058b2efe3424b991d679a02ba949b086ba12144c7df3e049b5d026f386e4ae712c9b0b4b02730dd6862ed4e72730224cb6ec9101c5cbb7ee4fc30d497bb1dbf74ffdd49d8cae6c7c9a364ede453d9ae25edf27a2153ab285f3e3be66b2968d67a56480f1f74c4fe61dc69db3451f5b113d7ca02e5afa8627f579c07a9b1814853fb8fdaf0c0f220f89725c757f5617ba4e43cb4f3ad9ce18f48f23d10f9e8950b0fd737070655730532896d93df8768860ebf941365d0634db399feab1f8a88bad28d25e689c5a57321debb8d1435130e90a699e17fa5255f2063f09659a432e9ab5f89eeabe12756bfc5e02fcae2b78a9d0f570934b8d4af8f4afbd57549176f465a0cea485dd89c95a8ae915b4b99548a4c939710c16908f968368baf5f547cfee07f3cbb6142041d6e6084aac253a0d3aeac628cfe76f87b94c3806cb14a912ce8e4981e316511d5ede36f526805d6c3fab5b72d9d91f4eacd26e28cb181ec66611818f5c206ddd52488707a940dc12144ae825d25929bc32b718f46e471fdb30762d299b45c84f6310a72b60",
"c20000000108afb466a232f7f9f2000044d00582e8683e329a63e5bf4dc93e93e325ff661e74b9cabefdfbf6065c7ab203c8a629534e87e5f2d4c0f463352904642358b8f137e99802c3a26cf22235782a777769ecd134c6b4d0dce6aa10b485c45ccdcf6deb805342e99ef97e2777aee0b2a44073843fccc2f8eb837031f76a8e968cb01c13c1268af095f54f860958e4062a84e2527bcc9b25a7791650a844de1b0c4b2476282a0e00c9de9d39a41914d1e797a88a8997b96b25a4c194762912b2ddee0e01a365f1afa1e82ea266c14ae94e47c90b5679e2cd00e63ee5a834505ca33463751bac22f3b87afc80099335dc7bfd12b7df224a23ced3d2e25b58a04c4b5cb089ca187abc54d782973c7bb157cc515c7508431ff5bdc227871da58b9ca8a9a576960f38edb384112b08e4c70672a6f23d17d9d901342e56c12370deaaafcb22810eb352f1a6d9377e96bdc1ad4dd397dbc6a227b70f204c1a4e9a4db2705763b82ec4df1fab11420aae547155c6b49abceeed997ff01b7d24e369c65f7edf18665d067c7d2bda5ec8623281fce8c77d893cb8a42053756713e910894a58ef5bf3d9f3a41071026660dd7cd05e1640767ec68f78e22c1716700ca9c0f076f90a65cffc394c10a32071c6532d07b59414181070d08c9c84e3d13842718d51bf90dd36ab1b3f708df7eeb3939dc8553787308983c3e9ba971e7d447788477a7140196c2f717b9ba4f5da92d73316dd11c1d1830b4200f26f733a6c65ec1cc21549b485e3a43dc7a2b68e95466a53544082a20d9a43387a7ccbfd353f7e590b7047f13bfc0d91923c2d75dad4f8091ea96502f98e83e5c30e52e4cd5c670f6c2248ce37cd6ee8b3970531fbf0c53c5fa9a0d73200442b755c91fa4f70524ffe8a36063b6709d3aa9f6b53eb0aaecc57a8c8c9a7ac5e57e03e9cfb290b67dd8222a245ff5439914147e2799fd1cd2ca2cb22fda299443b81e8024adc59d098058432fa4bde376b8e59075f6b86427b4ef6cd7c83b5c08add0c3d3543aee8d672c41cb287c1f0a17f1bc30f62a57490afb2d9f401bf302fd473ddbaf63f6883221579743d6aa1f386b8b2f5db06d7d6c36be81f29fafd14b82e863d744f116ce2be4921631f1fb2797289fffa9ee16a3e537ddfa52350546bc544459c0c9d66fdcbd41612cfc0e2744f50927983a3224291c1ae51608fbc00f40c60ec72573a7e128c3415b0d9a7db52de8ff763dd66e2eeb03ef2e67838c9e68cfddae4b86a3f34a69e0a473b5a73ab627282648df7912c11a4bf033ade185a8f438036b99b960aa6213c800abbbd751248a7ae600357ab888433125d49c5643705ecb8c86f2980050edd7e3c579ad6fcae9bbe2c8d8b38004426f35eadb543a3bef42355acb1b94c21d7eae7b6ed422ca0d58fa03b227b035628871465ed6509254c8a3bf43dfadbb247ecbc52d80d65e9c03c4bc7bc35a829502bde3868af9c33737cd88d70f7427790313eed4ed1938955c5dd360212ef700f274efcc8c26ea94c4e2e0937d475c5c4909edfb66714d15d12e153e5586725ce0c47e8a1506bb197366754ca8960508f22fe7b83a5eaa40f05f3cb87464dc6b848080c0e0cecf2dae82bfa42cc6f52694478dc3d00ab0e1ed696b98e26c7fd34d2efd969f83e284c28ce3f27b178f4691c772011f61722266153142dd0d526393e6c6848d201115b256e65f12b911a983bc2f96a5b4b99f63f0b58485a521553a3e1d4498ac5d4ee70c3f9",
},
domain: "quic.nginx.org",
},
{
input: []string{
"c00000000108e63b9140d034563d000044d066e1913892ec1d84c179dfa9596e0ce930171a134a09446a888d9e579a6f7bd77df6deda715b028d64f7866603c6deb468d60ecc6488b5e5ee2e2daa1840b76ead998023593c9ebc4178ec89cb198d3c79a867e27177a74ee5f3db74ea194e36e328047ffc3890192665a6feba09ba1e224967fa9575dc7b094e1c29c7f3be9961ba62e3e063f674a09786b7611138e1edaee32cd1d47839e840a74f25ed786463fc48bf3d38a4c793178ab7cbf5a3eb974415b9f9ef7861dfc73460594332f5545c7b7037043afdfc1aa62ac3dfb76ec2c6ae8ebd351f7483992c762d6483b3e2c1454c8ed939ce43f858ccca22d9149cc9da16af86a010be7f3248cf19fa442e94d625ec7f7144b01ac9afb8fb8c595d4cd12fcd2b2d9986371ae65f6f216bed152b79d2782d60f1f01e06b359f88900c4bb3f987f3ce336854a5beaaa616813af4e5f9bd82dca0af6886b544fff0261807bbd8cf90213299f5802b98edc27a6606be8e2bbc18fa7519eac260dcda139f164796a082908459c31aa964a5d3f6fed8944ad61bda126991468f3b7627f2470179619864f234a395ea3bd4f7ba4c0cdf9f5f0dd95d7d59476f2d2a36521c13886265a2fbbd4345e8d1d1e7b5d01a58fb11de23730b087e2b702200155a1ebd50db5751d279438822ac158173533140998a3056893bf470ac84720cb37a4a3205fa88267abc56520bcddacee06011d929c3a114314822d8ccf7cfef89f2fcf0a4fef800afbfca4a62ee848f22066f68c7d3c5c9a24402d422fc2fd5da6d3b470b0ea253f12a883705f7f78bd67006ade4f1c8a3e8fa052656b5b40dacd8062228871cc3bfb1a9c38472b0a720c3c750430edbcbdcecd46b144dfcaa009fee06770238d0270e80671e8ee5f5df18b86dfe8df2f121245c0710ccaefecbeda0ba3db945c768624dc38f21a4ac53741f4e58a5052f3d667fc466b69905f05d0843cfcb830163fae18dd1eb0ce62a59420db9c44958a0eca9ba4258c8060a9956343f155da6c55b2060427d07d9e311729d2971439c7541ae2babfce25a3f5f361fde86c39ef6c04e4e3cf7dd70c9cc0758ec5db3f0cb368e2447080af51c8a5fa6b84ec3175d2d3e6d877b6953e433b4e94b52e1a5f2a1ca37124c27e47f9de5d4c74644181cd37f3f3863ca529c0847bba91c246dadba94b4566b08eaa06a0db4d58b8cb0c8d3070533306a3089891b24a7c4e11b3aa50d5628fc1d136388e8bfbc420a6f12701333ccdc95dec25d09ce25fa4b654260965b91f05b1542c2ee02008d01de4419f14d6749c4bcfcc45a332ba0772def720ea3c8d207802418137b733e779eb406dace0b4b5f5e5e14c787f3e044e6d8160f90fc3c65bcc7f3449205b63294fbc11e9bb92c007d1cb59183eafbf76be9680224cb442806500d71870777d087bf864890848f4a79424c02304f2a6ee2b07f9257f4a2f185ee21239625e246cf680e74b85d292cca44261c6cee6da39bfac3882d28fe547a500f79519ffcd3f54ff5a905c99f22a5e8142c903c41adbe1eb9770b6cf554688529091b126ed2168a23bb191c2b89728e31773623bc58bcb9baebc2c664c79d6ffee7e4404e039723eb05e7f7835c87212431a0131603fcc3fe090cc2fda8239b8f42188b35f98d7fff949b3044544b3bb962ae236a664d76d0c751d9c9ed1271715d240f111febdf7045502f2afd7de8aaaac650511e7bc7716a5b6622ae925abb7",
"c90000000108e63b9140d034563d000044d00b2498988864d8b7f59a00d26165f5ae638fc9b1c12d546ffd86212ccd85f654259cb8b8c9d753c696ddad7ee4847bf3b3c10063606cf3972f75e17ae23e73b6a3029f23541f674256d19677665cdd0b8ac15c3f60984bc14ff5dc7a9ae37395516204f2020965713fccaf35cb0a5823085cd6211d681dc6b39be9db46cbfef154a2b9049ed202e9088961b0b710e94bd73259b0967e4d6b8cdfd5b72774fee2f2ceb16bcafa010f247c43b0a9ca25578e7d45bfda7edb82e91f8e1c0a2cfa990223bf97ece42862d3f329521fe2d12493b717f174f966d173102e5cca10943d5b612101d65d0dd48b44416f9ac1eac4575558ecaaa39c47ade2dee6e25fd219d799b499143b47a5bf449701b939c1dde111349cd0d63efd2ff74fbd3573ed40abfdb2310e2740da40fc50c7a137a3f32c3a26b3d407f80e669fe7f9a3542fdd412a9cb53f845d9c1af0814377bf92e30f05ee387fb8675807a6de083c85d3d7860601c8170923c53e5773ee388b68e510a28cd7009c485bd4cb861eddfdd265de042e5a018d20cb810614e2bb17b0f52d6bf620a6f173e0b41951e1b83ffb29e3b3b3c5d9fff13acd3b409021195201d003e281d8cda7b0f02c273e17b1f9b9e8cec4296d65a1c4923b78a2e4273cb42e4e159980472e440078e542eeddcc5a9bfefa5a72871fbcd9ebb74fef20a50215bf75cfd8572d5ab9ac5945e8d6ca35884caf0af0446ee9aab0a1cc3a452ec79c9de786119e63bb3a75fce0ae29c15a0c320fff87e87cc23a05e75b4f4b30b75c6aa036c4b6657f8200ea014185b31ee7fcd00d1eaf40973f347fae227f89d41794fa57ac1ed1efda3ba840ef27852cf33a9dc9e2d77b56af9ced9e75707837aa8c5395cdc15134ba132de87152ce53d506c53284dab912bbc276542504cc94afaca71a5173ff13ea6cb45b47dde9965428ba5d8eb968cc2a5729c2f9b8f1c1de208943a2cd565196e040dcc415d769ceb6300c7909d7e32bbbe83c4cbf4d49f6e34fe56b651838628f3a0001e99f39cafe45c98e455aff8d98f89942a862f7505b9f7fe3f64dacf8c574affacf91c2c05f094127acaa5187f9dfa188f67db421243a02e583942138c2edf45fec4c6b6a8a791da9055be247e9b252e9f7c1330e76f9cb3aa5feebb21f871315b5fb90a1df0b8056513b74daeb6ac995f85c64150ad115a14830d145e5f4e6638c26987b676a1dd19a9775df29ab442ce6143b0fbf8f8d4618084896e34812ed59d63041e2b4ccf6c959a6c849813dd926082bb7b1adedf69246547f335552bcdbae7e466ac31e07e442530ad114abebc6f58015b786e7f35644307fa7ad3d9248c56c8ff472735c6911da1843fe53821b8f5180f8844db4a9f7a826a919fd93c4db4d25861054929260dcdc46d085827c46d60f1097424a6ef250f5aaf3235c80230eda4eb580ce93e1ac8aac422a7aa1241562af601981b84b74949f1c476705c8030eb5d447b2414f9716ff3fd606cd750030b94345c016078bdcb97b7ebc24f661fbd08802f32df18d6a2aa85bfe2e9b8dc76b121c44ae9f29e4413051b527e99fde29720724337476c0eff325cb6220a290a9eb852151c84836729d6a223032e2c638857d9e7f469b84d7d650c45e56e763aee73f902e82b055425c4568725e2d4efd7fde8b02906bda48af86bf47ea27ff00f4528494b74be9bbff001cc841449a184a4e00d64e51a72660a2c21f704f",
},
domain: "chat.openai.com",
},
// Fragmented quic and 0-rtt packet sniff
{
input: []string{
"de0000000108c2751a596bd51c6e004041948ab7d9d493e9e1e9902a7734534fb9eaddc70ca7f821d1b58a406b23ba9db1d03266ae74765b03fac21c284fd50cb0a3d1ca71d8c3cabef5553dd1cb748ac662",
"c50000000108c2751a596bd51c6e000044d0538af4ba75e226a6fc7f43e7f1f59610973b8a6670bb8338ca7ef7d90f81aa59f179dae5f8f6dbd24ec6fe576b28f6ce6cd46f26de143b8c99cdadaecf2041948a61bd5a8591486e10022fd100aa20e6423b4f4ca5773edb1aba79b73d6150ee185e66da60e658b2a698098462122b6b80c7fbc5542b0b8e9532898c1f31aa2ef55cbdf036d74c3069abbb261660f048d950b00b7db279ec2bc39912102679ddbffb53f1b1921f137fce43e164af86c72908532f4cdc48eb462a9d9e9cdd6d3c3faaf8aa8aea312dcac5d6aa75b1ade4af6901576649da7e3efd4199b92107d7acee8bbf06734b2484957c3d8cbb1f3fc0ccd56c55223628ed8ea514ffd101bac370c97b28c7da81175ab0508c0002d458cf41f7159dfce22b447c1ec502c186b782c1854718b7fc0fc39e5c09aee31113fc4c5003803fc27ca48850c08a54dbbfdef6ea9a6a138cac0ecd045cfd5607cb6c99c39c0cb21778857f97416b78fa7c6ac8ae3fa2ef2adb3b85fe3fdba70ef9265bb3d54e56ec68b8887d54d02d4a571a6b793ae4df8ff171c881a554b5c5a7848351d446ab94c90ee9c600f03b785fee6300450a4ffc2a55d417952e15449a491296d463ac6942bce4ca93c99440396bc8984073ec028b11ad412e97e26f9248031dc4b1a6ae385803bb578fa1a3b3a58a8ef19c6c511f17b28a275e8c40e51fca8f410a4a1879b5d8749a44a6a9f97c0c9df25318cc28fd0cc61eea78ddc603a17e74eb542c8c08cdbaafa3b44566db4d67e8d1429332375cd30cdaead9594c46d8ce91bce9813c3ca23f55ec2f4dd3ff141471bc3df590367bc65e4830018ff7d845ec4987d11e471d114c48acd1ae9b7670341a34077ae59ea6c3bfc4675cf419d37db48a98a5573b69867039731f537098b46415a193f50b2c85bf9e5da45d6757c5c366e21f04ea62d64b81c28be5148d89e53535414067cf609e59686b7fd135f5cb473e57f6c82dbb291308a1065e0f755935d77517adecee55e72cf37ecaab1b5c0c6e0c7463a014e7e439757913f6e43abb6af775d21ab6e43cbdbcd1935a000cf8025ebc11378d86d6f72d51bf2dfe4be1db5d3b0fcacd13e1b9fbaac6e9153c3d1f4e876f2fa9c3cfc84fd0910b778105b66be70827b1830b7b3c9633af5d83ad527efd81498cbbdd112873cc5ced573e6579acfe817b62280c2122b582b591d52b96cac047bff91192a5cfc001d15c811e055dcb1c9710dc892258ed1ab5152af2cfc57a0b93205dd41fd82b86090b4281b1493a8828ebc96bbd603b888cbca4a15799a5f3eaef93655d5609948080ca57c696d0ffc9a07665bdb063b547bb5a862c3b058c9efb2e7b79cf405fd83efacaa4b8e3a1fd126270587119756562c03d69a9cb67550369030a0204e531cb8df91ab2dfa2e4106c590c59b1b13c447843937929a574d3ea1785db0d52b4b2eeefd1a07c69729bec7c2813c9eb1249f706b3cc14a3d489d6b42a641dfd9e91aa70c7d3222e154af2d7fc1a8f48e5ba11739ae128d1f32ff929aaf4b249df5ea23f7847301e36ffda02342cdf1bd9dfd1979cbd8de32eb8b1eb8c415ddd267efe53f54678d9fc32435b34b00ee2256d8b6190e30a280df5bc48cf9fd669a52469954deccb0f1da37371d513ea57f31ead22a34f9379c7931fd18286d9fde6ecfaac8ca2a9be79d688c5401c65407543c066532f6621f256551c4a98a86b543c576ed0f3254daa4915",
"cc0000000108c2751a596bd51c6e000044d07b624bf3d95fb3b7299b67dd836fbbbeb05a51650f9b2da3b2695070a0d19ab0d5334cc04de7ea7494fbe6c438f4e84fa56a3f246132468b5b4f1ba0fcc0251cf278338e15fdd715d5bfed18c1f98ca3cd3cc7b6f904aeeea2914a8b998dd3ae7df694c49c1742dccbf4c3472ddfe2e447959655459c11f18bddd9481eb597b887fb3f90a7d0f05224a144f87a5fdad502ea1e46c1c9f4b4154bafa4542c026296040228703bcd020202acceb772b596bf788341cceca864c8907037c39739e511b04e8ba956efa0fb5cb151ac90eb5817444f6488d593325ad4466058ba45214b965c5738f33d5591624584559ba18e89913b868619d498072e3aa1f333f5d6e3d1db88b28adf7d9350c3c383c1eda894f36bf1bb2a58c7a5e5c8b20597b71a099e46bbd3d8894877e43b0183919185b4e9f059472203979d3334c535fc4eaedebebc79bd1e423184765047a50e6dcc76ba2b23ad23511cae2edd2ad8e7f7f302226dbf6c0e4dbc8c08cda26340b9abfef1ef3333cd511295f14c87197d7890576b4076dd9686047854e67733599d96a99194aecf7b927cae2e5fa4568afc71e748dabd3bd71e6c3984f45b06a068a7c9c3a1ca7b5c245a9bb2cc7e2726e833e283430a25b6ccad55bc5b7644b44f99fedeff3c3bbf995a0387cf1e45a5684e5d1c01350d0cd2d615ffb6d1011d80ad16b75925efcbee483e4e2c0e2386e9e1b35b5a107ed97058adb60e323342989559856faeaafe5149bdcd60c113230f9923b2f654c95f986944a014198686f9c2275053c05080e3bf9fab7d46302948b152e2f2fb1ecbe71b412016b3f25ae512ad45cd096d5f284a0c2808b5eab03b4b9b2dff4d81bf234e75e30d480f39a5f9737563e31a19b14d1038296915af33e0ac0dc18e9c871e539e8772d525e5fa19afc582b1c00ae573af39fe293e16d182bbe57af5bee1c0939862ffb62e3d52a60aeb71e4db2a4a1708e75afa5f37d72cd6c0e036abcb4eb8db6515fbbdf98be95d0a6d261a9445797a8f38c3579a2f04c9f5b74dfd1ffaba2c6aa05959704b9b8cb0db30bcc360711c5afef0d1e7c2b076466dcaab104c70f3cc0cda33d7a47462c3fa3d7e34a99b2d8ff3fe5cafd27ede28b9e09b547cf955b97b0d0d4ec126957601c6982d176252be422df3366118895ca25fe27a96c9c234d484fe98634fb9e970e0d2b096f2ced5d56603505990a65363726c828aed2df0f112e0c44f058424ff5c25ae60aa2cb5fdfa289e8ebb63908365aa4e4609eae87e567f1e86d92c43992e6d505f55226fe3533f9fc9c9facff9dae02a3e3c97ca54191bebab93881c0e89b9de5bb4acd5c6fed5b1e7978803f693bfdbc125b4d08fd34fdc6aaf02444c4b06010b0eb2f15d86850a7aa5af05af438f6b7345fad4315f631bc5b017c7482e7af725a09844472f48e4de79b15284932a7e99a46ae72b187ee3faaa0f31a36726056e86eb706bc8eac04b68a3302307a157c91639f30bafc2d180670625673310a9a45a171063011e59c57c8eb67353a8ea344a87853e7b600c2b49a7a1b60a2904c0ea55951af6430667ecdfa6e90a8d2d0ed9857ba5b876cf78af190d5013d16208d2b30d02cf2c23e6ad1466f76c30d11034d5d2eca113e2764b2fb6298fc4940c16d971e28e3e6e5d0e8eea1ccb9b4b89741ed675861fc3680457ee08547f4efcf68bb6247313f8218ae3ec372e51ba8786ecae115dcff241e0",
},
domain: "fonts.gstatic.com",
},
// Test sniffer packet out of order
{
input: []string{
"c50000000108b4b6d5c8b9a19769004047007e07df0d887979774085206f2e7f0146b02a8699715a54fc71ef27ab5a9e8cfbf155497bed9e25934aa74db1b3b270112472b7bf7587423b3ab2aaf99de34cdc591bfe04cc0a448875483ec1f071622121a49c456dc3ce16bae5f61f84ceaef9e8b71db56479845b764507dd9416e8c44b8c93406a230945eb8e484471c1b6207c9afd944fa0fee555a5c966f27ccffb4bfed37fe3936f2c84e9852c0d46c7e2e94b897fcef18c4b0b83d966aef75c0af4240325a24668bc017e0d3f69680ea5b2f59bd0b964062bc40190be86aef3ed0716a18a67057f309faaf3a040222812142a399deb72ebb330d03d59961e2ca10cf78d40886dd094368a881db261068920968f6adf7a7b1266faf8842e71840a29859e877c66e3ebc47d7fe3ee586b6512d9b0e1bea82b302647706473e68dc8209f4e9ca19f1dd25fe386e62c21d9c741e75cb8b11606739ba3de6d6325ee3a9cd1bb2b9613746140ccdaaf936eefdaa1ca7ad73d684e5d82b1ba1dd3356ca0c881f6eee72c02c8b78d02a8217a8fd972e463c77374d0fcbb761459e3ab0bb5492e516d7d4304c19c16a4bed11ea7f4e75616a26a7c81b04ffa580cce04d59825b8ed929578f9219e64bdbc6352ae6e4150a993fc3cc27ce4d66c62893866b9053bb737ac40364094b53d91e8b325b9dab5f537af04f10bf8db644897b0b03b42b1bd6c3aedfe018a6e4f6533183649f4ef6a6300383430f86e802fb4e51976d056a3c40c3b53c847b8308cfbe54dc2d20b8cdc870c73f5fc22c376c35d9a85348ca6a2288ae03dda6b97f0f502f35219e19cff3a810143289cb1f0715f8785028f887bf02c656c9cc372bdc419290f05957ad3dee82b56db352db65aca58e6fa0bd2f753160dd9e7214968c0496be1ab49f978a9252e49266939fedf542760abd653dd38b1659bcf452c753cb89e8235bcf732afcff8f524331be9b6f4a5081c81255e68c358b3444fb1d57bf5659d86b6674544fe2826ca81ee52f93a17b3291826678e488c3074c259223845e4083a413af7fc93d9992823620a8d29d321438a760293e36c4232216207060dd3ee5c4036250ede71ca9cbe335a1e068eb3ff6c10a7f1c8204750d6d0f3145014949a7b4e88a723566ee5446f960a95d9f81cc45155443da561d85a3a311df8172a1c4eb118bf27ec4b3cc4573b1ab421d96d41cc1e5557797ca68f701fe75c474527144d30b9bb00a117637f88896b0b2dcb9bb29ba144ec384b5a085e82e7387e0560a4621423c306b041ad42e84928ce23bc2a7f995ef5c21616de43be8a1657847489b32c8e364846389e7c8cae99530c499f3662a2ae7090e54958ba940b5d3eaf1333ebcecc7f06f29f68ffd97defe65017519c29d355ecb0a4b47ab08dbad8cb0cc5c86de65dfa703110c60a0c55281925018fb4ef49fe5d0132dcc86602c2ab9921a8f3451480d3e931f01c2f9a81873435bb83860128aa78dcc950fb13e416d90ea969aa92763f9caefa0fe3ef4ea82e3af4a3e717fabcc589fe8cb9bfba6810ddf7def8c1445fc0048fb07be043a628e9c920bb72c04d3b9472caafc6c14bffb854a1ba2170dda919322a6d79eab92e3a88888a224093946b87840033fe41941f780f569eaf1fdac55e36b74514d72d09823d71f48f5d5f0ceb7b6d69c5da0e0408c1b13c265d4775db6a0f952ae72bd5c277b22c4be2f2728451ce31e921c856000d20da0489103bad6a6ab4",
"c60000000108b4b6d5c8b9a19769004047007e07df0d887979774085206f2e7f0146b02a8699715a54fc71ef27ab5a9e8cfbf155497bed9e25934aa74db1b3b270112472b7bf7587423b3ab2aaf99de34cdc591bfe04cc0a44884e9e716461869ca408431e1ba92740c598aa74e9cd45706f28942f9cc64dbfd7c292cd33e82b50ae0e2e08dc478c19886718cde33e56c38517f8834d64904bf4fb1d30650caedecb9567ea8ef50157c287a2741e98a00f8e7e19e76bbb0143ac7862a49393f17ec66aa0e2c02123ffb5abcc96ccf92cd542c8f571bd7a4382ff81432d11f83796959696c38f2029db6c6a536a9ea24b74c848b95882562d74739ac95f5a069d48e8756d1a9750c7ebc23d4ee22d617b29b415b7458b3bb8106c22de3a9ace9ec689e6e00471aa33e570f7481d15911d7cf46a429cee1a416558c5e78360795d905ff1e0c81d18fcf4954131fa5b9289ed2291e122cdffd666c66209aa2cab01730739249ce293b3ba3abb31683c108bdfd51f54593f47411077e948f01105bd9bfff1578d235674e96a8b9cfdde119edaa960b84e70fd681312514151de1d5939c79abdfe4953e22be5ad3e6e242d0ce9b3f2e589ce3c768f610d4d3a32e33225d8a5ce2ad74a9b40859cdd9ea99f14fa2a7018e4b6aa6e46a0d73d46d161ec5d3b30bd55078e23987865551a605a33472931428ce222040d20c07d1ebe970e576d9d54ae688a3fe9388adda3da4d011a7cbb604f1f19d2ef1be7ef4713bfa84d4d69ffa606a08b61a1ebb99aacc4e19d0c5034642da1ce2d7d5abecc8adbbc6d7f72ff2da4ce5228ff8626509b38e17b31717c0b7821558b021ba81502d54da7e778d4526367109333383e7c67d5d5bde86bd4001fa13a703ff9259e1c2268ca8f4ed2e6c022a7466e2178bc725f59792803ba28c629e3df7696c416dc294b510920077b2d2b258fdc3506c36c42d37796c8fdb20ba797ee68fdc410325a355f6c1189aa9fc9ee220d42186677e3955cd3c844ce505cd601f04201cc390e923db2ea6407fa2fb4ca7f3f82d0a82d52697ff5ba5d4633bb0d655d7ee3348b89c9cb42870cdfe7c0c162babab4208a9a54700c5785d4134e9e33361480e3512ac8b556e11775536e90ee1270a4cb4d6bf2faa72d7e1f23ceb4fc3aded0e423b6be6a55bc25e5a99163b4f5f72ec4a24fe96f68c739d1848c92c4236a5a637d19871456b8dae671ea6ae5c16ed4fc257612a0821e6dc1cbe2ef4963a1436925dcc4e6ce528fa75e41f7721b379fae8ca09e6fb51d0c3e3ae6c19b98860ab9f74013146c6d375656dd1f530abfa64670a510390e9a54bb9a4ad19977491377c8cd743597bc156ee3f58cfcafa5a547b20852749e66fa8838c100ebde039ea25c8ec32b0c6325b793797546a095e79b9388d8e67dc6b4b3892f93ecd13e64ba4b2ad26fc810fedc374b831921531344c581927da9ba822bd625584d98c7582759ae40f01e14277a0a13d30c2c12536df698330d8aa6a3613a42c493c42692b468b4a2cc6bb6dd45684ee6115848110bf517074efd93bf212c071013f4359f140cfed17bbe10328f2026cb8ada16427122d3fc8a933119a1e3e4cfe2b95cbc73af5044cb099cf34247228972495488ebaa4696280d17665c421be5f1727c5d5b013d8aac0e9943bbbb7fbc2162a4000a306dffe3bc4425cf272f1ebb63c8e4998f867fa6b05d71a8642e29392244d4e2e2351bc149d665efe1b9519cb1b15005393f938d",
},
domain: "ogads-pa.clients6.google.com",
},
{
input: []string{
"cf0000000108277148f2b916666000404700403986db57eaa4b165be8ab9c95452bddb922eb35b7610a8e664f6b4620d870507c241290ce885c36d7672c51d94063bf893e01bc79e1d81bf023338da3d22f63bc7aa433f9944884c88b10f198e849dddbc1e9f9bac61f98f67f27d5452da6e2bda1f5210a145b1f1416ad2fc15e60aa00444362630650bcd0ee47999b689a40100dcacf40a4c3d74fa6293d4a5cb0487d8c76787c04dd2b47ec7718df5a2dc6942069062617b3d40a95360802957419433436c9065bda5a6156291d909a079b6d3819941368d7e17a2e97e36be829bb421b44545af47e37d7815ee1f200ca28ffd361d955ebe0484fb234a7e8a7c68ad824fd14d517fa7b35f878beebaf3dd22bd9f7a39cb7e0fd8369cdd28c05a06323be7af0b2d69ed2a2f4ea9f25d000de71bf5bd6765a20ddf81d976cff2321f1a4584ad6c4b7e9a42a6d4aa3a02b59f7d994a8e4a3070a4646e51fdf354448420ebfd0aa9118d010d019cc168f2fe5a9ff0c42e6091676be11f28a372ea97d008a1a02efd58149106cfdec7ef86f5416c4b1a408d8efba6c8d4742d781374ff0a1a8ac183bffa1345dc8e3a7cce04f66cc865f434decb912dc9e8e811eb59b80d3e39d5788639ae7c5ede73a935edb47d907725656be0522195bd2c099b0241f36664fad1543e4ae43862252662707fb424a8f5f9486b8e3779ac24bac457671ad664475d1fc9eb1de3c46f624b559742b3477953552e44f20cc1725a11ab915423fdce7cfbc8dafebc0c43d1ac3d3373ca2f0210924433c46e5fcface47a65579efaa1999d52b2632f69c33c3c63537c01be68fb679f9229f8f68c5caaa23dc4c61d3c45dee90affed984dbbfb06b2659447400b4dcbf6e574719e8d49fe0dacea9509182a42f6463138d8693a3b8d797d3bb6b0b02648829d666341373939ac41a57e90fdc2469623b6e2d772199d7c806d5998f439603c0de8413f9d29f79323ec5410b409ab8c95547ab50bb921fe0c407b7aaaf663389bdea5ba56c023dc4622d6dd9cacd8f318a6a0297d041cc6ed455d906be50dc85a25ecb32f4a565432fec9f359833be1c6a6b7b4bd119d3c4b29932eeec8d140dd467ab4d969bd23e9d2a95b92835587f32428f957b6785b8206a4834e00a3013e0b6a5855f16207268bbdf311572c54d2e6ff9c659cd02c258f494c3b168ea170c69138b63e0dde487b72576e87657befa44548b0b4e1e5a837dbbe66a559cd1df8f2151ba513930243fd2b7705bd29b183dff966224d87ffabb74017d634ab2e4b368052504a7f6bc1c62d39a29dc2dcfba683bee2039e376ff391abbd13a0b89512fd8f6a4e66051dfa04e0e1a3cb4bd56a9b17e27651873bf2ed50f65cf1cc608afaf06fe7e6238347adb66f01d1f0b9b51f0078615553cb8ff8d6786b87e19dbc44000025693c4b34cfd695601a680efdc1e7465a981b0f028cbd3dbb938789f240e39223290e34ec303ff5c78a4a637ac04dad60d744f82e96c3c9e8ed6cb0248ac73b5b3a92007edfc1277c3cc6fa1d0045c1c371820f06bedaf046dd999665cc4745ddf8934084ae02e9238acae6dea330b5798e046138f5b15011875eae72d6eb6689e56e0ac5c5d9e25dc4fc1874cf37265e68ce5b8630b84ad8dab7704474f0bfd08ac295b3a508284fb6ff201f0aee6388d0e1d5cdaaf4c20429874792109f5b8e2f3eae6c397e46a510ed829a6746e523481465f64be4e145c83d6fa6951229d3",
"c90000000108277148f2b916666000404700403986db57eaa4b165be8ab9c95452bddb922eb35b7610a8e664f6b4620d870507c241290ce885c36d7672c51d94063bf893e01bc79e1d81bf023338da3d22f63bc7aa433f994488828e6082edd53e228164d8862067483762ea9523c90d565b9e4b185b7805eaf8220664264e82a95164ab6cab4fb3f5e795e246e7205aca236b3c94dde0ff4fa66ce0924d654829d59c3eb690470b20c5011c739102257e9c2247dee67c0b98190d0015154d31041aadb026b8d3a828c861a15dccdab0cec8cc99b8d6c2acddbb93ab66253e87ac39016507dba42e8fa9f5d22c7f27645a02361842a59ebb2eafefd0f3b92bd9692a96b93875defcfe2796243be8861c59ce5ab03f1d65d308ae456cb9656da1f01026ef0807cc9930021b29d69b36881c3e7d70fc68799ca81922008db93c9ca4a365ee191e214d9829481fd430194ad4583a0ab2e920c25244d7d64662872b3b69ab413ccf0dfb6bf2ea9a9b93e04ed19f8a0e146613ca9d511179f80aeab40d573590d38a7c10840e3f8b9ac1bd23b0826aecabf6d1cdb2aef02deb982c2029dd6d8bc21da6c262c8116b7b383ce8c9eec69da3e16c044dd96ae08a98595d128e89dd55e6dc8eb08b8d51327278027137f60a0e1b42878f98ca898587474f6d509c3a58ff4dd7f8b10905c200cf3170bdec725ee14a1ac8ebd1022509d3e499f5e72168eac43264d7246daa0bfc81a216ca97730b7e043cad8d8a9af5c443a5d15e9a88d82b6750c740eecfd63561712a185c69532b1a18b23513d7cf871f14d164ec544f22b6a8cc77d6fae5fc6e47eb64f08617098d229da78a378d6a0864684a7978f650c7922c907f97b0ebf2be29cc834ffe995c9636b310f4a8c2c5623c3b7b533518193d226923f111da1a0e8055b9053ad7f7504d194fbc3ae2b41cef30aa099624d5e229ffb56d5883a5a09163d22455cac52e37ee0ed5367b7c3bbcd4818a46b9b363b592c53c780eeae2c8b80a1d60d296614c998a9774f76453a58bc55d1c26bb10dc321c159858d7ba2f7855ba01aadf3585632c097e5471591dcc24d87e9b76509c10e2710310e4869de710ce0f484d326be751f8e9f765a685312423f1801aefb28dfe0c8f286432356d06857101a67a432497c5849111db2792fa0ee4ffff49a9124c152bcff82da1951258f989681e4f1338357f2c9f82333f6051b188f640bf200a0a75be1d35d2301e8d3813f7ba1926a28a0df05c21413cc0c4090c1e4ba4877dca8e129876c72ab3a801b4093320f5f685120680541d97889eea5dfcaf07a7ecb00c0ba0ae193969a4cddcbd753609a5304ea88783358ab0ae005c6af27bb58b2c4282186461ea50540845e2e2a2f4efced88c8ab9cd9fb4a226a265714c77ce7b79d1a40bd00b24cbba498dafde6bbad91686cf2e13e75669234bc342218887ba910ac81680122ddd36466e7e8a983d5a0fc18a6e9a386762c32132be08abe5554e334ec7d88734cfad9a378553b71222c55f6aa114392e015dfa2bb6cb4ad241c6bca82fdd0a00eb8d6b4afac61268130dea2807a97e4c0adc0e2be39abccbe64dca5c480e09c4bebb8b598e4f60afb0e92dce710859013b1ffa9c78fbf380160f31b1e72340dea86d353ff0e95884b72e2c2c10f6eff5f36c588ee845b7bc97c3b6bec4aa879dd0eb56b838b7bc2ec6e66a5b5517908197a67566dce7df421a8daedc98848c70d1d2c39b2f3538e6f17800bee3d3",
"df0000000108277148f2b916666000403a52317841946860def0d7829c06fec03ffe8b97f84e10116fafd93d1d2d39bbfda0d148778c21bb1e1667eb789b1ff70c2e3d557ed9c31570d20d",
"d00000000108277148f2b91666600044cd5e860e3fa7ac4769ec75d9b7d20f19e69265939a42afd3c4248a7f5210358a044f42869567e72a05642e96ddffa67bf24ec2d966d860c6accdee01d6917c8c43d4d089d8bb63ff848b617c13fbeefafcbb049ab0822a9ca7716c95af84d019b755b145dfe43c218555d1a7e047deda7d8db352a386b2b6d03f2e7f4510f47ff4ab199348dfa81c86bea5d09d7c7af4ef3f04e99fe4e6c21d53c4335407e27913129152033f17580f97d0345c8487a7ad329dc5c97b298ec7b80fee7813f1d6f94945a44ff662a69453c2dc7ac5e8a1cb90400e63818632d7f9654f140a61280df183b3d9f9b824e53d82f2c14ea3de89befdc79b84a4a3eb659a41db25622add94f2ad4b0d5977f1091aae0a4b83c7b41bca61c6c8d807ef02a8ce6240b76d442559a8b338b39418d27e99aff38840fc79a20995af65b3bbe1e3177074079a47578c51655a4016363364fd2c108d384e602deebd022da3c814549cd57d73c5bfc20e279045e2ad436fbd7e7c9e1985f0ec2f422e310e7aa8cfc48e637f9ac61d06d6482cb40b4376ff3c7abff3c3c26634689ae16d704bab1343d6413fc7b6c076eb0454eda2e0d1e077db40c922ebba6b0b1fa814e3ba76d8d6c4289abbd655f0cf5968eb2aba7131680b44da8910056a76647a6dfea95f27364a7ce694b8fbe19ebcb2a47e7350d33a36f7f5ca67af5e934f449125f4aae870a5b23b4370680ee02b194784d5d188ecdf58ae5454221406bde0ddd3e50d3363a564d6ca9fe0fb57d4df8716cb430cf553be573aa690e5645075ec74edd38cf23215bd50bcda0639dfbbe08dd6c476249e35da819ea6ccef808911b0eef6efaa4947244472795bc071d7154ed87e4a43575b3d61a551fcfccfb7ca3edaee9324f33f54dc9809747e59e24e79f256e8e72f01b8647f71c4b9dc260715fd9d83d3dbd9e124c432c04b3398e74efa3869fe129e368c15b6ca234a243fcac675adcc1db247e3f8485ac4a78f4a1ce2db3b437a1960b02f0c227901d165dcc05abdb3929a80dff2eaf72816185d4af4e28eea05430b736ddd2962e03ec64fa48649dd610e0e221c48f781b45cd9963c176126110e662369874e6a55f28039a23484c5c53714fc2d2030b48f1c895102ca9ad8acae1ec4eb0ae8d8bde31cd74fc515930078d22ad07dc3c7221ddbc4027c746207fa038b31080714091459c9a66ba4f5912d8d3905d3a9a47e4d8829a8110c96c0c9c81291c7985073808814109364df15b04520dc07e8d67cafcda71f0ca59423df5fadae92417a8661b3cdbbf6b1059780fb8b43eb4dcdacf731bb8db26294f978f6be7506b87d17a95367cdb83000565a4986e66dd60d0851f9b593d68790f8097434f62ea7a7396017c3c84754845d3a97f028cf8697d929a2826451653ccf84aba4d2f40fa530b258c13f08c6523c3c02d9669fd46b6a51f20ad323857d767150e3530a66bf88976dbadf99aeea549254c07e11e14085979b60f3b7e1728a4a2d7a35b0377c6501ae7d1d4bba338fb51a17ca8f7e698bd70cd01e8f30edf3e83591a2eb0038811e347bfcfab159b0d1ff6153e0f9ee4c129cfb7687e30b82eed74130c466eee06506dde50805b58c2acccd4cf4b2cc86c52fa2af602a8a7064eb9d90e1c568373b19e43ef4e7c1e4e1c9a58ccedf80a02a46ed64e68e72d4e75c7436e2bc0ba59f95a00456e5680af9e6cf4bf3a6d302ddaf8847cfd5ea606797",
"d30000000108277148f2b91666600043d24b66b2531ed9f9c13b07b2654186b0410a608592fdf728479734933197ec06a1cde860f36b3170fb2a9c85c62a7867ba6520dcb2d0ab2f6a484d9ebf8237d7a6f3c1fb16c1e0458ccf20e6d1b298a7530cea42636166027d92812915e76fbcc436a5e414147672dd7b0d19ff24513800e63cd86984f1c93ef1430bb848d37830eed61675d7c9999b92c6e5796d384554c74dd5a163de341ab309d6b0cb028aa08e56d79c60980d4a49a1c095456ca119fc3f04e496c93a084d017f60c6e031d6e9ad2e4fa699bb4b0c92fdcb44131129db0d30ce9efb740d3db0339127d9bdd1d4f677b1cb532a33647851ba9bb20bd8d6aa593271a85c3a9dc9835065663e61faa8dc6af209a0caf183d0fda3d4839d40edd5659dd053778642db8fba21f1f793e45c5c517e68bbef8543e3a727743c7bf87d047d441d13226b9021fac56904872774cf6768dc91db8ea489a244500e9e527acdc0088437357acf9397b014e66fef2db1248f9c6a578af07d7a02b1356fee02e27b8207e57633fa7bfd87ccd382e368c14b946aea780fcbe696d6e4fa3aa589184e104177db2fc3d91d4af120d9da3bdad021d003796b8261b590d8113f995dc1db4fac1c62cb68370d41cc87c982815017ae2143d5a469b742d019e5556d813877fec9d021cb37f80e5987d9f743c2b39093a34f6654164a8185a5caefbbef8ea17f62f6801a3fd89fae333c878cec9b25d10dfee2abca65d7c909ad2e4f11736d13b1642df4c5a0761f8f29f35f37def9ed327f4a9d8e53269fa6c7cedd0f4fd67d6cde81934e291d9fca695cc9745890cb54503e29e09f4a30f80e2f574bbbeedb7d20481c583d8362d22b2dbec09494095a043cdae283e86f905d8807f7b7c0f06ce968487bbca1e20b87245b68f24537a7c7e768c838f1bf26650afdabec2c0bb9736b345473f279c9b73ecf0d2c4aea49330ecfef0949ef7cb81861b05950ec0772db856365b136ba75d5509c01d7a970c84ebc77d8d5c3ceae1ef5f3079afc7d78965ffa3bc4c64ef1b4718ffb488a571528c83b615c43022616bb4c494c838b556df5ede711a688b0315c1ce6e2892247df582b7c3f2b06cac0bd8d670e2b581f074750596ba162189060b8af3dfc650ba3b45932edc4f94f08741d3072bfd1ef8159b27a7f3673a4fc504304c12116e3c2d7636c663c9fa1b2f5571be88769f33ccb94a09abd9c5a7dc8a8c2031bb2bc256b84aeb68a9abf7673151cec41b48bdd74f395a46acf30dae43e060e596bb2e739274210701ee9bb6cc3ff81ace751e375a01f17b3c5cc5f1234c488d69611bb27f6e3ee17e3c3843ebe4a280d6aa8ef017058a872810a437f85331adb3cb8d382650897b1b1589ee6",
"dd0000000108277148f2b9166660004053972df1beb451f73eb070e33ed63f681eb9b7e1e03f20baff3f54157598c7dd90a0de49850a3ccd6eb1b1cfc9dc6d3ba9ed1c0a19c69bf433da300d3cecc4ef151c44a721d680e3e3aaaf3eefec23091c5fde22",
"c90000000108277148f2b916666000404700403986db57eaa4b165be8ab9c95452bddb922eb35b7610a8e664f6b4620d870507c241290ce885c36d7672c51d94063bf893e01bc79e1d81bf023338da3d22f63bc7aa433f99448825ee873013b006b2a6c87c7581c7117bfeb4ec3d68405a68d9488f6d58474dd16539677e869812ead055e70d655a660062e17083995c0dbecd565c79800b11c8ca0c351ecffa61e707d62d443b3810bc60d4ef87aa99b979ff55ee1ea46b65436c15534e5315113138aed6daa9f04d3050d77a7e379c83b948d3797177c1793e59b2555423bd52595d93e293ea8ffa3c428c6dbba4e202d76933caf6a5609b0a4aa6cf4fd2aadb6505382381ef2d5b33efc43eba24c84b7805baf2ddab44a50180e5e6f2a31f9ea8089aef562d3b578a799d61befec99c016fadec3363f68a1be4ca1e13e8bdd2809a1dacc41134663e22f21978167c5ee8ef49652ae152fc6c1bcf52109cd3076cdd599cb43261941de7aed148d7d3e956cd615549a9647496f43f998daad4c841cc40ce1501fbfc152b957c94be558f6743061e312d746137db2ae6a44e181587dbf6b0d9508cef4aefd99ea5d3369898bd4c3df5e95ac89eaaba54019ffe0402b8f567c91b9371e80c621c67d3c831331acc063892bbd8a81cfc0498e78474b11e8c05dd8f540c449505342ae95f6281940aae973db35b8e31ff801f6bc8975f592538881ae9cc4cedcbdb39a784a9fe962a1f12be51c11b91d4dedf649bb5672dec8e03db97b0d69fce36edfbecb6836644bad1ab8e6d4e13644d9c3476db0e8a8eb4b5a5c32f7a5604c8e19700c53602839478531579cb4c4bb5cc969cf482f325dd837629318baf128920d9978e23296d7016e6c05c954f95881b4f9f7e43bcea393951e91af0e4a671400dc435bd2a1616c60618df2476d0ece060dbbda11e751e256956a0dbcd7e4a8d6d85a3319f22a2c5f26dad50e82f70f3dd91feff19c775aa60499a3b7daa57e344c07c3787e99d53303488801d2b17cdbfdee61ea3fc473f6c146f06eb60d70594a59e0ed79cee6ca4a5f78b037637ddab69fb8522c0f7bf37aa7f59cc7fa659e759db69966455944975cd22a1a1355f35a589a4978c8f3272e1c4f6793288a00ab879299aa6ad02d966e3dc67cee0c808b1a046458cff9bdac25a4071eb10038a6389a0ef7233003641bd4ee1efad0e9b2f693396a89ca0db3c05b6abfed3b246eb1b23a6b77e8b486f26d9c3dde9dd6f3637a8115940ed2ca762ca6320609f61c37ffc9c3f2f7a0f27edc9891c2eeac49ba258a0d09c35c4fe1dc52d4d9319aa9b1a271a5d8d2d3a75fef4d59fb04679ba526aecbd19d73f72fee537630444326e2543ce564c669bf378499738385dda9ac63521a1b91f580d0737a7326009f0ff0dcb05aa8b86222c934d9ddb4628e30b6e12ae370154ab39c605431b4c40683592afcfd6fccf35df9fe5850442595d24be3d9f4298bf3d541f09e7e71f552c88eed9642df46953622d5aea05b5060325304ec81c0447ac95b90f9da4359e3286938f06aea3d45030cb836be15b1c65e3edf44cbcfe2f01ef8d7209c69d7c81334c866ebee50e418a28336cea1982069b4df090eab81303761d1af337e083f1e0ad1440a02ef1eefb03506c39d2377807e335ee64bdb76527f786223cee5233299eda9fcb1d38f19c34480f790a328b0735f80908e3aa70086df828d56b6c79516f71a24c9d94f60335f86e9d29c0c5d3872b",
"dd0000000108277148f2b916666000406672db10ab41db38c01f7021709bac4d1659d872623eb5852b12b494535d13779a88d37e9685da572f6b2de35793a519a457493456ac4ee242933cf92d783f783656899c31832274bf1c26d24720d9d8ecfec598e19c58a478d2991dfc1cda3000f7bd7bd17e80",
"d60000000108277148f2b916666000404ed98b1b4ac35c0c0ef18c88adf08a6701ccb0876ea75aac8c128349936fa3cb6728e4e58de8673dd7dc8457b092957f26bc8194233bb81c7e78127844f9b833f196dc46c5cb4064c773f3c6e0bc73",
},
domain: "www.google.com",
},
} }
q, err := NewQuicSniffer(SnifferConfig{})
assert.NoError(t, err)
for _, test := range cases { for _, test := range cases {
pkt, err := hex.DecodeString(test.input) data, err := testQuicSniffer(test.input, true)
assert.NoError(t, err) assert.NoError(t, err)
oriPkt := bytes.Clone(pkt) assert.Equal(t, test.domain, data)
domain, err := q.SniffData(pkt)
data, err = testQuicSniffer(test.input, false)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, test.domain, domain) assert.Equal(t, test.domain, data)
assert.Equal(t, oriPkt, pkt) // ensure input data not changed
} }
} }

View File

@@ -10,6 +10,10 @@ type Sniffer interface {
SupportPort(port uint16) bool SupportPort(port uint16) bool
} }
type MultiPacketSniffer interface {
WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender
}
const ( const (
TLS Type = iota TLS Type = iota
HTTP HTTP

View File

@@ -449,6 +449,26 @@ proxies: # socks5
password: "shadow_tls_password" password: "shadow_tls_password"
version: 2 # support 1/2/3 version: 2 # support 1/2/3
- name: "ss5"
type: ss
server: server
port: 443
cipher: chacha20-ietf-poly1305
password: "password"
plugin: gost-plugin
plugin-opts:
mode: websocket
# tls: true # wss
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 配置指纹将实现 SSL Pining 效果
# fingerprint: xxxx
# skip-cert-verify: true
# host: bing.com
# path: "/"
# mux: true
# headers:
# custom: value
- name: "ss-restls-tls13" - name: "ss-restls-tls13"
type: ss type: ss
server: [YOUR_SERVER_IP] server: [YOUR_SERVER_IP]

View File

@@ -0,0 +1,81 @@
package gost
import (
"context"
"crypto/tls"
"net"
"net/http"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/transport/vmess"
smux "github.com/sagernet/smux"
)
// Option is options of gost websocket
type Option struct {
Host string
Port string
Path string
Headers map[string]string
TLS bool
SkipCertVerify bool
Fingerprint string
Mux bool
}
// NewGostWebsocket return a gost websocket
func NewGostWebsocket(ctx context.Context, conn net.Conn, option *Option) (net.Conn, error) {
header := http.Header{}
for k, v := range option.Headers {
header.Add(k, v)
}
config := &vmess.WebsocketConfig{
Host: option.Host,
Port: option.Port,
Path: option.Path,
Headers: header,
}
if option.TLS {
config.TLS = true
tlsConfig := &tls.Config{
ServerName: option.Host,
InsecureSkipVerify: option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
var err error
config.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
if host := config.Headers.Get("Host"); host != "" {
config.TLSConfig.ServerName = host
}
}
var err error
conn, err = vmess.StreamWebsocketConn(ctx, conn, config)
if err != nil {
return nil, err
}
if option.Mux {
config := smux.DefaultConfig()
config.KeepAliveDisabled = true
session, err := smux.Client(conn, config)
if err != nil {
return nil, err
}
stream, err := session.OpenStream()
if err != nil {
return nil, err
}
conn = stream
}
return conn, nil
}

View File

@@ -378,12 +378,14 @@ func handleUDPConn(packet C.PacketAdapter) {
return return
} }
if sniffingEnable && snifferDispatcher.Enable() {
snifferDispatcher.UDPSniff(packet)
}
key := packet.Key() key := packet.Key()
sender, loaded := natTable.GetOrCreate(key, newPacketSender) sender, loaded := natTable.GetOrCreate(key, func() C.PacketSender {
sender := newPacketSender()
if sniffingEnable && snifferDispatcher.Enable() {
return snifferDispatcher.UDPSniff(packet, sender)
}
return sender
})
if !loaded { if !loaded {
dial := func() (C.PacketConn, C.WriteBackProxy, error) { dial := func() (C.PacketConn, C.WriteBackProxy, error) {
if err := sender.ResolveUDP(metadata); err != nil { if err := sender.ResolveUDP(metadata); err != nil {

View File

@@ -1865,9 +1865,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.31" version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -1875,9 +1875,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.31" version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -1887,9 +1887,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.28" version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@@ -2086,7 +2086,7 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@@ -8128,9 +8128,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.39" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@@ -8503,9 +8503,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.12" version = "0.12.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@@ -8550,7 +8550,7 @@ dependencies = [
"wasm-streams", "wasm-streams",
"web-sys", "web-sys",
"webpki-roots", "webpki-roots",
"windows-registry 0.2.0", "windows-registry 0.4.0",
] ]
[[package]] [[package]]
@@ -12531,7 +12531,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@@ -12822,17 +12822,6 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-registry"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
dependencies = [
"windows-result 0.2.0",
"windows-strings 0.1.0",
"windows-targets 0.52.6",
]
[[package]] [[package]]
name = "windows-registry" name = "windows-registry"
version = "0.3.0" version = "0.3.0"
@@ -12844,6 +12833,17 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-registry"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
dependencies = [
"windows-result 0.3.1",
"windows-strings 0.3.1",
"windows-targets 0.53.0",
]
[[package]] [[package]]
name = "windows-registry" name = "windows-registry"
version = "0.5.0" version = "0.5.0"

View File

@@ -57,7 +57,7 @@
"@monaco-editor/react": "4.7.0", "@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.67.3", "@tanstack/react-query": "5.67.3",
"@tanstack/react-router": "1.114.17", "@tanstack/react-router": "1.114.17",
"@tanstack/router-devtools": "1.114.17", "@tanstack/router-devtools": "1.114.18",
"@tanstack/router-plugin": "1.114.17", "@tanstack/router-plugin": "1.114.17",
"@tauri-apps/plugin-clipboard-manager": "2.2.1", "@tauri-apps/plugin-clipboard-manager": "2.2.1",
"@tauri-apps/plugin-dialog": "2.2.0", "@tauri-apps/plugin-dialog": "2.2.0",

View File

@@ -2,7 +2,7 @@
"manifest_version": 1, "manifest_version": 1,
"latest": { "latest": {
"mihomo": "v1.19.3", "mihomo": "v1.19.3",
"mihomo_alpha": "alpha-f318b80", "mihomo_alpha": "alpha-0ed159e",
"clash_rs": "v0.7.6", "clash_rs": "v0.7.6",
"clash_premium": "2023-09-05-gdcc8d87", "clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.7.6-alpha+sha.3f14cba" "clash_rs_alpha": "0.7.6-alpha+sha.3f14cba"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
} }
}, },
"updated_at": "2025-03-11T22:20:51.411Z" "updated_at": "2025-03-12T22:22:30.010Z"
} }

View File

@@ -345,8 +345,8 @@ importers:
specifier: 1.114.17 specifier: 1.114.17
version: 1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0) version: 1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tanstack/router-devtools': '@tanstack/router-devtools':
specifier: 1.114.17 specifier: 1.114.18
version: 1.114.17(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@tanstack/router-devtools-core@1.114.3(@tanstack/router-core@1.114.17)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3))(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) version: 1.114.18(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@tanstack/router-core@1.114.17)(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tiny-invariant@1.3.3)
'@tanstack/router-plugin': '@tanstack/router-plugin':
specifier: 1.114.17 specifier: 1.114.17
version: 1.114.17(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0)) version: 1.114.17(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(less@4.2.0)(lightningcss@1.29.1)(sass-embedded@1.85.1)(sass@1.83.0)(stylus@0.62.0)(terser@5.36.0)(tsx@4.19.3)(yaml@2.7.0))
@@ -2742,12 +2742,11 @@ packages:
peerDependencies: peerDependencies:
react: ^18 || ^19 react: ^18 || ^19
'@tanstack/react-router-devtools@1.114.17': '@tanstack/react-router-devtools@1.114.18':
resolution: {integrity: sha512-/Of2oJGuWrVEXrFI8w+aPGkKhAxz8ATg1dT7dHWNwaISFOODHQGOk4kXd6nnzZNckIVGTBsAkHgQXvJ59GK43w==} resolution: {integrity: sha512-axyf1gR9amSg74RQXA9A0FgK9Y+sNm4JIGdhOqj9Bm2i2uzy3OYrKPZU/WVyP5Qq4oYVma2toZ7B2y3LlaGnCw==}
engines: {node: '>=12'} engines: {node: '>=12'}
peerDependencies: peerDependencies:
'@tanstack/react-router': ^1.114.17 '@tanstack/react-router': ^1.114.17
'@tanstack/router-devtools-core': ^1.114.17
react: '>=18.0.0 || >=19.0.0' react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0'
@@ -2781,11 +2780,11 @@ packages:
resolution: {integrity: sha512-t7ww8LZR0LH96TD9OEupuloyWGDmLGEG20rW1Adve/a5b+u4yap/Vf+aznzrMT+GZ/zyTWLJy2zQg+DjEEUMPA==} resolution: {integrity: sha512-t7ww8LZR0LH96TD9OEupuloyWGDmLGEG20rW1Adve/a5b+u4yap/Vf+aznzrMT+GZ/zyTWLJy2zQg+DjEEUMPA==}
engines: {node: '>=12'} engines: {node: '>=12'}
'@tanstack/router-devtools-core@1.114.3': '@tanstack/router-devtools-core@1.114.17':
resolution: {integrity: sha512-jhcxHlB+AlGz/sUQFxfQauaSvn2cRCteXPuQXJOKucUJN8bShPJ4ZF1AOO9Q/FuB5h0eiemolbNMf0zvGin6Nw==} resolution: {integrity: sha512-DOUvqXSI/GDhkPT9r9ltoTVvgBHSLIdR9f3k6Eg9fNd5Co3gBCrjWLuS9vbvMq/4xaTD6UbRzNWxaycZOF7NMw==}
engines: {node: '>=12'} engines: {node: '>=12'}
peerDependencies: peerDependencies:
'@tanstack/router-core': ^1.114.3 '@tanstack/router-core': ^1.114.17
csstype: ^3.0.10 csstype: ^3.0.10
solid-js: '>=1.9.5' solid-js: '>=1.9.5'
tiny-invariant: ^1.3.3 tiny-invariant: ^1.3.3
@@ -2793,8 +2792,8 @@ packages:
csstype: csstype:
optional: true optional: true
'@tanstack/router-devtools@1.114.17': '@tanstack/router-devtools@1.114.18':
resolution: {integrity: sha512-mRvatu8YTcaa459GV85NonuT52v/NVsGjQktsX1HSan+tn2f5j/A0JUQwinvk3/YFil6LLpT/D+OwMQgpW8rPw==} resolution: {integrity: sha512-SN7nEXWSqgieN1KVr4g7CSaQKRuTj994re45PRqyNrwR9dIMoHiLdhGV8cTpMqwQRbcyB54Vw7W8hCt6D/FmwQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
peerDependencies: peerDependencies:
'@tanstack/react-router': ^1.114.17 '@tanstack/react-router': ^1.114.17
@@ -10481,13 +10480,17 @@ snapshots:
'@tanstack/query-core': 5.67.3 '@tanstack/query-core': 5.67.3
react: 19.0.0 react: 19.0.0
'@tanstack/react-router-devtools@1.114.17(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@tanstack/router-devtools-core@1.114.3(@tanstack/router-core@1.114.17)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': '@tanstack/react-router-devtools@1.114.18(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@tanstack/router-core@1.114.17)(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tiny-invariant@1.3.3)':
dependencies: dependencies:
'@tanstack/react-router': 1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tanstack/react-router': 1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tanstack/router-devtools-core': 1.114.3(@tanstack/router-core@1.114.17)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) '@tanstack/router-devtools-core': 1.114.17(@tanstack/router-core@1.114.17)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)
react: 19.0.0 react: 19.0.0
react-dom: 19.0.0(react@19.0.0) react-dom: 19.0.0(react@19.0.0)
solid-js: 1.9.5 solid-js: 1.9.5
transitivePeerDependencies:
- '@tanstack/router-core'
- csstype
- tiny-invariant
'@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': '@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies: dependencies:
@@ -10524,7 +10527,7 @@ snapshots:
'@tanstack/history': 1.114.12 '@tanstack/history': 1.114.12
'@tanstack/store': 0.7.0 '@tanstack/store': 0.7.0
'@tanstack/router-devtools-core@1.114.3(@tanstack/router-core@1.114.17)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)': '@tanstack/router-devtools-core@1.114.17(@tanstack/router-core@1.114.17)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3)':
dependencies: dependencies:
'@tanstack/router-core': 1.114.17 '@tanstack/router-core': 1.114.17
clsx: 2.1.1 clsx: 2.1.1
@@ -10534,10 +10537,10 @@ snapshots:
optionalDependencies: optionalDependencies:
csstype: 3.1.3 csstype: 3.1.3
'@tanstack/router-devtools@1.114.17(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@tanstack/router-devtools-core@1.114.3(@tanstack/router-core@1.114.17)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3))(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': '@tanstack/router-devtools@1.114.18(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@tanstack/router-core@1.114.17)(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tiny-invariant@1.3.3)':
dependencies: dependencies:
'@tanstack/react-router': 1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tanstack/react-router': 1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@tanstack/react-router-devtools': 1.114.17(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@tanstack/router-devtools-core@1.114.3(@tanstack/router-core@1.114.17)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3))(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@tanstack/react-router-devtools': 1.114.18(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@tanstack/router-core@1.114.17)(csstype@3.1.3)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(tiny-invariant@1.3.3)
clsx: 2.1.1 clsx: 2.1.1
goober: 2.1.16(csstype@3.1.3) goober: 2.1.16(csstype@3.1.3)
react: 19.0.0 react: 19.0.0
@@ -10545,7 +10548,8 @@ snapshots:
optionalDependencies: optionalDependencies:
csstype: 3.1.3 csstype: 3.1.3
transitivePeerDependencies: transitivePeerDependencies:
- '@tanstack/router-devtools-core' - '@tanstack/router-core'
- tiny-invariant
'@tanstack/router-generator@1.114.17(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))': '@tanstack/router-generator@1.114.17(@tanstack/react-router@1.114.17(react-dom@19.0.0(react@19.0.0))(react@19.0.0))':
dependencies: dependencies:

View File

@@ -1 +1,16 @@
#!/bin/bash
pnpm pretty-quick --staged pnpm pretty-quick --staged
# 运行 clippy fmt
cargo fmt --manifest-path ./src-tauri/Cargo.toml
if [ $? -ne 0 ]; then
echo "rustfmt failed to format the code. Please fix the issues and try again."
exit 1
fi
git add .
# 允许提交
exit 0

View File

@@ -0,0 +1,13 @@
#!/bin/bash
# 运行 clippy
cargo clippy --manifest-path ./src-tauri/Cargo.toml --fix
# 如果 clippy 失败,阻止 push
if [ $? -ne 0 ]; then
echo "Clippy found issues in sub_crate. Please fix them before pushing."
exit 1
fi
# 允许 push
exit 0

View File

@@ -0,0 +1 @@
avoid-breaking-exported-api = true

View File

@@ -75,40 +75,41 @@ pub fn get_app_dir() -> CmdResult<String> {
pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> { pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String> {
let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache"); let icon_cache_dir = wrap_err!(dirs::app_home_dir())?.join("icons").join("cache");
let icon_path = icon_cache_dir.join(&name); let icon_path = icon_cache_dir.join(&name);
// 如果文件已存在,直接返回路径 // 如果文件已存在,直接返回路径
if icon_path.exists() { if icon_path.exists() {
return Ok(icon_path.to_string_lossy().to_string()); return Ok(icon_path.to_string_lossy().to_string());
} }
// 确保缓存目录存在 // 确保缓存目录存在
if !icon_cache_dir.exists() { if !icon_cache_dir.exists() {
let _ = std::fs::create_dir_all(&icon_cache_dir); let _ = std::fs::create_dir_all(&icon_cache_dir);
} }
// 使用临时文件名来下载 // 使用临时文件名来下载
let temp_path = icon_cache_dir.join(format!("{}.downloading", &name)); let temp_path = icon_cache_dir.join(format!("{}.downloading", &name));
// 下载文件到临时位置 // 下载文件到临时位置
let response = wrap_err!(reqwest::get(&url).await)?; let response = wrap_err!(reqwest::get(&url).await)?;
// 检查内容类型是否为图片 // 检查内容类型是否为图片
let content_type = response.headers() let content_type = response
.headers()
.get(reqwest::header::CONTENT_TYPE) .get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok()) .and_then(|v| v.to_str().ok())
.unwrap_or(""); .unwrap_or("");
let is_image = content_type.starts_with("image/"); let is_image = content_type.starts_with("image/");
// 获取响应内容 // 获取响应内容
let content = wrap_err!(response.bytes().await)?; let content = wrap_err!(response.bytes().await)?;
// 检查内容是否为HTML (针对CDN错误页面) // 检查内容是否为HTML (针对CDN错误页面)
let is_html = content.len() > 15 && let is_html = content.len() > 15
(content.starts_with(b"<!DOCTYPE html") || && (content.starts_with(b"<!DOCTYPE html")
content.starts_with(b"<html") || || content.starts_with(b"<html")
content.starts_with(b"<?xml")); || content.starts_with(b"<?xml"));
// 只有当内容确实是图片时才保存 // 只有当内容确实是图片时才保存
if is_image && !is_html { if is_image && !is_html {
{ {
@@ -122,14 +123,14 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
} }
} }
}; };
wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?; wrap_err!(std::io::copy(&mut content.as_ref(), &mut file))?;
} }
// 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件 // 再次检查目标文件是否已存在,避免重命名覆盖其他线程已完成的文件
if !icon_path.exists() { if !icon_path.exists() {
match std::fs::rename(&temp_path, &icon_path) { match std::fs::rename(&temp_path, &icon_path) {
Ok(_) => {}, Ok(_) => {}
Err(_) => { Err(_) => {
let _ = std::fs::remove_file(&temp_path); let _ = std::fs::remove_file(&temp_path);
if icon_path.exists() { if icon_path.exists() {
@@ -140,11 +141,11 @@ pub async fn download_icon_cache(url: String, name: String) -> CmdResult<String>
} else { } else {
let _ = std::fs::remove_file(&temp_path); let _ = std::fs::remove_file(&temp_path);
} }
Ok(icon_path.to_string_lossy().to_string()) Ok(icon_path.to_string_lossy().to_string())
} else { } else {
let _ = std::fs::remove_file(&temp_path); let _ = std::fs::remove_file(&temp_path);
Err(format!("下载的内容不是有效图片: {}", url).into()) Err(format!("下载的内容不是有效图片: {}", url))
} }
} }
@@ -158,8 +159,7 @@ pub struct IconInfo {
/// 复制图标文件 /// 复制图标文件
#[tauri::command] #[tauri::command]
pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> { pub fn copy_icon_file(path: String, icon_info: IconInfo) -> CmdResult<String> {
use std::fs; use std::{fs, path::Path};
use std::path::Path;
let file_path = Path::new(&path); let file_path = Path::new(&path);

View File

@@ -24,7 +24,8 @@ pub async fn patch_clash_config(payload: Mapping) -> CmdResult {
/// 修改Clash模式 /// 修改Clash模式
#[tauri::command] #[tauri::command]
pub async fn patch_clash_mode(payload: String) -> CmdResult { pub async fn patch_clash_mode(payload: String) -> CmdResult {
Ok(feat::change_clash_mode(payload)) feat::change_clash_mode(payload);
Ok(())
} }
/// 切换Clash核心 /// 切换Clash核心
@@ -98,9 +99,11 @@ pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
/// 应用或撤销DNS配置 /// 应用或撤销DNS配置
#[tauri::command] #[tauri::command]
pub fn apply_dns_config(apply: bool) -> CmdResult { pub fn apply_dns_config(apply: bool) -> CmdResult {
use crate::config::Config; use crate::{
use crate::core::{handle, CoreManager}; config::Config,
use crate::utils::dirs; core::{handle, CoreManager},
utils::dirs,
};
use tauri::async_runtime; use tauri::async_runtime;
// 使用spawn来处理异步操作 // 使用spawn来处理异步操作

View File

@@ -4,29 +4,29 @@ use anyhow::Result;
pub type CmdResult<T = ()> = Result<T, String>; pub type CmdResult<T = ()> = Result<T, String>;
// Command modules // Command modules
pub mod profile;
pub mod validate;
pub mod uwp;
pub mod webdav;
pub mod app; pub mod app;
pub mod network;
pub mod clash; pub mod clash;
pub mod verge; pub mod network;
pub mod profile;
pub mod proxy;
pub mod runtime; pub mod runtime;
pub mod save_profile; pub mod save_profile;
pub mod system; pub mod system;
pub mod proxy; pub mod uwp;
pub mod validate;
pub mod verge;
pub mod webdav;
// Re-export all command functions for backwards compatibility // Re-export all command functions for backwards compatibility
pub use profile::*;
pub use validate::*;
pub use uwp::*;
pub use webdav::*;
pub use app::*; pub use app::*;
pub use network::*;
pub use clash::*; pub use clash::*;
pub use verge::*; pub use network::*;
pub use profile::*;
pub use proxy::*;
pub use runtime::*; pub use runtime::*;
pub use save_profile::*; pub use save_profile::*;
pub use system::*; pub use system::*;
pub use proxy::*; pub use uwp::*;
pub use validate::*;
pub use verge::*;
pub use webdav::*;

View File

@@ -1,8 +1,8 @@
use crate::wrap_err;
use super::CmdResult; use super::CmdResult;
use sysproxy::{Autoproxy, Sysproxy}; use crate::wrap_err;
use serde_yaml::Mapping;
use network_interface::NetworkInterface; use network_interface::NetworkInterface;
use serde_yaml::Mapping;
use sysproxy::{Autoproxy, Sysproxy};
/// get the system proxy /// get the system proxy
#[tauri::command] #[tauri::command]
@@ -46,8 +46,7 @@ pub fn get_network_interfaces() -> Vec<String> {
/// 获取网络接口详细信息 /// 获取网络接口详细信息
#[tauri::command] #[tauri::command]
pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> { pub fn get_network_interfaces_info() -> CmdResult<Vec<NetworkInterface>> {
use network_interface::NetworkInterface; use network_interface::{NetworkInterface, NetworkInterfaceConfig};
use network_interface::NetworkInterfaceConfig;
let names = get_network_interfaces(); let names = get_network_interfaces();
let interfaces = wrap_err!(NetworkInterface::show())?; let interfaces = wrap_err!(NetworkInterface::show())?;

View File

@@ -1,11 +1,11 @@
use super::CmdResult;
use crate::{ use crate::{
config::*, config::*,
core::*, core::*,
feat, feat, log_err, ret_err,
utils::{dirs, help}, utils::{dirs, help},
log_err, ret_err, wrap_err, wrap_err,
}; };
use super::CmdResult;
/// 获取配置文件列表 /// 获取配置文件列表
#[tauri::command] #[tauri::command]
@@ -31,7 +31,7 @@ pub async fn enhance_profiles() -> CmdResult {
} }
Err(e) => { Err(e) => {
println!("[enhance_profiles] 更新过程发生错误: {}", e); println!("[enhance_profiles] 更新过程发生错误: {}", e);
handle::Handle::notice_message("config_validate::process_terminated", &e.to_string()); handle::Handle::notice_message("config_validate::process_terminated", e.to_string());
Ok(()) Ok(())
} }
} }
@@ -76,19 +76,17 @@ pub async fn delete_profile(index: String) -> CmdResult {
/// 修改profiles的配置 /// 修改profiles的配置
#[tauri::command] #[tauri::command]
pub async fn patch_profiles_config( pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult<bool> {
profiles: IProfiles
) -> CmdResult<bool> {
println!("[cmd配置patch] 开始修改配置文件"); println!("[cmd配置patch] 开始修改配置文件");
// 保存当前配置,以便在验证失败时恢复 // 保存当前配置,以便在验证失败时恢复
let current_profile = Config::profiles().latest().current.clone(); let current_profile = Config::profiles().latest().current.clone();
println!("[cmd配置patch] 当前配置: {:?}", current_profile); println!("[cmd配置patch] 当前配置: {:?}", current_profile);
// 更新profiles配置 // 更新profiles配置
println!("[cmd配置patch] 正在更新配置草稿"); println!("[cmd配置patch] 正在更新配置草稿");
wrap_err!({ Config::profiles().draft().patch_config(profiles) })?; wrap_err!({ Config::profiles().draft().patch_config(profiles) })?;
// 更新配置并进行验证 // 更新配置并进行验证
match CoreManager::global().update_config().await { match CoreManager::global().update_config().await {
Ok((true, _)) => { Ok((true, _)) => {
@@ -102,7 +100,7 @@ pub async fn patch_profiles_config(
Ok((false, error_msg)) => { Ok((false, error_msg)) => {
println!("[cmd配置patch] 配置验证失败: {}", error_msg); println!("[cmd配置patch] 配置验证失败: {}", error_msg);
Config::profiles().discard(); Config::profiles().discard();
// 如果验证失败,恢复到之前的配置 // 如果验证失败,恢复到之前的配置
if let Some(prev_profile) = current_profile { if let Some(prev_profile) = current_profile {
println!("[cmd配置patch] 尝试恢复到之前的配置: {}", prev_profile); println!("[cmd配置patch] 尝试恢复到之前的配置: {}", prev_profile);
@@ -124,7 +122,7 @@ pub async fn patch_profiles_config(
Err(e) => { Err(e) => {
println!("[cmd配置patch] 更新过程发生错误: {}", e); println!("[cmd配置patch] 更新过程发生错误: {}", e);
Config::profiles().discard(); Config::profiles().discard();
handle::Handle::notice_message("config_validate::boot_error", &e.to_string()); handle::Handle::notice_message("config_validate::boot_error", e.to_string());
Ok(false) Ok(false)
} }
} }
@@ -134,9 +132,12 @@ pub async fn patch_profiles_config(
#[tauri::command] #[tauri::command]
pub async fn patch_profiles_config_by_profile_index( pub async fn patch_profiles_config_by_profile_index(
_app_handle: tauri::AppHandle, _app_handle: tauri::AppHandle,
profile_index: String profile_index: String,
) -> CmdResult<bool> { ) -> CmdResult<bool> {
let profiles = IProfiles{current: Some(profile_index), items: None}; let profiles = IProfiles {
current: Some(profile_index),
items: None,
};
patch_profiles_config(profiles).await patch_profiles_config(profiles).await
} }

View File

@@ -1,11 +1,8 @@
use crate::{
config::*,
wrap_err,
};
use super::CmdResult; use super::CmdResult;
use crate::{config::*, wrap_err};
use anyhow::Context; use anyhow::Context;
use std::collections::HashMap;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::collections::HashMap;
/// 获取运行时配置 /// 获取运行时配置
#[tauri::command] #[tauri::command]

View File

@@ -1,10 +1,5 @@
use crate::{
config::*,
core::*,
utils::dirs,
wrap_err,
};
use super::CmdResult; use super::CmdResult;
use crate::{config::*, core::*, utils::dirs, wrap_err};
use std::fs; use std::fs;
/// 保存profiles的配置 /// 保存profiles的配置
@@ -20,7 +15,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
let profiles_guard = profiles.latest(); let profiles_guard = profiles.latest();
let item = wrap_err!(profiles_guard.get_item(&index))?; let item = wrap_err!(profiles_guard.get_item(&index))?;
// 确定是否为merge类型文件 // 确定是否为merge类型文件
let is_merge = item.itype.as_ref().map_or(false, |t| t == "merge"); let is_merge = item.itype.as_ref().is_some_and(|t| t == "merge");
let content = wrap_err!(item.read_file())?; let content = wrap_err!(item.read_file())?;
let path = item.file.clone().ok_or("file field is null")?; let path = item.file.clone().ok_or("file field is null")?;
let profiles_dir = wrap_err!(dirs::app_profiles_dir())?; let profiles_dir = wrap_err!(dirs::app_profiles_dir())?;
@@ -29,14 +24,20 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
// 保存新的配置文件 // 保存新的配置文件
wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?; wrap_err!(fs::write(&file_path, file_data.clone().unwrap()))?;
let file_path_str = file_path.to_string_lossy().to_string(); let file_path_str = file_path.to_string_lossy().to_string();
println!("[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}", file_path_str, is_merge_file); println!(
"[cmd配置save] 开始验证配置文件: {}, 是否为merge文件: {}",
file_path_str, is_merge_file
);
// 对于 merge 文件,只进行语法验证,不进行后续内核验证 // 对于 merge 文件,只进行语法验证,不进行后续内核验证
if is_merge_file { if is_merge_file {
println!("[cmd配置save] 检测到merge文件只进行语法验证"); println!("[cmd配置save] 检测到merge文件只进行语法验证");
match CoreManager::global().validate_config_file(&file_path_str, Some(true)).await { match CoreManager::global()
.validate_config_file(&file_path_str, Some(true))
.await
{
Ok((true, _)) => { Ok((true, _)) => {
println!("[cmd配置save] merge文件语法验证通过"); println!("[cmd配置save] merge文件语法验证通过");
// 成功后尝试更新整体配置 // 成功后尝试更新整体配置
@@ -63,9 +64,12 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
} }
} }
} }
// 非merge文件使用完整验证流程 // 非merge文件使用完整验证流程
match CoreManager::global().validate_config_file(&file_path_str, None).await { match CoreManager::global()
.validate_config_file(&file_path_str, None)
.await
{
Ok((true, _)) => { Ok((true, _)) => {
println!("[cmd配置save] 验证成功"); println!("[cmd配置save] 验证成功");
Ok(()) Ok(())
@@ -74,16 +78,17 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
println!("[cmd配置save] 验证失败: {}", error_msg); println!("[cmd配置save] 验证失败: {}", error_msg);
// 恢复原始配置文件 // 恢复原始配置文件
wrap_err!(fs::write(&file_path, original_content))?; wrap_err!(fs::write(&file_path, original_content))?;
// 智能判断错误类型 // 智能判断错误类型
let is_script_error = file_path_str.ends_with(".js") || let is_script_error = file_path_str.ends_with(".js")
error_msg.contains("Script syntax error") || || error_msg.contains("Script syntax error")
error_msg.contains("Script must contain a main function") || || error_msg.contains("Script must contain a main function")
error_msg.contains("Failed to read script file"); || error_msg.contains("Failed to read script file");
if error_msg.contains("YAML syntax error") || if error_msg.contains("YAML syntax error")
error_msg.contains("Failed to read file:") || || error_msg.contains("Failed to read file:")
(!file_path_str.ends_with(".js") && !is_script_error) { || (!file_path_str.ends_with(".js") && !is_script_error)
{
// 普通YAML错误使用YAML通知处理 // 普通YAML错误使用YAML通知处理
println!("[cmd配置save] YAML配置文件验证失败发送通知"); println!("[cmd配置save] YAML配置文件验证失败发送通知");
let result = (false, error_msg.clone()); let result = (false, error_msg.clone());
@@ -98,7 +103,7 @@ pub async fn save_profile_file(index: String, file_data: Option<String>) -> CmdR
println!("[cmd配置save] 其他类型验证失败,发送一般通知"); println!("[cmd配置save] 其他类型验证失败,发送一般通知");
handle::Handle::notice_message("config_validate::error", &error_msg); handle::Handle::notice_message("config_validate::error", &error_msg);
} }
Ok(()) Ok(())
} }
Err(e) => { Err(e) => {

View File

@@ -1,8 +1,10 @@
use super::CmdResult; use super::CmdResult;
use crate::core::handle; use crate::{
use crate::module::sysinfo::PlatformSpecification; core::{self, handle, service, CoreManager},
module::sysinfo::PlatformSpecification,
wrap_err,
};
use tauri_plugin_clipboard_manager::ClipboardExt; use tauri_plugin_clipboard_manager::ClipboardExt;
use crate::{core::{self, CoreManager, service}, wrap_err};
#[tauri::command] #[tauri::command]
pub async fn export_diagnostic_info() -> CmdResult<()> { pub async fn export_diagnostic_info() -> CmdResult<()> {
@@ -11,8 +13,7 @@ pub async fn export_diagnostic_info() -> CmdResult<()> {
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
let cliboard = app_handle.clipboard(); let cliboard = app_handle.clipboard();
if cliboard.write_text(info).is_err() {
if let Err(_) = cliboard.write_text(info) {
log::error!(target: "app", "Failed to write to clipboard"); log::error!(target: "app", "Failed to write to clipboard");
} }
Ok(()) Ok(())

View File

@@ -4,8 +4,7 @@ use super::CmdResult;
#[cfg(windows)] #[cfg(windows)]
mod platform { mod platform {
use super::CmdResult; use super::CmdResult;
use crate::core::win_uwp; use crate::{core::win_uwp, wrap_err};
use crate::wrap_err;
pub async fn invoke_uwp_tool() -> CmdResult { pub async fn invoke_uwp_tool() -> CmdResult {
wrap_err!(win_uwp::invoke_uwptools().await) wrap_err!(win_uwp::invoke_uwptools().await)

View File

@@ -1,5 +1,5 @@
use crate::core::*;
use super::CmdResult; use super::CmdResult;
use crate::core::*;
/// 发送脚本验证通知消息 /// 发送脚本验证通知消息
#[tauri::command] #[tauri::command]
@@ -13,7 +13,7 @@ pub async fn script_validate_notice(status: String, msg: String) -> CmdResult {
pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) { pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 { if !result.0 {
let error_msg = &result.1; let error_msg = &result.1;
// 根据错误消息内容判断错误类型 // 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") { let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found" "config_validate::file_not_found"
@@ -27,7 +27,7 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
// 如果是其他类型错误,作为一般脚本错误处理 // 如果是其他类型错误,作为一般脚本错误处理
"config_validate::script_error" "config_validate::script_error"
}; };
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg); log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
handle::Handle::notice_message(status, error_msg); handle::Handle::notice_message(status, error_msg);
} }
@@ -37,12 +37,15 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str)
#[tauri::command] #[tauri::command]
pub async fn validate_script_file(file_path: String) -> CmdResult<bool> { pub async fn validate_script_file(file_path: String) -> CmdResult<bool> {
log::info!(target: "app", "验证脚本文件: {}", file_path); log::info!(target: "app", "验证脚本文件: {}", file_path);
match CoreManager::global().validate_config_file(&file_path, None).await { match CoreManager::global()
.validate_config_file(&file_path, None)
.await
{
Ok(result) => { Ok(result) => {
handle_script_validation_notice(&result, "脚本文件"); handle_script_validation_notice(&result, "脚本文件");
Ok(result.0) // 返回验证结果布尔值 Ok(result.0) // 返回验证结果布尔值
}, }
Err(e) => { Err(e) => {
let error_msg = e.to_string(); let error_msg = e.to_string();
log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg); log::error!(target: "app", "验证脚本文件过程发生错误: {}", error_msg);
@@ -58,10 +61,10 @@ pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
if !result.0 { if !result.0 {
let error_msg = &result.1; let error_msg = &result.1;
println!("[通知] 处理{}验证错误: {}", file_type, error_msg); println!("[通知] 处理{}验证错误: {}", file_type, error_msg);
// 检查是否为merge文件 // 检查是否为merge文件
let is_merge_file = file_type.contains("合并"); let is_merge_file = file_type.contains("合并");
// 根据错误消息内容判断错误类型 // 根据错误消息内容判断错误类型
let status = if error_msg.starts_with("File not found:") { let status = if error_msg.starts_with("File not found:") {
"config_validate::file_not_found" "config_validate::file_not_found"
@@ -93,7 +96,7 @@ pub fn handle_yaml_validation_notice(result: &(bool, String), file_type: &str) {
"config_validate::yaml_error" "config_validate::yaml_error"
} }
}; };
log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg); log::warn!(target: "app", "{} 验证失败: {}", file_type, error_msg);
println!("[通知] 发送通知: status={}, msg={}", status, error_msg); println!("[通知] 发送通知: status={}, msg={}", status, error_msg);
handle::Handle::notice_message(status, error_msg); handle::Handle::notice_message(status, error_msg);

View File

@@ -1,9 +1,5 @@
use crate::{
config::*,
feat,
wrap_err,
};
use super::CmdResult; use super::CmdResult;
use crate::{config::*, feat, wrap_err};
/// 获取Verge配置 /// 获取Verge配置
#[tauri::command] #[tauri::command]

View File

@@ -1,10 +1,5 @@
use crate::{
core,
config::*,
feat,
wrap_err,
};
use super::CmdResult; use super::CmdResult;
use crate::{config::*, core, feat, wrap_err};
use reqwest_dav::list_cmd::ListFile; use reqwest_dav::list_cmd::ListFile;
/// 保存 WebDAV 配置 /// 保存 WebDAV 配置

View File

@@ -1,9 +1,9 @@
use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge}; use super::{Draft, IClashTemp, IProfiles, IRuntime, IVerge};
use crate::{ use crate::{
config::PrfItem, config::PrfItem,
core::{handle, CoreManager},
enhance, enhance,
utils::{dirs, help}, utils::{dirs, help},
core::{handle, CoreManager},
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
@@ -73,14 +73,17 @@ impl Config {
// 生成运行时配置文件并验证 // 生成运行时配置文件并验证
let config_result = Self::generate_file(ConfigType::Run); let config_result = Self::generate_file(ConfigType::Run);
let validation_result = if let Ok(_) = config_result { let validation_result = if config_result.is_ok() {
// 验证配置文件 // 验证配置文件
println!("[首次启动] 开始验证配置"); println!("[首次启动] 开始验证配置");
match CoreManager::global().validate_config().await { match CoreManager::global().validate_config().await {
Ok((is_valid, error_msg)) => { Ok((is_valid, error_msg)) => {
if !is_valid { if !is_valid {
println!("[首次启动] 配置验证失败,使用默认最小配置启动: {}", error_msg); println!(
"[首次启动] 配置验证失败,使用默认最小配置启动: {}",
error_msg
);
CoreManager::global() CoreManager::global()
.use_default_config("config_validate::boot_error", &error_msg) .use_default_config("config_validate::boot_error", &error_msg)
.await?; .await?;
@@ -101,10 +104,7 @@ impl Config {
} else { } else {
println!("[首次启动] 生成配置文件失败,使用默认配置"); println!("[首次启动] 生成配置文件失败,使用默认配置");
CoreManager::global() CoreManager::global()
.use_default_config( .use_default_config("config_validate::error", "")
"config_validate::error",
"",
)
.await?; .await?;
Some(("config_validate::error", String::new())) Some(("config_validate::error", String::new()))
}; };

View File

@@ -8,14 +8,9 @@ mod profiles;
mod runtime; mod runtime;
mod verge; mod verge;
pub use self::clash::*; pub use self::{
pub use self::config::*; clash::*, config::*, draft::*, encrypt::*, prfitem::*, profiles::*, runtime::*, verge::*,
pub use self::draft::*; };
pub use self::encrypt::*;
pub use self::prfitem::*;
pub use self::profiles::*;
pub use self::runtime::*;
pub use self::verge::*;
pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) { pub const DEFAULT_PAC: &str = r#"function FindProxyForURL(url, host) {
return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;"; return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;";

View File

@@ -234,10 +234,10 @@ impl PrfItem {
option: Option<PrfOption>, option: Option<PrfOption>,
) -> Result<PrfItem> { ) -> Result<PrfItem> {
let opt_ref = option.as_ref(); let opt_ref = option.as_ref();
let with_proxy = opt_ref.map_or(false, |o| o.with_proxy.unwrap_or(false)); let with_proxy = opt_ref.is_some_and(|o| o.with_proxy.unwrap_or(false));
let self_proxy = opt_ref.map_or(false, |o| o.self_proxy.unwrap_or(false)); let self_proxy = opt_ref.is_some_and(|o| o.self_proxy.unwrap_or(false));
let accept_invalid_certs = let accept_invalid_certs =
opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false)); opt_ref.is_some_and(|o| o.danger_accept_invalid_certs.unwrap_or(false));
let user_agent = opt_ref.and_then(|o| o.user_agent.clone()); let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
let update_interval = opt_ref.and_then(|o| o.update_interval); let update_interval = opt_ref.and_then(|o| o.update_interval);
let mut merge = opt_ref.and_then(|o| o.merge.clone()); let mut merge = opt_ref.and_then(|o| o.merge.clone());

View File

@@ -472,15 +472,17 @@ impl IProfiles {
/// 获取所有的profiles(uid名称) /// 获取所有的profiles(uid名称)
pub fn all_profile_uid_and_name(&self) -> Option<Vec<(String, String)>> { pub fn all_profile_uid_and_name(&self) -> Option<Vec<(String, String)>> {
match self.items.as_ref() { self.items.as_ref().map(|items| {
Some(items) => Some(items.iter().filter_map(|e| { items
if let (Some(uid), Some(name)) = (e.uid.clone(), e.name.clone()) { .iter()
Some((uid, name)) .filter_map(|e| {
} else { if let (Some(uid), Some(name)) = (e.uid.clone(), e.name.clone()) {
None Some((uid, name))
} } else {
}).collect()), None
None => None, }
} })
.collect()
})
} }
} }

View File

@@ -1,7 +1,7 @@
use crate::config::DEFAULT_PAC; use crate::{
use crate::config::{deserialize_encrypted, serialize_encrypted}; config::{deserialize_encrypted, serialize_encrypted, DEFAULT_PAC},
use crate::utils::i18n; utils::{dirs, help, i18n},
use crate::utils::{dirs, help}; };
use anyhow::Result; use anyhow::Result;
use log::LevelFilter; use log::LevelFilter;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -101,7 +101,7 @@ pub struct IVerge {
/// hotkey map /// hotkey map
/// format: {func},{key} /// format: {func},{key}
pub hotkeys: Option<Vec<String>>, pub hotkeys: Option<Vec<String>>,
/// enable global hotkey /// enable global hotkey
pub enable_global_hotkey: Option<bool>, pub enable_global_hotkey: Option<bool>,

View File

@@ -1,16 +1,17 @@
use crate::config::Config; use crate::{config::Config, utils::dirs};
use crate::utils::dirs;
use anyhow::Error; use anyhow::Error;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use reqwest_dav::list_cmd::{ListEntity, ListFile}; use reqwest_dav::list_cmd::{ListEntity, ListFile};
use std::collections::HashMap; use std::{
use std::env::{consts::OS, temp_dir}; collections::HashMap,
use std::fs; env::{consts::OS, temp_dir},
use std::io::Write; fs,
use std::path::PathBuf; io::Write,
use std::sync::Arc; path::PathBuf,
use std::time::Duration; sync::Arc,
time::Duration,
};
use tokio::time::timeout; use tokio::time::timeout;
use zip::write::SimpleFileOptions; use zip::write::SimpleFileOptions;

View File

@@ -1,16 +1,17 @@
use crate::config::*;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use crate::core::tray::Tray; use crate::core::tray::Tray;
use crate::core::{handle, service}; use crate::{
use crate::log_err; config::*,
use crate::module::mihomo::MihomoManager; core::{handle, service},
use crate::utils::{dirs, help}; log_err,
module::mihomo::MihomoManager,
utils::{dirs, help},
};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::{path::PathBuf, sync::Arc, time::Duration}; use std::{path::PathBuf, sync::Arc, time::Duration};
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
use tokio::sync::Mutex; use tokio::{sync::Mutex, time::sleep};
use tokio::time::sleep;
#[derive(Debug)] #[derive(Debug)]
pub struct CoreManager { pub struct CoreManager {

View File

@@ -1,13 +1,10 @@
use crate::core::handle; use crate::{config::Config, core::handle, feat, log_err, utils::resolve};
use crate::{config::Config, feat, log_err};
use crate::utils::resolve;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{collections::HashMap, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use tauri::Manager; use tauri::{async_runtime, Manager};
use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState}; use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState};
use tauri::async_runtime;
pub struct Hotkey { pub struct Hotkey {
current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置 current: Arc<Mutex<Vec<String>>>, // 保存当前的热键设置
@@ -26,7 +23,10 @@ impl Hotkey {
let verge = Config::verge(); let verge = Config::verge();
let enable_global_hotkey = verge.latest().enable_global_hotkey.unwrap_or(true); let enable_global_hotkey = verge.latest().enable_global_hotkey.unwrap_or(true);
println!("Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey); println!(
"Initializing hotkeys, global hotkey enabled: {}",
enable_global_hotkey
);
log::info!(target: "app", "Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey); log::info!(target: "app", "Initializing hotkeys, global hotkey enabled: {}", enable_global_hotkey);
// 如果全局热键被禁用,则不注册热键 // 如果全局热键被禁用,则不注册热键
@@ -85,11 +85,17 @@ impl Hotkey {
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
let manager = app_handle.global_shortcut(); let manager = app_handle.global_shortcut();
println!("Attempting to register hotkey: {} for function: {}", hotkey, func); println!(
"Attempting to register hotkey: {} for function: {}",
hotkey, func
);
log::info!(target: "app", "Attempting to register hotkey: {} for function: {}", hotkey, func); log::info!(target: "app", "Attempting to register hotkey: {} for function: {}", hotkey, func);
if manager.is_registered(hotkey) { if manager.is_registered(hotkey) {
println!("Hotkey {} was already registered, unregistering first", hotkey); println!(
"Hotkey {} was already registered, unregistering first",
hotkey
);
log::info!(target: "app", "Hotkey {} was already registered, unregistering first", hotkey); log::info!(target: "app", "Hotkey {} was already registered, unregistering first", hotkey);
manager.unregister(hotkey)?; manager.unregister(hotkey)?;
} }
@@ -101,12 +107,12 @@ impl Hotkey {
|| { || {
println!("=== Hotkey Dashboard Window Operation Start ==="); println!("=== Hotkey Dashboard Window Operation Start ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation Start ==="); log::info!(target: "app", "=== Hotkey Dashboard Window Operation Start ===");
// 使用 spawn_blocking 来确保在正确的线程上执行 // 使用 spawn_blocking 来确保在正确的线程上执行
async_runtime::spawn_blocking(|| { async_runtime::spawn_blocking(|| {
println!("Toggle dashboard window visibility"); println!("Toggle dashboard window visibility");
log::info!(target: "app", "Toggle dashboard window visibility"); log::info!(target: "app", "Toggle dashboard window visibility");
// 检查窗口是否存在 // 检查窗口是否存在
if let Some(window) = handle::Handle::global().get_window() { if let Some(window) = handle::Handle::global().get_window() {
// 如果窗口可见,则隐藏它 // 如果窗口可见,则隐藏它
@@ -131,11 +137,11 @@ impl Hotkey {
resolve::create_window(); resolve::create_window();
} }
}); });
println!("=== Hotkey Dashboard Window Operation End ==="); println!("=== Hotkey Dashboard Window Operation End ===");
log::info!(target: "app", "=== Hotkey Dashboard Window Operation End ==="); log::info!(target: "app", "=== Hotkey Dashboard Window Operation End ===");
} }
}, }
"clash_mode_rule" => || feat::change_clash_mode("rule".into()), "clash_mode_rule" => || feat::change_clash_mode("rule".into()),
"clash_mode_global" => || feat::change_clash_mode("global".into()), "clash_mode_global" => || feat::change_clash_mode("global".into()),
"clash_mode_direct" => || feat::change_clash_mode("direct".into()), "clash_mode_direct" => || feat::change_clash_mode("direct".into()),
@@ -169,11 +175,14 @@ impl Hotkey {
// 直接执行函数,不做任何状态检查 // 直接执行函数,不做任何状态检查
println!("Executing function directly"); println!("Executing function directly");
log::info!(target: "app", "Executing function directly"); log::info!(target: "app", "Executing function directly");
// 获取轻量模式状态和全局热键状态 // 获取轻量模式状态和全局热键状态
let is_lite_mode = Config::verge().latest().enable_lite_mode.unwrap_or(false); let is_lite_mode = Config::verge().latest().enable_lite_mode.unwrap_or(false);
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true); let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
// 在轻量模式下或配置了全局热键时,始终执行热键功能 // 在轻量模式下或配置了全局热键时,始终执行热键功能
if is_lite_mode || is_enable_global_hotkey { if is_lite_mode || is_enable_global_hotkey {
f(); f();
@@ -181,7 +190,7 @@ impl Hotkey {
// 非轻量模式且未启用全局热键时,只在窗口可见且有焦点的情况下响应热键 // 非轻量模式且未启用全局热键时,只在窗口可见且有焦点的情况下响应热键
let is_visible = window.is_visible().unwrap_or(false); let is_visible = window.is_visible().unwrap_or(false);
let is_focused = window.is_focused().unwrap_or(false); let is_focused = window.is_focused().unwrap_or(false);
if is_focused && is_visible { if is_focused && is_visible {
f(); f();
} }

View File

@@ -1,10 +1,7 @@
use crate::config::Config; use crate::{config::Config, utils::dirs};
use crate::utils::dirs;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::{collections::HashMap, env::current_exe, path::PathBuf, process::Command as StdCommand};
use std::path::PathBuf;
use std::{env::current_exe, process::Command as StdCommand};
use tokio::time::Duration; use tokio::time::Duration;
// Windows only // Windows only
@@ -154,11 +151,13 @@ pub async fn reinstall_service() -> Result<()> {
let install_shell: String = install_path.to_string_lossy().into_owned(); let install_shell: String = install_path.to_string_lossy().into_owned();
let uninstall_shell: String = uninstall_path.to_string_lossy().into_owned(); let uninstall_shell: String = uninstall_path.to_string_lossy().into_owned();
// 获取提示文本,如果 i18n 失败则使用硬编码默认值 // 获取提示文本,如果 i18n 失败则使用硬编码默认值
let prompt = crate::utils::i18n::t("Service Administrator Prompt"); let prompt = crate::utils::i18n::t("Service Administrator Prompt");
let prompt = if prompt == "Service Administrator Prompt" { let prompt = if prompt == "Service Administrator Prompt" {
if Config::verge().latest().language.as_deref() == Some("zh") || Config::verge().latest().language.is_none() { if Config::verge().latest().language.as_deref() == Some("zh")
|| Config::verge().latest().language.is_none()
{
"Clash Verge 需要使用管理员权限来重新安装系统服务" "Clash Verge 需要使用管理员权限来重新安装系统服务"
} else { } else {
"Clash Verge needs administrator privileges to reinstall the system service" "Clash Verge needs administrator privileges to reinstall the system service"
@@ -166,7 +165,7 @@ pub async fn reinstall_service() -> Result<()> {
} else { } else {
&prompt &prompt
}; };
let command = format!( let command = format!(
r#"do shell script "sudo '{uninstall_shell}' && sudo '{install_shell}'" with administrator privileges with prompt "{prompt}""# r#"do shell script "sudo '{uninstall_shell}' && sudo '{install_shell}'" with administrator privileges with prompt "{prompt}""#
); );

View File

@@ -1,6 +1,6 @@
use crate::core::handle::Handle;
use crate::{ use crate::{
config::{Config, IVerge}, config::{Config, IVerge},
core::handle::Handle,
log_err, log_err,
}; };
use anyhow::Result; use anyhow::Result;
@@ -126,8 +126,7 @@ impl Sysopt {
if !sys_enable { if !sys_enable {
return self.reset_sysproxy().await; return self.reset_sysproxy().await;
} }
use crate::core::handle::Handle; use crate::{core::handle::Handle, utils::dirs};
use crate::utils::dirs;
use anyhow::bail; use anyhow::bail;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
@@ -185,8 +184,7 @@ impl Sysopt {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
use crate::core::handle::Handle; use crate::{core::handle::Handle, utils::dirs};
use crate::utils::dirs;
use anyhow::bail; use anyhow::bail;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
@@ -305,8 +303,7 @@ impl Sysopt {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
{ {
use crate::core::handle::Handle; use crate::{core::handle::Handle, utils::dirs};
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
let app_handle = Handle::global().app_handle().unwrap(); let app_handle = Handle::global().app_handle().unwrap();

View File

@@ -1,12 +1,9 @@
use crate::config::Config; use crate::{config::Config, core::CoreManager, feat};
use crate::feat;
use crate::core::CoreManager;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder}; use delay_timer::prelude::{DelayTimer, DelayTimerBuilder, TaskBuilder};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::collections::HashMap; use std::{collections::HashMap, sync::Arc};
use std::sync::Arc;
type TaskID = u64; type TaskID = u64;
@@ -195,16 +192,14 @@ impl Timer {
log::info!(target: "app", "Running timer task `{}`", uid); log::info!(target: "app", "Running timer task `{}`", uid);
match feat::update_profile(uid.clone(), None).await { match feat::update_profile(uid.clone(), None).await {
Ok(_) => { Ok(_) => match CoreManager::global().update_config().await {
match CoreManager::global().update_config().await { Ok(_) => {
Ok(_) => { log::info!(target: "app", "Timer task completed successfully for uid: {}", uid);
log::info!(target: "app", "Timer task completed successfully for uid: {}", uid);
}
Err(e) => {
log::error!(target: "app", "Timer task refresh error for uid {}: {}", uid, e);
}
} }
} Err(e) => {
log::error!(target: "app", "Timer task refresh error for uid {}: {}", uid, e);
}
},
Err(e) => { Err(e) => {
log::error!(target: "app", "Timer task update error for uid {}: {}", uid, e); log::error!(target: "app", "Timer task update error for uid {}: {}", uid, e);
} }

View File

@@ -66,12 +66,18 @@ impl Tray {
} }
pub fn create_systray(&self, app: &App) -> Result<()> { pub fn create_systray(&self, app: &App) -> Result<()> {
let builder = TrayIconBuilder::with_id("main") let mut builder = TrayIconBuilder::with_id("main")
.icon(app.default_window_icon().unwrap().clone()) .icon(app.default_window_icon().unwrap().clone())
.icon_as_template(false); .icon_as_template(false);
#[cfg(any(target_os = "macos", target_os = "windows"))] #[cfg(any(target_os = "macos", target_os = "windows"))]
let builder = builder.show_menu_on_left_click(false); {
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
if tray_event.as_str() != "tray_menu" {
builder = builder.show_menu_on_left_click(false);
}
}
let tray = builder.build(app)?; let tray = builder.build(app)?;
@@ -79,22 +85,6 @@ impl Tray {
let tray_event = { Config::verge().latest().tray_event.clone() }; let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into()); let tray_event: String = tray_event.unwrap_or("main_window".into());
#[cfg(target_os = "macos")]
if let TrayIconEvent::Click {
button: MouseButton::Left,
button_state: MouseButtonState::Down,
..
} = event
{
match tray_event.as_str() {
"system_proxy" => feat::toggle_system_proxy(),
"tun_mode" => feat::toggle_tun_mode(None),
"main_window" => resolve::create_window(),
_ => {}
}
}
#[cfg(not(target_os = "macos"))]
if let TrayIconEvent::Click { if let TrayIconEvent::Click {
button: MouseButton::Left, button: MouseButton::Left,
button_state: MouseButtonState::Down, button_state: MouseButtonState::Down,
@@ -113,6 +103,19 @@ impl Tray {
Ok(()) Ok(())
} }
/// 更新托盘点击行为
pub fn update_click_behavior(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap();
let tray_event = { Config::verge().latest().tray_event.clone() };
let tray_event: String = tray_event.unwrap_or("main_window".into());
let tray = app_handle.tray_by_id("main").unwrap();
match tray_event.as_str() {
"tray_menu" => tray.set_show_menu_on_left_click(true)?,
_ => tray.set_show_menu_on_left_click(false)?,
}
Ok(())
}
/// 更新托盘菜单 /// 更新托盘菜单
pub fn update_menu(&self) -> Result<()> { pub fn update_menu(&self) -> Result<()> {
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
@@ -131,7 +134,7 @@ impl Tray {
let profile_uid_and_name = Config::profiles() let profile_uid_and_name = Config::profiles()
.data() .data()
.all_profile_uid_and_name() .all_profile_uid_and_name()
.unwrap_or(Vec::new()); .unwrap_or_default();
let tray = app_handle.tray_by_id("main").unwrap(); let tray = app_handle.tray_by_id("main").unwrap();
let _ = tray.set_menu(Some(create_tray_menu( let _ = tray.set_menu(Some(create_tray_menu(
@@ -405,8 +408,8 @@ fn create_tray_menu(
.is_current_profile_index(profile_uid.to_string()); .is_current_profile_index(profile_uid.to_string());
CheckMenuItem::with_id( CheckMenuItem::with_id(
app_handle, app_handle,
&format!("profiles_{}", profile_uid), format!("profiles_{}", profile_uid),
t(&profile_name), t(profile_name),
true, true,
is_current_profile, is_current_profile,
None::<&str>, None::<&str>,

View File

@@ -1,16 +1,15 @@
use crate::module::mihomo::Rate; use crate::{
use crate::module::mihomo::MihomoManager; module::mihomo::{MihomoManager, Rate},
use crate::utils::help::format_bytes_speed; utils::help::format_bytes_speed,
};
use ab_glyph::FontArc; use ab_glyph::FontArc;
use anyhow::Result; use anyhow::Result;
use futures::Stream; use futures::Stream;
use image::{GenericImageView, Rgba, RgbaImage}; use image::{GenericImageView, Rgba, RgbaImage};
use imageproc::drawing::draw_text_mut; use imageproc::drawing::draw_text_mut;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::io::Cursor; use std::{io::Cursor, sync::Arc};
use std::sync::Arc; use tokio_tungstenite::tungstenite::{http, Message};
use tokio_tungstenite::tungstenite::http;
use tokio_tungstenite::tungstenite::Message;
use tungstenite::client::IntoClientRequest; use tungstenite::client::IntoClientRequest;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SpeedRate { pub struct SpeedRate {

View File

@@ -15,7 +15,7 @@ impl MihomoManager {
proxies: serde_json::Value::Null, proxies: serde_json::Value::Null,
providers_proxies: serde_json::Value::Null, providers_proxies: serde_json::Value::Null,
})), })),
headers: headers, headers,
} }
} }
@@ -52,7 +52,7 @@ impl MihomoManager {
let client_response = reqwest::ClientBuilder::new() let client_response = reqwest::ClientBuilder::new()
.default_headers(self.headers.clone()) .default_headers(self.headers.clone())
.no_proxy() .no_proxy()
.timeout(Duration::from_secs(2)) .timeout(Duration::from_secs(60))
.build() .build()
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
.request( .request(
@@ -76,7 +76,7 @@ impl MihomoManager {
client_response.text().await.map(|text| json!(text)) client_response.text().await.map(|text| json!(text))
} }
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
return Ok(response); Ok(response)
} }
pub async fn refresh_proxies(&self) -> Result<&Self, String> { pub async fn refresh_proxies(&self) -> Result<&Self, String> {
@@ -129,6 +129,6 @@ impl MihomoManager {
self.mihomo_server, name, test_url, timeout self.mihomo_server, name, test_url, timeout
); );
let response = self.send_request("GET", url, None).await?; let response = self.send_request("GET", url, None).await?;
return Ok(response); Ok(response)
} }
} }

View File

@@ -5,17 +5,10 @@ mod script;
pub mod seq; pub mod seq;
mod tun; mod tun;
use self::chain::*; use self::{chain::*, field::*, merge::*, script::*, seq::*, tun::*};
use self::field::*; use crate::{config::Config, utils::tmpl};
use self::merge::*;
use self::script::*;
use self::seq::*;
use self::tun::*;
use crate::config::Config;
use crate::utils::tmpl;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
type ResultLog = Vec<(String, String)>; type ResultLog = Vec<(String, String)>;
@@ -267,11 +260,11 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
if enable_dns_settings { if enable_dns_settings {
use crate::utils::dirs; use crate::utils::dirs;
use std::fs; use std::fs;
// 尝试读取dns_config.yaml // 尝试读取dns_config.yaml
if let Ok(app_dir) = dirs::app_home_dir() { if let Ok(app_dir) = dirs::app_home_dir() {
let dns_path = app_dir.join("dns_config.yaml"); let dns_path = app_dir.join("dns_config.yaml");
if dns_path.exists() { if dns_path.exists() {
if let Ok(dns_yaml) = fs::read_to_string(&dns_path) { if let Ok(dns_yaml) = fs::read_to_string(&dns_path) {
if let Ok(dns_config) = serde_yaml::from_str::<serde_yaml::Mapping>(&dns_yaml) { if let Ok(dns_config) = serde_yaml::from_str::<serde_yaml::Mapping>(&dns_yaml) {

View File

@@ -108,7 +108,7 @@ proxy-groups:
- "proxy1" - "proxy1"
"#; "#;
let mut config: Mapping = serde_yaml::from_str(config_str).unwrap(); let mut config: Mapping = serde_yaml::from_str(config_str).unwrap();
let seq = SeqMap { let seq = SeqMap {
prepend: Sequence::new(), prepend: Sequence::new(),
append: Sequence::new(), append: Sequence::new(),
@@ -121,16 +121,32 @@ proxy-groups:
let proxies = config.get("proxies").unwrap().as_sequence().unwrap(); let proxies = config.get("proxies").unwrap().as_sequence().unwrap();
assert_eq!(proxies.len(), 1); assert_eq!(proxies.len(), 1);
assert_eq!( assert_eq!(
proxies[0].as_mapping().unwrap().get("name").unwrap().as_str().unwrap(), proxies[0]
.as_mapping()
.unwrap()
.get("name")
.unwrap()
.as_str()
.unwrap(),
"proxy2" "proxy2"
); );
// Check if proxy1 is removed from all groups // Check if proxy1 is removed from all groups
let groups = config.get("proxy-groups").unwrap().as_sequence().unwrap(); let groups = config.get("proxy-groups").unwrap().as_sequence().unwrap();
let group1_proxies = groups[0].as_mapping().unwrap() let group1_proxies = groups[0]
.get("proxies").unwrap().as_sequence().unwrap(); .as_mapping()
let group2_proxies = groups[1].as_mapping().unwrap() .unwrap()
.get("proxies").unwrap().as_sequence().unwrap(); .get("proxies")
.unwrap()
.as_sequence()
.unwrap();
let group2_proxies = groups[1]
.as_mapping()
.unwrap()
.get("proxies")
.unwrap()
.as_sequence()
.unwrap();
assert_eq!(group1_proxies.len(), 1); assert_eq!(group1_proxies.len(), 1);
assert_eq!(group1_proxies[0].as_str().unwrap(), "proxy2"); assert_eq!(group1_proxies[0].as_str().unwrap(), "proxy2");

View File

@@ -24,7 +24,7 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
let mut tun_val = tun_val.map_or(Mapping::new(), |val| { let mut tun_val = tun_val.map_or(Mapping::new(), |val| {
val.as_mapping().cloned().unwrap_or(Mapping::new()) val.as_mapping().cloned().unwrap_or(Mapping::new())
}); });
if enable { if enable {
// 读取DNS配置 // 读取DNS配置
let dns_key = Value::from("dns"); let dns_key = Value::from("dns");
@@ -40,20 +40,20 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
// 检查现有的 enhanced-mode 设置 // 检查现有的 enhanced-mode 设置
let current_mode = dns_val let current_mode = dns_val
.get(&Value::from("enhanced-mode")) .get(Value::from("enhanced-mode"))
.and_then(|v| v.as_str()) .and_then(|v| v.as_str())
.unwrap_or("fake-ip"); .unwrap_or("fake-ip");
// 只有当 enhanced-mode 是 fake-ip 或未设置时才修改 DNS 配置 // 只有当 enhanced-mode 是 fake-ip 或未设置时才修改 DNS 配置
if current_mode == "fake-ip" || !dns_val.contains_key(&Value::from("enhanced-mode")) { if current_mode == "fake-ip" || !dns_val.contains_key(Value::from("enhanced-mode")) {
revise!(dns_val, "enable", true); revise!(dns_val, "enable", true);
revise!(dns_val, "ipv6", ipv6_val); revise!(dns_val, "ipv6", ipv6_val);
if !dns_val.contains_key(&Value::from("enhanced-mode")) { if !dns_val.contains_key(Value::from("enhanced-mode")) {
revise!(dns_val, "enhanced-mode", "fake-ip"); revise!(dns_val, "enhanced-mode", "fake-ip");
} }
if !dns_val.contains_key(&Value::from("fake-ip-range")) { if !dns_val.contains_key(Value::from("fake-ip-range")) {
revise!(dns_val, "fake-ip-range", "198.18.0.1/16"); revise!(dns_val, "fake-ip-range", "198.18.0.1/16");
} }
@@ -63,7 +63,7 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
crate::utils::resolve::set_public_dns("223.6.6.6".to_string()).await; crate::utils::resolve::set_public_dns("223.6.6.6".to_string()).await;
} }
} }
// 当TUN启用时将修改后的DNS配置写回 // 当TUN启用时将修改后的DNS配置写回
revise!(config, "dns", dns_val); revise!(config, "dns", dns_val);
} else { } else {
@@ -75,6 +75,6 @@ pub async fn use_tun(mut config: Mapping, enable: bool) -> Mapping {
// 更新TUN配置 // 更新TUN配置
revise!(tun_val, "enable", enable); revise!(tun_val, "enable", enable);
revise!(config, "tun", tun_val); revise!(config, "tun", tun_val);
config config
} }

View File

@@ -1,7 +1,9 @@
use crate::config::{Config, IVerge}; use crate::{
use crate::core::backup; config::{Config, IVerge},
use crate::log_err; core::backup,
use crate::utils::dirs::app_home_dir; log_err,
utils::dirs::app_home_dir,
};
use anyhow::Result; use anyhow::Result;
use reqwest_dav::list_cmd::ListFile; use reqwest_dav::list_cmd::ListFile;
use std::fs; use std::fs;

View File

@@ -1,8 +1,10 @@
use crate::config::Config; use crate::{
use crate::core::{handle, tray, CoreManager}; config::Config,
use crate::log_err; core::{handle, tray, CoreManager},
use crate::module::mihomo::MihomoManager; log_err,
use crate::utils::resolve; module::mihomo::MihomoManager,
utils::resolve,
};
use serde_yaml::{Mapping, Value}; use serde_yaml::{Mapping, Value};
use tauri::Manager; use tauri::Manager;

View File

@@ -1,7 +1,9 @@
use crate::config::{Config, IVerge}; use crate::{
use crate::core::{handle, hotkey, sysopt, tray, CoreManager}; config::{Config, IVerge},
use crate::log_err; core::{handle, hotkey, sysopt, tray, CoreManager},
use crate::utils::resolve; log_err,
utils::resolve,
};
use anyhow::Result; use anyhow::Result;
use serde_yaml::Mapping; use serde_yaml::Mapping;
use tauri::Manager; use tauri::Manager;
@@ -73,6 +75,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
let http_port = patch.verge_port; let http_port = patch.verge_port;
let enable_tray_speed = patch.enable_tray_speed; let enable_tray_speed = patch.enable_tray_speed;
let enable_global_hotkey = patch.enable_global_hotkey; let enable_global_hotkey = patch.enable_global_hotkey;
let tray_event = patch.tray_event;
let res: std::result::Result<(), anyhow::Error> = { let res: std::result::Result<(), anyhow::Error> = {
let mut should_restart_core = false; let mut should_restart_core = false;
@@ -84,6 +87,7 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
let mut should_update_hotkey = false; let mut should_update_hotkey = false;
let mut should_update_systray_menu = false; let mut should_update_systray_menu = false;
let mut should_update_systray_tooltip = false; let mut should_update_systray_tooltip = false;
let mut should_update_systray_click_behavior = false;
if tun_mode.is_some() { if tun_mode.is_some() {
should_update_clash_config = true; should_update_clash_config = true;
@@ -145,6 +149,10 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
should_update_systray_icon = true; should_update_systray_icon = true;
} }
if tray_event.is_some() {
should_update_systray_click_behavior = true;
}
if should_restart_core { if should_restart_core {
CoreManager::global().restart_core().await?; CoreManager::global().restart_core().await?;
} }
@@ -180,6 +188,10 @@ pub async fn patch_verge(patch: IVerge, not_save_file: bool) -> Result<()> {
tray::Tray::global().update_tooltip()?; tray::Tray::global().update_tooltip()?;
} }
if should_update_systray_click_behavior {
tray::Tray::global().update_click_behavior()?;
}
// 处理轻量模式切换 // 处理轻量模式切换
if lite_mode.is_some() { if lite_mode.is_some() {
if let Some(window) = handle::Handle::global().get_window() { if let Some(window) = handle::Handle::global().get_window() {

View File

@@ -1,8 +1,8 @@
use crate::cmd; use crate::{
use crate::config::{Config, PrfItem, PrfOption}; cmd,
use crate::core::handle; config::{Config, PrfItem, PrfOption},
use crate::core::CoreManager; core::{handle, CoreManager, *},
use crate::core::*; };
use anyhow::{bail, Result}; use anyhow::{bail, Result};
/// Toggle proxy profile /// Toggle proxy profile
@@ -29,7 +29,7 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
let profiles = Config::profiles(); let profiles = Config::profiles();
let profiles = profiles.latest(); let profiles = profiles.latest();
let item = profiles.get_item(&uid)?; let item = profiles.get_item(&uid)?;
let is_remote = item.itype.as_ref().map_or(false, |s| s == "remote"); let is_remote = item.itype.as_ref().is_some_and(|s| s == "remote");
if !is_remote { if !is_remote {
println!("[订阅更新] {} 不是远程订阅,跳过更新", uid); println!("[订阅更新] {} 不是远程订阅,跳过更新", uid);

View File

@@ -1,6 +1,7 @@
use crate::config::Config; use crate::{
use crate::config::IVerge; config::{Config, IVerge},
use crate::core::handle; core::handle,
};
use std::env; use std::env;
use tauri_plugin_clipboard_manager::ClipboardExt; use tauri_plugin_clipboard_manager::ClipboardExt;
@@ -72,10 +73,16 @@ pub fn copy_clash_env() {
}; };
let export_text = match env_type.as_str() { let export_text = match env_type.as_str() {
"bash" => format!("export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"), "bash" => format!(
"export https_proxy={http_proxy} http_proxy={http_proxy} all_proxy={socks5_proxy}"
),
"cmd" => format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}"), "cmd" => format!("set http_proxy={http_proxy}\r\nset https_proxy={http_proxy}"),
"powershell" => format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\""), "powershell" => {
"nushell" => format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}"), format!("$env:HTTP_PROXY=\"{http_proxy}\"; $env:HTTPS_PROXY=\"{http_proxy}\"")
}
"nushell" => {
format!("load-env {{ http_proxy: \"{http_proxy}\", https_proxy: \"{http_proxy}\" }}")
}
"fish" => format!("set -x http_proxy {http_proxy}; set -x https_proxy {http_proxy}"), "fish" => format!("set -x http_proxy {http_proxy}; set -x https_proxy {http_proxy}"),
_ => { _ => {
log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}"); log::error!(target: "app", "copy_clash_env: Invalid env type! {env_type}");
@@ -83,7 +90,7 @@ pub fn copy_clash_env() {
} }
}; };
if let Err(_) = cliboard.write_text(export_text) { if cliboard.write_text(export_text).is_err() {
log::error!(target: "app", "Failed to write to clipboard"); log::error!(target: "app", "Failed to write to clipboard");
} }
} }

View File

@@ -1,9 +1,9 @@
use crate::config::Config; use crate::{
use crate::core::handle; config::Config,
use crate::core::{sysopt, CoreManager}; core::{handle, sysopt, CoreManager},
use crate::module::mihomo::MihomoManager; module::mihomo::MihomoManager,
use crate::utils::resolve; utils::resolve,
use futures; };
use tauri::Manager; use tauri::Manager;
use tauri_plugin_window_state::{AppHandleExt, StateFlags}; use tauri_plugin_window_state::{AppHandleExt, StateFlags};

View File

@@ -3,17 +3,19 @@ mod config;
mod core; mod core;
mod enhance; mod enhance;
mod feat; mod feat;
mod utils;
mod module; mod module;
use crate::core::hotkey; mod utils;
use crate::utils::{resolve, resolve::resolve_scheme, server}; use crate::{
core::hotkey,
utils::{resolve, resolve::resolve_scheme, server},
};
use config::Config; use config::Config;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
use std::sync::{Mutex, Once}; use std::sync::{Mutex, Once};
use tauri::AppHandle; use tauri::AppHandle;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use tauri::Manager; use tauri::Manager;
use tauri_plugin_autostart::MacosLauncher;
use tauri_plugin_deep_link::DeepLinkExt;
/// A global singleton handle to the application. /// A global singleton handle to the application.
pub struct AppHandleManager { pub struct AppHandleManager {
@@ -57,7 +59,7 @@ impl AppHandleManager {
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Regular); let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Regular);
} }
} }
pub fn set_activation_policy_accessory(&self) { pub fn set_activation_policy_accessory(&self) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
@@ -66,7 +68,7 @@ impl AppHandleManager {
let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Accessory); let _ = app_handle.set_activation_policy(tauri::ActivationPolicy::Accessory);
} }
} }
pub fn set_activation_policy_prohibited(&self) { pub fn set_activation_policy_prohibited(&self) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
@@ -77,6 +79,7 @@ impl AppHandleManager {
} }
} }
#[allow(clippy::panic)]
pub fn run() { pub fn run() {
// 单例检测 // 单例检测
let app_exists: bool = tauri::async_runtime::block_on(async move { let app_exists: bool = tauri::async_runtime::block_on(async move {
@@ -219,12 +222,18 @@ pub fn run() {
AppHandleManager::global().init(app_handle.clone()); AppHandleManager::global().init(app_handle.clone());
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let main_window = AppHandleManager::global().get_handle().get_webview_window("main").unwrap(); let main_window = AppHandleManager::global()
.get_handle()
.get_webview_window("main")
.unwrap();
let _ = main_window.set_title("Clash Verge"); let _ = main_window.set_title("Clash Verge");
} }
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
tauri::RunEvent::Reopen { has_visible_windows, .. } => { tauri::RunEvent::Reopen {
has_visible_windows,
..
} => {
if !has_visible_windows { if !has_visible_windows {
AppHandleManager::global().set_activation_policy_regular(); AppHandleManager::global().set_activation_policy_regular();
} }
@@ -259,8 +268,11 @@ pub fn run() {
{ {
log_err!(hotkey::Hotkey::global().register("Control+Q", "quit")); log_err!(hotkey::Hotkey::global().register("Control+Q", "quit"));
}; };
{ {
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true); let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
if !is_enable_global_hotkey { if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().init()) log_err!(hotkey::Hotkey::global().init())
} }
@@ -275,8 +287,11 @@ pub fn run() {
{ {
log_err!(hotkey::Hotkey::global().unregister("Control+Q")); log_err!(hotkey::Hotkey::global().unregister("Control+Q"));
}; };
{ {
let is_enable_global_hotkey = Config::verge().latest().enable_global_hotkey.unwrap_or(true); let is_enable_global_hotkey = Config::verge()
.latest()
.enable_global_hotkey
.unwrap_or(true);
if !is_enable_global_hotkey { if !is_enable_global_hotkey {
log_err!(hotkey::Hotkey::global().reset()) log_err!(hotkey::Hotkey::global().reset())
} }

View File

@@ -65,6 +65,6 @@ impl MihomoManager {
.unwrap() .unwrap()
.to_string(); .to_string();
let token = http::header::HeaderValue::from_str(&auth).unwrap(); let token = http::header::HeaderValue::from_str(&auth).unwrap();
return (ws_url, token); (ws_url, token)
} }
} }

View File

@@ -1,2 +1,2 @@
pub mod mihomo;
pub mod sysinfo; pub mod sysinfo;
pub mod mihomo;

View File

@@ -43,7 +43,6 @@ impl PlatformSpecification {
}) })
}); });
Self { Self {
system_name, system_name,
system_version, system_version,

View File

@@ -1,8 +1,7 @@
use crate::core::handle; use crate::core::handle;
use anyhow::Result; use anyhow::Result;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::fs; use std::{fs, path::PathBuf};
use std::path::PathBuf;
use tauri::Manager; use tauri::Manager;
#[cfg(not(feature = "verge-dev"))] #[cfg(not(feature = "verge-dev"))]

View File

@@ -207,7 +207,7 @@ macro_rules! t {
/// # Examples /// # Examples
/// ```not_run /// ```not_run
/// format_bytes_speed(1000) // returns "1000B/s" /// format_bytes_speed(1000) // returns "1000B/s"
/// format_bytes_speed(1024) // returns "1.0KB/s" /// format_bytes_speed(1024) // returns "1.0KB/s"
/// format_bytes_speed(1024 * 1024) // returns "1.0MB/s" /// format_bytes_speed(1024 * 1024) // returns "1.0MB/s"
/// ``` /// ```
/// ``` /// ```

View File

@@ -1,5 +1,4 @@
use crate::config::Config; use crate::{config::Config, utils::dirs};
use crate::utils::dirs;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use serde_json::Value; use serde_json::Value;
use std::{collections::HashMap, fs, path::PathBuf}; use std::{collections::HashMap, fs, path::PathBuf};

View File

@@ -1,16 +1,21 @@
use crate::config::*; use crate::{
use crate::core::handle; config::*,
use crate::utils::{dirs, help}; core::handle,
utils::{dirs, help},
};
use anyhow::Result; use anyhow::Result;
use chrono::{Local, TimeZone}; use chrono::{Local, TimeZone};
use log::LevelFilter; use log::LevelFilter;
use log4rs::append::console::ConsoleAppender; use log4rs::{
use log4rs::append::file::FileAppender; append::{console::ConsoleAppender, file::FileAppender},
use log4rs::config::{Appender, Logger, Root}; config::{Appender, Logger, Root},
use log4rs::encode::pattern::PatternEncoder; encode::pattern::PatternEncoder,
use std::fs::{self, DirEntry}; };
use std::path::PathBuf; use std::{
use std::str::FromStr; fs::{self, DirEntry},
path::PathBuf,
str::FromStr,
};
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
/// initialize this instance's log file /// initialize this instance's log file
@@ -142,69 +147,106 @@ fn init_dns_config() -> Result<()> {
("enable".into(), Value::Bool(true)), ("enable".into(), Value::Bool(true)),
("listen".into(), Value::String(":53".into())), ("listen".into(), Value::String(":53".into())),
("enhanced-mode".into(), Value::String("fake-ip".into())), ("enhanced-mode".into(), Value::String("fake-ip".into())),
("fake-ip-range".into(), Value::String("198.18.0.1/16".into())), (
("fake-ip-filter-mode".into(), Value::String("blacklist".into())), "fake-ip-range".into(),
Value::String("198.18.0.1/16".into()),
),
(
"fake-ip-filter-mode".into(),
Value::String("blacklist".into()),
),
("prefer-h3".into(), Value::Bool(false)), ("prefer-h3".into(), Value::Bool(false)),
("respect-rules".into(), Value::Bool(false)), ("respect-rules".into(), Value::Bool(false)),
("use-hosts".into(), Value::Bool(false)), ("use-hosts".into(), Value::Bool(false)),
("use-system-hosts".into(), Value::Bool(false)), ("use-system-hosts".into(), Value::Bool(false)),
("fake-ip-filter".into(), Value::Sequence(vec![ (
Value::String("*.lan".into()), "fake-ip-filter".into(),
Value::String("*.local".into()), Value::Sequence(vec![
Value::String("*.arpa".into()), Value::String("*.lan".into()),
Value::String("time.*.com".into()), Value::String("*.local".into()),
Value::String("ntp.*.com".into()), Value::String("*.arpa".into()),
Value::String("time.*.com".into()), Value::String("time.*.com".into()),
Value::String("+.market.xiaomi.com".into()), Value::String("ntp.*.com".into()),
Value::String("localhost.ptlogin2.qq.com".into()), Value::String("time.*.com".into()),
Value::String("*.msftncsi.com".into()), Value::String("+.market.xiaomi.com".into()),
Value::String("www.msftconnecttest.com".into()), Value::String("localhost.ptlogin2.qq.com".into()),
])), Value::String("*.msftncsi.com".into()),
("default-nameserver".into(), Value::Sequence(vec![ Value::String("www.msftconnecttest.com".into()),
Value::String("223.6.6.6".into()), ]),
Value::String("8.8.8.8".into()), ),
])), (
("nameserver".into(), Value::Sequence(vec![ "default-nameserver".into(),
Value::String("8.8.8.8".into()), Value::Sequence(vec![
Value::String("https://doh.pub/dns-query".into()), Value::String("223.6.6.6".into()),
Value::String("https://dns.alidns.com/dns-query".into()), Value::String("8.8.8.8".into()),
])), ]),
("fallback".into(), Value::Sequence(vec![ ),
Value::String("https://dns.alidns.com/dns-query".into()), (
Value::String("https://dns.google/dns-query".into()), "nameserver".into(),
Value::String("https://cloudflare-dns.com/dns-query".into()), Value::Sequence(vec![
])), Value::String("8.8.8.8".into()),
("nameserver-policy".into(), Value::Mapping(serde_yaml::Mapping::new())), Value::String("https://doh.pub/dns-query".into()),
("proxy-server-nameserver".into(), Value::Sequence(vec![ Value::String("https://dns.alidns.com/dns-query".into()),
Value::String("https://doh.pub/dns-query".into()), ]),
Value::String("https://dns.alidns.com/dns-query".into()), ),
])), (
"fallback".into(),
Value::Sequence(vec![
Value::String("https://dns.alidns.com/dns-query".into()),
Value::String("https://dns.google/dns-query".into()),
Value::String("https://cloudflare-dns.com/dns-query".into()),
]),
),
(
"nameserver-policy".into(),
Value::Mapping(serde_yaml::Mapping::new()),
),
(
"proxy-server-nameserver".into(),
Value::Sequence(vec![
Value::String("https://doh.pub/dns-query".into()),
Value::String("https://dns.alidns.com/dns-query".into()),
]),
),
("direct-nameserver".into(), Value::Sequence(vec![])), ("direct-nameserver".into(), Value::Sequence(vec![])),
("direct-nameserver-follow-policy".into(), Value::Bool(false)), ("direct-nameserver-follow-policy".into(), Value::Bool(false)),
("fallback-filter".into(), Value::Mapping(serde_yaml::Mapping::from_iter([ (
("geoip".into(), Value::Bool(true)), "fallback-filter".into(),
("geoip-code".into(), Value::String("CN".into())), Value::Mapping(serde_yaml::Mapping::from_iter([
("ipcidr".into(), Value::Sequence(vec![ ("geoip".into(), Value::Bool(true)),
Value::String("240.0.0.0/4".into()), ("geoip-code".into(), Value::String("CN".into())),
Value::String("0.0.0.0/32".into()), (
"ipcidr".into(),
Value::Sequence(vec![
Value::String("240.0.0.0/4".into()),
Value::String("0.0.0.0/32".into()),
]),
),
(
"domain".into(),
Value::Sequence(vec![
Value::String("+.google.com".into()),
Value::String("+.facebook.com".into()),
Value::String("+.youtube.com".into()),
]),
),
])), ])),
("domain".into(), Value::Sequence(vec![ ),
Value::String("+.google.com".into()),
Value::String("+.facebook.com".into()),
Value::String("+.youtube.com".into()),
])),
]))),
]); ]);
// 检查DNS配置文件是否存在 // 检查DNS配置文件是否存在
let app_dir = dirs::app_home_dir()?; let app_dir = dirs::app_home_dir()?;
let dns_path = app_dir.join("dns_config.yaml"); let dns_path = app_dir.join("dns_config.yaml");
if !dns_path.exists() { if !dns_path.exists() {
log::info!(target: "app", "Creating default DNS config file"); log::info!(target: "app", "Creating default DNS config file");
help::save_yaml(&dns_path, &default_dns_config, Some("# Clash Verge DNS Config"))?; help::save_yaml(
&dns_path,
&default_dns_config,
Some("# Clash Verge DNS Config"),
)?;
} }
Ok(()) Ok(())
} }
@@ -323,8 +365,7 @@ pub fn init_resources() -> Result<()> {
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
pub fn init_scheme() -> Result<()> { pub fn init_scheme() -> Result<()> {
use tauri::utils::platform::current_exe; use tauri::utils::platform::current_exe;
use winreg::enums::*; use winreg::{enums::*, RegKey};
use winreg::RegKey;
let app_exe = current_exe()?; let app_exe = current_exe()?;
let app_exe = dunce::canonicalize(app_exe)?; let app_exe = dunce::canonicalize(app_exe)?;

View File

@@ -1,8 +1,8 @@
pub mod dirs; pub mod dirs;
pub mod error; pub mod error;
pub mod help; pub mod help;
pub mod i18n;
pub mod init; pub mod init;
pub mod resolve; pub mod resolve;
pub mod server; pub mod server;
pub mod tmpl; pub mod tmpl;
pub mod i18n;

View File

@@ -1,9 +1,12 @@
use crate::config::IVerge;
use crate::utils::error;
use crate::{config::Config, config::PrfItem, core::*, utils::init, utils::server};
use crate::{log_err, wrap_err};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use crate::AppHandleManager; use crate::AppHandleManager;
use crate::{
config::{Config, IVerge, PrfItem},
core::*,
log_err,
utils::{error, init, server},
wrap_err,
};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use percent_encoding::percent_decode_str; use percent_encoding::percent_decode_str;
@@ -201,7 +204,7 @@ pub fn create_window() {
log::info!(target: "app", "Window created successfully, attempting to show"); log::info!(target: "app", "Window created successfully, attempting to show");
let _ = window.show(); let _ = window.show();
let _ = window.set_focus(); let _ = window.set_focus();
// 设置窗口状态监控,实时保存窗口位置和大小 // 设置窗口状态监控,实时保存窗口位置和大小
crate::feat::setup_window_state_monitor(&app_handle); crate::feat::setup_window_state_monitor(&app_handle);
} }
@@ -315,8 +318,7 @@ fn resolve_random_port_config() -> Result<()> {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub async fn set_public_dns(dns_server: String) { pub async fn set_public_dns(dns_server: String) {
use crate::core::handle; use crate::{core::handle, utils::dirs};
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
@@ -352,8 +354,7 @@ pub async fn set_public_dns(dns_server: String) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
pub async fn restore_public_dns() { pub async fn restore_public_dns() {
use crate::core::handle; use crate::{core::handle, utils::dirs};
use crate::utils::dirs;
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
let app_handle = handle::Handle::global().app_handle().unwrap(); let app_handle = handle::Handle::global().app_handle().unwrap();
log::info!(target: "app", "try to unset system dns"); log::info!(target: "app", "try to unset system dns");

View File

@@ -1,8 +1,10 @@
extern crate warp; extern crate warp;
use super::resolve; use super::resolve;
use crate::config::{Config, IVerge, DEFAULT_PAC}; use crate::{
use crate::log_err; config::{Config, IVerge, DEFAULT_PAC},
log_err,
};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use port_scanner::local_port_available; use port_scanner::local_port_available;
use std::convert::Infallible; use std::convert::Infallible;

View File

@@ -30,10 +30,5 @@
"./sidecar/verge-mihomo", "./sidecar/verge-mihomo",
"./sidecar/verge-mihomo-alpha" "./sidecar/verge-mihomo-alpha"
] ]
},
"app": {
"trayIcon": {
"iconPath": "icons/tray-icon.ico"
}
} }
} }

View File

@@ -119,6 +119,7 @@ const SettingVergeBasic = ({ onError }: Props) => {
> >
<Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}> <Select size="small" sx={{ width: 140, "> div": { py: "7.5px" } }}>
<MenuItem value="main_window">{t("Show Main Window")}</MenuItem> <MenuItem value="main_window">{t("Show Main Window")}</MenuItem>
<MenuItem value="tray_menu">{t("Show Tray Menu")}</MenuItem>
<MenuItem value="system_proxy">{t("System Proxy")}</MenuItem> <MenuItem value="system_proxy">{t("System Proxy")}</MenuItem>
<MenuItem value="tun_mode">{t("Tun Mode")}</MenuItem> <MenuItem value="tun_mode">{t("Tun Mode")}</MenuItem>
<MenuItem value="disable">{t("Disable")}</MenuItem> <MenuItem value="disable">{t("Disable")}</MenuItem>

View File

@@ -288,6 +288,7 @@
"theme.system": "System", "theme.system": "System",
"Tray Click Event": "Tray Click Event", "Tray Click Event": "Tray Click Event",
"Show Main Window": "Show Main Window", "Show Main Window": "Show Main Window",
"Show Tray Menu": "Show Tray Menu",
"Copy Env Type": "Copy Env Type", "Copy Env Type": "Copy Env Type",
"Copy Success": "Copy Success", "Copy Success": "Copy Success",
"Start Page": "Start Page", "Start Page": "Start Page",
@@ -343,7 +344,7 @@
"Backup Setting Info": "Support WebDAV backup configuration files", "Backup Setting Info": "Support WebDAV backup configuration files",
"Runtime Config": "Runtime Config", "Runtime Config": "Runtime Config",
"Open Conf Dir": "Open Conf Dir", "Open Conf Dir": "Open Conf Dir",
"Open Conf Dir Info": "If the software runs abnormally, BACKUP and delete all files in this folder than restart the software", "Open Conf Dir Info": "If the software runs abnormally, BACKUP and delete all files in this folder then restart the software",
"Open Core Dir": "Open Core Dir", "Open Core Dir": "Open Core Dir",
"Open Logs Dir": "Open Logs Dir", "Open Logs Dir": "Open Logs Dir",
"Check for Updates": "Check for Updates", "Check for Updates": "Check for Updates",

View File

@@ -287,6 +287,7 @@
"theme.system": "系统", "theme.system": "系统",
"Tray Click Event": "托盘点击事件", "Tray Click Event": "托盘点击事件",
"Show Main Window": "显示主窗口", "Show Main Window": "显示主窗口",
"Show Tray Menu": "显示托盘菜单",
"Copy Env Type": "复制环境变量类型", "Copy Env Type": "复制环境变量类型",
"Copy Success": "复制成功", "Copy Success": "复制成功",
"Start Page": "启动页面", "Start Page": "启动页面",

View File

@@ -718,7 +718,12 @@ interface IProxyConfig
interface IVergeConfig { interface IVergeConfig {
app_log_level?: "trace" | "debug" | "info" | "warn" | "error" | string; app_log_level?: "trace" | "debug" | "info" | "warn" | "error" | string;
language?: string; language?: string;
tray_event?: "main_window" | "system_proxy" | "tun_mode" | string; tray_event?:
| "main_window"
| "tray_menu"
| "system_proxy"
| "tun_mode"
| string;
env_type?: "bash" | "cmd" | "powershell" | "fish" | string; env_type?: "bash" | "cmd" | "powershell" | "fish" | string;
startup_script?: string; startup_script?: string;
start_page?: string; start_page?: string;

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-5.10 = .233 LINUX_VERSION-5.10 = .234
LINUX_KERNEL_HASH-5.10.233 = 239e57280f8c9159187e383d7b730d29f5cc8e1b30be218f004e0c90d1137f52 LINUX_KERNEL_HASH-5.10.234 = 9597c4fee2f1ce452acfec516f4325ad342155872052fd5f0d9ce2ddcc26ebe5

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-5.15 = .177 LINUX_VERSION-5.15 = .178
LINUX_KERNEL_HASH-5.15.177 = ea9eb8088d4231f8a01b191ceef5f4d92238f6c7519f6fbcb57e448ee9e0a6e0 LINUX_KERNEL_HASH-5.15.178 = efe9f7eb5ea4d26cec6290689343e1804eb3b4a88ff5a60497a696fc08157c42

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-5.4 = .289 LINUX_VERSION-5.4 = .290
LINUX_KERNEL_HASH-5.4.289 = 1e3e5fc052c8e15f8eaa37d30bf4f0b3eef7430dd234d9fed0d0005852a06d10 LINUX_KERNEL_HASH-5.4.290 = 6cc73cf2a7f50580f7d8c7e99d2f2e8ada8b7d2f4e76f5896f0daf691cc2a456

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-6.1 = .128 LINUX_VERSION-6.1 = .130
LINUX_KERNEL_HASH-6.1.128 = 874d67d3181570e69ac6b33853f0448f05fc90d4cf3e4baaadc4a9cede7c50f3 LINUX_KERNEL_HASH-6.1.130 = 9416b2c2d448ec7f54bb0ce5713fb34c32dae4a4edf1abd8cf7a8995cbac66fd

View File

@@ -1,2 +1,2 @@
LINUX_VERSION-6.6 = .75 LINUX_VERSION-6.6 = .82
LINUX_KERNEL_HASH-6.6.75 = f7dfb1fa9716ba139d0b4c8161535816d400dea21d5943f513448429b1790290 LINUX_KERNEL_HASH-6.6.82 = f3c2389b8c23cabe747f104a3e434201ca6e7725bbbfb3a8c59a063ac4820e41

View File

@@ -51,7 +51,7 @@ sed -i '/option disabled/d' /etc/config/wireless
sed -i '/set wireless.radio${devidx}.disabled/d' /lib/wifi/mac80211.sh sed -i '/set wireless.radio${devidx}.disabled/d' /lib/wifi/mac80211.sh
sed -i '/DISTRIB_REVISION/d' /etc/openwrt_release sed -i '/DISTRIB_REVISION/d' /etc/openwrt_release
echo "DISTRIB_REVISION='R25.2.5'" >> /etc/openwrt_release echo "DISTRIB_REVISION='R25.3.15'" >> /etc/openwrt_release
sed -i '/DISTRIB_DESCRIPTION/d' /etc/openwrt_release sed -i '/DISTRIB_DESCRIPTION/d' /etc/openwrt_release
echo "DISTRIB_DESCRIPTION='LEDE '" >> /etc/openwrt_release echo "DISTRIB_DESCRIPTION='LEDE '" >> /etc/openwrt_release

View File

@@ -25,7 +25,7 @@ Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
--- a/drivers/nvmem/core.c --- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c
@@ -846,14 +846,6 @@ static int nvmem_add_cells_from_layout(s @@ -823,14 +823,6 @@ static int nvmem_add_cells_from_layout(s
} }
#if IS_ENABLED(CONFIG_OF) #if IS_ENABLED(CONFIG_OF)
@@ -65,7 +65,7 @@ Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
#endif /* ifndef _LINUX_NVMEM_CONSUMER_H */ #endif /* ifndef _LINUX_NVMEM_CONSUMER_H */
--- a/include/linux/nvmem-provider.h --- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h +++ b/include/linux/nvmem-provider.h
@@ -244,6 +244,27 @@ nvmem_layout_get_match_data(struct nvmem @@ -241,6 +241,27 @@ nvmem_layout_get_match_data(struct nvmem
#endif /* CONFIG_NVMEM */ #endif /* CONFIG_NVMEM */

View File

@@ -1,91 +0,0 @@
From ec9c08a1cb8dc5e8e003f95f5f62de41dde235bb Mon Sep 17 00:00:00 2001
From: Miquel Raynal <miquel.raynal@bootlin.com>
Date: Fri, 15 Dec 2023 11:15:29 +0000
Subject: [PATCH] nvmem: Create a header for internal sharing
Before adding all the NVMEM layout bus infrastructure to the core, let's
move the main nvmem_device structure in an internal header, only
available to the core. This way all the additional code can be added in
a dedicated file in order to keep the current core file tidy.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-4-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/nvmem/core.c | 24 +-----------------------
drivers/nvmem/internals.h | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 23 deletions(-)
create mode 100644 drivers/nvmem/internals.h
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -19,29 +19,7 @@
#include <linux/of.h>
#include <linux/slab.h>
-struct nvmem_device {
- struct module *owner;
- struct device dev;
- int stride;
- int word_size;
- int id;
- struct kref refcnt;
- size_t size;
- bool read_only;
- bool root_only;
- int flags;
- enum nvmem_type type;
- struct bin_attribute eeprom;
- struct device *base_dev;
- struct list_head cells;
- const struct nvmem_keepout *keepout;
- unsigned int nkeepout;
- nvmem_reg_read_t reg_read;
- nvmem_reg_write_t reg_write;
- struct gpio_desc *wp_gpio;
- struct nvmem_layout *layout;
- void *priv;
-};
+#include "internals.h"
#define to_nvmem_device(d) container_of(d, struct nvmem_device, dev)
--- /dev/null
+++ b/drivers/nvmem/internals.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_NVMEM_INTERNALS_H
+#define _LINUX_NVMEM_INTERNALS_H
+
+#include <linux/device.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
+
+struct nvmem_device {
+ struct module *owner;
+ struct device dev;
+ struct list_head node;
+ int stride;
+ int word_size;
+ int id;
+ struct kref refcnt;
+ size_t size;
+ bool read_only;
+ bool root_only;
+ int flags;
+ enum nvmem_type type;
+ struct bin_attribute eeprom;
+ struct device *base_dev;
+ struct list_head cells;
+ const struct nvmem_keepout *keepout;
+ unsigned int nkeepout;
+ nvmem_reg_read_t reg_read;
+ nvmem_reg_write_t reg_write;
+ struct gpio_desc *wp_gpio;
+ struct nvmem_layout *layout;
+ void *priv;
+};
+
+#endif /* ifndef _LINUX_NVMEM_INTERNALS_H */

View File

@@ -1,79 +0,0 @@
From 1b7c298a4ecbc28cc6ee94005734bff55eb83d22 Mon Sep 17 00:00:00 2001
From: Miquel Raynal <miquel.raynal@bootlin.com>
Date: Fri, 15 Dec 2023 11:15:30 +0000
Subject: [PATCH] nvmem: Simplify the ->add_cells() hook
The layout entry is not used and will anyway be made useless by the new
layout bus infrastructure coming next, so drop it. While at it, clarify
the kdoc entry.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-5-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/nvmem/core.c | 2 +-
drivers/nvmem/layouts/onie-tlv.c | 3 +--
drivers/nvmem/layouts/sl28vpd.c | 3 +--
include/linux/nvmem-provider.h | 8 +++-----
4 files changed, 6 insertions(+), 10 deletions(-)
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -815,7 +815,7 @@ static int nvmem_add_cells_from_layout(s
int ret;
if (layout && layout->add_cells) {
- ret = layout->add_cells(&nvmem->dev, nvmem, layout);
+ ret = layout->add_cells(&nvmem->dev, nvmem);
if (ret)
return ret;
}
--- a/drivers/nvmem/layouts/onie-tlv.c
+++ b/drivers/nvmem/layouts/onie-tlv.c
@@ -182,8 +182,7 @@ static bool onie_tlv_crc_is_valid(struct
return true;
}
-static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem,
- struct nvmem_layout *layout)
+static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem)
{
struct onie_tlv_hdr hdr;
size_t table_len, data_len, hdr_len;
--- a/drivers/nvmem/layouts/sl28vpd.c
+++ b/drivers/nvmem/layouts/sl28vpd.c
@@ -80,8 +80,7 @@ static int sl28vpd_v1_check_crc(struct d
return 0;
}
-static int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem,
- struct nvmem_layout *layout)
+static int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem)
{
const struct nvmem_cell_info *pinfo;
struct nvmem_cell_info info = {0};
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -156,9 +156,8 @@ struct nvmem_cell_table {
*
* @name: Layout name.
* @of_match_table: Open firmware match table.
- * @add_cells: Will be called if a nvmem device is found which
- * has this layout. The function will add layout
- * specific cells with nvmem_add_one_cell().
+ * @add_cells: Called to populate the layout using
+ * nvmem_add_one_cell().
* @fixup_cell_info: Will be called before a cell is added. Can be
* used to modify the nvmem_cell_info.
* @owner: Pointer to struct module.
@@ -172,8 +171,7 @@ struct nvmem_cell_table {
struct nvmem_layout {
const char *name;
const struct of_device_id *of_match_table;
- int (*add_cells)(struct device *dev, struct nvmem_device *nvmem,
- struct nvmem_layout *layout);
+ int (*add_cells)(struct device *dev, struct nvmem_device *nvmem);
void (*fixup_cell_info)(struct nvmem_device *nvmem,
struct nvmem_layout *layout,
struct nvmem_cell_info *cell);

View File

@@ -1,169 +0,0 @@
From 1172460e716784ac7e1049a537bdca8edbf97360 Mon Sep 17 00:00:00 2001
From: Miquel Raynal <miquel.raynal@bootlin.com>
Date: Fri, 15 Dec 2023 11:15:31 +0000
Subject: [PATCH] nvmem: Move and rename ->fixup_cell_info()
This hook is meant to be used by any provider and instantiating a layout
just for this is useless. Let's instead move this hook to the nvmem
device and add it to the config structure to be easily shared by the
providers.
While at moving this hook, rename it ->fixup_dt_cell_info() to clarify
its main intended purpose.
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20231215111536.316972-6-srinivas.kandagatla@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/nvmem/core.c | 6 +++---
drivers/nvmem/imx-ocotp.c | 11 +++--------
drivers/nvmem/internals.h | 2 ++
drivers/nvmem/mtk-efuse.c | 11 +++--------
include/linux/nvmem-provider.h | 9 ++++-----
5 files changed, 15 insertions(+), 24 deletions(-)
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -674,7 +674,6 @@ static int nvmem_validate_keepouts(struc
static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
{
- struct nvmem_layout *layout = nvmem->layout;
struct device *dev = &nvmem->dev;
struct device_node *child;
const __be32 *addr;
@@ -704,8 +703,8 @@ static int nvmem_add_cells_from_dt(struc
info.np = of_node_get(child);
- if (layout && layout->fixup_cell_info)
- layout->fixup_cell_info(nvmem, layout, &info);
+ if (nvmem->fixup_dt_cell_info)
+ nvmem->fixup_dt_cell_info(nvmem, &info);
ret = nvmem_add_one_cell(nvmem, &info);
kfree(info.name);
@@ -894,6 +893,7 @@ struct nvmem_device *nvmem_register(cons
kref_init(&nvmem->refcnt);
INIT_LIST_HEAD(&nvmem->cells);
+ nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info;
nvmem->owner = config->owner;
if (!nvmem->owner && config->dev->driver)
--- a/drivers/nvmem/imx-ocotp.c
+++ b/drivers/nvmem/imx-ocotp.c
@@ -583,17 +583,12 @@ static const struct of_device_id imx_oco
};
MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids);
-static void imx_ocotp_fixup_cell_info(struct nvmem_device *nvmem,
- struct nvmem_layout *layout,
- struct nvmem_cell_info *cell)
+static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *cell)
{
cell->read_post_process = imx_ocotp_cell_pp;
}
-static struct nvmem_layout imx_ocotp_layout = {
- .fixup_cell_info = imx_ocotp_fixup_cell_info,
-};
-
static int imx_ocotp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -619,7 +614,7 @@ static int imx_ocotp_probe(struct platfo
imx_ocotp_nvmem_config.size = 4 * priv->params->nregs;
imx_ocotp_nvmem_config.dev = dev;
imx_ocotp_nvmem_config.priv = priv;
- imx_ocotp_nvmem_config.layout = &imx_ocotp_layout;
+ imx_ocotp_nvmem_config.fixup_dt_cell_info = &imx_ocotp_fixup_dt_cell_info;
priv->config = &imx_ocotp_nvmem_config;
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -23,6 +23,8 @@ struct nvmem_device {
struct bin_attribute eeprom;
struct device *base_dev;
struct list_head cells;
+ void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *cell);
const struct nvmem_keepout *keepout;
unsigned int nkeepout;
nvmem_reg_read_t reg_read;
--- a/drivers/nvmem/mtk-efuse.c
+++ b/drivers/nvmem/mtk-efuse.c
@@ -45,9 +45,8 @@ static int mtk_efuse_gpu_speedbin_pp(voi
return 0;
}
-static void mtk_efuse_fixup_cell_info(struct nvmem_device *nvmem,
- struct nvmem_layout *layout,
- struct nvmem_cell_info *cell)
+static void mtk_efuse_fixup_dt_cell_info(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *cell)
{
size_t sz = strlen(cell->name);
@@ -61,10 +60,6 @@ static void mtk_efuse_fixup_cell_info(st
cell->read_post_process = mtk_efuse_gpu_speedbin_pp;
}
-static struct nvmem_layout mtk_efuse_layout = {
- .fixup_cell_info = mtk_efuse_fixup_cell_info,
-};
-
static int mtk_efuse_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -91,7 +86,7 @@ static int mtk_efuse_probe(struct platfo
econfig.priv = priv;
econfig.dev = dev;
if (pdata->uses_post_processing)
- econfig.layout = &mtk_efuse_layout;
+ econfig.fixup_dt_cell_info = &mtk_efuse_fixup_dt_cell_info;
nvmem = devm_nvmem_register(dev, &econfig);
return PTR_ERR_OR_ZERO(nvmem);
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -83,6 +83,8 @@ struct nvmem_cell_info {
* @cells: Optional array of pre-defined NVMEM cells.
* @ncells: Number of elements in cells.
* @add_legacy_fixed_of_cells: Read fixed NVMEM cells from old OF syntax.
+ * @fixup_dt_cell_info: Will be called before a cell is added. Can be
+ * used to modify the nvmem_cell_info.
* @keepout: Optional array of keepout ranges (sorted ascending by start).
* @nkeepout: Number of elements in the keepout array.
* @type: Type of the nvmem storage
@@ -113,6 +115,8 @@ struct nvmem_config {
const struct nvmem_cell_info *cells;
int ncells;
bool add_legacy_fixed_of_cells;
+ void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
+ struct nvmem_cell_info *cell);
const struct nvmem_keepout *keepout;
unsigned int nkeepout;
enum nvmem_type type;
@@ -158,8 +162,6 @@ struct nvmem_cell_table {
* @of_match_table: Open firmware match table.
* @add_cells: Called to populate the layout using
* nvmem_add_one_cell().
- * @fixup_cell_info: Will be called before a cell is added. Can be
- * used to modify the nvmem_cell_info.
* @owner: Pointer to struct module.
* @node: List node.
*
@@ -172,9 +174,6 @@ struct nvmem_layout {
const char *name;
const struct of_device_id *of_match_table;
int (*add_cells)(struct device *dev, struct nvmem_device *nvmem);
- void (*fixup_cell_info)(struct nvmem_device *nvmem,
- struct nvmem_layout *layout,
- struct nvmem_cell_info *cell);
/* private */
struct module *owner;

View File

@@ -330,7 +330,7 @@ Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
} }
EXPORT_SYMBOL_GPL(nvmem_cell_put); EXPORT_SYMBOL_GPL(nvmem_cell_put);
@@ -2116,11 +2057,22 @@ EXPORT_SYMBOL_GPL(nvmem_dev_size); @@ -2118,11 +2059,22 @@ EXPORT_SYMBOL_GPL(nvmem_dev_size);
static int __init nvmem_init(void) static int __init nvmem_init(void)
{ {

View File

@@ -178,9 +178,9 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
--- a/net/core/sysctl_net_core.c --- a/net/core/sysctl_net_core.c
+++ b/net/core/sysctl_net_core.c +++ b/net/core/sysctl_net_core.c
@@ -30,6 +30,7 @@ static int min_sndbuf = SOCK_MIN_SNDBUF; @@ -30,6 +30,7 @@ static int min_sndbuf = SOCK_MIN_SNDBUF;
static int min_rcvbuf = SOCK_MIN_RCVBUF;
static int max_skb_frags = MAX_SKB_FRAGS; static int max_skb_frags = MAX_SKB_FRAGS;
static int min_mem_pcpu_rsv = SK_MEMORY_PCPU_RESERVE; static int min_mem_pcpu_rsv = SK_MEMORY_PCPU_RESERVE;
static int netdev_budget_usecs_min = 2 * USEC_PER_SEC / HZ;
+static int backlog_threaded; +static int backlog_threaded;
static int net_msg_warn; /* Unused, but still a sysctl */ static int net_msg_warn; /* Unused, but still a sysctl */

View File

@@ -519,8 +519,8 @@
+ +
+ mrt->vif_table[vif].pkt_in += pkts_in; + mrt->vif_table[vif].pkt_in += pkts_in;
+ mrt->vif_table[vif].bytes_in += bytes_in; + mrt->vif_table[vif].bytes_in += bytes_in;
+ cache->_c.mfc_un.res.pkt += pkts_out; + atomic_long_add(pkts_out, &cache->_c.mfc_un.res.pkt);
+ cache->_c.mfc_un.res.bytes += bytes_out; + atomic_long_add(bytes_out, &cache->_c.mfc_un.res.bytes);
+ +
+ for (vifi = cache->_c.mfc_un.res.minvif; + for (vifi = cache->_c.mfc_un.res.minvif;
+ vifi < cache->_c.mfc_un.res.maxvif; vifi++) { + vifi < cache->_c.mfc_un.res.maxvif; vifi++) {
@@ -806,8 +806,8 @@
+ +
+ mrt->vif_table[vif].pkt_in += pkts_in; + mrt->vif_table[vif].pkt_in += pkts_in;
+ mrt->vif_table[vif].bytes_in += bytes_in; + mrt->vif_table[vif].bytes_in += bytes_in;
+ cache->_c.mfc_un.res.pkt += pkts_out; + atomic64_add(pkts_out, &cache->_c.mfc_un.res.pkt);
+ cache->_c.mfc_un.res.bytes += bytes_out; + atomic64_add(bytes_out, &cache->_c.mfc_un.res.bytes);
+ +
+ for (vifi = cache->_c.mfc_un.res.minvif; + for (vifi = cache->_c.mfc_un.res.minvif;
+ vifi < cache->_c.mfc_un.res.maxvif; vifi++) { + vifi < cache->_c.mfc_un.res.maxvif; vifi++) {

View File

@@ -1,42 +0,0 @@
--- a/arch/arm64/boot/dts/rockchip/rk3568.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk3568.dtsi
@@ -225,6 +225,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY0_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY0>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf0>;
#phy-cells = <1>;
--- a/arch/arm64/boot/dts/rockchip/rk356x.dtsi
+++ b/arch/arm64/boot/dts/rockchip/rk356x.dtsi
@@ -1719,6 +1719,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY1_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY1>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf1>;
#phy-cells = <1>;
@@ -1735,6 +1736,7 @@
assigned-clocks = <&pmucru CLK_PCIEPHY2_REF>;
assigned-clock-rates = <100000000>;
resets = <&cru SRST_PIPEPHY2>;
+ reset-names = "phy";
rockchip,pipe-grf = <&pipegrf>;
rockchip,pipe-phy-grf = <&pipe_phy_grf2>;
#phy-cells = <1>;
--- a/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
+++ b/drivers/phy/rockchip/phy-rockchip-naneng-combphy.c
@@ -324,7 +324,10 @@ static int rockchip_combphy_parse_dt(str
priv->ext_refclk = device_property_present(dev, "rockchip,ext-refclk");
- priv->phy_rst = devm_reset_control_get(dev, "phy");
+ priv->phy_rst = devm_reset_control_get_exclusive(dev, "phy");
+ /* fallback to old behaviour */
+ if (PTR_ERR(priv->phy_rst) == -ENOENT)
+ priv->phy_rst = devm_reset_control_array_get_exclusive(dev);
if (IS_ERR(priv->phy_rst))
return dev_err_probe(dev, PTR_ERR(priv->phy_rst), "failed to get phy reset\n");

View File

@@ -289,6 +289,7 @@ CONFIG_MEMFD_CREATE=y
# CONFIG_MGEODEGX1 is not set # CONFIG_MGEODEGX1 is not set
# CONFIG_MGEODE_LX is not set # CONFIG_MGEODE_LX is not set
CONFIG_MICROCODE=y CONFIG_MICROCODE=y
CONFIG_MICROCODE_INITRD32=y
CONFIG_MICROCODE_AMD=y CONFIG_MICROCODE_AMD=y
CONFIG_MICROCODE_INTEL=y CONFIG_MICROCODE_INTEL=y
# CONFIG_MICROCODE_LATE_LOADING is not set # CONFIG_MICROCODE_LATE_LOADING is not set

View File

@@ -13,6 +13,7 @@ import (
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
gost "github.com/metacubex/mihomo/transport/gost-plugin"
"github.com/metacubex/mihomo/transport/restls" "github.com/metacubex/mihomo/transport/restls"
obfs "github.com/metacubex/mihomo/transport/simple-obfs" obfs "github.com/metacubex/mihomo/transport/simple-obfs"
shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls" shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls"
@@ -34,6 +35,7 @@ type ShadowSocks struct {
obfsMode string obfsMode string
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
gostOption *gost.Option
shadowTLSOption *shadowtls.ShadowTLSOption shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restlsC.Config restlsConfig *restlsC.Config
} }
@@ -71,6 +73,17 @@ type v2rayObfsOption struct {
V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"` V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"`
} }
type gostObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
}
type shadowTLSOption struct { type shadowTLSOption struct {
Password string `obfs:"password"` Password string `obfs:"password"`
Host string `obfs:"host"` Host string `obfs:"host"`
@@ -97,7 +110,13 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port) c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket": case "websocket":
var err error var err error
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption) if ss.v2rayOption != nil {
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
} else if ss.gostOption != nil {
c, err = gost.NewGostWebsocket(ctx, c, ss.gostOption)
} else {
return nil, fmt.Errorf("plugin options is required")
}
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
@@ -240,6 +259,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
} }
var v2rayOption *v2rayObfs.Option var v2rayOption *v2rayObfs.Option
var gostOption *gost.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restlsC.Config var restlsConfig *restlsC.Config
@@ -281,6 +301,28 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.SkipCertVerify = opts.SkipCertVerify
v2rayOption.Fingerprint = opts.Fingerprint v2rayOption.Fingerprint = opts.Fingerprint
} }
} else if option.Plugin == "gost-plugin" {
opts := gostObfsOption{Host: "bing.com", Mux: true}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize gost-plugin error: %w", addr, err)
}
if opts.Mode != "websocket" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
}
obfsMode = opts.Mode
gostOption = &gost.Option{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
Mux: opts.Mux,
}
if opts.TLS {
gostOption.TLS = true
gostOption.SkipCertVerify = opts.SkipCertVerify
gostOption.Fingerprint = opts.Fingerprint
}
} else if option.Plugin == shadowtls.Mode { } else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode obfsMode = shadowtls.Mode
opt := &shadowTLSOption{ opt := &shadowTLSOption{
@@ -336,6 +378,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
option: &option, option: &option,
obfsMode: obfsMode, obfsMode: obfsMode,
v2rayOption: v2rayOption, v2rayOption: v2rayOption,
gostOption: gostOption,
obfsOption: obfsOption, obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt, shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig, restlsConfig: restlsConfig,

View File

@@ -60,28 +60,35 @@ func (sd *Dispatcher) forceSniff(metadata *C.Metadata) bool {
return false return false
} }
func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter) bool { // UDPSniff is called when a UDP NAT is created and passed the first initialization packet.
// It may return a wrapped packetSender if the sniffer process needs to wait for multiple packets.
// This function must be non-blocking, and any blocking operations should be done in the wrapped packetSender.
func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter, packetSender C.PacketSender) C.PacketSender {
metadata := packet.Metadata() metadata := packet.Metadata()
if sd.shouldOverride(metadata) { if sd.shouldOverride(metadata) {
for sniffer, config := range sd.sniffers { for current, config := range sd.sniffers {
if sniffer.SupportNetwork() == C.UDP || sniffer.SupportNetwork() == C.ALLNet { if current.SupportNetwork() == C.UDP || current.SupportNetwork() == C.ALLNet {
inWhitelist := sniffer.SupportPort(metadata.DstPort) inWhitelist := current.SupportPort(metadata.DstPort)
overrideDest := config.OverrideDest overrideDest := config.OverrideDest
if inWhitelist { if inWhitelist {
host, err := sniffer.SniffData(packet.Data()) if wrapable, ok := current.(sniffer.MultiPacketSniffer); ok {
return wrapable.WrapperSender(packetSender, overrideDest)
}
host, err := current.SniffData(packet.Data())
if err != nil { if err != nil {
continue continue
} }
sd.replaceDomain(metadata, host, overrideDest) replaceDomain(metadata, host, overrideDest)
return true return packetSender
} }
} }
} }
} }
return false return packetSender
} }
// TCPSniff returns true if the connection is sniffed to have a domain // TCPSniff returns true if the connection is sniffed to have a domain
@@ -130,13 +137,13 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
sd.skipList.Delete(dst) sd.skipList.Delete(dst)
sd.replaceDomain(metadata, host, overrideDest) replaceDomain(metadata, host, overrideDest)
return true return true
} }
return false return false
} }
func (sd *Dispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) { func replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
metadata.SniffHost = host metadata.SniffHost = host
if overrideDest { if overrideDest {
log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]", log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]",

View File

@@ -7,10 +7,14 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"sync"
"time"
"github.com/metacubex/mihomo/common/buf" "github.com/metacubex/mihomo/common/buf"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/constant"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/sniffer"
"github.com/metacubex/quic-go/quicvarint" "github.com/metacubex/quic-go/quicvarint"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
@@ -21,6 +25,12 @@ import (
const ( const (
versionDraft29 uint32 = 0xff00001d versionDraft29 uint32 = 0xff00001d
version1 uint32 = 0x1 version1 uint32 = 0x1
quicPacketTypeInitial = 0x00
quicPacketType0RTT = 0x01
// Timeout before quic sniffer all packets
quicWaitConn = time.Second * 3
) )
var ( var (
@@ -30,6 +40,9 @@ var (
errNotQuicInitial = errors.New("not QUIC initial packet") errNotQuicInitial = errors.New("not QUIC initial packet")
) )
var _ sniffer.Sniffer = (*QuicSniffer)(nil)
var _ sniffer.MultiPacketSniffer = (*QuicSniffer)(nil)
type QuicSniffer struct { type QuicSniffer struct {
*BaseSniffer *BaseSniffer
} }
@@ -44,67 +57,156 @@ func NewQuicSniffer(snifferConfig SnifferConfig) (*QuicSniffer, error) {
}, nil }, nil
} }
func (quic QuicSniffer) Protocol() string { func (sniffer *QuicSniffer) Protocol() string {
return "quic" return "quic"
} }
func (quic QuicSniffer) SupportNetwork() C.NetWork { func (sniffer *QuicSniffer) SupportNetwork() C.NetWork {
return C.UDP return C.UDP
} }
func (quic QuicSniffer) SniffData(b []byte) (string, error) { func (sniffer *QuicSniffer) SniffData(b []byte) (string, error) {
return "", ErrorUnsupportedSniffer
}
func (sniffer *QuicSniffer) WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender {
return &quicPacketSender{
sender: packetSender,
buffer: make([]quicDataBlock, 0),
chClose: make(chan struct{}),
override: override,
}
}
type quicDataBlock struct {
offset uint64
length uint64
data []byte
}
var _ constant.PacketSender = (*quicPacketSender)(nil)
type quicPacketSender struct {
lock sync.RWMutex
buffer []quicDataBlock
sender constant.PacketSender
result string
override bool
chClose chan struct{}
closed bool
}
// Send will send PacketAdapter nonblocking
// the implement must call UDPPacket.Drop() inside Send
func (q *quicPacketSender) Send(current constant.PacketAdapter) {
defer q.sender.Send(current)
q.lock.RLock()
if q.closed {
q.lock.RUnlock()
return
}
q.lock.RUnlock()
err := q.readQuicData(current.Data())
if err != nil {
q.close()
return
}
}
// Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy
func (q *quicPacketSender) Process(conn constant.PacketConn, proxy constant.WriteBackProxy) {
q.sender.Process(conn, proxy)
}
// ResolveUDP wait sniffer recv all fragments and update the domain
func (q *quicPacketSender) ResolveUDP(data *constant.Metadata) error {
select {
case <-q.chClose:
q.lock.RLock()
replaceDomain(data, q.result, q.override)
q.lock.RUnlock()
break
case <-time.After(quicWaitConn):
q.close()
}
return q.sender.ResolveUDP(data)
}
// Close stop the Process loop
func (q *quicPacketSender) Close() {
q.sender.Close()
q.close()
}
func (q *quicPacketSender) close() {
q.lock.Lock()
if !q.closed {
close(q.chClose)
q.closed = true
}
q.lock.Unlock()
}
func (q *quicPacketSender) readQuicData(b []byte) error {
buffer := buf.As(b) buffer := buf.As(b)
typeByte, err := buffer.ReadByte() typeByte, err := buffer.ReadByte()
if err != nil { if err != nil {
return "", errNotQuic return errNotQuic
} }
isLongHeader := typeByte&0x80 > 0 isLongHeader := typeByte&0x80 > 0
if !isLongHeader || typeByte&0x40 == 0 { if !isLongHeader || typeByte&0x40 == 0 {
return "", errNotQuicInitial return errNotQuicInitial
} }
vb, err := buffer.ReadBytes(4) vb, err := buffer.ReadBytes(4)
if err != nil { if err != nil {
return "", errNotQuic return errNotQuic
} }
versionNumber := binary.BigEndian.Uint32(vb) versionNumber := binary.BigEndian.Uint32(vb)
if versionNumber != 0 && typeByte&0x40 == 0 { if versionNumber != 0 && typeByte&0x40 == 0 {
return "", errNotQuic return errNotQuic
} else if versionNumber != versionDraft29 && versionNumber != version1 { } else if versionNumber != versionDraft29 && versionNumber != version1 {
return "", errNotQuic return errNotQuic
} }
if (typeByte&0x30)>>4 != 0x0 { connIdLen, err := buffer.ReadByte()
return "", errNotQuicInitial if err != nil || connIdLen == 0 {
return errNotQuic
}
destConnID := make([]byte, int(connIdLen))
if _, err := io.ReadFull(buffer, destConnID); err != nil {
return errNotQuic
} }
var destConnID []byte packetType := (typeByte & 0x30) >> 4
if l, err := buffer.ReadByte(); err != nil { if packetType != quicPacketTypeInitial {
return "", errNotQuic return nil
} else if destConnID, err = buffer.ReadBytes(int(l)); err != nil {
return "", errNotQuic
} }
if l, err := buffer.ReadByte(); err != nil { if l, err := buffer.ReadByte(); err != nil {
return "", errNotQuic return errNotQuic
} else if _, err := buffer.ReadBytes(int(l)); err != nil { } else if _, err := buffer.ReadBytes(int(l)); err != nil {
return "", errNotQuic return errNotQuic
} }
tokenLen, err := quicvarint.Read(buffer) tokenLen, err := quicvarint.Read(buffer)
if err != nil || tokenLen > uint64(len(b)) { if err != nil || tokenLen > uint64(len(b)) {
return "", errNotQuic return errNotQuic
} }
if _, err = buffer.ReadBytes(int(tokenLen)); err != nil { if _, err = buffer.ReadBytes(int(tokenLen)); err != nil {
return "", errNotQuic return errNotQuic
} }
packetLen, err := quicvarint.Read(buffer) packetLen, err := quicvarint.Read(buffer)
if err != nil { if err != nil {
return "", errNotQuic return errNotQuic
} }
hdrLen := len(b) - buffer.Len() hdrLen := len(b) - buffer.Len()
@@ -120,7 +222,7 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
hpKey := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic hp", 16) hpKey := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic hp", 16)
block, err := aes.NewCipher(hpKey) block, err := aes.NewCipher(hpKey)
if err != nil { if err != nil {
return "", err return err
} }
cache := buf.NewPacket() cache := buf.NewPacket()
@@ -130,6 +232,7 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16]) block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16])
firstByte := b[0] firstByte := b[0]
// Encrypt/decrypt first byte. // Encrypt/decrypt first byte.
if isLongHeader { if isLongHeader {
// Long header: 4 bits masked // Long header: 4 bits masked
// High 4 bits are not protected. // High 4 bits are not protected.
@@ -153,8 +256,8 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
packetNumber[i] ^= mask[1+i] packetNumber[i] ^= mask[1+i]
} }
if packetNumber[0] != 0 && packetNumber[0] != 1 { if int(packetLen)+hdrLen > len(b) || extHdrLen > len(b) {
return "", errNotQuicInitial return errNotQuic
} }
data := b[extHdrLen : int(packetLen)+hdrLen] data := b[extHdrLen : int(packetLen)+hdrLen]
@@ -163,12 +266,13 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12) iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
aesCipher, err := aes.NewCipher(key) aesCipher, err := aes.NewCipher(key)
if err != nil { if err != nil {
return "", err return err
} }
aead, err := cipher.NewGCM(aesCipher) aead, err := cipher.NewGCM(aesCipher)
if err != nil { if err != nil {
return "", err return err
} }
// We only decrypt once, so we do not need to XOR it back. // We only decrypt once, so we do not need to XOR it back.
// https://github.com/quic-go/qtls-go1-20/blob/e132a0e6cb45e20ac0b705454849a11d09ba5a54/cipher_suites.go#L496 // https://github.com/quic-go/qtls-go1-20/blob/e132a0e6cb45e20ac0b705454849a11d09ba5a54/cipher_suites.go#L496
for i, b := range packetNumber { for i, b := range packetNumber {
@@ -177,12 +281,11 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
dst := cache.Extend(len(data)) dst := cache.Extend(len(data))
decrypted, err := aead.Open(dst[:0], iv, data, extHdr) decrypted, err := aead.Open(dst[:0], iv, data, extHdr)
if err != nil { if err != nil {
return "", err return err
} }
buffer = buf.As(decrypted) buffer = buf.As(decrypted)
cryptoLen := uint(0)
cryptoData := cache.Extend(buffer.Len())
for i := 0; !buffer.IsEmpty(); i++ { for i := 0; !buffer.IsEmpty(); i++ {
frameType := byte(0x0) // Default to PADDING frame frameType := byte(0x0) // Default to PADDING frame
for frameType == 0x0 && !buffer.IsEmpty() { for frameType == 0x0 && !buffer.IsEmpty() {
@@ -193,79 +296,141 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
case 0x01: // PING frame case 0x01: // PING frame
case 0x02, 0x03: // ACK frame case 0x02, 0x03: // ACK frame
if _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged if _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
ackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count ackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count
if err != nil { if err != nil {
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range if _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
} }
if frameType == 0x03 { if frameType == 0x03 {
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count if _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
} }
case 0x06: // CRYPTO frame, we will use this frame case 0x06: // CRYPTO frame, we will use this frame
offset, err := quicvarint.Read(buffer) // Field: Offset offset, err := quicvarint.Read(buffer) // Field: Offset
if err != nil { if err != nil {
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
length, err := quicvarint.Read(buffer) // Field: Length length, err := quicvarint.Read(buffer) // Field: Length
if err != nil || length > uint64(buffer.Len()) { if err != nil || length > uint64(buffer.Len()) {
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if cryptoLen < uint(offset+length) {
cryptoLen = uint(offset + length) q.lock.RLock()
if q.buffer == nil {
q.lock.RUnlock()
// sniffDone() was called, return the connection
return nil
} }
if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data q.lock.RUnlock()
return "", io.ErrUnexpectedEOF
data = make([]byte, length)
if _, err := buffer.Read(data); err != nil { // Field: Crypto Data
return io.ErrUnexpectedEOF
} }
q.lock.Lock()
q.buffer = append(q.buffer, quicDataBlock{
offset: offset,
length: length,
data: data,
})
q.lock.Unlock()
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type if _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
length, err := quicvarint.Read(buffer) // Field: Reason Phrase Length length, err := quicvarint.Read(buffer) // Field: Reason Phrase Length
if err != nil { if err != nil {
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
if _, err := buffer.ReadBytes(int(length)); err != nil { // Field: Reason Phrase if _, err := buffer.ReadBytes(int(length)); err != nil { // Field: Reason Phrase
return "", io.ErrUnexpectedEOF return io.ErrUnexpectedEOF
} }
default: default:
// Only above frame types are permitted in initial packet. // Only above frame types are permitted in initial packet.
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8 // See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
return "", errNotQuicInitial return errNotQuicInitial
} }
} }
domain, err := ReadClientHello(cryptoData[:cryptoLen]) _ = q.tryAssemble()
if err != nil {
return "", err return nil
}
func (q *quicPacketSender) tryAssemble() error {
q.lock.RLock()
if q.buffer == nil {
q.lock.RUnlock()
return nil
} }
return *domain, nil var frameLen uint64
for _, fragment := range q.buffer {
frameLen += fragment.length
}
buffer := buf.NewSize(int(frameLen))
var index uint64
var length int
loop:
for {
for _, fragment := range q.buffer {
if fragment.offset == index {
if _, err := buffer.Write(fragment.data); err != nil {
return err
}
index = fragment.offset + fragment.length
length++
continue loop
}
}
break
}
domain, err := ReadClientHello(buffer.Bytes())
if err != nil {
q.lock.RUnlock()
return err
}
q.lock.RUnlock()
q.lock.Lock()
q.result = *domain
q.lock.Unlock()
q.close()
return err
} }
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte { func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {

View File

@@ -3,35 +3,184 @@ package sniffer
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"github.com/stretchr/testify/assert" "net"
"net/netip"
"testing" "testing"
"github.com/metacubex/mihomo/constant"
"github.com/stretchr/testify/assert"
) )
type fakeSender struct {
resultCh chan *constant.Metadata
}
var _ constant.PacketSender = (*fakeSender)(nil)
func (e *fakeSender) Send(packet constant.PacketAdapter) {
// Ensure that the wrapper's Send can correctly handle the situation where the packet is directly discarded.
packet.Drop()
}
func (e *fakeSender) Process(constant.PacketConn, constant.WriteBackProxy) {
panic("not implemented")
}
func (e *fakeSender) ResolveUDP(metadata *constant.Metadata) error {
e.resultCh <- metadata
return nil
}
func (e *fakeSender) Close() {
panic("not implemented")
}
type fakeUDPPacket struct {
data []byte
data2 []byte // backup
}
func (s *fakeUDPPacket) InAddr() net.Addr {
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
}
func (s *fakeUDPPacket) LocalAddr() net.Addr {
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
}
func (s *fakeUDPPacket) Data() []byte {
return s.data
}
func (s *fakeUDPPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
return 0, net.ErrClosed
}
func (s *fakeUDPPacket) Drop() {
for i := range s.data {
if s.data[i] != s.data2[i] { // ensure input data not changed
panic("data has been changed!")
}
s.data[i] = 0 // forcing data to become illegal
}
s.data = nil
}
var _ constant.UDPPacket = (*fakeUDPPacket)(nil)
func asPacket(data string) constant.PacketAdapter {
pktData, _ := hex.DecodeString(data)
meta := &constant.Metadata{}
pkt := &fakeUDPPacket{data: pktData, data2: bytes.Clone(pktData)}
pktAdp := constant.NewPacketAdapter(pkt, meta)
return pktAdp
}
func testQuicSniffer(data []string, async bool) (string, error) {
q, err := NewQuicSniffer(SnifferConfig{})
if err != nil {
return "", err
}
resultCh := make(chan *constant.Metadata, 1)
emptySender := &fakeSender{resultCh: resultCh}
sender := q.WrapperSender(emptySender, true)
go func() {
meta := constant.Metadata{}
err = sender.ResolveUDP(&meta)
if err != nil {
panic(err)
}
}()
for _, d := range data {
if async {
go sender.Send(asPacket(d))
} else {
sender.Send(asPacket(d))
}
}
meta := <-resultCh
return meta.SniffHost, nil
}
func TestQuicHeaders(t *testing.T) { func TestQuicHeaders(t *testing.T) {
cases := []struct { cases := []struct {
input string input []string
domain string domain string
}{ }{
//Normal domain quic sniff
{ {
input: "cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b", input: []string{"cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b"},
domain: "www.google.com", domain: "www.google.com",
}, },
{ {
input: "c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738", input: []string{"c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738"},
domain: "cloudflare-dns.com", domain: "cloudflare-dns.com",
}, },
// Fragmented quic sniff
{
input: []string{
"c70000000108afb466a232f7f9f2000044d0168a15a021477ecb9731ed77784d42301462e2d59b0395adc1fa6b569d428583f100860d6b6ae29b6c1b8c0f9c0d9081475ff801f34a9e0677adf685f02b1169fe86c683fb51934915ff43921a73b98fb0b734406f8dd90ce6060d75e923b0d3c738291b421bf16de27ed4785d727ce589f5d0957c413c81d6ee75052e3ab50fe53f1abbb24a138a52e1412683992ad769e65ed301a736914843543e2a3e11eb395726d4fcc9283f8607b38685069f63d05ab8bf38aa24d4073a1e68fa1b6087cec44d7fa628342e9d88a0d20b381014cdd1a07b9d913a3bbcad0cfbddd0560617cf26054138075eb86e06db1e68781541587302e6dda86cae779f9848fcefcc33626f8953bfe4dc293d23e74c87020e79e9ffd58ee345382bd4d1d6e5a3389b0a977124708d05e3c305545857041734dc7092901ab54604b3750b3139dd3b8f2bd94cda89d85be3756fda6f0cfb6f66af3d2e36a7808ff7bce271a0272f8dbc88193ede31613433985cd35c7bd9b627d434e7b2e94b38402b8f1b5619a903572dcf4c2b864c6ee66657c9ec81e03fbe765037f83b2229171888ba08651fc78a1b50c7cc52f6dfe8273723e08932b1a16a6b717a80b5520cf3f40e46f9d9c350eaa914bf99dd4ab700cfdae21437daf695916d4f3121235e4913e0657d8cdcf4afd8f2c7ef977a2dfe49f46fef46c8fa6932e745311d4a6eb3124d5e0a204b9e3227e86a55e662f7002d4f4a72cba8c77c3adc3eff076dfb9195cf68455cecbbfc9b5444d9c4a4775bba68d57ff52edac6ce6ff4efbf6466579bf68308f2ba9a59b2c09506064091a86af621e9dae52366a90599db0d64a23944bc48966b6d3ab8e20f4afb5b0e94370d26a89a9c4207b454554e58ac74f62ffb3eb2686eaa596b9610322a5ce8eeb42f2ead1c71b11b51bc4f1800eb549a2bb529ca4a0d165ae461e45b556b2365e9459d531489d59d0dfa544a76c5c00b0a01270741d4061a331c32fd6cd0e68bbce49137b852e215c9db52f3e430416d8979520e5270be324f3d93132358c0eac35a4618ea7aad997dbbd8e99d4ea577271b935e3fe928f90abd94593806d272a565a414686b8e56c28e34b77671de6a696b09414380bc658c69a309d3225ba8493e9076dac776c845ce11a7ccd6cae58fba5434014250f3e211058b2efe3424b991d679a02ba949b086ba12144c7df3e049b5d026f386e4ae712c9b0b4b02730dd6862ed4e72730224cb6ec9101c5cbb7ee4fc30d497bb1dbf74ffdd49d8cae6c7c9a364ede453d9ae25edf27a2153ab285f3e3be66b2968d67a56480f1f74c4fe61dc69db3451f5b113d7ca02e5afa8627f579c07a9b1814853fb8fdaf0c0f220f89725c757f5617ba4e43cb4f3ad9ce18f48f23d10f9e8950b0fd737070655730532896d93df8768860ebf941365d0634db399feab1f8a88bad28d25e689c5a57321debb8d1435130e90a699e17fa5255f2063f09659a432e9ab5f89eeabe12756bfc5e02fcae2b78a9d0f570934b8d4af8f4afbd57549176f465a0cea485dd89c95a8ae915b4b99548a4c939710c16908f968368baf5f547cfee07f3cbb6142041d6e6084aac253a0d3aeac628cfe76f87b94c3806cb14a912ce8e4981e316511d5ede36f526805d6c3fab5b72d9d91f4eacd26e28cb181ec66611818f5c206ddd52488707a940dc12144ae825d25929bc32b718f46e471fdb30762d299b45c84f6310a72b60",
"c20000000108afb466a232f7f9f2000044d00582e8683e329a63e5bf4dc93e93e325ff661e74b9cabefdfbf6065c7ab203c8a629534e87e5f2d4c0f463352904642358b8f137e99802c3a26cf22235782a777769ecd134c6b4d0dce6aa10b485c45ccdcf6deb805342e99ef97e2777aee0b2a44073843fccc2f8eb837031f76a8e968cb01c13c1268af095f54f860958e4062a84e2527bcc9b25a7791650a844de1b0c4b2476282a0e00c9de9d39a41914d1e797a88a8997b96b25a4c194762912b2ddee0e01a365f1afa1e82ea266c14ae94e47c90b5679e2cd00e63ee5a834505ca33463751bac22f3b87afc80099335dc7bfd12b7df224a23ced3d2e25b58a04c4b5cb089ca187abc54d782973c7bb157cc515c7508431ff5bdc227871da58b9ca8a9a576960f38edb384112b08e4c70672a6f23d17d9d901342e56c12370deaaafcb22810eb352f1a6d9377e96bdc1ad4dd397dbc6a227b70f204c1a4e9a4db2705763b82ec4df1fab11420aae547155c6b49abceeed997ff01b7d24e369c65f7edf18665d067c7d2bda5ec8623281fce8c77d893cb8a42053756713e910894a58ef5bf3d9f3a41071026660dd7cd05e1640767ec68f78e22c1716700ca9c0f076f90a65cffc394c10a32071c6532d07b59414181070d08c9c84e3d13842718d51bf90dd36ab1b3f708df7eeb3939dc8553787308983c3e9ba971e7d447788477a7140196c2f717b9ba4f5da92d73316dd11c1d1830b4200f26f733a6c65ec1cc21549b485e3a43dc7a2b68e95466a53544082a20d9a43387a7ccbfd353f7e590b7047f13bfc0d91923c2d75dad4f8091ea96502f98e83e5c30e52e4cd5c670f6c2248ce37cd6ee8b3970531fbf0c53c5fa9a0d73200442b755c91fa4f70524ffe8a36063b6709d3aa9f6b53eb0aaecc57a8c8c9a7ac5e57e03e9cfb290b67dd8222a245ff5439914147e2799fd1cd2ca2cb22fda299443b81e8024adc59d098058432fa4bde376b8e59075f6b86427b4ef6cd7c83b5c08add0c3d3543aee8d672c41cb287c1f0a17f1bc30f62a57490afb2d9f401bf302fd473ddbaf63f6883221579743d6aa1f386b8b2f5db06d7d6c36be81f29fafd14b82e863d744f116ce2be4921631f1fb2797289fffa9ee16a3e537ddfa52350546bc544459c0c9d66fdcbd41612cfc0e2744f50927983a3224291c1ae51608fbc00f40c60ec72573a7e128c3415b0d9a7db52de8ff763dd66e2eeb03ef2e67838c9e68cfddae4b86a3f34a69e0a473b5a73ab627282648df7912c11a4bf033ade185a8f438036b99b960aa6213c800abbbd751248a7ae600357ab888433125d49c5643705ecb8c86f2980050edd7e3c579ad6fcae9bbe2c8d8b38004426f35eadb543a3bef42355acb1b94c21d7eae7b6ed422ca0d58fa03b227b035628871465ed6509254c8a3bf43dfadbb247ecbc52d80d65e9c03c4bc7bc35a829502bde3868af9c33737cd88d70f7427790313eed4ed1938955c5dd360212ef700f274efcc8c26ea94c4e2e0937d475c5c4909edfb66714d15d12e153e5586725ce0c47e8a1506bb197366754ca8960508f22fe7b83a5eaa40f05f3cb87464dc6b848080c0e0cecf2dae82bfa42cc6f52694478dc3d00ab0e1ed696b98e26c7fd34d2efd969f83e284c28ce3f27b178f4691c772011f61722266153142dd0d526393e6c6848d201115b256e65f12b911a983bc2f96a5b4b99f63f0b58485a521553a3e1d4498ac5d4ee70c3f9",
},
domain: "quic.nginx.org",
},
{
input: []string{
"c00000000108e63b9140d034563d000044d066e1913892ec1d84c179dfa9596e0ce930171a134a09446a888d9e579a6f7bd77df6deda715b028d64f7866603c6deb468d60ecc6488b5e5ee2e2daa1840b76ead998023593c9ebc4178ec89cb198d3c79a867e27177a74ee5f3db74ea194e36e328047ffc3890192665a6feba09ba1e224967fa9575dc7b094e1c29c7f3be9961ba62e3e063f674a09786b7611138e1edaee32cd1d47839e840a74f25ed786463fc48bf3d38a4c793178ab7cbf5a3eb974415b9f9ef7861dfc73460594332f5545c7b7037043afdfc1aa62ac3dfb76ec2c6ae8ebd351f7483992c762d6483b3e2c1454c8ed939ce43f858ccca22d9149cc9da16af86a010be7f3248cf19fa442e94d625ec7f7144b01ac9afb8fb8c595d4cd12fcd2b2d9986371ae65f6f216bed152b79d2782d60f1f01e06b359f88900c4bb3f987f3ce336854a5beaaa616813af4e5f9bd82dca0af6886b544fff0261807bbd8cf90213299f5802b98edc27a6606be8e2bbc18fa7519eac260dcda139f164796a082908459c31aa964a5d3f6fed8944ad61bda126991468f3b7627f2470179619864f234a395ea3bd4f7ba4c0cdf9f5f0dd95d7d59476f2d2a36521c13886265a2fbbd4345e8d1d1e7b5d01a58fb11de23730b087e2b702200155a1ebd50db5751d279438822ac158173533140998a3056893bf470ac84720cb37a4a3205fa88267abc56520bcddacee06011d929c3a114314822d8ccf7cfef89f2fcf0a4fef800afbfca4a62ee848f22066f68c7d3c5c9a24402d422fc2fd5da6d3b470b0ea253f12a883705f7f78bd67006ade4f1c8a3e8fa052656b5b40dacd8062228871cc3bfb1a9c38472b0a720c3c750430edbcbdcecd46b144dfcaa009fee06770238d0270e80671e8ee5f5df18b86dfe8df2f121245c0710ccaefecbeda0ba3db945c768624dc38f21a4ac53741f4e58a5052f3d667fc466b69905f05d0843cfcb830163fae18dd1eb0ce62a59420db9c44958a0eca9ba4258c8060a9956343f155da6c55b2060427d07d9e311729d2971439c7541ae2babfce25a3f5f361fde86c39ef6c04e4e3cf7dd70c9cc0758ec5db3f0cb368e2447080af51c8a5fa6b84ec3175d2d3e6d877b6953e433b4e94b52e1a5f2a1ca37124c27e47f9de5d4c74644181cd37f3f3863ca529c0847bba91c246dadba94b4566b08eaa06a0db4d58b8cb0c8d3070533306a3089891b24a7c4e11b3aa50d5628fc1d136388e8bfbc420a6f12701333ccdc95dec25d09ce25fa4b654260965b91f05b1542c2ee02008d01de4419f14d6749c4bcfcc45a332ba0772def720ea3c8d207802418137b733e779eb406dace0b4b5f5e5e14c787f3e044e6d8160f90fc3c65bcc7f3449205b63294fbc11e9bb92c007d1cb59183eafbf76be9680224cb442806500d71870777d087bf864890848f4a79424c02304f2a6ee2b07f9257f4a2f185ee21239625e246cf680e74b85d292cca44261c6cee6da39bfac3882d28fe547a500f79519ffcd3f54ff5a905c99f22a5e8142c903c41adbe1eb9770b6cf554688529091b126ed2168a23bb191c2b89728e31773623bc58bcb9baebc2c664c79d6ffee7e4404e039723eb05e7f7835c87212431a0131603fcc3fe090cc2fda8239b8f42188b35f98d7fff949b3044544b3bb962ae236a664d76d0c751d9c9ed1271715d240f111febdf7045502f2afd7de8aaaac650511e7bc7716a5b6622ae925abb7",
"c90000000108e63b9140d034563d000044d00b2498988864d8b7f59a00d26165f5ae638fc9b1c12d546ffd86212ccd85f654259cb8b8c9d753c696ddad7ee4847bf3b3c10063606cf3972f75e17ae23e73b6a3029f23541f674256d19677665cdd0b8ac15c3f60984bc14ff5dc7a9ae37395516204f2020965713fccaf35cb0a5823085cd6211d681dc6b39be9db46cbfef154a2b9049ed202e9088961b0b710e94bd73259b0967e4d6b8cdfd5b72774fee2f2ceb16bcafa010f247c43b0a9ca25578e7d45bfda7edb82e91f8e1c0a2cfa990223bf97ece42862d3f329521fe2d12493b717f174f966d173102e5cca10943d5b612101d65d0dd48b44416f9ac1eac4575558ecaaa39c47ade2dee6e25fd219d799b499143b47a5bf449701b939c1dde111349cd0d63efd2ff74fbd3573ed40abfdb2310e2740da40fc50c7a137a3f32c3a26b3d407f80e669fe7f9a3542fdd412a9cb53f845d9c1af0814377bf92e30f05ee387fb8675807a6de083c85d3d7860601c8170923c53e5773ee388b68e510a28cd7009c485bd4cb861eddfdd265de042e5a018d20cb810614e2bb17b0f52d6bf620a6f173e0b41951e1b83ffb29e3b3b3c5d9fff13acd3b409021195201d003e281d8cda7b0f02c273e17b1f9b9e8cec4296d65a1c4923b78a2e4273cb42e4e159980472e440078e542eeddcc5a9bfefa5a72871fbcd9ebb74fef20a50215bf75cfd8572d5ab9ac5945e8d6ca35884caf0af0446ee9aab0a1cc3a452ec79c9de786119e63bb3a75fce0ae29c15a0c320fff87e87cc23a05e75b4f4b30b75c6aa036c4b6657f8200ea014185b31ee7fcd00d1eaf40973f347fae227f89d41794fa57ac1ed1efda3ba840ef27852cf33a9dc9e2d77b56af9ced9e75707837aa8c5395cdc15134ba132de87152ce53d506c53284dab912bbc276542504cc94afaca71a5173ff13ea6cb45b47dde9965428ba5d8eb968cc2a5729c2f9b8f1c1de208943a2cd565196e040dcc415d769ceb6300c7909d7e32bbbe83c4cbf4d49f6e34fe56b651838628f3a0001e99f39cafe45c98e455aff8d98f89942a862f7505b9f7fe3f64dacf8c574affacf91c2c05f094127acaa5187f9dfa188f67db421243a02e583942138c2edf45fec4c6b6a8a791da9055be247e9b252e9f7c1330e76f9cb3aa5feebb21f871315b5fb90a1df0b8056513b74daeb6ac995f85c64150ad115a14830d145e5f4e6638c26987b676a1dd19a9775df29ab442ce6143b0fbf8f8d4618084896e34812ed59d63041e2b4ccf6c959a6c849813dd926082bb7b1adedf69246547f335552bcdbae7e466ac31e07e442530ad114abebc6f58015b786e7f35644307fa7ad3d9248c56c8ff472735c6911da1843fe53821b8f5180f8844db4a9f7a826a919fd93c4db4d25861054929260dcdc46d085827c46d60f1097424a6ef250f5aaf3235c80230eda4eb580ce93e1ac8aac422a7aa1241562af601981b84b74949f1c476705c8030eb5d447b2414f9716ff3fd606cd750030b94345c016078bdcb97b7ebc24f661fbd08802f32df18d6a2aa85bfe2e9b8dc76b121c44ae9f29e4413051b527e99fde29720724337476c0eff325cb6220a290a9eb852151c84836729d6a223032e2c638857d9e7f469b84d7d650c45e56e763aee73f902e82b055425c4568725e2d4efd7fde8b02906bda48af86bf47ea27ff00f4528494b74be9bbff001cc841449a184a4e00d64e51a72660a2c21f704f",
},
domain: "chat.openai.com",
},
// Fragmented quic and 0-rtt packet sniff
{
input: []string{
"de0000000108c2751a596bd51c6e004041948ab7d9d493e9e1e9902a7734534fb9eaddc70ca7f821d1b58a406b23ba9db1d03266ae74765b03fac21c284fd50cb0a3d1ca71d8c3cabef5553dd1cb748ac662",
"c50000000108c2751a596bd51c6e000044d0538af4ba75e226a6fc7f43e7f1f59610973b8a6670bb8338ca7ef7d90f81aa59f179dae5f8f6dbd24ec6fe576b28f6ce6cd46f26de143b8c99cdadaecf2041948a61bd5a8591486e10022fd100aa20e6423b4f4ca5773edb1aba79b73d6150ee185e66da60e658b2a698098462122b6b80c7fbc5542b0b8e9532898c1f31aa2ef55cbdf036d74c3069abbb261660f048d950b00b7db279ec2bc39912102679ddbffb53f1b1921f137fce43e164af86c72908532f4cdc48eb462a9d9e9cdd6d3c3faaf8aa8aea312dcac5d6aa75b1ade4af6901576649da7e3efd4199b92107d7acee8bbf06734b2484957c3d8cbb1f3fc0ccd56c55223628ed8ea514ffd101bac370c97b28c7da81175ab0508c0002d458cf41f7159dfce22b447c1ec502c186b782c1854718b7fc0fc39e5c09aee31113fc4c5003803fc27ca48850c08a54dbbfdef6ea9a6a138cac0ecd045cfd5607cb6c99c39c0cb21778857f97416b78fa7c6ac8ae3fa2ef2adb3b85fe3fdba70ef9265bb3d54e56ec68b8887d54d02d4a571a6b793ae4df8ff171c881a554b5c5a7848351d446ab94c90ee9c600f03b785fee6300450a4ffc2a55d417952e15449a491296d463ac6942bce4ca93c99440396bc8984073ec028b11ad412e97e26f9248031dc4b1a6ae385803bb578fa1a3b3a58a8ef19c6c511f17b28a275e8c40e51fca8f410a4a1879b5d8749a44a6a9f97c0c9df25318cc28fd0cc61eea78ddc603a17e74eb542c8c08cdbaafa3b44566db4d67e8d1429332375cd30cdaead9594c46d8ce91bce9813c3ca23f55ec2f4dd3ff141471bc3df590367bc65e4830018ff7d845ec4987d11e471d114c48acd1ae9b7670341a34077ae59ea6c3bfc4675cf419d37db48a98a5573b69867039731f537098b46415a193f50b2c85bf9e5da45d6757c5c366e21f04ea62d64b81c28be5148d89e53535414067cf609e59686b7fd135f5cb473e57f6c82dbb291308a1065e0f755935d77517adecee55e72cf37ecaab1b5c0c6e0c7463a014e7e439757913f6e43abb6af775d21ab6e43cbdbcd1935a000cf8025ebc11378d86d6f72d51bf2dfe4be1db5d3b0fcacd13e1b9fbaac6e9153c3d1f4e876f2fa9c3cfc84fd0910b778105b66be70827b1830b7b3c9633af5d83ad527efd81498cbbdd112873cc5ced573e6579acfe817b62280c2122b582b591d52b96cac047bff91192a5cfc001d15c811e055dcb1c9710dc892258ed1ab5152af2cfc57a0b93205dd41fd82b86090b4281b1493a8828ebc96bbd603b888cbca4a15799a5f3eaef93655d5609948080ca57c696d0ffc9a07665bdb063b547bb5a862c3b058c9efb2e7b79cf405fd83efacaa4b8e3a1fd126270587119756562c03d69a9cb67550369030a0204e531cb8df91ab2dfa2e4106c590c59b1b13c447843937929a574d3ea1785db0d52b4b2eeefd1a07c69729bec7c2813c9eb1249f706b3cc14a3d489d6b42a641dfd9e91aa70c7d3222e154af2d7fc1a8f48e5ba11739ae128d1f32ff929aaf4b249df5ea23f7847301e36ffda02342cdf1bd9dfd1979cbd8de32eb8b1eb8c415ddd267efe53f54678d9fc32435b34b00ee2256d8b6190e30a280df5bc48cf9fd669a52469954deccb0f1da37371d513ea57f31ead22a34f9379c7931fd18286d9fde6ecfaac8ca2a9be79d688c5401c65407543c066532f6621f256551c4a98a86b543c576ed0f3254daa4915",
"cc0000000108c2751a596bd51c6e000044d07b624bf3d95fb3b7299b67dd836fbbbeb05a51650f9b2da3b2695070a0d19ab0d5334cc04de7ea7494fbe6c438f4e84fa56a3f246132468b5b4f1ba0fcc0251cf278338e15fdd715d5bfed18c1f98ca3cd3cc7b6f904aeeea2914a8b998dd3ae7df694c49c1742dccbf4c3472ddfe2e447959655459c11f18bddd9481eb597b887fb3f90a7d0f05224a144f87a5fdad502ea1e46c1c9f4b4154bafa4542c026296040228703bcd020202acceb772b596bf788341cceca864c8907037c39739e511b04e8ba956efa0fb5cb151ac90eb5817444f6488d593325ad4466058ba45214b965c5738f33d5591624584559ba18e89913b868619d498072e3aa1f333f5d6e3d1db88b28adf7d9350c3c383c1eda894f36bf1bb2a58c7a5e5c8b20597b71a099e46bbd3d8894877e43b0183919185b4e9f059472203979d3334c535fc4eaedebebc79bd1e423184765047a50e6dcc76ba2b23ad23511cae2edd2ad8e7f7f302226dbf6c0e4dbc8c08cda26340b9abfef1ef3333cd511295f14c87197d7890576b4076dd9686047854e67733599d96a99194aecf7b927cae2e5fa4568afc71e748dabd3bd71e6c3984f45b06a068a7c9c3a1ca7b5c245a9bb2cc7e2726e833e283430a25b6ccad55bc5b7644b44f99fedeff3c3bbf995a0387cf1e45a5684e5d1c01350d0cd2d615ffb6d1011d80ad16b75925efcbee483e4e2c0e2386e9e1b35b5a107ed97058adb60e323342989559856faeaafe5149bdcd60c113230f9923b2f654c95f986944a014198686f9c2275053c05080e3bf9fab7d46302948b152e2f2fb1ecbe71b412016b3f25ae512ad45cd096d5f284a0c2808b5eab03b4b9b2dff4d81bf234e75e30d480f39a5f9737563e31a19b14d1038296915af33e0ac0dc18e9c871e539e8772d525e5fa19afc582b1c00ae573af39fe293e16d182bbe57af5bee1c0939862ffb62e3d52a60aeb71e4db2a4a1708e75afa5f37d72cd6c0e036abcb4eb8db6515fbbdf98be95d0a6d261a9445797a8f38c3579a2f04c9f5b74dfd1ffaba2c6aa05959704b9b8cb0db30bcc360711c5afef0d1e7c2b076466dcaab104c70f3cc0cda33d7a47462c3fa3d7e34a99b2d8ff3fe5cafd27ede28b9e09b547cf955b97b0d0d4ec126957601c6982d176252be422df3366118895ca25fe27a96c9c234d484fe98634fb9e970e0d2b096f2ced5d56603505990a65363726c828aed2df0f112e0c44f058424ff5c25ae60aa2cb5fdfa289e8ebb63908365aa4e4609eae87e567f1e86d92c43992e6d505f55226fe3533f9fc9c9facff9dae02a3e3c97ca54191bebab93881c0e89b9de5bb4acd5c6fed5b1e7978803f693bfdbc125b4d08fd34fdc6aaf02444c4b06010b0eb2f15d86850a7aa5af05af438f6b7345fad4315f631bc5b017c7482e7af725a09844472f48e4de79b15284932a7e99a46ae72b187ee3faaa0f31a36726056e86eb706bc8eac04b68a3302307a157c91639f30bafc2d180670625673310a9a45a171063011e59c57c8eb67353a8ea344a87853e7b600c2b49a7a1b60a2904c0ea55951af6430667ecdfa6e90a8d2d0ed9857ba5b876cf78af190d5013d16208d2b30d02cf2c23e6ad1466f76c30d11034d5d2eca113e2764b2fb6298fc4940c16d971e28e3e6e5d0e8eea1ccb9b4b89741ed675861fc3680457ee08547f4efcf68bb6247313f8218ae3ec372e51ba8786ecae115dcff241e0",
},
domain: "fonts.gstatic.com",
},
// Test sniffer packet out of order
{
input: []string{
"c50000000108b4b6d5c8b9a19769004047007e07df0d887979774085206f2e7f0146b02a8699715a54fc71ef27ab5a9e8cfbf155497bed9e25934aa74db1b3b270112472b7bf7587423b3ab2aaf99de34cdc591bfe04cc0a448875483ec1f071622121a49c456dc3ce16bae5f61f84ceaef9e8b71db56479845b764507dd9416e8c44b8c93406a230945eb8e484471c1b6207c9afd944fa0fee555a5c966f27ccffb4bfed37fe3936f2c84e9852c0d46c7e2e94b897fcef18c4b0b83d966aef75c0af4240325a24668bc017e0d3f69680ea5b2f59bd0b964062bc40190be86aef3ed0716a18a67057f309faaf3a040222812142a399deb72ebb330d03d59961e2ca10cf78d40886dd094368a881db261068920968f6adf7a7b1266faf8842e71840a29859e877c66e3ebc47d7fe3ee586b6512d9b0e1bea82b302647706473e68dc8209f4e9ca19f1dd25fe386e62c21d9c741e75cb8b11606739ba3de6d6325ee3a9cd1bb2b9613746140ccdaaf936eefdaa1ca7ad73d684e5d82b1ba1dd3356ca0c881f6eee72c02c8b78d02a8217a8fd972e463c77374d0fcbb761459e3ab0bb5492e516d7d4304c19c16a4bed11ea7f4e75616a26a7c81b04ffa580cce04d59825b8ed929578f9219e64bdbc6352ae6e4150a993fc3cc27ce4d66c62893866b9053bb737ac40364094b53d91e8b325b9dab5f537af04f10bf8db644897b0b03b42b1bd6c3aedfe018a6e4f6533183649f4ef6a6300383430f86e802fb4e51976d056a3c40c3b53c847b8308cfbe54dc2d20b8cdc870c73f5fc22c376c35d9a85348ca6a2288ae03dda6b97f0f502f35219e19cff3a810143289cb1f0715f8785028f887bf02c656c9cc372bdc419290f05957ad3dee82b56db352db65aca58e6fa0bd2f753160dd9e7214968c0496be1ab49f978a9252e49266939fedf542760abd653dd38b1659bcf452c753cb89e8235bcf732afcff8f524331be9b6f4a5081c81255e68c358b3444fb1d57bf5659d86b6674544fe2826ca81ee52f93a17b3291826678e488c3074c259223845e4083a413af7fc93d9992823620a8d29d321438a760293e36c4232216207060dd3ee5c4036250ede71ca9cbe335a1e068eb3ff6c10a7f1c8204750d6d0f3145014949a7b4e88a723566ee5446f960a95d9f81cc45155443da561d85a3a311df8172a1c4eb118bf27ec4b3cc4573b1ab421d96d41cc1e5557797ca68f701fe75c474527144d30b9bb00a117637f88896b0b2dcb9bb29ba144ec384b5a085e82e7387e0560a4621423c306b041ad42e84928ce23bc2a7f995ef5c21616de43be8a1657847489b32c8e364846389e7c8cae99530c499f3662a2ae7090e54958ba940b5d3eaf1333ebcecc7f06f29f68ffd97defe65017519c29d355ecb0a4b47ab08dbad8cb0cc5c86de65dfa703110c60a0c55281925018fb4ef49fe5d0132dcc86602c2ab9921a8f3451480d3e931f01c2f9a81873435bb83860128aa78dcc950fb13e416d90ea969aa92763f9caefa0fe3ef4ea82e3af4a3e717fabcc589fe8cb9bfba6810ddf7def8c1445fc0048fb07be043a628e9c920bb72c04d3b9472caafc6c14bffb854a1ba2170dda919322a6d79eab92e3a88888a224093946b87840033fe41941f780f569eaf1fdac55e36b74514d72d09823d71f48f5d5f0ceb7b6d69c5da0e0408c1b13c265d4775db6a0f952ae72bd5c277b22c4be2f2728451ce31e921c856000d20da0489103bad6a6ab4",
"c60000000108b4b6d5c8b9a19769004047007e07df0d887979774085206f2e7f0146b02a8699715a54fc71ef27ab5a9e8cfbf155497bed9e25934aa74db1b3b270112472b7bf7587423b3ab2aaf99de34cdc591bfe04cc0a44884e9e716461869ca408431e1ba92740c598aa74e9cd45706f28942f9cc64dbfd7c292cd33e82b50ae0e2e08dc478c19886718cde33e56c38517f8834d64904bf4fb1d30650caedecb9567ea8ef50157c287a2741e98a00f8e7e19e76bbb0143ac7862a49393f17ec66aa0e2c02123ffb5abcc96ccf92cd542c8f571bd7a4382ff81432d11f83796959696c38f2029db6c6a536a9ea24b74c848b95882562d74739ac95f5a069d48e8756d1a9750c7ebc23d4ee22d617b29b415b7458b3bb8106c22de3a9ace9ec689e6e00471aa33e570f7481d15911d7cf46a429cee1a416558c5e78360795d905ff1e0c81d18fcf4954131fa5b9289ed2291e122cdffd666c66209aa2cab01730739249ce293b3ba3abb31683c108bdfd51f54593f47411077e948f01105bd9bfff1578d235674e96a8b9cfdde119edaa960b84e70fd681312514151de1d5939c79abdfe4953e22be5ad3e6e242d0ce9b3f2e589ce3c768f610d4d3a32e33225d8a5ce2ad74a9b40859cdd9ea99f14fa2a7018e4b6aa6e46a0d73d46d161ec5d3b30bd55078e23987865551a605a33472931428ce222040d20c07d1ebe970e576d9d54ae688a3fe9388adda3da4d011a7cbb604f1f19d2ef1be7ef4713bfa84d4d69ffa606a08b61a1ebb99aacc4e19d0c5034642da1ce2d7d5abecc8adbbc6d7f72ff2da4ce5228ff8626509b38e17b31717c0b7821558b021ba81502d54da7e778d4526367109333383e7c67d5d5bde86bd4001fa13a703ff9259e1c2268ca8f4ed2e6c022a7466e2178bc725f59792803ba28c629e3df7696c416dc294b510920077b2d2b258fdc3506c36c42d37796c8fdb20ba797ee68fdc410325a355f6c1189aa9fc9ee220d42186677e3955cd3c844ce505cd601f04201cc390e923db2ea6407fa2fb4ca7f3f82d0a82d52697ff5ba5d4633bb0d655d7ee3348b89c9cb42870cdfe7c0c162babab4208a9a54700c5785d4134e9e33361480e3512ac8b556e11775536e90ee1270a4cb4d6bf2faa72d7e1f23ceb4fc3aded0e423b6be6a55bc25e5a99163b4f5f72ec4a24fe96f68c739d1848c92c4236a5a637d19871456b8dae671ea6ae5c16ed4fc257612a0821e6dc1cbe2ef4963a1436925dcc4e6ce528fa75e41f7721b379fae8ca09e6fb51d0c3e3ae6c19b98860ab9f74013146c6d375656dd1f530abfa64670a510390e9a54bb9a4ad19977491377c8cd743597bc156ee3f58cfcafa5a547b20852749e66fa8838c100ebde039ea25c8ec32b0c6325b793797546a095e79b9388d8e67dc6b4b3892f93ecd13e64ba4b2ad26fc810fedc374b831921531344c581927da9ba822bd625584d98c7582759ae40f01e14277a0a13d30c2c12536df698330d8aa6a3613a42c493c42692b468b4a2cc6bb6dd45684ee6115848110bf517074efd93bf212c071013f4359f140cfed17bbe10328f2026cb8ada16427122d3fc8a933119a1e3e4cfe2b95cbc73af5044cb099cf34247228972495488ebaa4696280d17665c421be5f1727c5d5b013d8aac0e9943bbbb7fbc2162a4000a306dffe3bc4425cf272f1ebb63c8e4998f867fa6b05d71a8642e29392244d4e2e2351bc149d665efe1b9519cb1b15005393f938d",
},
domain: "ogads-pa.clients6.google.com",
},
{
input: []string{
"cf0000000108277148f2b916666000404700403986db57eaa4b165be8ab9c95452bddb922eb35b7610a8e664f6b4620d870507c241290ce885c36d7672c51d94063bf893e01bc79e1d81bf023338da3d22f63bc7aa433f9944884c88b10f198e849dddbc1e9f9bac61f98f67f27d5452da6e2bda1f5210a145b1f1416ad2fc15e60aa00444362630650bcd0ee47999b689a40100dcacf40a4c3d74fa6293d4a5cb0487d8c76787c04dd2b47ec7718df5a2dc6942069062617b3d40a95360802957419433436c9065bda5a6156291d909a079b6d3819941368d7e17a2e97e36be829bb421b44545af47e37d7815ee1f200ca28ffd361d955ebe0484fb234a7e8a7c68ad824fd14d517fa7b35f878beebaf3dd22bd9f7a39cb7e0fd8369cdd28c05a06323be7af0b2d69ed2a2f4ea9f25d000de71bf5bd6765a20ddf81d976cff2321f1a4584ad6c4b7e9a42a6d4aa3a02b59f7d994a8e4a3070a4646e51fdf354448420ebfd0aa9118d010d019cc168f2fe5a9ff0c42e6091676be11f28a372ea97d008a1a02efd58149106cfdec7ef86f5416c4b1a408d8efba6c8d4742d781374ff0a1a8ac183bffa1345dc8e3a7cce04f66cc865f434decb912dc9e8e811eb59b80d3e39d5788639ae7c5ede73a935edb47d907725656be0522195bd2c099b0241f36664fad1543e4ae43862252662707fb424a8f5f9486b8e3779ac24bac457671ad664475d1fc9eb1de3c46f624b559742b3477953552e44f20cc1725a11ab915423fdce7cfbc8dafebc0c43d1ac3d3373ca2f0210924433c46e5fcface47a65579efaa1999d52b2632f69c33c3c63537c01be68fb679f9229f8f68c5caaa23dc4c61d3c45dee90affed984dbbfb06b2659447400b4dcbf6e574719e8d49fe0dacea9509182a42f6463138d8693a3b8d797d3bb6b0b02648829d666341373939ac41a57e90fdc2469623b6e2d772199d7c806d5998f439603c0de8413f9d29f79323ec5410b409ab8c95547ab50bb921fe0c407b7aaaf663389bdea5ba56c023dc4622d6dd9cacd8f318a6a0297d041cc6ed455d906be50dc85a25ecb32f4a565432fec9f359833be1c6a6b7b4bd119d3c4b29932eeec8d140dd467ab4d969bd23e9d2a95b92835587f32428f957b6785b8206a4834e00a3013e0b6a5855f16207268bbdf311572c54d2e6ff9c659cd02c258f494c3b168ea170c69138b63e0dde487b72576e87657befa44548b0b4e1e5a837dbbe66a559cd1df8f2151ba513930243fd2b7705bd29b183dff966224d87ffabb74017d634ab2e4b368052504a7f6bc1c62d39a29dc2dcfba683bee2039e376ff391abbd13a0b89512fd8f6a4e66051dfa04e0e1a3cb4bd56a9b17e27651873bf2ed50f65cf1cc608afaf06fe7e6238347adb66f01d1f0b9b51f0078615553cb8ff8d6786b87e19dbc44000025693c4b34cfd695601a680efdc1e7465a981b0f028cbd3dbb938789f240e39223290e34ec303ff5c78a4a637ac04dad60d744f82e96c3c9e8ed6cb0248ac73b5b3a92007edfc1277c3cc6fa1d0045c1c371820f06bedaf046dd999665cc4745ddf8934084ae02e9238acae6dea330b5798e046138f5b15011875eae72d6eb6689e56e0ac5c5d9e25dc4fc1874cf37265e68ce5b8630b84ad8dab7704474f0bfd08ac295b3a508284fb6ff201f0aee6388d0e1d5cdaaf4c20429874792109f5b8e2f3eae6c397e46a510ed829a6746e523481465f64be4e145c83d6fa6951229d3",
"c90000000108277148f2b916666000404700403986db57eaa4b165be8ab9c95452bddb922eb35b7610a8e664f6b4620d870507c241290ce885c36d7672c51d94063bf893e01bc79e1d81bf023338da3d22f63bc7aa433f994488828e6082edd53e228164d8862067483762ea9523c90d565b9e4b185b7805eaf8220664264e82a95164ab6cab4fb3f5e795e246e7205aca236b3c94dde0ff4fa66ce0924d654829d59c3eb690470b20c5011c739102257e9c2247dee67c0b98190d0015154d31041aadb026b8d3a828c861a15dccdab0cec8cc99b8d6c2acddbb93ab66253e87ac39016507dba42e8fa9f5d22c7f27645a02361842a59ebb2eafefd0f3b92bd9692a96b93875defcfe2796243be8861c59ce5ab03f1d65d308ae456cb9656da1f01026ef0807cc9930021b29d69b36881c3e7d70fc68799ca81922008db93c9ca4a365ee191e214d9829481fd430194ad4583a0ab2e920c25244d7d64662872b3b69ab413ccf0dfb6bf2ea9a9b93e04ed19f8a0e146613ca9d511179f80aeab40d573590d38a7c10840e3f8b9ac1bd23b0826aecabf6d1cdb2aef02deb982c2029dd6d8bc21da6c262c8116b7b383ce8c9eec69da3e16c044dd96ae08a98595d128e89dd55e6dc8eb08b8d51327278027137f60a0e1b42878f98ca898587474f6d509c3a58ff4dd7f8b10905c200cf3170bdec725ee14a1ac8ebd1022509d3e499f5e72168eac43264d7246daa0bfc81a216ca97730b7e043cad8d8a9af5c443a5d15e9a88d82b6750c740eecfd63561712a185c69532b1a18b23513d7cf871f14d164ec544f22b6a8cc77d6fae5fc6e47eb64f08617098d229da78a378d6a0864684a7978f650c7922c907f97b0ebf2be29cc834ffe995c9636b310f4a8c2c5623c3b7b533518193d226923f111da1a0e8055b9053ad7f7504d194fbc3ae2b41cef30aa099624d5e229ffb56d5883a5a09163d22455cac52e37ee0ed5367b7c3bbcd4818a46b9b363b592c53c780eeae2c8b80a1d60d296614c998a9774f76453a58bc55d1c26bb10dc321c159858d7ba2f7855ba01aadf3585632c097e5471591dcc24d87e9b76509c10e2710310e4869de710ce0f484d326be751f8e9f765a685312423f1801aefb28dfe0c8f286432356d06857101a67a432497c5849111db2792fa0ee4ffff49a9124c152bcff82da1951258f989681e4f1338357f2c9f82333f6051b188f640bf200a0a75be1d35d2301e8d3813f7ba1926a28a0df05c21413cc0c4090c1e4ba4877dca8e129876c72ab3a801b4093320f5f685120680541d97889eea5dfcaf07a7ecb00c0ba0ae193969a4cddcbd753609a5304ea88783358ab0ae005c6af27bb58b2c4282186461ea50540845e2e2a2f4efced88c8ab9cd9fb4a226a265714c77ce7b79d1a40bd00b24cbba498dafde6bbad91686cf2e13e75669234bc342218887ba910ac81680122ddd36466e7e8a983d5a0fc18a6e9a386762c32132be08abe5554e334ec7d88734cfad9a378553b71222c55f6aa114392e015dfa2bb6cb4ad241c6bca82fdd0a00eb8d6b4afac61268130dea2807a97e4c0adc0e2be39abccbe64dca5c480e09c4bebb8b598e4f60afb0e92dce710859013b1ffa9c78fbf380160f31b1e72340dea86d353ff0e95884b72e2c2c10f6eff5f36c588ee845b7bc97c3b6bec4aa879dd0eb56b838b7bc2ec6e66a5b5517908197a67566dce7df421a8daedc98848c70d1d2c39b2f3538e6f17800bee3d3",
"df0000000108277148f2b916666000403a52317841946860def0d7829c06fec03ffe8b97f84e10116fafd93d1d2d39bbfda0d148778c21bb1e1667eb789b1ff70c2e3d557ed9c31570d20d",
"d00000000108277148f2b91666600044cd5e860e3fa7ac4769ec75d9b7d20f19e69265939a42afd3c4248a7f5210358a044f42869567e72a05642e96ddffa67bf24ec2d966d860c6accdee01d6917c8c43d4d089d8bb63ff848b617c13fbeefafcbb049ab0822a9ca7716c95af84d019b755b145dfe43c218555d1a7e047deda7d8db352a386b2b6d03f2e7f4510f47ff4ab199348dfa81c86bea5d09d7c7af4ef3f04e99fe4e6c21d53c4335407e27913129152033f17580f97d0345c8487a7ad329dc5c97b298ec7b80fee7813f1d6f94945a44ff662a69453c2dc7ac5e8a1cb90400e63818632d7f9654f140a61280df183b3d9f9b824e53d82f2c14ea3de89befdc79b84a4a3eb659a41db25622add94f2ad4b0d5977f1091aae0a4b83c7b41bca61c6c8d807ef02a8ce6240b76d442559a8b338b39418d27e99aff38840fc79a20995af65b3bbe1e3177074079a47578c51655a4016363364fd2c108d384e602deebd022da3c814549cd57d73c5bfc20e279045e2ad436fbd7e7c9e1985f0ec2f422e310e7aa8cfc48e637f9ac61d06d6482cb40b4376ff3c7abff3c3c26634689ae16d704bab1343d6413fc7b6c076eb0454eda2e0d1e077db40c922ebba6b0b1fa814e3ba76d8d6c4289abbd655f0cf5968eb2aba7131680b44da8910056a76647a6dfea95f27364a7ce694b8fbe19ebcb2a47e7350d33a36f7f5ca67af5e934f449125f4aae870a5b23b4370680ee02b194784d5d188ecdf58ae5454221406bde0ddd3e50d3363a564d6ca9fe0fb57d4df8716cb430cf553be573aa690e5645075ec74edd38cf23215bd50bcda0639dfbbe08dd6c476249e35da819ea6ccef808911b0eef6efaa4947244472795bc071d7154ed87e4a43575b3d61a551fcfccfb7ca3edaee9324f33f54dc9809747e59e24e79f256e8e72f01b8647f71c4b9dc260715fd9d83d3dbd9e124c432c04b3398e74efa3869fe129e368c15b6ca234a243fcac675adcc1db247e3f8485ac4a78f4a1ce2db3b437a1960b02f0c227901d165dcc05abdb3929a80dff2eaf72816185d4af4e28eea05430b736ddd2962e03ec64fa48649dd610e0e221c48f781b45cd9963c176126110e662369874e6a55f28039a23484c5c53714fc2d2030b48f1c895102ca9ad8acae1ec4eb0ae8d8bde31cd74fc515930078d22ad07dc3c7221ddbc4027c746207fa038b31080714091459c9a66ba4f5912d8d3905d3a9a47e4d8829a8110c96c0c9c81291c7985073808814109364df15b04520dc07e8d67cafcda71f0ca59423df5fadae92417a8661b3cdbbf6b1059780fb8b43eb4dcdacf731bb8db26294f978f6be7506b87d17a95367cdb83000565a4986e66dd60d0851f9b593d68790f8097434f62ea7a7396017c3c84754845d3a97f028cf8697d929a2826451653ccf84aba4d2f40fa530b258c13f08c6523c3c02d9669fd46b6a51f20ad323857d767150e3530a66bf88976dbadf99aeea549254c07e11e14085979b60f3b7e1728a4a2d7a35b0377c6501ae7d1d4bba338fb51a17ca8f7e698bd70cd01e8f30edf3e83591a2eb0038811e347bfcfab159b0d1ff6153e0f9ee4c129cfb7687e30b82eed74130c466eee06506dde50805b58c2acccd4cf4b2cc86c52fa2af602a8a7064eb9d90e1c568373b19e43ef4e7c1e4e1c9a58ccedf80a02a46ed64e68e72d4e75c7436e2bc0ba59f95a00456e5680af9e6cf4bf3a6d302ddaf8847cfd5ea606797",
"d30000000108277148f2b91666600043d24b66b2531ed9f9c13b07b2654186b0410a608592fdf728479734933197ec06a1cde860f36b3170fb2a9c85c62a7867ba6520dcb2d0ab2f6a484d9ebf8237d7a6f3c1fb16c1e0458ccf20e6d1b298a7530cea42636166027d92812915e76fbcc436a5e414147672dd7b0d19ff24513800e63cd86984f1c93ef1430bb848d37830eed61675d7c9999b92c6e5796d384554c74dd5a163de341ab309d6b0cb028aa08e56d79c60980d4a49a1c095456ca119fc3f04e496c93a084d017f60c6e031d6e9ad2e4fa699bb4b0c92fdcb44131129db0d30ce9efb740d3db0339127d9bdd1d4f677b1cb532a33647851ba9bb20bd8d6aa593271a85c3a9dc9835065663e61faa8dc6af209a0caf183d0fda3d4839d40edd5659dd053778642db8fba21f1f793e45c5c517e68bbef8543e3a727743c7bf87d047d441d13226b9021fac56904872774cf6768dc91db8ea489a244500e9e527acdc0088437357acf9397b014e66fef2db1248f9c6a578af07d7a02b1356fee02e27b8207e57633fa7bfd87ccd382e368c14b946aea780fcbe696d6e4fa3aa589184e104177db2fc3d91d4af120d9da3bdad021d003796b8261b590d8113f995dc1db4fac1c62cb68370d41cc87c982815017ae2143d5a469b742d019e5556d813877fec9d021cb37f80e5987d9f743c2b39093a34f6654164a8185a5caefbbef8ea17f62f6801a3fd89fae333c878cec9b25d10dfee2abca65d7c909ad2e4f11736d13b1642df4c5a0761f8f29f35f37def9ed327f4a9d8e53269fa6c7cedd0f4fd67d6cde81934e291d9fca695cc9745890cb54503e29e09f4a30f80e2f574bbbeedb7d20481c583d8362d22b2dbec09494095a043cdae283e86f905d8807f7b7c0f06ce968487bbca1e20b87245b68f24537a7c7e768c838f1bf26650afdabec2c0bb9736b345473f279c9b73ecf0d2c4aea49330ecfef0949ef7cb81861b05950ec0772db856365b136ba75d5509c01d7a970c84ebc77d8d5c3ceae1ef5f3079afc7d78965ffa3bc4c64ef1b4718ffb488a571528c83b615c43022616bb4c494c838b556df5ede711a688b0315c1ce6e2892247df582b7c3f2b06cac0bd8d670e2b581f074750596ba162189060b8af3dfc650ba3b45932edc4f94f08741d3072bfd1ef8159b27a7f3673a4fc504304c12116e3c2d7636c663c9fa1b2f5571be88769f33ccb94a09abd9c5a7dc8a8c2031bb2bc256b84aeb68a9abf7673151cec41b48bdd74f395a46acf30dae43e060e596bb2e739274210701ee9bb6cc3ff81ace751e375a01f17b3c5cc5f1234c488d69611bb27f6e3ee17e3c3843ebe4a280d6aa8ef017058a872810a437f85331adb3cb8d382650897b1b1589ee6",
"dd0000000108277148f2b9166660004053972df1beb451f73eb070e33ed63f681eb9b7e1e03f20baff3f54157598c7dd90a0de49850a3ccd6eb1b1cfc9dc6d3ba9ed1c0a19c69bf433da300d3cecc4ef151c44a721d680e3e3aaaf3eefec23091c5fde22",
"c90000000108277148f2b916666000404700403986db57eaa4b165be8ab9c95452bddb922eb35b7610a8e664f6b4620d870507c241290ce885c36d7672c51d94063bf893e01bc79e1d81bf023338da3d22f63bc7aa433f99448825ee873013b006b2a6c87c7581c7117bfeb4ec3d68405a68d9488f6d58474dd16539677e869812ead055e70d655a660062e17083995c0dbecd565c79800b11c8ca0c351ecffa61e707d62d443b3810bc60d4ef87aa99b979ff55ee1ea46b65436c15534e5315113138aed6daa9f04d3050d77a7e379c83b948d3797177c1793e59b2555423bd52595d93e293ea8ffa3c428c6dbba4e202d76933caf6a5609b0a4aa6cf4fd2aadb6505382381ef2d5b33efc43eba24c84b7805baf2ddab44a50180e5e6f2a31f9ea8089aef562d3b578a799d61befec99c016fadec3363f68a1be4ca1e13e8bdd2809a1dacc41134663e22f21978167c5ee8ef49652ae152fc6c1bcf52109cd3076cdd599cb43261941de7aed148d7d3e956cd615549a9647496f43f998daad4c841cc40ce1501fbfc152b957c94be558f6743061e312d746137db2ae6a44e181587dbf6b0d9508cef4aefd99ea5d3369898bd4c3df5e95ac89eaaba54019ffe0402b8f567c91b9371e80c621c67d3c831331acc063892bbd8a81cfc0498e78474b11e8c05dd8f540c449505342ae95f6281940aae973db35b8e31ff801f6bc8975f592538881ae9cc4cedcbdb39a784a9fe962a1f12be51c11b91d4dedf649bb5672dec8e03db97b0d69fce36edfbecb6836644bad1ab8e6d4e13644d9c3476db0e8a8eb4b5a5c32f7a5604c8e19700c53602839478531579cb4c4bb5cc969cf482f325dd837629318baf128920d9978e23296d7016e6c05c954f95881b4f9f7e43bcea393951e91af0e4a671400dc435bd2a1616c60618df2476d0ece060dbbda11e751e256956a0dbcd7e4a8d6d85a3319f22a2c5f26dad50e82f70f3dd91feff19c775aa60499a3b7daa57e344c07c3787e99d53303488801d2b17cdbfdee61ea3fc473f6c146f06eb60d70594a59e0ed79cee6ca4a5f78b037637ddab69fb8522c0f7bf37aa7f59cc7fa659e759db69966455944975cd22a1a1355f35a589a4978c8f3272e1c4f6793288a00ab879299aa6ad02d966e3dc67cee0c808b1a046458cff9bdac25a4071eb10038a6389a0ef7233003641bd4ee1efad0e9b2f693396a89ca0db3c05b6abfed3b246eb1b23a6b77e8b486f26d9c3dde9dd6f3637a8115940ed2ca762ca6320609f61c37ffc9c3f2f7a0f27edc9891c2eeac49ba258a0d09c35c4fe1dc52d4d9319aa9b1a271a5d8d2d3a75fef4d59fb04679ba526aecbd19d73f72fee537630444326e2543ce564c669bf378499738385dda9ac63521a1b91f580d0737a7326009f0ff0dcb05aa8b86222c934d9ddb4628e30b6e12ae370154ab39c605431b4c40683592afcfd6fccf35df9fe5850442595d24be3d9f4298bf3d541f09e7e71f552c88eed9642df46953622d5aea05b5060325304ec81c0447ac95b90f9da4359e3286938f06aea3d45030cb836be15b1c65e3edf44cbcfe2f01ef8d7209c69d7c81334c866ebee50e418a28336cea1982069b4df090eab81303761d1af337e083f1e0ad1440a02ef1eefb03506c39d2377807e335ee64bdb76527f786223cee5233299eda9fcb1d38f19c34480f790a328b0735f80908e3aa70086df828d56b6c79516f71a24c9d94f60335f86e9d29c0c5d3872b",
"dd0000000108277148f2b916666000406672db10ab41db38c01f7021709bac4d1659d872623eb5852b12b494535d13779a88d37e9685da572f6b2de35793a519a457493456ac4ee242933cf92d783f783656899c31832274bf1c26d24720d9d8ecfec598e19c58a478d2991dfc1cda3000f7bd7bd17e80",
"d60000000108277148f2b916666000404ed98b1b4ac35c0c0ef18c88adf08a6701ccb0876ea75aac8c128349936fa3cb6728e4e58de8673dd7dc8457b092957f26bc8194233bb81c7e78127844f9b833f196dc46c5cb4064c773f3c6e0bc73",
},
domain: "www.google.com",
},
} }
q, err := NewQuicSniffer(SnifferConfig{})
assert.NoError(t, err)
for _, test := range cases { for _, test := range cases {
pkt, err := hex.DecodeString(test.input) data, err := testQuicSniffer(test.input, true)
assert.NoError(t, err) assert.NoError(t, err)
oriPkt := bytes.Clone(pkt) assert.Equal(t, test.domain, data)
domain, err := q.SniffData(pkt)
data, err = testQuicSniffer(test.input, false)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, test.domain, domain) assert.Equal(t, test.domain, data)
assert.Equal(t, oriPkt, pkt) // ensure input data not changed
} }
} }

View File

@@ -10,6 +10,10 @@ type Sniffer interface {
SupportPort(port uint16) bool SupportPort(port uint16) bool
} }
type MultiPacketSniffer interface {
WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender
}
const ( const (
TLS Type = iota TLS Type = iota
HTTP HTTP

View File

@@ -449,6 +449,26 @@ proxies: # socks5
password: "shadow_tls_password" password: "shadow_tls_password"
version: 2 # support 1/2/3 version: 2 # support 1/2/3
- name: "ss5"
type: ss
server: server
port: 443
cipher: chacha20-ietf-poly1305
password: "password"
plugin: gost-plugin
plugin-opts:
mode: websocket
# tls: true # wss
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 配置指纹将实现 SSL Pining 效果
# fingerprint: xxxx
# skip-cert-verify: true
# host: bing.com
# path: "/"
# mux: true
# headers:
# custom: value
- name: "ss-restls-tls13" - name: "ss-restls-tls13"
type: ss type: ss
server: [YOUR_SERVER_IP] server: [YOUR_SERVER_IP]

View File

@@ -0,0 +1,81 @@
package gost
import (
"context"
"crypto/tls"
"net"
"net/http"
"github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/transport/vmess"
smux "github.com/sagernet/smux"
)
// Option is options of gost websocket
type Option struct {
Host string
Port string
Path string
Headers map[string]string
TLS bool
SkipCertVerify bool
Fingerprint string
Mux bool
}
// NewGostWebsocket return a gost websocket
func NewGostWebsocket(ctx context.Context, conn net.Conn, option *Option) (net.Conn, error) {
header := http.Header{}
for k, v := range option.Headers {
header.Add(k, v)
}
config := &vmess.WebsocketConfig{
Host: option.Host,
Port: option.Port,
Path: option.Path,
Headers: header,
}
if option.TLS {
config.TLS = true
tlsConfig := &tls.Config{
ServerName: option.Host,
InsecureSkipVerify: option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
var err error
config.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
if host := config.Headers.Get("Host"); host != "" {
config.TLSConfig.ServerName = host
}
}
var err error
conn, err = vmess.StreamWebsocketConn(ctx, conn, config)
if err != nil {
return nil, err
}
if option.Mux {
config := smux.DefaultConfig()
config.KeepAliveDisabled = true
session, err := smux.Client(conn, config)
if err != nil {
return nil, err
}
stream, err := session.OpenStream()
if err != nil {
return nil, err
}
conn = stream
}
return conn, nil
}

View File

@@ -378,12 +378,14 @@ func handleUDPConn(packet C.PacketAdapter) {
return return
} }
if sniffingEnable && snifferDispatcher.Enable() {
snifferDispatcher.UDPSniff(packet)
}
key := packet.Key() key := packet.Key()
sender, loaded := natTable.GetOrCreate(key, newPacketSender) sender, loaded := natTable.GetOrCreate(key, func() C.PacketSender {
sender := newPacketSender()
if sniffingEnable && snifferDispatcher.Enable() {
return snifferDispatcher.UDPSniff(packet, sender)
}
return sender
})
if !loaded { if !loaded {
dial := func() (C.PacketConn, C.WriteBackProxy, error) { dial := func() (C.PacketConn, C.WriteBackProxy, error) {
if err := sender.ResolveUDP(metadata); err != nil { if err := sender.ResolveUDP(metadata); err != nil {

View File

@@ -1,11 +1,12 @@
module github.com/esrrhs/pingtunnel module github.com/esrrhs/pingtunnel
go 1.21 go 1.21
toolchain go1.23.7
require ( require (
github.com/esrrhs/gohome v0.0.0-20231129030235-4e6f13493d6c github.com/esrrhs/gohome v0.0.0-20231129030235-4e6f13493d6c
github.com/golang/protobuf v1.5.3 github.com/golang/protobuf v1.5.3
golang.org/x/net v0.23.0 golang.org/x/net v0.36.0
) )
require ( require (
@@ -13,6 +14,6 @@ require (
github.com/google/uuid v1.4.0 // indirect github.com/google/uuid v1.4.0 // indirect
github.com/oschwald/geoip2-golang v1.9.0 // indirect github.com/oschwald/geoip2-golang v1.9.0 // indirect
github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.30.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect google.golang.org/protobuf v1.33.0 // indirect
) )

View File

@@ -19,10 +19,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=

View File

@@ -30,9 +30,7 @@ jobs:
uses: ilammy/setup-nasm@v1 uses: ilammy/setup-nasm@v1
- if: ${{ matrix.platform == 'ubuntu-latest' }} - if: ${{ matrix.platform == 'ubuntu-latest' }}
name: Install LLVM and Clang name: Install LLVM and Clang
uses: KyleMayes/install-llvm-action@v2 run: sudo apt install -y clang
with:
version: "18"
- name: Install Rust - name: Install Rust
run: | run: |
rustup set profile minimal rustup set profile minimal

View File

@@ -28,9 +28,9 @@ jobs:
- name: Install Rust - name: Install Rust
run: | run: |
rustup set profile minimal rustup set profile minimal
rustup toolchain install 1.83 rustup toolchain install 1.85
rustup default 1.83 rustup default 1.85
rustup override set 1.83 rustup override set 1.85
- name: Build with All Features Enabled (Unix) - name: Build with All Features Enabled (Unix)
if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }} if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
run: cargo build --verbose --features "full-extra local-flow-stat utility-url-outline" run: cargo build --verbose --features "full-extra local-flow-stat utility-url-outline"
@@ -55,9 +55,9 @@ jobs:
- name: Install Rust - name: Install Rust
run: | run: |
rustup set profile minimal rustup set profile minimal
rustup toolchain install 1.83 rustup toolchain install 1.85
rustup default 1.83 rustup default 1.85
rustup override set 1.83 rustup override set 1.85
- name: Build with All Features Enabled - name: Build with All Features Enabled
run: cargo build --manifest-path crates/shadowsocks-service/Cargo.toml --verbose --features "full dns-over-tls dns-over-https dns-over-h3 local-dns local-flow-stat local-http-rustls local-tun local-fake-dns local-online-config stream-cipher aead-cipher-extra aead-cipher-2022 aead-cipher-2022-extra security-replay-attack-detect" run: cargo build --manifest-path crates/shadowsocks-service/Cargo.toml --verbose --features "full dns-over-tls dns-over-https dns-over-h3 local-dns local-flow-stat local-http-rustls local-tun local-fake-dns local-online-config stream-cipher aead-cipher-extra aead-cipher-2022 aead-cipher-2022-extra security-replay-attack-detect"
@@ -78,8 +78,8 @@ jobs:
- name: Install Rust - name: Install Rust
run: | run: |
rustup set profile minimal rustup set profile minimal
rustup toolchain install 1.77 rustup toolchain install 1.85
rustup default 1.77 rustup default 1.85
rustup override set 1.77 rustup override set 1.85
- name: Build with All Features Enabled - name: Build with All Features Enabled
run: cargo build --manifest-path crates/shadowsocks/Cargo.toml --verbose --features "stream-cipher aead-cipher-extra aead-cipher-2022 aead-cipher-2022-extra security-replay-attack-detect" run: cargo build --manifest-path crates/shadowsocks/Cargo.toml --verbose --features "stream-cipher aead-cipher-extra aead-cipher-2022 aead-cipher-2022-extra security-replay-attack-detect"

View File

@@ -38,7 +38,7 @@ jobs:
rustup override set ${{ matrix.platform.toolchain }} rustup override set ${{ matrix.platform.toolchain }}
- name: Install cross - name: Install cross
run: cargo install cross run: cargo install cross --git https://github.com/cross-rs/cross
- name: Build ${{ matrix.platform.target }} - name: Build ${{ matrix.platform.target }}
timeout-minutes: 120 timeout-minutes: 120
@@ -58,11 +58,11 @@ jobs:
fi fi
compile_nightly="-n" compile_nightly="-n"
compile_features="-Z build-std=std,panic_abort,proc_macro" #compile_cargo_flags="-Z build-std=std,panic_abort,proc_macro"
fi fi
cd build cd build
./build-release -t ${{ matrix.platform.target }} $compile_features $compile_compress $compile_nightly $compile_features ./build-release -t ${{ matrix.platform.target }} $compile_features $compile_compress $compile_nightly $compile_cargo_flags
- name: Upload Artifacts - name: Upload Artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@@ -82,7 +82,7 @@ jobs:
rustup override set ${{ matrix.platform.toolchain }} rustup override set ${{ matrix.platform.toolchain }}
- name: Install cross - name: Install cross
run: cargo install cross run: cargo install cross --git https://github.com/cross-rs/cross
- name: Build ${{ matrix.platform.target }} - name: Build ${{ matrix.platform.target }}
timeout-minutes: 120 timeout-minutes: 120
@@ -102,11 +102,11 @@ jobs:
fi fi
compile_nightly="-n" compile_nightly="-n"
compile_features="-Z build-std=std,panic_abort,proc_macro" #compile_cargo_flags="-Z build-std=std,panic_abort,proc_macro"
fi fi
cd build cd build
./build-release -t ${{ matrix.platform.target }} $compile_features $compile_compress $compile_nightly $compile_features ./build-release -t ${{ matrix.platform.target }} $compile_features $compile_compress $compile_nightly $compile_cargo_flags
- name: Upload Github Assets - name: Upload Github Assets
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2

View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@@ -212,7 +212,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -229,7 +229,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -300,7 +300,7 @@ dependencies = [
"regex", "regex",
"rustc-hash 1.1.0", "rustc-hash 1.1.0",
"shlex", "shlex",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -424,7 +424,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -792,7 +792,7 @@ dependencies = [
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -880,7 +880,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -901,7 +901,7 @@ checksum = "16c187d1e575ef546d24f0fcd7701cc04abfe6b5e7e2758aabc450b99e835ac3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -967,7 +967,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -982,14 +982,14 @@ dependencies = [
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.11.6" version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"env_filter", "env_filter",
"humantime", "jiff",
"log", "log",
] ]
@@ -1006,7 +1006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.59.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -1188,7 +1188,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -1743,7 +1743,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -1874,6 +1874,30 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "jiff"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d699bc6dfc879fb1bf9bdff0d4c56f0884fc6f0d0eb0fba397a6d00cd9a6b85e"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d16e75759ee0aa64c57a56acbf43916987b20c77373cb7e808979e02b93c9f9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]] [[package]]
name = "jobserver" name = "jobserver"
version = "0.1.32" version = "0.1.32"
@@ -2333,7 +2357,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -2472,7 +2496,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -2503,7 +2527,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -2574,6 +2598,15 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@@ -2617,14 +2650,14 @@ dependencies = [
"proc-macro-error-attr2", "proc-macro-error-attr2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.92" version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -2691,7 +2724,7 @@ dependencies = [
"once_cell", "once_cell",
"socket2", "socket2",
"tracing", "tracing",
"windows-sys 0.59.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -3017,7 +3050,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.59.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -3219,7 +3252,7 @@ checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -3598,9 +3631,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.94" version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3624,7 +3657,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -3691,7 +3724,7 @@ dependencies = [
"fastrand", "fastrand",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -3730,7 +3763,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -3741,7 +3774,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -3848,7 +3881,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -3948,7 +3981,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -3999,7 +4032,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -4200,7 +4233,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -4235,7 +4268,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -4352,7 +4385,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -4363,7 +4396,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -4647,7 +4680,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
"synstructure", "synstructure",
] ]
@@ -4678,7 +4711,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -4689,7 +4722,7 @@ checksum = "06718a168365cad3d5ff0bb133aad346959a2074bd4a85c121255a11304a8626"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]
@@ -4709,7 +4742,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
"synstructure", "synstructure",
] ]
@@ -4738,7 +4771,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.94", "syn 2.0.100",
] ]
[[package]] [[package]]

View File

@@ -8,8 +8,8 @@ readme = "README.md"
documentation = "https://docs.rs/shadowsocks-rust" documentation = "https://docs.rs/shadowsocks-rust"
keywords = ["shadowsocks", "proxy", "socks", "socks5", "firewall"] keywords = ["shadowsocks", "proxy", "socks", "socks5", "firewall"]
license = "MIT" license = "MIT"
edition = "2021" edition = "2024"
rust-version = "1.83" rust-version = "1.85"
[badges] [badges]
maintenance = { status = "passively-maintained" } maintenance = { status = "passively-maintained" }
@@ -85,7 +85,6 @@ full = [
"local-dns", "local-dns",
"local-redir", "local-redir",
"local-tun", "local-tun",
"local-fake-dns",
"local-online-config", "local-online-config",
"multi-threaded", "multi-threaded",
"stream-cipher", "stream-cipher",
@@ -97,6 +96,7 @@ full = [
full-extra = [ full-extra = [
"full", "full",
"dns-over-h3", "dns-over-h3",
"local-fake-dns",
"aead-cipher-extra", "aead-cipher-extra",
"aead-cipher-2022-extra", "aead-cipher-2022-extra",
"security-replay-attack-detect", "security-replay-attack-detect",

View File

@@ -6,6 +6,10 @@
# "cargo install --force --locked bindgen-cli && mv $HOME/.cargo/bin/bindgen /usr/bin", # "cargo install --force --locked bindgen-cli && mv $HOME/.cargo/bin/bindgen /usr/bin",
# "rm -rf $HOME/.cargo" # "rm -rf $HOME/.cargo"
# ] # ]
# pre-build = [
# "apt update",
# "apt install --assume-yes --no-install-recommends build-essential llvm-8-dev libclang-8-dev clang-8",
# ]
[build.env] [build.env]
passthrough = ["RUSTFLAGS"] passthrough = ["RUSTFLAGS"]
@@ -13,19 +17,19 @@ passthrough = ["RUSTFLAGS"]
# MIPS targets are dropped to Tier 3 # MIPS targets are dropped to Tier 3
# https://github.com/rust-lang/compiler-team/issues/648 # https://github.com/rust-lang/compiler-team/issues/648
# FIXME: build-std with sequence is supported only on git # FIXME: build-std with sequence is supported only on git
# [target.mips-unknown-linux-gnu] [target.mips-unknown-linux-gnu]
# build-std = ["std", "panic_abort", "proc_macro"] build-std = ["std", "panic_abort", "proc_macro"]
# [target.mips-unknown-linux-musl] [target.mips-unknown-linux-musl]
# build-std = ["std", "panic_abort", "proc_macro"] build-std = ["std", "panic_abort", "proc_macro"]
# [target.mips64-unknown-linux-gnuabi64] [target.mips64-unknown-linux-gnuabi64]
# build-std = ["std", "panic_abort", "proc_macro"] build-std = ["std", "panic_abort", "proc_macro"]
# [target.mips64-unknown-linux-muslabi64] [target.mips64-unknown-linux-muslabi64]
# build-std = ["std", "panic_abort", "proc_macro"] build-std = ["std", "panic_abort", "proc_macro"]
# [target.mips64el-unknown-linux-gnuabi64] [target.mips64el-unknown-linux-gnuabi64]
# build-std = ["std", "panic_abort", "proc_macro"] build-std = ["std", "panic_abort", "proc_macro"]
# [target.mips64el-unknown-linux-muslabi64] [target.mips64el-unknown-linux-muslabi64]
# build-std = ["std", "panic_abort", "proc_macro"] build-std = ["std", "panic_abort", "proc_macro"]
# [target.mipsel-unknown-linux-gnu] [target.mipsel-unknown-linux-gnu]
# build-std = ["std", "panic_abort", "proc_macro"] build-std = ["std", "panic_abort", "proc_macro"]
# [target.mipsel-unknown-linux-musl] [target.mipsel-unknown-linux-musl]
# build-std = ["std", "panic_abort", "proc_macro"] build-std = ["std", "panic_abort", "proc_macro"]

View File

@@ -8,8 +8,8 @@ readme = "README.md"
documentation = "https://docs.rs/shadowsocks-service" documentation = "https://docs.rs/shadowsocks-service"
keywords = ["shadowsocks", "proxy", "socks", "socks5", "firewall"] keywords = ["shadowsocks", "proxy", "socks", "socks5", "firewall"]
license = "MIT" license = "MIT"
edition = "2021" edition = "2024"
rust-version = "1.83" rust-version = "1.85"
[badges] [badges]
maintenance = { status = "passively-maintained" } maintenance = { status = "passively-maintained" }

Some files were not shown because too many files have changed in this diff Show More