Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Restls-V1 in Clash.Meta #441

Merged
merged 9 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 48 additions & 16 deletions adapter/outbound/shadowsocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import (
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/restls"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls"
"github.com/Dreamacro/clash/transport/socks5"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"

restlsC "github.com/3andne/restls-client-go"
shadowsocks "github.com/metacubex/sing-shadowsocks"
"github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/sagernet/sing/common/bufio"
Expand All @@ -34,19 +36,21 @@ type ShadowSocks struct {
obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option
shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restlsC.Config
}

type ShadowSocksOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}

type simpleObfsOption struct {
Expand All @@ -66,12 +70,18 @@ type v2rayObfsOption struct {
}

type shadowTLSOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"`
ClientFingerprint string `obfs:"client-fingerprint,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Version int `obfs:"version,omitempty"`
Password string `obfs:"password"`
Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Version int `obfs:"version,omitempty"`
}

type restlsOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
VersionHint string `obfs:"version-hint"`
RestlsScript string `obfs:"restls-script,omitempty"`
}

// StreamConn implements C.ProxyAdapter
Expand All @@ -86,6 +96,7 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
if err != nil {
return nil, err
}

}
return ss.streamConn(c, metadata)
}
Expand All @@ -103,6 +114,12 @@ func (ss *ShadowSocks) streamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
case restls.Mode:
var err error
c, err = restls.NewRestls(c, ss.restlsConfig)
if err != nil {
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
}
}
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
if N.NeedHandshake(c) {
Expand Down Expand Up @@ -202,6 +219,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restlsC.Config
obfsMode := ""

decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
Expand Down Expand Up @@ -250,10 +268,23 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
Password: opt.Password,
Host: opt.Host,
Fingerprint: opt.Fingerprint,
ClientFingerprint: opt.ClientFingerprint,
ClientFingerprint: option.ClientFingerprint,
SkipCertVerify: opt.SkipCertVerify,
Version: opt.Version,
}
} else if option.Plugin == restls.Mode {
obfsMode = restls.Mode
restlsOpt := &restlsOption{}
if err := decoder.Decode(option.PluginOpts, restlsOpt); err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}

restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
restlsConfig.SessionTicketsDisabled = true
if err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}

}

return &ShadowSocks{
Expand All @@ -274,6 +305,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption: v2rayOption,
obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig,
}, nil
}

Expand Down
5 changes: 5 additions & 0 deletions adapter/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
switch proxyType {
case "ss":
ssOption := &outbound.ShadowSocksOption{}

if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
ssOption.ClientFingerprint = GlobalUtlsClient
}

err = decoder.Decode(mapping, ssOption)
if err != nil {
break
Expand Down
40 changes: 39 additions & 1 deletion docs/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -331,17 +331,55 @@ proxies: # socks5
# headers:
# custom: value

- name: "ss4"
- name: "ss4-shadow-tls"
type: ss
server: server
port: 443
cipher: chacha20-ietf-poly1305
password: "password"
plugin: shadow-tls
client-fingerprint: chrome
plugin-opts:
host: "cloud.tencent.com"
password: "shadow_tls_password"
version: 2 # support 1/2/3

- name: "ss-restls-tls13"
type: ss
server: [YOUR_SERVER_IP]
port: 443
cipher: chacha20-ietf-poly1305
password: [YOUR_SS_PASSWORD]
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
# 可以是chrome, ios, firefox, safari中的一个
plugin: restls
plugin-opts:
host: "www.microsoft.com" # Must be a TLS 1.3 server
# 应当是一个TLS 1.3 服务器
password: [YOUR_RESTLS_PASSWORD]
version-hint: "tls13"
# Control your post-handshake traffic through restls-script
# Hide proxy behaviors like "tls in tls".
# see https://github.com/3andne/restls/blob/main/Restls-Script:%20Hide%20Your%20Proxy%20Traffic%20Behavior.md
# 用restls剧本来控制握手后的行为,隐藏"tls in tls"等特征
# 详情:https://github.com/3andne/restls/blob/main/Restls-Script:%20%E9%9A%90%E8%97%8F%E4%BD%A0%E7%9A%84%E4%BB%A3%E7%90%86%E8%A1%8C%E4%B8%BA.md
restls-script: "300?100<1,400~100,350~100,600~100,300~200,300~100"

- name: "ss-restls-tls12"
type: ss
server: [YOUR_SERVER_IP]
port: 443
cipher: chacha20-ietf-poly1305
password: [YOUR_SS_PASSWORD]
client-fingerprint: chrome # One of: chrome, ios, firefox or safari
# 可以是chrome, ios, firefox, safari中的一个
plugin: restls
plugin-opts:
host: "vscode.dev" # Must be a TLS 1.2 server
# 应当是一个TLS 1.2 服务器
password: [YOUR_RESTLS_PASSWORD]
version-hint: "tls12"
restls-script: "1000?100<1,500~100,350~100,600~100,400~200"

# vmess
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ require (
)

require (
github.com/3andne/restls-client-go v0.1.4
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/3andne/restls-client-go v0.1.4 h1:kLNC2aSRHPlEVYmTj6EOqJoorCpobEe2toMRSfBF7FU=
github.com/3andne/restls-client-go v0.1.4/go.mod h1:04CGbRk1BwBiEDles8b5mlKgTqIwE5MqF7JDloJV47I=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
Expand Down
57 changes: 57 additions & 0 deletions transport/restls/restls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package restls

import (
"net"

tls "github.com/3andne/restls-client-go"
)

const (
Mode string = "restls"
)

// Restls
type Restls struct {
net.Conn
firstPacketCache []byte
firstPacket bool
}

func (r *Restls) Read(b []byte) (int, error) {
if err := r.Conn.(*tls.UConn).Handshake(); err != nil {
return 0, err
}
n, err := r.Conn.(*tls.UConn).Read(b)
return n, err
}

func (r *Restls) Write(b []byte) (int, error) {
if r.firstPacket {
r.firstPacketCache = append([]byte(nil), b...)
r.firstPacket = false
return len(b), nil
}
if len(r.firstPacketCache) != 0 {
b = append(r.firstPacketCache, b...)
r.firstPacketCache = nil
}
n, err := r.Conn.(*tls.UConn).Write(b)
return n, err
}

// NewRestls return a Restls Connection
func NewRestls(conn net.Conn, config *tls.Config) (net.Conn, error) {
if config != nil {
clientIDPtr := config.ClientID.Load()
if clientIDPtr != nil {
return &Restls{
Conn: tls.UClient(conn, config, *clientIDPtr),
firstPacket: true,
}, nil
}
}
return &Restls{
Conn: tls.UClient(conn, config, tls.HelloChrome_Auto),
firstPacket: true,
}, nil
}