diff --git a/config/config.go b/config/config.go index 56b2aa7bc..294f5cf87 100644 --- a/config/config.go +++ b/config/config.go @@ -174,6 +174,7 @@ type Profile struct { type TLS struct { Certificate string PrivateKey string + EchKey string CustomTrustCert []string } @@ -360,6 +361,7 @@ type RawSniffingConfig struct { type RawTLS struct { Certificate string `yaml:"certificate" json:"certificate"` PrivateKey string `yaml:"private-key" json:"private-key"` + EchKey string `yaml:"ech-key" json:"ech-key"` CustomTrustCert []string `yaml:"custom-certifactes" json:"custom-certifactes"` } @@ -814,6 +816,7 @@ func parseTLS(cfg *RawConfig) (*TLS, error) { return &TLS{ Certificate: cfg.TLS.Certificate, PrivateKey: cfg.TLS.PrivateKey, + EchKey: cfg.TLS.EchKey, CustomTrustCert: cfg.TLS.CustomTrustCert, }, nil } diff --git a/docs/config.yaml b/docs/config.yaml index b74bc7c19..774a6e712 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -48,6 +48,13 @@ ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS tls: certificate: string # 证书 PEM 格式,或者 证书的路径 private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径 + # 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成) + # ech-key: | + # -----BEGIN ECH KEYS----- + # ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK + # madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz + # dC5jb20AAA== + # -----END ECH KEYS----- custom-certifactes: - | -----BEGIN CERTIFICATE----- diff --git a/hub/hub.go b/hub/hub.go index 69f627ffa..fc4fe81a5 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -57,6 +57,7 @@ func applyRoute(cfg *config.Config) { Secret: cfg.Controller.Secret, Certificate: cfg.TLS.Certificate, PrivateKey: cfg.TLS.PrivateKey, + EchKey: cfg.TLS.EchKey, DohServer: cfg.Controller.ExternalDohServer, IsDebug: cfg.General.LogLevel == log.DEBUG, Cors: route.Cors{ diff --git a/hub/route/server.go b/hub/route/server.go index 2ccd8596d..c41f51a5e 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -3,7 +3,6 @@ package route import ( "bytes" "crypto/subtle" - "crypto/tls" "encoding/json" "net" "net/http" @@ -17,6 +16,8 @@ import ( "github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/ech" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/tunnel/statistic" @@ -27,6 +28,8 @@ import ( "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" "github.com/sagernet/cors" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" ) var ( @@ -62,6 +65,7 @@ type Config struct { Secret string Certificate string PrivateKey string + EchKey string DohServer string IsDebug bool Cors Cors @@ -186,7 +190,7 @@ func startTLS(cfg *Config) { // handle tlsAddr if len(cfg.TLSAddr) > 0 { - c, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path) if err != nil { log.Errorln("External controller tls listen error: %s", err) return @@ -199,14 +203,23 @@ func startTLS(cfg *Config) { } log.Infoln("RESTful API tls listening at: %s", l.Addr().String()) + tlsConfig := &tlsC.Config{} + tlsConfig.NextProtos = []string{"h2", "http/1.1"} + tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)} + + if cfg.EchKey != "" { + err = ech.LoadECHKey(cfg.EchKey, tlsConfig, C.Path) + if err != nil { + log.Errorln("External controller tls serve error: %s", err) + return + } + } server := &http.Server{ - Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors), - TLSConfig: &tls.Config{ - Certificates: []tls.Certificate{c}, - }, + // using h2c.NewHandler to ensure we can work in plain http2 and some tls conn is not *tls.Conn + Handler: h2c.NewHandler(router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors), &http2.Server{}), } tlsServer = server - if err = server.ServeTLS(l, "", ""); err != nil { + if err = server.Serve(tlsC.NewListener(l, tlsConfig)); err != nil { log.Errorln("External controller tls serve error: %s", err) } }