Przeglądaj źródła

DNS DoH: Use EDNS0 with 100-300 padding by default (body padding)

https://github.com/XTLS/Xray-core/pull/4516#issuecomment-2744093003
RPRX 1 rok temu
rodzic
commit
607c2a6d31

+ 43 - 33
app/dns/dnscommon.go

@@ -68,49 +68,59 @@ type dnsRequest struct {
 	msg     *dnsmessage.Message
 }
 
-func genEDNS0Options(clientIP net.IP) *dnsmessage.Resource {
-	if len(clientIP) == 0 {
+func genEDNS0Options(clientIP net.IP, padding int) *dnsmessage.Resource {
+	if len(clientIP) == 0 && padding == 0 {
 		return nil
 	}
 
-	var netmask int
-	var family uint16
+	const EDNS0SUBNET = 0x8
+	const EDNS0PADDING = 0xc
 
-	if len(clientIP) == 4 {
-		family = 1
-		netmask = 24 // 24 for IPV4, 96 for IPv6
-	} else {
-		family = 2
-		netmask = 96
-	}
+	opt := new(dnsmessage.Resource)
+	common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
+	body := dnsmessage.OPTResource{}
+	opt.Body = &body
 
-	b := make([]byte, 4)
-	binary.BigEndian.PutUint16(b[0:], family)
-	b[2] = byte(netmask)
-	b[3] = 0
-	switch family {
-	case 1:
-		ip := clientIP.To4().Mask(net.CIDRMask(netmask, net.IPv4len*8))
-		needLength := (netmask + 8 - 1) / 8 // division rounding up
-		b = append(b, ip[:needLength]...)
-	case 2:
-		ip := clientIP.Mask(net.CIDRMask(netmask, net.IPv6len*8))
-		needLength := (netmask + 8 - 1) / 8 // division rounding up
-		b = append(b, ip[:needLength]...)
-	}
+	if len(clientIP) != 0 {
+		var netmask int
+		var family uint16
 
-	const EDNS0SUBNET = 0x08
+		if len(clientIP) == 4 {
+			family = 1
+			netmask = 24 // 24 for IPV4, 96 for IPv6
+		} else {
+			family = 2
+			netmask = 96
+		}
 
-	opt := new(dnsmessage.Resource)
-	common.Must(opt.Header.SetEDNS0(1350, 0xfe00, true))
+		b := make([]byte, 4)
+		binary.BigEndian.PutUint16(b[0:], family)
+		b[2] = byte(netmask)
+		b[3] = 0
+		switch family {
+		case 1:
+			ip := clientIP.To4().Mask(net.CIDRMask(netmask, net.IPv4len*8))
+			needLength := (netmask + 8 - 1) / 8 // division rounding up
+			b = append(b, ip[:needLength]...)
+		case 2:
+			ip := clientIP.Mask(net.CIDRMask(netmask, net.IPv6len*8))
+			needLength := (netmask + 8 - 1) / 8 // division rounding up
+			b = append(b, ip[:needLength]...)
+		}
 
-	opt.Body = &dnsmessage.OPTResource{
-		Options: []dnsmessage.Option{
-			{
+		body.Options = append(body.Options,
+			dnsmessage.Option{
 				Code: EDNS0SUBNET,
 				Data: b,
-			},
-		},
+			})
+	}
+
+	if padding != 0 {
+		body.Options = append(body.Options,
+			dnsmessage.Option{
+				Code: EDNS0PADDING,
+				Data: make([]byte, padding),
+			})
 	}
 
 	return opt

+ 1 - 1
app/dns/dnscommon_test.go

@@ -156,7 +156,7 @@ func Test_genEDNS0Options(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if got := genEDNS0Options(tt.args.clientIP); got == nil {
+			if got := genEDNS0Options(tt.args.clientIP, 0); got == nil {
 				t.Errorf("genEDNS0Options() = %v, want %v", got, tt.want)
 			}
 		})

+ 3 - 1
app/dns/nameserver_doh.go

@@ -219,7 +219,9 @@ func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP n
 		return
 	}
 
-	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
+	// As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
+	// Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
+	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, int(crypto.RandBetween(100, 300))))
 
 	var deadline time.Time
 	if d, ok := ctx.Deadline(); ok {

+ 1 - 1
app/dns/nameserver_quic.go

@@ -160,7 +160,7 @@ func (s *QUICNameServer) newReqID() uint16 {
 func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
 	errors.LogInfo(ctx, s.name, " querying: ", domain)
 
-	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
+	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, 0))
 
 	var deadline time.Time
 	if d, ok := ctx.Deadline(); ok {

+ 1 - 1
app/dns/nameserver_tcp.go

@@ -192,7 +192,7 @@ func (s *TCPNameServer) newReqID() uint16 {
 func (s *TCPNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
 	errors.LogDebug(ctx, s.name, " querying DNS for: ", domain)
 
-	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
+	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, 0))
 
 	var deadline time.Time
 	if d, ok := ctx.Deadline(); ok {

+ 1 - 1
app/dns/nameserver_udp.go

@@ -217,7 +217,7 @@ func (s *ClassicNameServer) addPendingRequest(req *udpDnsRequest) {
 func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
 	errors.LogDebug(ctx, s.name, " querying DNS for: ", domain)
 
-	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
+	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP, 0))
 
 	for _, req := range reqs {
 		udpReq := &udpDnsRequest{