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:
gVisor bot 2023-02-07 01:26:08 +08:00
parent 601a40d068
commit 5cab8afec7
7 changed files with 123 additions and 55 deletions

View file

@ -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 {

View file

@ -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":

View file

@ -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":

View file

@ -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

View file

@ -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"
@ -58,6 +58,7 @@ type General struct {
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
@ -260,6 +261,7 @@ type RawConfig struct {
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
} }
@ -563,6 +570,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
TCPConcurrent: cfg.TCPConcurrent, TCPConcurrent: cfg.TCPConcurrent,
FindProcessMode: cfg.FindProcessMode, FindProcessMode: cfg.FindProcessMode,
EBpf: cfg.EBpf, EBpf: cfg.EBpf,
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
}, nil }, nil
} }

View file

@ -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

View file

@ -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