Sfoglia il codice sorgente

Add classic UDP DNS support for ECH Config

风扇滑翔翼 10 mesi fa
parent
commit
6d5be86947

+ 2 - 2
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
 }

+ 1 - 1
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)

+ 5 - 5
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,

+ 1 - 1
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;
 }

+ 82 - 48
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 {