From 9bdf866471a5387f0b7809a9197bdc1d58c549aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 8 Feb 2025 10:07:42 +0000 Subject: [PATCH 01/11] ECH: client support TLS Encrypted Client Hello --- infra/conf/transport_internet.go | 11 +++ transport/internet/tls/config.go | 6 ++ transport/internet/tls/config.pb.go | 40 ++++++-- transport/internet/tls/config.proto | 6 ++ transport/internet/tls/ech.go | 137 ++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 transport/internet/tls/ech.go diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 78bd76fe..d04588e1 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -412,6 +412,8 @@ type TLSConfig struct { MasterKeyLog string `json:"masterKeyLog"` ServerNameToVerify string `json:"serverNameToVerify"` VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` + ECHConfig string `json:"echConfig"` + ECHDOHServer string `json:"echDohServer"` } // Build implements Buildable. @@ -483,6 +485,15 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.VerifyPeerCertInNames = c.VerifyPeerCertInNames + if c.ECHConfig != "" { + ECHConfig, err := base64.StdEncoding.DecodeString(c.ECHConfig) + if err != nil { + return nil, errors.New("invalid ECH Config", c.ECHConfig) + } + config.EchConfig = ECHConfig + } + config.Ech_DOHserver = c.ECHDOHServer + return config, nil } diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 171b30b6..0ab889ac 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -444,6 +444,12 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { config.KeyLogWriter = writer } } + if len(c.EchConfig) > 0 || len(c.Ech_DOHserver) > 0 { + err := ApplyECH(c, config) + if err != nil { + errors.LogError(context.Background(), err) + } + } return config } diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index bc45dc4e..5808e6e4 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -217,6 +217,8 @@ type Config struct { // @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried. // @Critical VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"` + EchConfig []byte `protobuf:"bytes,20,opt,name=ech_config,json=echConfig,proto3" json:"ech_config,omitempty"` + Ech_DOHserver string `protobuf:"bytes,21,opt,name=ech_DOHserver,json=echDOHserver,proto3" json:"ech_DOHserver,omitempty"` } func (x *Config) Reset() { @@ -361,6 +363,20 @@ func (x *Config) GetVerifyPeerCertInNames() []string { return nil } +func (x *Config) GetEchConfig() []byte { + if x != nil { + return x.EchConfig + } + return nil +} + +func (x *Config) GetEch_DOHserver() string { + if x != nil { + return x.Ech_DOHserver + } + return "" +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_tls_config_proto_rawDesc = []byte{ @@ -392,7 +408,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, - 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x9a, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, + 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xde, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65, @@ -442,15 +458,19 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e, - 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, - 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, - 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x63, 0x68, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x63, 0x68, 0x5f, 0x44, 0x4f, 0x48, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x63, 0x68, + 0x44, 0x4f, 0x48, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, + 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, + 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, + 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 3fac25af..01ea5eb5 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -91,4 +91,10 @@ message Config { @Critical */ repeated string verify_peer_cert_in_names = 17; + + + + bytes ech_config = 20; + + string ech_DOHserver = 21; } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go new file mode 100644 index 00000000..46518af4 --- /dev/null +++ b/transport/internet/tls/ech.go @@ -0,0 +1,137 @@ +package tls + +import ( + "bytes" + "context" + "crypto/tls" + "io" + "net/http" + "sync" + "time" + + "github.com/miekg/dns" + "github.com/xtls/xray-core/common/errors" + "github.com/xtls/xray-core/common/net" + "github.com/xtls/xray-core/transport/internet" +) + +func ApplyECH(c *Config, config *tls.Config) error { + var ECHConfig []byte + var err error + + if len(c.EchConfig) > 0 { + ECHConfig = c.EchConfig + } else { // ECH config > DOH lookup + if config.ServerName == "" { + return errors.New("Using DOH for ECH needs serverName") + } + ECHConfig, err = QueryRecord(c.ServerName, c.Ech_DOHserver) + if err != nil { + return err + } + } + + config.EncryptedClientHelloConfigList = ECHConfig + return nil +} + +type record struct { + record []byte + expire time.Time +} + +var ( + dnsCache = make(map[string]record) + mutex sync.RWMutex +) + +func QueryRecord(domain string, server string) ([]byte, error) { + mutex.Lock() + rec, found := dnsCache[domain] + if found && rec.expire.After(time.Now()) { + mutex.Unlock() + return rec.record, nil + } + mutex.Unlock() + + errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server) + record, ttl, err := dohQuery(server, domain) + if err != nil { + return []byte{}, err + } + + if ttl < 600 { + ttl = 600 + } + + mutex.Lock() + defer mutex.Unlock() + rec.record = record + rec.expire = time.Now().Add(time.Second * time.Duration(ttl)) + dnsCache[domain] = rec + return record, nil +} + +func dohQuery(server string, domain string) ([]byte, uint32, error) { + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS) + m.Id = 0 + msg, err := m.Pack() + if err != nil { + return []byte{}, 0, err + } + tr := &http.Transport{ + IdleConnTimeout: 90 * time.Second, + ForceAttemptHTTP2: true, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + dest, err := net.ParseDestination(network + ":" + addr) + if err != nil { + return nil, err + } + conn, err := internet.DialSystem(ctx, dest, nil) + if err != nil { + return nil, err + } + return conn, nil + }, + } + client := &http.Client{ + Timeout: 5 * time.Second, + Transport: tr, + } + req, err := http.NewRequest("POST", server, bytes.NewReader(msg)) + if err != nil { + return []byte{}, 0, err + } + req.Header.Set("Content-Type", "application/dns-message") + resp, err := client.Do(req) + if err != nil { + return []byte{}, 0, err + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return []byte{}, 0, err + } + if resp.StatusCode != http.StatusOK { + return []byte{}, 0, errors.New("query failed with response code:", resp.StatusCode) + } + respMsg := new(dns.Msg) + err = respMsg.Unpack(respBody) + if err != nil { + return []byte{}, 0, err + } + if len(respMsg.Answer) > 0 { + for _, answer := range respMsg.Answer { + if https, ok := answer.(*dns.HTTPS); ok && https.Hdr.Name == dns.Fqdn(domain) { + for _, v := range https.Value { + if echConfig, ok := v.(*dns.SVCBECHConfig); ok { + errors.LogDebug(context.Background(), "Get ECH config:", echConfig.String(), " TTL:", respMsg.Answer[0].Header().Ttl) + return echConfig.ECH, answer.Header().Ttl, nil + } + } + } + } + } + return []byte{}, 0, errors.New("no ech record found") +} From 52c46bc508d16b1b853d8f49ce572563fc89ace1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 8 Feb 2025 12:12:12 +0000 Subject: [PATCH 02/11] Update mutex usage --- transport/internet/tls/ech.go | 79 ++++++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 46518af4..086eb5c3 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -36,50 +36,79 @@ func ApplyECH(c *Config, config *tls.Config) error { } type record struct { - record []byte - expire time.Time + echConfig []byte + expire time.Time } var ( - dnsCache = make(map[string]record) - mutex sync.RWMutex + dnsCache = make(map[string]record) + // global Lock? I'm not sure if I need finer grained locks. + // If we do this, we will need to nest another layer of struct + dnsCacheLock sync.RWMutex + updating sync.Mutex ) + +// QueryRecord returns the ECH config for given domain. +// If the record is not in cache or expired, it will query the DOH server and update the cache. func QueryRecord(domain string, server string) ([]byte, error) { - mutex.Lock() - rec, found := dnsCache[domain] - if found && rec.expire.After(time.Now()) { - mutex.Unlock() - return rec.record, nil - } - mutex.Unlock() + dnsCacheLock.RLock() + rec, found := dnsCache[domain] + dnsCacheLock.RUnlock() + if found && rec.expire.After(time.Now()) { + errors.LogDebug(context.Background(), "Cache hit for domain: ", domain) + return rec.echConfig, nil + } - errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server) - record, ttl, err := dohQuery(server, domain) - if err != nil { - return []byte{}, err - } + updating.Lock() + defer updating.Unlock() + // Try to get cache again after lock, in case another goroutine has updated it + // This might happen when the core tring is just stared and multiple goroutines are trying to query the same domain + dnsCacheLock.RLock() + rec, found = dnsCache[domain] + dnsCacheLock.RUnlock() + if found && rec.expire.After(time.Now()) { + errors.LogDebug(context.Background(), "ECH Config cache hit for domain: ", domain, " after trying to get update lock") + return rec.echConfig, nil + } - if ttl < 600 { - ttl = 600 - } + // Query ECH config from DOH server + errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server) + echConfig, ttl, err := dohQuery(server, domain) + if err != nil { + return []byte{}, err + } - mutex.Lock() - defer mutex.Unlock() - rec.record = record - rec.expire = time.Now().Add(time.Second * time.Duration(ttl)) - dnsCache[domain] = rec - return record, nil + // Set minimum TTL to 600 seconds + if ttl < 600 { + ttl = 600 + } + + // Get write lock and update cache + dnsCacheLock.Lock() + defer dnsCacheLock.Unlock() + newRecored := record{ + echConfig: echConfig, + expire: time.Now().Add(time.Second * time.Duration(ttl)), + } + dnsCache[domain] = newRecored + return echConfig, nil } + +// dohQuery is the real func for sending type65 query for given domain to given DOH server. +// return ECH config, TTL and error func dohQuery(server string, domain string) ([]byte, uint32, error) { m := new(dns.Msg) m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS) + // always 0 in DOH m.Id = 0 msg, err := m.Pack() if err != nil { return []byte{}, 0, err } + // All traffic sent by core should via xray's internet.DialSystem + // This involves the behavior of some Android VPN GUI clients tr := &http.Transport{ IdleConnTimeout: 90 * time.Second, ForceAttemptHTTP2: true, From 73e98665ac6b9f4dba92c964dcf7cd21e45e187f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 8 Feb 2025 18:29:09 +0000 Subject: [PATCH 03/11] Add new doh server format --- transport/internet/tls/ech.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 086eb5c3..c56d0f2c 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -6,6 +6,7 @@ import ( "crypto/tls" "io" "net/http" + "strings" "sync" "time" @@ -19,13 +20,28 @@ func ApplyECH(c *Config, config *tls.Config) error { var ECHConfig []byte var err error + nameToQuery := c.ServerName + var DOHServer string + + parts := strings.Split(c.Ech_DOHserver, "+") + if len(parts) == 2 { + // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" + nameToQuery = parts[0] + DOHServer = parts[1] + } else if len(parts) == 1 { + // normal format + DOHServer = parts[0] + } else { + return errors.New("Invalid ECH DOH server format: ", c.Ech_DOHserver) + } + if len(c.EchConfig) > 0 { ECHConfig = c.EchConfig } else { // ECH config > DOH lookup - if config.ServerName == "" { - return errors.New("Using DOH for ECH needs serverName") + if nameToQuery == "" { + return errors.New("Using DOH for ECH needs serverName or use dohServer format example.com+https://1.1.1.1/dns-query") } - ECHConfig, err = QueryRecord(c.ServerName, c.Ech_DOHserver) + ECHConfig, err = QueryRecord(nameToQuery, DOHServer) if err != nil { return err } @@ -41,14 +57,13 @@ type record struct { } var ( - dnsCache = make(map[string]record) + dnsCache = make(map[string]record) // global Lock? I'm not sure if I need finer grained locks. // If we do this, we will need to nest another layer of struct dnsCacheLock sync.RWMutex updating sync.Mutex ) - // QueryRecord returns the ECH config for given domain. // If the record is not in cache or expired, it will query the DOH server and update the cache. func QueryRecord(domain string, server string) ([]byte, error) { @@ -95,7 +110,6 @@ func QueryRecord(domain string, server string) ([]byte, error) { return echConfig, nil } - // dohQuery is the real func for sending type65 query for given domain to given DOH server. // return ECH config, TTL and error func dohQuery(server string, domain string) ([]byte, uint32, error) { From 53ceaf87a5214a4aec8586ba81f116bb83c51aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 15 Feb 2025 15:19:18 +0000 Subject: [PATCH 04/11] Use sync.Map --- transport/internet/tls/ech.go | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index c56d0f2c..a5a6a305 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -57,19 +57,17 @@ type record struct { } var ( - dnsCache = make(map[string]record) - // global Lock? I'm not sure if I need finer grained locks. + dnsCache sync.Map + // global Lock? I'm not sure if this needs finer grained locks. // If we do this, we will need to nest another layer of struct - dnsCacheLock sync.RWMutex - updating sync.Mutex + updating sync.Mutex ) // QueryRecord returns the ECH config for given domain. // If the record is not in cache or expired, it will query the DOH server and update the cache. func QueryRecord(domain string, server string) ([]byte, error) { - dnsCacheLock.RLock() - rec, found := dnsCache[domain] - dnsCacheLock.RUnlock() + val, found := dnsCache.Load(domain) + rec, _ := val.(record) if found && rec.expire.After(time.Now()) { errors.LogDebug(context.Background(), "Cache hit for domain: ", domain) return rec.echConfig, nil @@ -79,9 +77,8 @@ func QueryRecord(domain string, server string) ([]byte, error) { defer updating.Unlock() // Try to get cache again after lock, in case another goroutine has updated it // This might happen when the core tring is just stared and multiple goroutines are trying to query the same domain - dnsCacheLock.RLock() - rec, found = dnsCache[domain] - dnsCacheLock.RUnlock() + val, found = dnsCache.Load(domain) + rec, _ = val.(record) if found && rec.expire.After(time.Now()) { errors.LogDebug(context.Background(), "ECH Config cache hit for domain: ", domain, " after trying to get update lock") return rec.echConfig, nil @@ -99,14 +96,12 @@ func QueryRecord(domain string, server string) ([]byte, error) { ttl = 600 } - // Get write lock and update cache - dnsCacheLock.Lock() - defer dnsCacheLock.Unlock() + // Update cache newRecored := record{ echConfig: echConfig, expire: time.Now().Add(time.Second * time.Duration(ttl)), } - dnsCache[domain] = newRecored + dnsCache.Store(domain, newRecored) return echConfig, nil } From 26199629f7cf4c994a67d9230af82d93b18629ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Thu, 27 Feb 2025 07:14:03 +0000 Subject: [PATCH 05/11] Update goech to v0.0.1 --- go.mod | 2 +- go.sum | 4 ++-- main/commands/all/tls/ech.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index a3bae638..f3fdabc8 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/xtls/xray-core go 1.24 require ( - github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 + github.com/OmarTariq612/goech v0.0.1 github.com/cloudflare/circl v1.6.0 github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 github.com/golang/mock v1.7.0-rc.1 diff --git a/go.sum b/go.sum index 9f20efd4..27664cde 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I= -github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM= +github.com/OmarTariq612/goech v0.0.1 h1:/0c918Bk1ik65GXDj2k7SOK78DyZr30Jmq9euy1/HXg= +github.com/OmarTariq612/goech v0.0.1/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= diff --git a/main/commands/all/tls/ech.go b/main/commands/all/tls/ech.go index d4e17f9b..184f20f4 100644 --- a/main/commands/all/tls/ech.go +++ b/main/commands/all/tls/ech.go @@ -40,7 +40,7 @@ func executeECH(cmd *base.Command, args []string) { kem = hpke.KEM_X25519_HKDF_SHA256 } - echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem) + echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem, nil) common.Must(err) configBuffer, _ := echKeySet.ECHConfig.MarshalBinary() From cab43219565416fb03555c9906bb363266d63aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 1 Mar 2025 19:23:56 +0000 Subject: [PATCH 06/11] Add server support --- infra/conf/transport_internet.go | 8 ++++ main/commands/all/tls/ech.go | 9 +++- transport/internet/tls/config.go | 2 +- transport/internet/tls/config.pb.go | 38 ++++++++++------- transport/internet/tls/config.proto | 6 +-- transport/internet/tls/ech.go | 64 ++++++++++++++++++++--------- 6 files changed, 87 insertions(+), 40 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index d04588e1..39dbe73d 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -414,6 +414,7 @@ type TLSConfig struct { VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` ECHConfig string `json:"echConfig"` ECHDOHServer string `json:"echDohServer"` + EchKeySets string `json:"echKeySets"` } // Build implements Buildable. @@ -492,6 +493,13 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.EchConfig = ECHConfig } + if c.EchKeySets != "" { + EchPrivateKey, err := base64.StdEncoding.DecodeString(c.EchKeySets) + if err != nil { + return nil, errors.New("invalid ECH Config", c.EchKeySets) + } + config.EchKeySets = EchPrivateKey + } config.Ech_DOHserver = c.ECHDOHServer return config, nil diff --git a/main/commands/all/tls/ech.go b/main/commands/all/tls/ech.go index 184f20f4..c8aa2953 100644 --- a/main/commands/all/tls/ech.go +++ b/main/commands/all/tls/ech.go @@ -43,8 +43,13 @@ func executeECH(cmd *base.Command, args []string) { echKeySet, err := goech.GenerateECHKeySet(0, *input_serverName, kem, nil) common.Must(err) - configBuffer, _ := echKeySet.ECHConfig.MarshalBinary() - keyBuffer, _ := echKeySet.MarshalBinary() + // Make single key set to a list with only one element + ECHConfigList := make(goech.ECHConfigList, 1) + ECHConfigList[0] = echKeySet.ECHConfig + ECHKeySetList := make(goech.ECHKeySetList, 1) + ECHKeySetList[0] = echKeySet + configBuffer, _ := ECHConfigList.MarshalBinary() + keyBuffer, _ := ECHKeySetList.MarshalBinary() configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer})) keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer})) diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 0ab889ac..0193f931 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -444,7 +444,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { config.KeyLogWriter = writer } } - if len(c.EchConfig) > 0 || len(c.Ech_DOHserver) > 0 { + if len(c.EchConfig) > 0 || len(c.Ech_DOHserver) > 0 || len(c.EchKeySets) > 0 { err := ApplyECH(c, config) if err != nil { errors.LogError(context.Background(), err) diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index 5808e6e4..ef483451 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -217,8 +217,9 @@ type Config struct { // @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried. // @Critical VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"` - EchConfig []byte `protobuf:"bytes,20,opt,name=ech_config,json=echConfig,proto3" json:"ech_config,omitempty"` - Ech_DOHserver string `protobuf:"bytes,21,opt,name=ech_DOHserver,json=echDOHserver,proto3" json:"ech_DOHserver,omitempty"` + EchConfig []byte `protobuf:"bytes,18,opt,name=ech_config,json=echConfig,proto3" json:"ech_config,omitempty"` + Ech_DOHserver string `protobuf:"bytes,19,opt,name=ech_DOHserver,json=echDOHserver,proto3" json:"ech_DOHserver,omitempty"` + EchKeySets []byte `protobuf:"bytes,20,opt,name=ech_key_sets,json=echKeySets,proto3" json:"ech_key_sets,omitempty"` } func (x *Config) Reset() { @@ -377,6 +378,13 @@ func (x *Config) GetEch_DOHserver() string { return "" } +func (x *Config) GetEchKeySets() []byte { + if x != nil { + return x.EchKeySets + } + return nil +} + var File_transport_internet_tls_config_proto protoreflect.FileDescriptor var file_transport_internet_tls_config_proto_rawDesc = []byte{ @@ -408,7 +416,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, - 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xde, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, + 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x80, 0x07, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65, @@ -459,18 +467,20 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x63, 0x68, 0x43, 0x6f, + 0x66, 0x69, 0x67, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x63, 0x68, 0x5f, 0x44, 0x4f, 0x48, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x63, 0x68, - 0x44, 0x4f, 0x48, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, - 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, - 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, - 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, - 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, - 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x63, 0x68, + 0x44, 0x4f, 0x48, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x65, 0x63, 0x68, + 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0a, 0x65, 0x63, 0x68, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, + 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, + 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, + 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, + 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 01ea5eb5..09b93999 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -92,9 +92,9 @@ message Config { */ repeated string verify_peer_cert_in_names = 17; + bytes ech_config = 18; + string ech_DOHserver = 19; - bytes ech_config = 20; - - string ech_DOHserver = 21; + bytes ech_key_sets = 20; } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index a5a6a305..572db5b3 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -4,12 +4,14 @@ import ( "bytes" "context" "crypto/tls" + "fmt" "io" "net/http" "strings" "sync" "time" + "github.com/OmarTariq612/goech" "github.com/miekg/dns" "github.com/xtls/xray-core/common/errors" "github.com/xtls/xray-core/common/net" @@ -23,31 +25,53 @@ func ApplyECH(c *Config, config *tls.Config) error { nameToQuery := c.ServerName var DOHServer string - parts := strings.Split(c.Ech_DOHserver, "+") - if len(parts) == 2 { - // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" - nameToQuery = parts[0] - DOHServer = parts[1] - } else if len(parts) == 1 { - // normal format - DOHServer = parts[0] - } else { - return errors.New("Invalid ECH DOH server format: ", c.Ech_DOHserver) + if len(c.EchConfig) != 0 { + parts := strings.Split(c.Ech_DOHserver, "+") + if len(parts) == 2 { + // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" + nameToQuery = parts[0] + DOHServer = parts[1] + } else if len(parts) == 1 { + // normal format + DOHServer = parts[0] + } else { + return errors.New("Invalid ECH DOH server format: ", c.Ech_DOHserver) + } + + if len(c.EchConfig) > 0 { + ECHConfig = c.EchConfig + } else { // ECH config > DOH lookup + if nameToQuery == "" { + return errors.New("Using DOH for ECH needs serverName or use dohServer format example.com+https://1.1.1.1/dns-query") + } + ECHConfig, err = QueryRecord(nameToQuery, DOHServer) + if err != nil { + return err + } + } + + config.EncryptedClientHelloConfigList = ECHConfig } - if len(c.EchConfig) > 0 { - ECHConfig = c.EchConfig - } else { // ECH config > DOH lookup - if nameToQuery == "" { - return errors.New("Using DOH for ECH needs serverName or use dohServer format example.com+https://1.1.1.1/dns-query") - } - ECHConfig, err = QueryRecord(nameToQuery, DOHServer) + if len(c.EchKeySets) != 0 { + var keys []tls.EncryptedClientHelloKey + KeySets, err := goech.UnmarshalECHKeySetList(c.EchKeySets) if err != nil { - return err + return errors.New("Failed to unmarshal ECHKeySetList: ", err) } + for idx, keySet := range KeySets { + ECHConfig, err := keySet.ECHConfig.MarshalBinary() + ECHPrivateKey, err := keySet.PrivateKey.MarshalBinary() + if err != nil { + return errors.New("Failed to marshal ECHKey in index: ", idx, "with err: ", err) + } + keys = append(keys, tls.EncryptedClientHelloKey{ + Config: ECHConfig, + PrivateKey: ECHPrivateKey}) + } + config.EncryptedClientHelloKeys = keys + fmt.Println(config.EncryptedClientHelloKeys) } - - config.EncryptedClientHelloConfigList = ECHConfig return nil } From 8554549f2c9937d46e1b61cad986b391c98dc3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sat, 1 Mar 2025 19:38:41 +0000 Subject: [PATCH 07/11] Refine xray tls ech output format --- main/commands/all/tls/ech.go | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/main/commands/all/tls/ech.go b/main/commands/all/tls/ech.go index c8aa2953..701cf937 100644 --- a/main/commands/all/tls/ech.go +++ b/main/commands/all/tls/ech.go @@ -1,10 +1,8 @@ package tls import ( - "encoding/json" "encoding/pem" "os" - "strings" "github.com/OmarTariq612/goech" "github.com/cloudflare/circl/hpke" @@ -13,13 +11,13 @@ import ( ) var cmdECH = &base.Command{ - UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--json]`, + UsageLine: `{{.Exec}} tls ech [--serverName (string)] [--pem]`, Short: `Generate TLS-ECH certificates`, Long: ` Generate TLS-ECH certificates. Set serverName to your custom string: {{.Exec}} tls ech --serverName (string) -Generate into json format: {{.Exec}} tls ech --json +Generate into pem format: {{.Exec}} tls ech --pem `, // Enable PQ signature schemes: {{.Exec}} tls ech --pq-signature-schemes-enabled } @@ -29,7 +27,7 @@ func init() { var input_pqSignatureSchemesEnabled = cmdECH.Flag.Bool("pqSignatureSchemesEnabled", false, "") var input_serverName = cmdECH.Flag.String("serverName", "cloudflare-ech.com", "") -var input_json = cmdECH.Flag.Bool("json", false, "True == turn on json output") +var input_pem = cmdECH.Flag.Bool("pem", false, "True == turn on pem output") func executeECH(cmd *base.Command, args []string) { var kem hpke.KEM @@ -50,25 +48,16 @@ func executeECH(cmd *base.Command, args []string) { ECHKeySetList[0] = echKeySet configBuffer, _ := ECHConfigList.MarshalBinary() keyBuffer, _ := ECHKeySetList.MarshalBinary() + configStr, _ := ECHConfigList.ToBase64() + keySetStr, _ := ECHKeySetList.ToBase64() configPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer})) keyPEM := string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer})) - if *input_json { - jECHConfigs := map[string]interface{}{ - "configs": strings.Split(strings.TrimSpace(string(configPEM)), "\n"), - } - jECHKey := map[string]interface{}{ - "key": strings.Split(strings.TrimSpace(string(keyPEM)), "\n"), - } - - for _, i := range []map[string]interface{}{jECHConfigs, jECHKey} { - content, err := json.MarshalIndent(i, "", " ") - common.Must(err) - os.Stdout.Write(content) - os.Stdout.WriteString("\n") - } - } else { + if *input_pem { os.Stdout.WriteString(configPEM) os.Stdout.WriteString(keyPEM) + } else { + os.Stdout.WriteString("ECH config list: \n" + configStr + "\n") + os.Stdout.WriteString("ECH Key sets: \n" + keySetStr + "\n") } } From 0923f53b213d793032f26e06d22afdf88765d535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sun, 2 Mar 2025 13:30:58 +0000 Subject: [PATCH 08/11] bugfix --- transport/internet/tls/ech.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 572db5b3..20156fde 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -25,7 +25,7 @@ func ApplyECH(c *Config, config *tls.Config) error { nameToQuery := c.ServerName var DOHServer string - if len(c.EchConfig) != 0 { + if len(c.EchConfig) != 0 || len(c.Ech_DOHserver) != 0 { parts := strings.Split(c.Ech_DOHserver, "+") if len(parts) == 2 { // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" From 6d5be8694736fd40c7e36752bf874bb5c376fa62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sun, 9 Mar 2025 09:43:40 +0000 Subject: [PATCH 09/11] Add classic UDP DNS support for ECH Config --- infra/conf/transport_internet.go | 4 +- transport/internet/tls/config.go | 2 +- transport/internet/tls/config.pb.go | 10 +-- transport/internet/tls/config.proto | 2 +- transport/internet/tls/ech.go | 130 ++++++++++++++++++---------- 5 files changed, 91 insertions(+), 57 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 39dbe73d..07fe4aa3 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -413,7 +413,7 @@ type TLSConfig struct { ServerNameToVerify string `json:"serverNameToVerify"` VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` ECHConfig string `json:"echConfig"` - ECHDOHServer string `json:"echDohServer"` + ECHDNSServer string `json:"echDnsServer"` EchKeySets string `json:"echKeySets"` } @@ -500,7 +500,7 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.EchKeySets = EchPrivateKey } - config.Ech_DOHserver = c.ECHDOHServer + config.Ech_DNSserver = c.ECHDNSServer return config, nil } diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 0193f931..7c86b406 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -444,7 +444,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { config.KeyLogWriter = writer } } - if len(c.EchConfig) > 0 || len(c.Ech_DOHserver) > 0 || len(c.EchKeySets) > 0 { + if len(c.EchConfig) > 0 || len(c.Ech_DNSserver) > 0 || len(c.EchKeySets) > 0 { err := ApplyECH(c, config) if err != nil { errors.LogError(context.Background(), err) diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index ef483451..e8d218a4 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -218,7 +218,7 @@ type Config struct { // @Critical VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"` EchConfig []byte `protobuf:"bytes,18,opt,name=ech_config,json=echConfig,proto3" json:"ech_config,omitempty"` - Ech_DOHserver string `protobuf:"bytes,19,opt,name=ech_DOHserver,json=echDOHserver,proto3" json:"ech_DOHserver,omitempty"` + Ech_DNSserver string `protobuf:"bytes,19,opt,name=ech_DNSserver,json=echDNSserver,proto3" json:"ech_DNSserver,omitempty"` EchKeySets []byte `protobuf:"bytes,20,opt,name=ech_key_sets,json=echKeySets,proto3" json:"ech_key_sets,omitempty"` } @@ -371,9 +371,9 @@ func (x *Config) GetEchConfig() []byte { return nil } -func (x *Config) GetEch_DOHserver() string { +func (x *Config) GetEch_DNSserver() string { if x != nil { - return x.Ech_DOHserver + return x.Ech_DNSserver } return "" } @@ -468,9 +468,9 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x63, 0x68, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x63, 0x68, 0x5f, 0x44, 0x4f, 0x48, 0x73, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x63, 0x68, 0x5f, 0x44, 0x4e, 0x53, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x63, 0x68, - 0x44, 0x4f, 0x48, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x65, 0x63, 0x68, + 0x44, 0x4e, 0x53, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x65, 0x63, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x65, 0x63, 0x68, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index 09b93999..df3bcb05 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -94,7 +94,7 @@ message Config { bytes ech_config = 18; - string ech_DOHserver = 19; + string ech_DNSserver = 19; bytes ech_key_sets = 20; } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 20156fde..cbe13042 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -25,8 +25,8 @@ func ApplyECH(c *Config, config *tls.Config) error { nameToQuery := c.ServerName var DOHServer string - if len(c.EchConfig) != 0 || len(c.Ech_DOHserver) != 0 { - parts := strings.Split(c.Ech_DOHserver, "+") + if len(c.EchConfig) != 0 || len(c.Ech_DNSserver) != 0 { + parts := strings.Split(c.Ech_DNSserver, "+") if len(parts) == 2 { // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" nameToQuery = parts[0] @@ -35,7 +35,7 @@ func ApplyECH(c *Config, config *tls.Config) error { // normal format DOHServer = parts[0] } else { - return errors.New("Invalid ECH DOH server format: ", c.Ech_DOHserver) + return errors.New("Invalid ECH DOH server format: ", c.Ech_DNSserver) } if len(c.EchConfig) > 0 { @@ -133,55 +133,89 @@ func QueryRecord(domain string, server string) ([]byte, error) { // return ECH config, TTL and error func dohQuery(server string, domain string) ([]byte, uint32, error) { m := new(dns.Msg) + var dnsResolve []byte m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS) - // always 0 in DOH - m.Id = 0 - msg, err := m.Pack() - if err != nil { - return []byte{}, 0, err - } - // All traffic sent by core should via xray's internet.DialSystem - // This involves the behavior of some Android VPN GUI clients - tr := &http.Transport{ - IdleConnTimeout: 90 * time.Second, - ForceAttemptHTTP2: true, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - dest, err := net.ParseDestination(network + ":" + addr) - if err != nil { - return nil, err - } - conn, err := internet.DialSystem(ctx, dest, nil) - if err != nil { - return nil, err - } - return conn, nil - }, - } - client := &http.Client{ - Timeout: 5 * time.Second, - Transport: tr, - } - req, err := http.NewRequest("POST", server, bytes.NewReader(msg)) - if err != nil { - return []byte{}, 0, err - } - req.Header.Set("Content-Type", "application/dns-message") - resp, err := client.Do(req) - if err != nil { - return []byte{}, 0, err - } - defer resp.Body.Close() - respBody, err := io.ReadAll(resp.Body) - if err != nil { - return []byte{}, 0, err - } - if resp.StatusCode != http.StatusOK { - return []byte{}, 0, errors.New("query failed with response code:", resp.StatusCode) + // for DOH server + if strings.HasPrefix(server, "https://") { + // always 0 in DOH + m.Id = 0 + msg, err := m.Pack() + if err != nil { + return []byte{}, 0, err + } + // All traffic sent by core should via xray's internet.DialSystem + // This involves the behavior of some Android VPN GUI clients + tr := &http.Transport{ + IdleConnTimeout: 90 * time.Second, + ForceAttemptHTTP2: true, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + dest, err := net.ParseDestination(network + ":" + addr) + if err != nil { + return nil, err + } + conn, err := internet.DialSystem(ctx, dest, nil) + if err != nil { + return nil, err + } + return conn, nil + }, + } + client := &http.Client{ + Timeout: 5 * time.Second, + Transport: tr, + } + req, err := http.NewRequest("POST", server, bytes.NewReader(msg)) + if err != nil { + return []byte{}, 0, err + } + req.Header.Set("Content-Type", "application/dns-message") + resp, err := client.Do(req) + if err != nil { + return []byte{}, 0, err + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return []byte{}, 0, err + } + if resp.StatusCode != http.StatusOK { + return []byte{}, 0, errors.New("query failed with response code:", resp.StatusCode) + } + dnsResolve = respBody + } else if strings.HasPrefix(server, "udp://") { // for classic udp dns server + udpServerAddr := server[len("udp://"):] + // default port 53 if not specified + if !strings.Contains(udpServerAddr, ":") { + udpServerAddr = udpServerAddr + ":53" + } + dest, err := net.ParseDestination("udp" + ":" + udpServerAddr) + if err != nil { + return nil, 0, errors.New("failed to parse udp dns server ", udpServerAddr, " for ECH: ", err) + } + dnsTimeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + // use xray's internet.DialSystem as mentioned above + conn, err := internet.DialSystem(dnsTimeoutCtx, dest, nil) + defer conn.Close() + if err != nil { + return []byte{}, 0, err + } + msg, err := m.Pack() + if err != nil { + return []byte{}, 0, err + } + conn.Write(msg) + udpResponse := make([]byte, 512) + _, err = conn.Read(udpResponse) + if err != nil { + return []byte{}, 0, err + } + dnsResolve = udpResponse } respMsg := new(dns.Msg) - err = respMsg.Unpack(respBody) + err := respMsg.Unpack(dnsResolve) if err != nil { - return []byte{}, 0, err + return []byte{}, 0, errors.New("failed to unpack dns response for ECH: ", err) } if len(respMsg.Answer) > 0 { for _, answer := range respMsg.Answer { From 7f6a825bfe12edf1fa1dff53ceeff7d753f9d22f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sun, 9 Mar 2025 11:40:10 +0000 Subject: [PATCH 10/11] Do some rename --- infra/conf/transport_internet.go | 13 ++------ transport/internet/tls/config.go | 2 +- transport/internet/tls/config.pb.go | 46 +++++++++++------------------ transport/internet/tls/config.proto | 6 ++-- transport/internet/tls/ech.go | 42 +++++++++++++++----------- 5 files changed, 48 insertions(+), 61 deletions(-) diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 07fe4aa3..f28d68fc 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -412,8 +412,7 @@ type TLSConfig struct { MasterKeyLog string `json:"masterKeyLog"` ServerNameToVerify string `json:"serverNameToVerify"` VerifyPeerCertInNames []string `json:"verifyPeerCertInNames"` - ECHConfig string `json:"echConfig"` - ECHDNSServer string `json:"echDnsServer"` + ECHConfigList string `json:"echConfigList"` EchKeySets string `json:"echKeySets"` } @@ -486,13 +485,8 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.VerifyPeerCertInNames = c.VerifyPeerCertInNames - if c.ECHConfig != "" { - ECHConfig, err := base64.StdEncoding.DecodeString(c.ECHConfig) - if err != nil { - return nil, errors.New("invalid ECH Config", c.ECHConfig) - } - config.EchConfig = ECHConfig - } + config.EchConfigList = c.ECHConfigList + if c.EchKeySets != "" { EchPrivateKey, err := base64.StdEncoding.DecodeString(c.EchKeySets) if err != nil { @@ -500,7 +494,6 @@ func (c *TLSConfig) Build() (proto.Message, error) { } config.EchKeySets = EchPrivateKey } - config.Ech_DNSserver = c.ECHDNSServer return config, nil } diff --git a/transport/internet/tls/config.go b/transport/internet/tls/config.go index 7c86b406..1ba242a9 100644 --- a/transport/internet/tls/config.go +++ b/transport/internet/tls/config.go @@ -444,7 +444,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config { config.KeyLogWriter = writer } } - if len(c.EchConfig) > 0 || len(c.Ech_DNSserver) > 0 || len(c.EchKeySets) > 0 { + if len(c.EchConfigList) > 0 || len(c.EchKeySets) > 0 { err := ApplyECH(c, config) if err != nil { errors.LogError(context.Background(), err) diff --git a/transport/internet/tls/config.pb.go b/transport/internet/tls/config.pb.go index e8d218a4..064c795a 100644 --- a/transport/internet/tls/config.pb.go +++ b/transport/internet/tls/config.pb.go @@ -217,9 +217,8 @@ type Config struct { // @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried. // @Critical VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"` - EchConfig []byte `protobuf:"bytes,18,opt,name=ech_config,json=echConfig,proto3" json:"ech_config,omitempty"` - Ech_DNSserver string `protobuf:"bytes,19,opt,name=ech_DNSserver,json=echDNSserver,proto3" json:"ech_DNSserver,omitempty"` - EchKeySets []byte `protobuf:"bytes,20,opt,name=ech_key_sets,json=echKeySets,proto3" json:"ech_key_sets,omitempty"` + EchConfigList string `protobuf:"bytes,18,opt,name=ech_config_list,json=echConfigList,proto3" json:"ech_config_list,omitempty"` + EchKeySets []byte `protobuf:"bytes,19,opt,name=ech_key_sets,json=echKeySets,proto3" json:"ech_key_sets,omitempty"` } func (x *Config) Reset() { @@ -364,16 +363,9 @@ func (x *Config) GetVerifyPeerCertInNames() []string { return nil } -func (x *Config) GetEchConfig() []byte { +func (x *Config) GetEchConfigList() string { if x != nil { - return x.EchConfig - } - return nil -} - -func (x *Config) GetEch_DNSserver() string { - if x != nil { - return x.Ech_DNSserver + return x.EchConfigList } return "" } @@ -416,7 +408,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46, 0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, - 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x80, 0x07, 0x0a, 0x06, 0x43, 0x6f, 0x6e, + 0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0xe4, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65, @@ -466,21 +458,19 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{ 0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e, - 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x63, 0x68, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x63, 0x68, 0x5f, 0x44, 0x4e, 0x53, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x63, 0x68, - 0x44, 0x4e, 0x53, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x65, 0x63, 0x68, - 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0a, 0x65, 0x63, 0x68, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, - 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, - 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, - 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, - 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, - 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, - 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x65, 0x63, 0x68, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x12, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x65, 0x63, 0x68, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x20, 0x0a, + 0x0c, 0x65, 0x63, 0x68, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x13, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x65, 0x63, 0x68, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x74, 0x73, 0x42, + 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, + 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, + 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, + 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/transport/internet/tls/config.proto b/transport/internet/tls/config.proto index df3bcb05..3e032183 100644 --- a/transport/internet/tls/config.proto +++ b/transport/internet/tls/config.proto @@ -92,9 +92,7 @@ message Config { */ repeated string verify_peer_cert_in_names = 17; - bytes ech_config = 18; + string ech_config_list = 18; - string ech_DNSserver = 19; - - bytes ech_key_sets = 20; + bytes ech_key_sets = 19; } diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index cbe13042..716bde34 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "crypto/tls" - "fmt" "io" "net/http" "strings" @@ -25,22 +24,28 @@ func ApplyECH(c *Config, config *tls.Config) error { nameToQuery := c.ServerName var DOHServer string - if len(c.EchConfig) != 0 || len(c.Ech_DNSserver) != 0 { - parts := strings.Split(c.Ech_DNSserver, "+") - if len(parts) == 2 { - // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" - nameToQuery = parts[0] - DOHServer = parts[1] - } else if len(parts) == 1 { - // normal format - DOHServer = parts[0] - } else { - return errors.New("Invalid ECH DOH server format: ", c.Ech_DNSserver) - } - - if len(c.EchConfig) > 0 { - ECHConfig = c.EchConfig - } else { // ECH config > DOH lookup + // for client + if len(c.EchConfigList) != 0 { + // direct base64 config + if strings.HasPrefix(c.EchConfigList, "base64") { + Base64ECHConfigList := c.EchConfigList[len("base64://"):] + ECHConfigList, err := goech.ECHConfigListFromBase64(Base64ECHConfigList) + if err != nil { + return errors.New("Failed to unmarshal ECHConfigList: ", err) + } + ECHConfig, _ = ECHConfigList.MarshalBinary() + } else { // query config from dns + parts := strings.Split(c.EchConfigList, "+") + if len(parts) == 2 { + // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" + nameToQuery = parts[0] + DOHServer = parts[1] + } else if len(parts) == 1 { + // normal format + DOHServer = parts[0] + } else { + return errors.New("Invalid ECH DNS server format: ", c.EchConfigList) + } if nameToQuery == "" { return errors.New("Using DOH for ECH needs serverName or use dohServer format example.com+https://1.1.1.1/dns-query") } @@ -53,6 +58,7 @@ func ApplyECH(c *Config, config *tls.Config) error { config.EncryptedClientHelloConfigList = ECHConfig } + // for server if len(c.EchKeySets) != 0 { var keys []tls.EncryptedClientHelloKey KeySets, err := goech.UnmarshalECHKeySetList(c.EchKeySets) @@ -70,8 +76,8 @@ func ApplyECH(c *Config, config *tls.Config) error { PrivateKey: ECHPrivateKey}) } config.EncryptedClientHelloKeys = keys - fmt.Println(config.EncryptedClientHelloKeys) } + return nil } From 4999fd5b7be046081bade43c024d50a7b18cdf78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A3=8E=E6=89=87=E6=BB=91=E7=BF=94=E7=BF=BC?= Date: Sun, 9 Mar 2025 13:30:15 +0000 Subject: [PATCH 11/11] Missing rename --- transport/internet/tls/ech.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/transport/internet/tls/ech.go b/transport/internet/tls/ech.go index 716bde34..498199ff 100644 --- a/transport/internet/tls/ech.go +++ b/transport/internet/tls/ech.go @@ -22,7 +22,7 @@ func ApplyECH(c *Config, config *tls.Config) error { var err error nameToQuery := c.ServerName - var DOHServer string + var DNSServer string // for client if len(c.EchConfigList) != 0 { @@ -37,19 +37,19 @@ func ApplyECH(c *Config, config *tls.Config) error { } else { // query config from dns parts := strings.Split(c.EchConfigList, "+") if len(parts) == 2 { - // parse ECH DOH server in format of "example.com+https://1.1.1.1/dns-query" + // parse ECH DNS server in format of "example.com+https://1.1.1.1/dns-query" nameToQuery = parts[0] - DOHServer = parts[1] + DNSServer = parts[1] } else if len(parts) == 1 { // normal format - DOHServer = parts[0] + DNSServer = parts[0] } else { return errors.New("Invalid ECH DNS server format: ", c.EchConfigList) } if nameToQuery == "" { - return errors.New("Using DOH for ECH needs serverName or use dohServer format example.com+https://1.1.1.1/dns-query") + return errors.New("Using DNS for ECH Config needs serverName or use Server format example.com+https://1.1.1.1/dns-query") } - ECHConfig, err = QueryRecord(nameToQuery, DOHServer) + ECHConfig, err = QueryRecord(nameToQuery, DNSServer) if err != nil { return err } @@ -94,7 +94,7 @@ var ( ) // QueryRecord returns the ECH config for given domain. -// If the record is not in cache or expired, it will query the DOH server and update the cache. +// If the record is not in cache or expired, it will query the DNS server and update the cache. func QueryRecord(domain string, server string) ([]byte, error) { val, found := dnsCache.Load(domain) rec, _ := val.(record) @@ -114,9 +114,9 @@ func QueryRecord(domain string, server string) ([]byte, error) { return rec.echConfig, nil } - // Query ECH config from DOH server + // Query ECH config from DNS server errors.LogDebug(context.Background(), "Trying to query ECH config for domain: ", domain, " with ECH server: ", server) - echConfig, ttl, err := dohQuery(server, domain) + echConfig, ttl, err := dnsQuery(server, domain) if err != nil { return []byte{}, err } @@ -135,9 +135,9 @@ func QueryRecord(domain string, server string) ([]byte, error) { return echConfig, nil } -// dohQuery is the real func for sending type65 query for given domain to given DOH server. +// dnsQuery is the real func for sending type65 query for given domain to given DNS server. // return ECH config, TTL and error -func dohQuery(server string, domain string) ([]byte, uint32, error) { +func dnsQuery(server string, domain string) ([]byte, uint32, error) { m := new(dns.Msg) var dnsResolve []byte m.SetQuestion(dns.Fqdn(domain), dns.TypeHTTPS)