mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-10-11 17:58:13 +08:00
feat: support realm-opts for hysteria2 outbound and listener
This commit is contained in:
1 parent
8b8eae56d7
commit
98aa7e63c2
7 files changed
+190
-3
No files matched your search
@@ -5,19 +5,23 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/transport/tuic/common"
|
||||
|
||||
"github.com/metacubex/http"
|
||||
"github.com/metacubex/quic-go"
|
||||
qtls "github.com/metacubex/sing-quic"
|
||||
"github.com/metacubex/sing-quic/hysteria2"
|
||||
"github.com/metacubex/sing-quic/hysteria2/realm"
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
@@ -55,6 +59,8 @@ type Hysteria2Option struct {
|
||||
BBRProfile string `proxy:"bbr-profile,omitempty"`
|
||||
UdpMTU int `proxy:"udp-mtu,omitempty"`
|
||||
|
||||
RealmOpts Hysteria2RealmOption `proxy:"realm-opts,omitempty"`
|
||||
|
||||
// quic-go special config
|
||||
InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"`
|
||||
MaxStreamReceiveWindow uint64 `proxy:"max-stream-receive-window,omitempty"`
|
||||
@@ -62,6 +68,22 @@ type Hysteria2Option struct {
|
||||
MaxConnectionReceiveWindow uint64 `proxy:"max-connection-receive-window,omitempty"`
|
||||
}
|
||||
|
||||
type Hysteria2RealmOption struct {
|
||||
Enable bool `proxy:"enable,omitempty"`
|
||||
ServerURL string `proxy:"server-url,omitempty"`
|
||||
Token string `proxy:"token,omitempty"`
|
||||
RealmID string `proxy:"realm-id,omitempty"`
|
||||
STUNServers []string `proxy:"stun-servers,omitempty"`
|
||||
|
||||
// for ServerURL
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
Certificate string `proxy:"certificate,omitempty"`
|
||||
PrivateKey string `proxy:"private-key,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||
c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
if err != nil {
|
||||
@@ -229,6 +251,48 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
return nil, errors.New("invalid port")
|
||||
}
|
||||
|
||||
if option.RealmOpts.Enable {
|
||||
httpTLSClientConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: option.RealmOpts.SNI,
|
||||
InsecureSkipVerify: option.RealmOpts.SkipCertVerify,
|
||||
},
|
||||
Fingerprint: option.RealmOpts.Fingerprint,
|
||||
Certificate: option.RealmOpts.Certificate,
|
||||
PrivateKey: option.RealmOpts.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientOptions.RealmOptions = &realm.Options{
|
||||
ServerURL: option.RealmOpts.ServerURL,
|
||||
Token: option.RealmOpts.Token,
|
||||
RealmID: option.RealmOpts.RealmID,
|
||||
STUNServers: option.RealmOpts.STUNServers,
|
||||
HTTPClient: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: outbound.dialer.DialContext,
|
||||
TLSClientConfig: httpTLSClientConfig,
|
||||
// from http.DefaultTransport
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
},
|
||||
},
|
||||
Resolver: func(ctx context.Context, host string, ipv4, ipv6 bool) ([]netip.Addr, error) {
|
||||
if ipv4 && !ipv6 {
|
||||
return resolver.LookupIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
} else if ipv6 && !ipv4 {
|
||||
return resolver.LookupIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
}
|
||||
return resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
},
|
||||
Logger: log.SingLogger,
|
||||
}
|
||||
}
|
||||
|
||||
client, err := hysteria2.NewClient(clientOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1014,6 +1014,18 @@ proxies: # socks5
|
||||
# private-key: ./client.key # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# alpn:
|
||||
# - h3
|
||||
# realm-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# server-url: https://realm.hy2.io
|
||||
# token: public
|
||||
# realm-id: my-cabin-1f3a8c2e9b
|
||||
# stun-servers:
|
||||
# - stun.nextcloud.com:3478
|
||||
# - stun.sip.us:3478
|
||||
# - global.stun.twilio.com:3478
|
||||
# # 下面支持填写针对server-url的TLS配置(sni, skip-cert-verify, fingerprint, certificate, private-key, alpn)
|
||||
# # skip-cert-verify: false
|
||||
# # ......
|
||||
###quic-go特殊配置项,不要随意修改除非你知道你在干什么###
|
||||
# initial-stream-receive-window: 8388608
|
||||
# max-stream-receive-window: 8388608
|
||||
@@ -1903,6 +1915,18 @@ listeners:
|
||||
# masquerade: file:///var/www # 作为文件服务器
|
||||
# masquerade: http://127.0.0.1:8080 #作为反向代理
|
||||
# masquerade: https://127.0.0.1:8080 #作为反向代理
|
||||
# realm-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# server-url: https://realm.hy2.io
|
||||
# token: public
|
||||
# realm-id: my-cabin-1f3a8c2e9b
|
||||
# stun-servers:
|
||||
# - stun.nextcloud.com:3478
|
||||
# - stun.sip.us:3478
|
||||
# - global.stun.twilio.com:3478
|
||||
# # 下面支持填写针对server-url的TLS配置(sni, skip-cert-verify, fingerprint, certificate, private-key, alpn)
|
||||
# # skip-cert-verify: false
|
||||
# # ......
|
||||
|
||||
- name: trusttunnel-in-1
|
||||
type: trusttunnel
|
||||
|
||||
@@ -30,7 +30,7 @@ require (
|
||||
github.com/metacubex/restls-client-go v0.1.7
|
||||
github.com/metacubex/sing v0.5.7
|
||||
github.com/metacubex/sing-mux v0.3.9
|
||||
github.com/metacubex/sing-quic v0.0.0-20260414034501-3ea3410d197a
|
||||
github.com/metacubex/sing-quic v0.0.0-20260511111944-ed400da99ad4
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
|
||||
@@ -127,8 +127,8 @@ github.com/metacubex/sing v0.5.7 h1:8OC+fhKFSv/l9ehEhJRaZZAOuthfZo68SteBVLe8QqM=
|
||||
github.com/metacubex/sing v0.5.7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.9 h1:/aoBD2+sK2qsXDlNDe3hkR0GZuFDtwIZhOeGUx9W0Yk=
|
||||
github.com/metacubex/sing-mux v0.3.9/go.mod h1:8bT7ZKT3clRrJjYc/x5CRYibC1TX/bK73a3r3+2E+Fc=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260414034501-3ea3410d197a h1:977o0ZYYbiQAGuOxql7Q6UN3rEy59OyAE0tELq4gZfI=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260414034501-3ea3410d197a/go.mod h1:6ayFGfzzBE85csgQkM3gf4neFq6s0losHlPRSxY+nuk=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260511111944-ed400da99ad4 h1:WwMH5gADSmQ2RgudpoAym4nSk5U70QwfovgCqXBX34M=
|
||||
github.com/metacubex/sing-quic v0.0.0-20260511111944-ed400da99ad4/go.mod h1:6ayFGfzzBE85csgQkM3gf4neFq6s0losHlPRSxY+nuk=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
|
||||
|
||||
@@ -28,6 +28,8 @@ type Hysteria2Server struct {
|
||||
UdpMTU int `yaml:"udp-mtu" json:"udp-mtu,omitempty"`
|
||||
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||
|
||||
RealmOpts Hysteria2RealmOption `yaml:"realm-opts" json:"realm-opts,omitempty"`
|
||||
|
||||
// quic-go special config
|
||||
InitialStreamReceiveWindow uint64 `yaml:"initial-stream-receive-window" json:"initial-stream-receive-window,omitempty"`
|
||||
MaxStreamReceiveWindow uint64 `yaml:"max-stream-receive-window" json:"max-stream-receive-window,omitempty"`
|
||||
@@ -35,6 +37,22 @@ type Hysteria2Server struct {
|
||||
MaxConnectionReceiveWindow uint64 `yaml:"max-connection-receive-window" json:"max-connection-receive-window,omitempty"`
|
||||
}
|
||||
|
||||
type Hysteria2RealmOption struct {
|
||||
Enable bool `yaml:"enable" json:"enable,omitempty"`
|
||||
ServerURL string `yaml:"server-url" json:"server-url,omitempty"`
|
||||
Token string `yaml:"token" json:"token,omitempty"`
|
||||
RealmID string `yaml:"realm-id" json:"realm-id,omitempty"`
|
||||
STUNServers []string `yaml:"stun-servers" json:"stun-servers,omitempty"`
|
||||
|
||||
// for ServerURL
|
||||
SNI string `yaml:"sni" json:"sni,omitempty"`
|
||||
SkipCertVerify bool `yaml:"skip-cert-verify" json:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `yaml:"fingerprint" json:"fingerprint,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate,omitempty"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
}
|
||||
|
||||
func (h Hysteria2Server) String() string {
|
||||
b, _ := json.Marshal(h)
|
||||
return string(b)
|
||||
|
||||
@@ -30,6 +30,8 @@ type Hysteria2Option struct {
|
||||
UdpMTU int `inbound:"udp-mtu,omitempty"`
|
||||
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||
|
||||
RealmOpts Hysteria2RealmOption `inbound:"realm-opts,omitempty"`
|
||||
|
||||
// quic-go special config
|
||||
InitialStreamReceiveWindow uint64 `inbound:"initial-stream-receive-window,omitempty"`
|
||||
MaxStreamReceiveWindow uint64 `inbound:"max-stream-receive-window,omitempty"`
|
||||
@@ -37,6 +39,38 @@ type Hysteria2Option struct {
|
||||
MaxConnectionReceiveWindow uint64 `inbound:"max-connection-receive-window,omitempty"`
|
||||
}
|
||||
|
||||
type Hysteria2RealmOption struct {
|
||||
Enable bool `inbound:"enable,omitempty"`
|
||||
ServerURL string `inbound:"server-url,omitempty"`
|
||||
Token string `inbound:"token,omitempty"`
|
||||
RealmID string `inbound:"realm-id,omitempty"`
|
||||
STUNServers []string `inbound:"stun-servers,omitempty"`
|
||||
|
||||
// for ServerURL
|
||||
SNI string `inbound:"sni,omitempty"`
|
||||
SkipCertVerify bool `inbound:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `inbound:"fingerprint,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
PrivateKey string `inbound:"private-key,omitempty"`
|
||||
ALPN []string `inbound:"alpn,omitempty"`
|
||||
}
|
||||
|
||||
func (o Hysteria2RealmOption) Build() LC.Hysteria2RealmOption {
|
||||
return LC.Hysteria2RealmOption{
|
||||
Enable: o.Enable,
|
||||
ServerURL: o.ServerURL,
|
||||
Token: o.Token,
|
||||
RealmID: o.RealmID,
|
||||
STUNServers: o.STUNServers,
|
||||
SNI: o.SNI,
|
||||
SkipCertVerify: o.SkipCertVerify,
|
||||
Fingerprint: o.Fingerprint,
|
||||
Certificate: o.Certificate,
|
||||
PrivateKey: o.PrivateKey,
|
||||
ALPN: o.ALPN,
|
||||
}
|
||||
}
|
||||
|
||||
func (o Hysteria2Option) Equal(config C.InboundConfig) bool {
|
||||
return optionToString(o) == optionToString(config)
|
||||
}
|
||||
@@ -77,6 +111,7 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
|
||||
BBRProfile: options.BBRProfile,
|
||||
UdpMTU: options.UdpMTU,
|
||||
MuxOption: options.MuxOption.Build(),
|
||||
RealmOpts: options.RealmOpts.Build(),
|
||||
// quic-go special config
|
||||
InitialStreamReceiveWindow: options.InitialStreamReceiveWindow,
|
||||
MaxStreamReceiveWindow: options.MaxStreamReceiveWindow,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/metacubex/mihomo/common/sockopt"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/inner"
|
||||
@@ -26,6 +28,7 @@ import (
|
||||
"github.com/metacubex/http/httputil"
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/metacubex/sing-quic/hysteria2"
|
||||
"github.com/metacubex/sing-quic/hysteria2/realm"
|
||||
E "github.com/metacubex/sing/common/exceptions"
|
||||
"github.com/metacubex/tls"
|
||||
)
|
||||
@@ -146,6 +149,48 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
|
||||
return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme)
|
||||
}
|
||||
}
|
||||
var realmOptions *realm.Options
|
||||
if config.RealmOpts.Enable {
|
||||
httpTLSClientConfig, err := ca.GetTLSConfig(ca.Option{
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: config.RealmOpts.SNI,
|
||||
InsecureSkipVerify: config.RealmOpts.SkipCertVerify,
|
||||
},
|
||||
Fingerprint: config.RealmOpts.Fingerprint,
|
||||
Certificate: config.RealmOpts.Certificate,
|
||||
PrivateKey: config.RealmOpts.PrivateKey,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
realmOptions = &realm.Options{
|
||||
ServerURL: config.RealmOpts.ServerURL,
|
||||
Token: config.RealmOpts.Token,
|
||||
RealmID: config.RealmOpts.RealmID,
|
||||
STUNServers: config.RealmOpts.STUNServers,
|
||||
HTTPClient: &http.Client{Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return inner.HandleTcp(tunnel, address, "")
|
||||
},
|
||||
TLSClientConfig: httpTLSClientConfig,
|
||||
// from http.DefaultTransport
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}},
|
||||
Resolver: func(ctx context.Context, host string, ipv4, ipv6 bool) ([]netip.Addr, error) {
|
||||
if ipv4 && !ipv6 {
|
||||
return resolver.LookupIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
} else if ipv6 && !ipv4 {
|
||||
return resolver.LookupIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
}
|
||||
return resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
},
|
||||
Logger: log.SingLogger,
|
||||
}
|
||||
}
|
||||
|
||||
if config.UdpMTU == 0 {
|
||||
// "1200" from quic-go's MaxDatagramSize
|
||||
@@ -173,6 +218,7 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
|
||||
Handler: h,
|
||||
MasqueradeHandler: masqueradeHandler,
|
||||
UdpMTU: config.UdpMTU,
|
||||
RealmOptions: realmOptions,
|
||||
SetBBRCongestion: func(quicConn *quic.Conn) {
|
||||
common.SetCongestionController(quicConn, "bbr", config.CWND, config.BBRProfile)
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user