feat: add global-client-fingerprint.
* Available: "chrome","firefox","safari","ios","random","none". * global-client-fingerprint will NOT overwrite the proxy's client-fingerprint setting when "client-fingerprint: none".
This commit is contained in:
parent
601a40d068
commit
5cab8afec7
7 changed files with 123 additions and 55 deletions
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/Dreamacro/clash/transport/gun"
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
"github.com/Dreamacro/clash/transport/trojan"
|
"github.com/Dreamacro/clash/transport/trojan"
|
||||||
"github.com/Dreamacro/clash/transport/vless"
|
"github.com/Dreamacro/clash/transport/vless"
|
||||||
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Trojan struct {
|
type Trojan struct {
|
||||||
|
@ -77,6 +78,11 @@ func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
if vmess.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 {
|
||||||
|
t.option.ClientFingerprint = vmess.GetGlobalFingerprint()
|
||||||
|
}
|
||||||
|
|
||||||
if t.transport != nil {
|
if t.transport != nil {
|
||||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -71,6 +71,11 @@ type VlessOption struct {
|
||||||
|
|
||||||
func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
if vmess.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
|
||||||
|
v.option.ClientFingerprint = vmess.GetGlobalFingerprint()
|
||||||
|
}
|
||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,11 @@ type WSOptions struct {
|
||||||
// StreamConn implements C.ProxyAdapter
|
// StreamConn implements C.ProxyAdapter
|
||||||
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
if clashVMess.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
|
||||||
|
v.option.ClientFingerprint = clashVMess.GetGlobalFingerprint()
|
||||||
|
}
|
||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,11 @@ package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/structure"
|
"github.com/Dreamacro/clash/common/structure"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
|
@ -54,6 +56,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
Path: []string{"/"},
|
Path: []string{"/"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if GlobalUtlsClient := vmess.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
|
||||||
|
vmessOption.ClientFingerprint = GlobalUtlsClient
|
||||||
|
}
|
||||||
|
|
||||||
err = decoder.Decode(mapping, vmessOption)
|
err = decoder.Decode(mapping, vmessOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
@ -61,6 +68,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
proxy, err = outbound.NewVmess(*vmessOption)
|
proxy, err = outbound.NewVmess(*vmessOption)
|
||||||
case "vless":
|
case "vless":
|
||||||
vlessOption := &outbound.VlessOption{}
|
vlessOption := &outbound.VlessOption{}
|
||||||
|
|
||||||
|
if GlobalUtlsClient := vmess.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
|
||||||
|
vlessOption.ClientFingerprint = GlobalUtlsClient
|
||||||
|
}
|
||||||
|
|
||||||
err = decoder.Decode(mapping, vlessOption)
|
err = decoder.Decode(mapping, vlessOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
@ -75,6 +87,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
proxy, err = outbound.NewSnell(*snellOption)
|
proxy, err = outbound.NewSnell(*snellOption)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
trojanOption := &outbound.TrojanOption{}
|
trojanOption := &outbound.TrojanOption{}
|
||||||
|
|
||||||
|
if GlobalUtlsClient := vmess.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
|
||||||
|
trojanOption.ClientFingerprint = GlobalUtlsClient
|
||||||
|
}
|
||||||
|
|
||||||
err = decoder.Decode(mapping, trojanOption)
|
err = decoder.Decode(mapping, trojanOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
|
112
config/config.go
112
config/config.go
|
@ -14,9 +14,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
P "github.com/Dreamacro/clash/component/process"
|
|
||||||
SNIFF "github.com/Dreamacro/clash/component/sniffer"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
"github.com/Dreamacro/clash/adapter/outboundgroup"
|
||||||
|
@ -27,6 +24,8 @@ import (
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
|
P "github.com/Dreamacro/clash/component/process"
|
||||||
|
SNIFF "github.com/Dreamacro/clash/component/sniffer"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
providerTypes "github.com/Dreamacro/clash/constant/provider"
|
||||||
|
@ -37,6 +36,7 @@ import (
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
R "github.com/Dreamacro/clash/rules"
|
R "github.com/Dreamacro/clash/rules"
|
||||||
RP "github.com/Dreamacro/clash/rules/provider"
|
RP "github.com/Dreamacro/clash/rules/provider"
|
||||||
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
T "github.com/Dreamacro/clash/tunnel"
|
T "github.com/Dreamacro/clash/tunnel"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
@ -46,18 +46,19 @@ import (
|
||||||
type General struct {
|
type General struct {
|
||||||
Inbound
|
Inbound
|
||||||
Controller
|
Controller
|
||||||
Mode T.TunnelMode `json:"mode"`
|
Mode T.TunnelMode `json:"mode"`
|
||||||
UnifiedDelay bool
|
UnifiedDelay bool
|
||||||
LogLevel log.LogLevel `json:"log-level"`
|
LogLevel log.LogLevel `json:"log-level"`
|
||||||
IPv6 bool `json:"ipv6"`
|
IPv6 bool `json:"ipv6"`
|
||||||
Interface string `json:"interface-name"`
|
Interface string `json:"interface-name"`
|
||||||
RoutingMark int `json:"-"`
|
RoutingMark int `json:"-"`
|
||||||
GeodataMode bool `json:"geodata-mode"`
|
GeodataMode bool `json:"geodata-mode"`
|
||||||
GeodataLoader string `json:"geodata-loader"`
|
GeodataLoader string `json:"geodata-loader"`
|
||||||
TCPConcurrent bool `json:"tcp-concurrent"`
|
TCPConcurrent bool `json:"tcp-concurrent"`
|
||||||
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
||||||
Sniffing bool `json:"sniffing"`
|
Sniffing bool `json:"sniffing"`
|
||||||
EBpf EBpf `json:"-"`
|
EBpf EBpf `json:"-"`
|
||||||
|
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound config
|
// Inbound config
|
||||||
|
@ -234,32 +235,33 @@ type RawTuicServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
SocksPort int `yaml:"socks-port"`
|
SocksPort int `yaml:"socks-port"`
|
||||||
RedirPort int `yaml:"redir-port"`
|
RedirPort int `yaml:"redir-port"`
|
||||||
TProxyPort int `yaml:"tproxy-port"`
|
TProxyPort int `yaml:"tproxy-port"`
|
||||||
MixedPort int `yaml:"mixed-port"`
|
MixedPort int `yaml:"mixed-port"`
|
||||||
ShadowSocksConfig string `yaml:"ss-config"`
|
ShadowSocksConfig string `yaml:"ss-config"`
|
||||||
VmessConfig string `yaml:"vmess-config"`
|
VmessConfig string `yaml:"vmess-config"`
|
||||||
InboundTfo bool `yaml:"inbound-tfo"`
|
InboundTfo bool `yaml:"inbound-tfo"`
|
||||||
Authentication []string `yaml:"authentication"`
|
Authentication []string `yaml:"authentication"`
|
||||||
AllowLan bool `yaml:"allow-lan"`
|
AllowLan bool `yaml:"allow-lan"`
|
||||||
BindAddress string `yaml:"bind-address"`
|
BindAddress string `yaml:"bind-address"`
|
||||||
Mode T.TunnelMode `yaml:"mode"`
|
Mode T.TunnelMode `yaml:"mode"`
|
||||||
UnifiedDelay bool `yaml:"unified-delay"`
|
UnifiedDelay bool `yaml:"unified-delay"`
|
||||||
LogLevel log.LogLevel `yaml:"log-level"`
|
LogLevel log.LogLevel `yaml:"log-level"`
|
||||||
IPv6 bool `yaml:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
ExternalController string `yaml:"external-controller"`
|
ExternalController string `yaml:"external-controller"`
|
||||||
ExternalControllerTLS string `yaml:"external-controller-tls"`
|
ExternalControllerTLS string `yaml:"external-controller-tls"`
|
||||||
ExternalUI string `yaml:"external-ui"`
|
ExternalUI string `yaml:"external-ui"`
|
||||||
Secret string `yaml:"secret"`
|
Secret string `yaml:"secret"`
|
||||||
Interface string `yaml:"interface-name"`
|
Interface string `yaml:"interface-name"`
|
||||||
RoutingMark int `yaml:"routing-mark"`
|
RoutingMark int `yaml:"routing-mark"`
|
||||||
Tunnels []LC.Tunnel `yaml:"tunnels"`
|
Tunnels []LC.Tunnel `yaml:"tunnels"`
|
||||||
GeodataMode bool `yaml:"geodata-mode"`
|
GeodataMode bool `yaml:"geodata-mode"`
|
||||||
GeodataLoader string `yaml:"geodata-loader"`
|
GeodataLoader string `yaml:"geodata-loader"`
|
||||||
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
||||||
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
||||||
|
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
||||||
|
|
||||||
Sniffer RawSniffer `yaml:"sniffer"`
|
Sniffer RawSniffer `yaml:"sniffer"`
|
||||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||||
|
@ -519,6 +521,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
||||||
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
|
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
|
||||||
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
|
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
|
||||||
|
|
||||||
|
if len(config.General.GlobalClientFingerprint) != 0 {
|
||||||
|
log.Debugln("GlobalClientFingerprint:%s", config.General.GlobalClientFingerprint)
|
||||||
|
vmess.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -552,17 +559,18 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||||
Secret: cfg.Secret,
|
Secret: cfg.Secret,
|
||||||
ExternalControllerTLS: cfg.ExternalControllerTLS,
|
ExternalControllerTLS: cfg.ExternalControllerTLS,
|
||||||
},
|
},
|
||||||
UnifiedDelay: cfg.UnifiedDelay,
|
UnifiedDelay: cfg.UnifiedDelay,
|
||||||
Mode: cfg.Mode,
|
Mode: cfg.Mode,
|
||||||
LogLevel: cfg.LogLevel,
|
LogLevel: cfg.LogLevel,
|
||||||
IPv6: cfg.IPv6,
|
IPv6: cfg.IPv6,
|
||||||
Interface: cfg.Interface,
|
Interface: cfg.Interface,
|
||||||
RoutingMark: cfg.RoutingMark,
|
RoutingMark: cfg.RoutingMark,
|
||||||
GeodataMode: cfg.GeodataMode,
|
GeodataMode: cfg.GeodataMode,
|
||||||
GeodataLoader: cfg.GeodataLoader,
|
GeodataLoader: cfg.GeodataLoader,
|
||||||
TCPConcurrent: cfg.TCPConcurrent,
|
TCPConcurrent: cfg.TCPConcurrent,
|
||||||
FindProcessMode: cfg.FindProcessMode,
|
FindProcessMode: cfg.FindProcessMode,
|
||||||
EBpf: cfg.EBpf,
|
EBpf: cfg.EBpf,
|
||||||
|
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,11 @@ bind-address: "*" # 绑定IP地址,仅作用于 allow-lan 为 true,'*'表示
|
||||||
# - off, 不匹配进程,推荐在路由器上使用此模式
|
# - off, 不匹配进程,推荐在路由器上使用此模式
|
||||||
find-process-mode: strict
|
find-process-mode: strict
|
||||||
|
|
||||||
|
# global-client-fingerprint:全局TLS指纹,优先低于proxy内的 client-fingerprint
|
||||||
|
# accepts "chrome","firefox","safari","ios","random","none" options.
|
||||||
|
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
||||||
|
global-client-fingerprint: chrome
|
||||||
|
|
||||||
mode: rule
|
mode: rule
|
||||||
|
|
||||||
#自定义 geox-url
|
#自定义 geox-url
|
||||||
|
@ -423,7 +428,7 @@ proxies:
|
||||||
server: server
|
server: server
|
||||||
port: 443
|
port: 443
|
||||||
password: yourpsk
|
password: yourpsk
|
||||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random"
|
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# udp: true
|
# udp: true
|
||||||
# sni: example.com # aka server name
|
# sni: example.com # aka server name
|
||||||
|
@ -483,7 +488,7 @@ proxies:
|
||||||
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
|
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random"
|
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||||
|
|
||||||
- name: "vless-ws"
|
- name: "vless-ws"
|
||||||
type: vless
|
type: vless
|
||||||
|
@ -493,7 +498,7 @@ proxies:
|
||||||
udp: true
|
udp: true
|
||||||
tls: true
|
tls: true
|
||||||
network: ws
|
network: ws
|
||||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random"
|
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||||
servername: example.com # priority over wss host
|
servername: example.com # priority over wss host
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
|
|
|
@ -15,20 +15,42 @@ type UConn struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var initRandomFingerprint *utls.ClientHelloID
|
var initRandomFingerprint *utls.ClientHelloID
|
||||||
|
var initUtlsClient string
|
||||||
|
|
||||||
func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
|
func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) net.Conn {
|
||||||
utlsConn := utls.UClient(c, CopyConfig(config), *fingerprint)
|
utlsConn := utls.UClient(c, CopyConfig(config), *fingerprint)
|
||||||
return &UConn{UConn: utlsConn}
|
return &UConn{UConn: utlsConn}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetGlobalUtlsClient(Client string) {
|
||||||
|
initUtlsClient = Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func HaveGlobalFingerprint() bool {
|
||||||
|
if len(initUtlsClient) != 0 && initUtlsClient != "none" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGlobalFingerprint() string {
|
||||||
|
return initUtlsClient
|
||||||
|
}
|
||||||
|
|
||||||
func GetFingerprint(ClientFingerprint string) (*utls.ClientHelloID, bool) {
|
func GetFingerprint(ClientFingerprint string) (*utls.ClientHelloID, bool) {
|
||||||
|
if ClientFingerprint == "none" {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
if initRandomFingerprint == nil {
|
if initRandomFingerprint == nil {
|
||||||
initRandomFingerprint, _ = RollFingerprint()
|
initRandomFingerprint, _ = RollFingerprint()
|
||||||
}
|
}
|
||||||
|
|
||||||
if ClientFingerprint == "random" {
|
if ClientFingerprint == "random" {
|
||||||
log.Debugln("use initial random HelloID:%s", initRandomFingerprint.Client)
|
log.Debugln("use initial random HelloID:%s", initRandomFingerprint.Client)
|
||||||
return initRandomFingerprint, true
|
return initRandomFingerprint, true
|
||||||
}
|
}
|
||||||
|
|
||||||
fingerprint, ok := Fingerprints[ClientFingerprint]
|
fingerprint, ok := Fingerprints[ClientFingerprint]
|
||||||
log.Debugln("use specified fingerprint:%s", fingerprint.Client)
|
log.Debugln("use specified fingerprint:%s", fingerprint.Client)
|
||||||
return fingerprint, ok
|
return fingerprint, ok
|
||||||
|
|
Loading…
Reference in a new issue