mirror of
https://github.com/bolucat/Archive.git
synced 2025-10-14 12:24:03 +08:00
Update On Thu Mar 13 19:35:42 CET 2025
This commit is contained in:
1
.github/update.log
vendored
1
.github/update.log
vendored
@@ -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
|
||||||
|
@@ -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,
|
||||||
|
@@ -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]",
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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]
|
||||||
|
81
clash-meta/transport/gost-plugin/websocket.go
Normal file
81
clash-meta/transport/gost-plugin/websocket.go
Normal 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
|
||||||
|
}
|
@@ -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 {
|
||||||
|
48
clash-nyanpasu/backend/Cargo.lock
generated
48
clash-nyanpasu/backend/Cargo.lock
generated
@@ -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"
|
||||||
|
@@ -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",
|
||||||
|
@@ -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"
|
||||||
}
|
}
|
||||||
|
36
clash-nyanpasu/pnpm-lock.yaml
generated
36
clash-nyanpasu/pnpm-lock.yaml
generated
@@ -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:
|
||||||
|
@@ -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
|
||||||
|
13
clash-verge-rev/.husky/pre-push
Normal file
13
clash-verge-rev/.husky/pre-push
Normal 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
|
1
clash-verge-rev/src-tauri/.clippy.toml
Normal file
1
clash-verge-rev/src-tauri/.clippy.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
avoid-breaking-exported-api = true
|
@@ -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);
|
||||||
|
|
||||||
|
@@ -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来处理异步操作
|
||||||
|
@@ -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::*;
|
||||||
|
@@ -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())?;
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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]
|
||||||
|
@@ -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) => {
|
||||||
|
@@ -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(())
|
||||||
|
@@ -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)
|
||||||
|
@@ -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);
|
||||||
|
@@ -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]
|
||||||
|
@@ -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 配置
|
||||||
|
@@ -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()))
|
||||||
};
|
};
|
||||||
|
@@ -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;";
|
||||||
|
@@ -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());
|
||||||
|
@@ -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()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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>,
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
|
@@ -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}""#
|
||||||
);
|
);
|
||||||
|
@@ -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();
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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>,
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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) {
|
||||||
|
@@ -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");
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -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() {
|
||||||
|
@@ -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);
|
||||||
|
@@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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};
|
||||||
|
|
||||||
|
@@ -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())
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
|
pub mod mihomo;
|
||||||
pub mod sysinfo;
|
pub mod sysinfo;
|
||||||
pub mod mihomo;
|
|
@@ -43,7 +43,6 @@ impl PlatformSpecification {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
system_name,
|
system_name,
|
||||||
system_version,
|
system_version,
|
||||||
|
@@ -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"))]
|
||||||
|
@@ -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"
|
||||||
/// ```
|
/// ```
|
||||||
/// ```
|
/// ```
|
||||||
|
@@ -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};
|
||||||
|
@@ -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)?;
|
||||||
|
@@ -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;
|
|
||||||
|
@@ -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");
|
||||||
|
@@ -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;
|
||||||
|
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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>
|
||||||
|
@@ -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",
|
||||||
|
@@ -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": "启动页面",
|
||||||
|
7
clash-verge-rev/src/services/types.d.ts
vendored
7
clash-verge-rev/src/services/types.d.ts
vendored
@@ -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;
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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 */
|
||||||
|
|
||||||
|
@@ -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 */
|
|
@@ -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);
|
|
@@ -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;
|
|
@@ -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)
|
||||||
{
|
{
|
||||||
|
@@ -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 */
|
||||||
|
@@ -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++) {
|
||||||
|
@@ -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");
|
|
||||||
|
|
@@ -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
|
||||||
|
@@ -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,
|
||||||
|
@@ -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]",
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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]
|
||||||
|
81
mihomo/transport/gost-plugin/websocket.go
Normal file
81
mihomo/transport/gost-plugin/websocket.go
Normal 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
|
||||||
|
}
|
@@ -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 {
|
||||||
|
@@ -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
|
||||||
)
|
)
|
||||||
|
@@ -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=
|
||||||
|
@@ -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
|
||||||
|
@@ -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"
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
117
shadowsocks-rust/Cargo.lock
generated
117
shadowsocks-rust/Cargo.lock
generated
@@ -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]]
|
||||||
|
@@ -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",
|
||||||
|
@@ -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"]
|
||||||
|
@@ -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
Reference in New Issue
Block a user