mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-07-14 17:08:54 +00:00
TLS ECH: Update ECH configuration parsing logic to be more robust (#6441)
https://github.com/XTLS/Xray-core/pull/6441#issuecomment-4903624643 --------- Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
This commit is contained in:
@@ -450,7 +450,7 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
|||||||
for _, s := range tls.CipherSuites() {
|
for _, s := range tls.CipherSuites() {
|
||||||
id[s.Name] = s.ID
|
id[s.Name] = s.ID
|
||||||
}
|
}
|
||||||
for _, n := range strings.Split(c.CipherSuites, ":") {
|
for n := range strings.SplitSeq(c.CipherSuites, ":") {
|
||||||
if id[n] != 0 {
|
if id[n] != 0 {
|
||||||
config.CipherSuites = append(config.CipherSuites, id[n])
|
config.CipherSuites = append(config.CipherSuites, id[n])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,19 +55,16 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
|||||||
}
|
}
|
||||||
config.EncryptedClientHelloConfigList = ECHConfig
|
config.EncryptedClientHelloConfigList = ECHConfig
|
||||||
}()
|
}()
|
||||||
// direct base64 config
|
// query config from dns
|
||||||
if strings.Contains(c.EchConfigList, "://") {
|
if strings.Contains(c.EchConfigList, "://") {
|
||||||
// query config from dns
|
// parse ECH DNS server in format of "example.com+https://1.1.1.1/dns-query"
|
||||||
parts := strings.Split(c.EchConfigList, "+")
|
parts := strings.SplitN(c.EchConfigList, "+", 2)
|
||||||
if len(parts) == 2 {
|
if len(parts) == 2 {
|
||||||
// parse ECH DNS server in format of "example.com+https://1.1.1.1/dns-query"
|
|
||||||
nameToQuery = parts[0]
|
nameToQuery = parts[0]
|
||||||
DNSServer = parts[1]
|
DNSServer = parts[1]
|
||||||
} else if len(parts) == 1 {
|
} else {
|
||||||
// normal format
|
// normal format
|
||||||
DNSServer = parts[0]
|
DNSServer = parts[0]
|
||||||
} else {
|
|
||||||
return errors.New("Invalid ECH DNS server format: ", c.EchConfigList)
|
|
||||||
}
|
}
|
||||||
if nameToQuery == "" {
|
if nameToQuery == "" {
|
||||||
return errors.New("Using DNS for ECH Config needs serverName or use Server 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")
|
||||||
@@ -77,6 +74,7 @@ func ApplyECH(c *Config, config *tls.Config) error {
|
|||||||
return errors.New("Failed to query ECH DNS record for domain: ", nameToQuery, " at server: ", DNSServer).Base(err)
|
return errors.New("Failed to query ECH DNS record for domain: ", nameToQuery, " at server: ", DNSServer).Base(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// direct base64 config
|
||||||
ECHConfig, err = base64.StdEncoding.DecodeString(c.EchConfigList)
|
ECHConfig, err = base64.StdEncoding.DecodeString(c.EchConfigList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("Failed to unmarshal ECHConfigList: ", err)
|
return errors.New("Failed to unmarshal ECHConfigList: ", err)
|
||||||
@@ -157,7 +155,7 @@ func QueryRecord(domain string, server string, sockopt *internet.SocketConfig) (
|
|||||||
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
// If expire is zero value, it means we are in initial state, wait for the query to finish
|
||||||
// otherwise return old value immediately and update in a goroutine
|
// otherwise return old value immediately and update in a goroutine
|
||||||
// but if the cache is too old, wait for update
|
// but if the cache is too old, wait for update
|
||||||
if configRecord.expire == (time.Time{}) || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
if configRecord.expire.IsZero() || configRecord.expire.Add(time.Hour*4).Before(time.Now()) {
|
||||||
return echConfigCache.Update(domain, server, false, sockopt)
|
return echConfigCache.Update(domain, server, false, sockopt)
|
||||||
} else {
|
} else {
|
||||||
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
// If someone already acquired the lock, it means it is updating, do not start another update goroutine
|
||||||
@@ -256,14 +254,17 @@ func dnsQuery(server string, domain string, sockopt *internet.SocketConfig) ([]b
|
|||||||
}
|
}
|
||||||
dnsResolve = respBody
|
dnsResolve = respBody
|
||||||
} else if strings.HasPrefix(server, "udp://") { // for classic udp dns server
|
} else if strings.HasPrefix(server, "udp://") { // for classic udp dns server
|
||||||
udpServerAddr := server[len("udp://"):]
|
udpServerURL, err := url.Parse(server)
|
||||||
// default port 53 if not specified
|
|
||||||
if !strings.Contains(udpServerAddr, ":") {
|
|
||||||
udpServerAddr = udpServerAddr + ":53"
|
|
||||||
}
|
|
||||||
dest, err := net.ParseDestination("udp" + ":" + udpServerAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, errors.New("failed to parse udp dns server ", udpServerAddr, " for ECH: ", err)
|
return nil, 0, errors.New("failed to parse udp dns server ", server, " for ECH: ", err)
|
||||||
|
}
|
||||||
|
// default port 53 if not specified
|
||||||
|
if udpServerURL.Port() == "" {
|
||||||
|
udpServerURL.Host = udpServerURL.Host + ":53"
|
||||||
|
}
|
||||||
|
dest, err := net.ParseDestination("udp" + ":" + udpServerURL.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.New("failed to parse udp dns server ", udpServerURL.Host, " for ECH: ", err)
|
||||||
}
|
}
|
||||||
dnsTimeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
dnsTimeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ func TestECHDial(t *testing.T) {
|
|||||||
// test concurrent Dial(to test cache problem)
|
// test concurrent Dial(to test cache problem)
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
for range 10 {
|
for range 10 {
|
||||||
wg.Add(1)
|
wg.Go(func() {
|
||||||
go func() {
|
|
||||||
TLSConfig := config.GetTLSConfig()
|
TLSConfig := config.GetTLSConfig()
|
||||||
TLSConfig.NextProtos = []string{"http/1.1"}
|
TLSConfig.NextProtos = []string{"http/1.1"}
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
@@ -36,8 +35,7 @@ func TestECHDial(t *testing.T) {
|
|||||||
if !strings.Contains(string(body), "sni=encrypted") {
|
if !strings.Contains(string(body), "sni=encrypted") {
|
||||||
t.Error("ECH Dial success but SNI is not encrypted")
|
t.Error("ECH Dial success but SNI is not encrypted")
|
||||||
}
|
}
|
||||||
wg.Done()
|
})
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
// check cache
|
// check cache
|
||||||
|
|||||||
Reference in New Issue
Block a user