feat: support restls for anytls outbound and listener

This commit is contained in:
wwqgtxx committed 2026-07-15 16:52:21 +08:00
1 parent 769ea6f5b4
commit 0e7c3c79e3
6 files changed
+82 -5

No files matched your search

+16 -2
View File
@@ -33,6 +33,7 @@ type AnyTLSOption struct {
SNI string `proxy:"sni,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
ShadowTLSOpts ShadowTLSOptions `proxy:"shadow-tls-opts,omitempty"`
RestlsOpts RestlsOptions `proxy:"restls-opts,omitempty"`
JLSOpts JLSOptions `proxy:"jls-opts,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
@@ -123,12 +124,24 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
if err != nil {
return nil, err
}
restlsConfig, err := option.RestlsOpts.Parse(option.SNI, option.ClientFingerprint)
if err != nil {
return nil, err
}
jlsConfig, err := option.JLSOpts.Parse()
if err != nil {
return nil, err
}
if shadowTLSConfig != nil && jlsConfig != nil {
return nil, errors.New("ShadowTLS is incompatible with JLS")
if shadowTLSConfig != nil {
if restlsConfig != nil {
return nil, errors.New("ShadowTLS is incompatible with Restls")
}
if jlsConfig != nil {
return nil, errors.New("ShadowTLS is incompatible with JLS")
}
}
if restlsConfig != nil && jlsConfig != nil {
return nil, errors.New("Restls is incompatible with JLS")
}
tlsConfig := &vmess.TLSConfig{
Host: option.SNI,
@@ -141,6 +154,7 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
ClientFingerprint: option.ClientFingerprint,
ECH: echConfig,
ShadowTLS: shadowTLSConfig,
Restls: restlsConfig,
JLS: jlsConfig,
}
if tlsConfig.Host == "" {
+13 -2
View File
@@ -1579,6 +1579,10 @@ proxies: # socks5
# shadow-tls-opts: # 使用 sni 作为 ShadowTLS SNI
# version: 3 # 支持 v1/v2/v3;留空时默认为 v2
# password: shadow-tls-password
# restls-opts: # 使用 sni 作为 Restls SNI
# password: restls-password
# version-hint: tls13 # 可选值:tls12、tls13
# # restls-script: ""
# jls-opts: # 使用 sni 作为 JLS SNI
# username: jls-user
# password: jls-password
@@ -2368,7 +2372,7 @@ listeners:
users:
username1: password1
username2: password2
# "shadow-tls" 和 "jls-config" 均未启用且 "allow-insecure" 不为 true 时,必须填写 "certificate" 和 "private-key";启用 ShadowTLS 或 JLS 时不要填写
# "shadow-tls"、"res-tls" 和 "jls-config" 均未启用且 "allow-insecure" 不为 true 时,必须填写 "certificate" 和 "private-key";启用 ShadowTLS、ResTLS 或 JLS 时不要填写
certificate: ./server.crt # 证书 PEM 格式,或者 证书的路径
private-key: ./server.key
# 下面两项为mTLS配置项,如果client-auth-type设置为 "verify-if-given" 或 "require-and-verify" 则client-auth-cert必须不为空
@@ -2391,6 +2395,13 @@ listeners:
# handshake:
# dest: www.example.com:443
# # proxy: ""
# res-tls:
# enable: true
# dest: www.example.com:443
# password: restls-password
# # restls-script: ""
# # min-record-len: 0
# # proxy: ""
# jls-config: # JLS 替代普通 TLS;未认证连接回落到 dest
# enable: true
# users:
@@ -2401,7 +2412,7 @@ listeners:
# # alpn: [h2, http/1.1]
# # proxy: ""
# # rate-limit: 0 # fallback 转发限速,单位 bit/s;0 表示不限速
### 注意,anytls listener, 如果 "allow-insecure" 不为 true, 至少需要填写 “certificate和private-key” 或 “shadow-tls” 或 “jls-config” 的其中一项 ###
### 注意,anytls listener, 如果 "allow-insecure" 不为 true, 至少需要填写 “certificate和private-key” 或 “shadow-tls” 或 “res-tls” 或 “jls-config” 的其中一项 ###
# allow-insecure: false # 是否允许不开启tls加密(注意:仅用于有 nginx, caddy 前置的情况)
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
+20 -1
View File
@@ -16,6 +16,7 @@ import (
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/jls"
"github.com/metacubex/mihomo/listener/restls"
"github.com/metacubex/mihomo/listener/shadowtls"
"github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/ntp"
@@ -46,6 +47,7 @@ func New(config LC.AnyTLSServer, lc C.InboundListenConfig, tunnel C.Tunnel, addi
}
var shadowTLSBuilder *shadowtls.Builder
var restlsBuilder *restls.Builder
var jlsBuilder *jls.Builder
tlsConfig := &tls.Config{Time: ntp.Now}
if config.Certificate != "" && config.PrivateKey != "" {
@@ -89,6 +91,18 @@ func New(config LC.AnyTLSServer, lc C.InboundListenConfig, tunnel C.Tunnel, addi
return nil, err
}
}
if config.ResTLS.Enable {
if tlsConfig.GetCertificate != nil {
return nil, errors.New("certificate is unavailable in Restls")
}
if tlsConfig.ClientAuth != tls.NoClientCert {
return nil, errors.New("client-auth is unavailable in Restls")
}
if shadowTLSBuilder != nil {
return nil, errors.New("ShadowTLS is unavailable in Restls")
}
restlsBuilder = restls.New(config.ResTLS, tunnel)
}
if config.JLSConfig.Enable {
if tlsConfig.GetCertificate != nil {
return nil, errors.New("certificate is unavailable in JLS")
@@ -99,6 +113,9 @@ func New(config LC.AnyTLSServer, lc C.InboundListenConfig, tunnel C.Tunnel, addi
if shadowTLSBuilder != nil {
return nil, errors.New("ShadowTLS is unavailable in JLS")
}
if restlsBuilder != nil {
return nil, errors.New("Restls is unavailable in JLS")
}
jlsBuilder, err = jls.New(config.JLSConfig, tunnel)
if err != nil {
return nil, err
@@ -143,12 +160,14 @@ func New(config LC.AnyTLSServer, lc C.InboundListenConfig, tunnel C.Tunnel, addi
}
if shadowTLSBuilder != nil {
l = shadowTLSBuilder.NewListener(l)
} else if restlsBuilder != nil {
l = restlsBuilder.NewListener(l)
} else if jlsBuilder != nil {
l = jlsBuilder.NewListener(l)
} else if tlsConfig.GetCertificate != nil {
l = tls.NewListener(l, tlsConfig)
} else if !config.AllowInsecure {
return nil, errors.New("disallow using AnyTLS without certificates/shadow-tls/jls/allow-insecure config")
return nil, errors.New("disallow using AnyTLS without certificates/shadow-tls/res-tls/jls/allow-insecure config")
}
sl.listeners = append(sl.listeners, l)
+1
View File
@@ -14,6 +14,7 @@ type AnyTLSServer struct {
ClientAuthCert string `yaml:"client-auth-cert" json:"client-auth-cert,omitempty"`
EchKey string `yaml:"ech-key" json:"ech-key,omitempty"`
ShadowTLS ShadowTLS `yaml:"shadow-tls" json:"shadow-tls,omitempty"`
ResTLS ResTLS `yaml:"res-tls" json:"res-tls,omitempty"`
JLSConfig JLSConfig `yaml:"jls-config" json:"jls-config,omitempty"`
AllowInsecure bool `yaml:"allow-insecure" json:"allow-insecure,omitempty"`
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
+2
View File
@@ -18,6 +18,7 @@ type AnyTLSOption struct {
ClientAuthCert string `inbound:"client-auth-cert,omitempty"`
EchKey string `inbound:"ech-key,omitempty"`
ShadowTLS ShadowTLS `inbound:"shadow-tls,omitempty"`
ResTLS ResTLS `inbound:"res-tls,omitempty"`
JLSConfig JLSConfig `inbound:"jls-config,omitempty"`
AllowInsecure bool `inbound:"allow-insecure,omitempty"`
PaddingScheme string `inbound:"padding-scheme,omitempty"`
@@ -52,6 +53,7 @@ func NewAnyTLS(options *AnyTLSOption) (*AnyTLS, error) {
ClientAuthCert: options.ClientAuthCert,
EchKey: options.EchKey,
ShadowTLS: options.ShadowTLS.Build(),
ResTLS: options.ResTLS.Build(),
JLSConfig: options.JLSConfig.Build(),
AllowInsecure: options.AllowInsecure,
PaddingScheme: options.PaddingScheme,
+30
View File
@@ -127,6 +127,36 @@ func TestInboundAnyTLS_ShadowTLS(t *testing.T) {
testInboundAnyTLSShadowTLS(t, inboundOptions, outboundOptions)
}
func testInboundAnyTLSRestls(t *testing.T, inboundOptions inbound.AnyTLSOption, outboundOptions outbound.AnyTLSOption) {
t.Parallel()
t.Run("Conn", func(t *testing.T) {
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
testInboundAnyTLS(t, inboundOptions, outboundOptions)
})
t.Run("UConn", func(t *testing.T) {
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
outboundOptions.ClientFingerprint = "chrome"
testInboundAnyTLS(t, inboundOptions, outboundOptions)
})
}
func TestInboundAnyTLS_Restls(t *testing.T) {
const password = "restls-password"
inboundOptions := inbound.AnyTLSOption{
ResTLS: inbound.ResTLS{
Enable: true,
Dest: net.JoinHostPort(realityDest, "443"),
Password: password,
},
}
outboundOptions := outbound.AnyTLSOption{
SNI: realityDest,
Fingerprint: tlsFingerprint,
RestlsOpts: outbound.RestlsOptions{Password: password, VersionHint: "tls13"},
}
testInboundAnyTLSRestls(t, inboundOptions, outboundOptions)
}
func testInboundAnyTLSJLS(t *testing.T, inboundOptions inbound.AnyTLSOption, outboundOptions outbound.AnyTLSOption) {
t.Parallel()
t.Run("Conn", func(t *testing.T) {