[DNS Server] stage 7 (#YC9) passed but the response is malformed

My code passed #YC9 even if the answer section doesn’t contain proper response

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"net"
)

/*
*	 0   1 2 3 4    5  6  7  8   9 10 11   12 13 14 15
*  |QR|   Opcode   |AA|TC|RD|RA|    Z    |    RCODE   |
*
*	QR      	Query/Response Indicator, 1 bit
* 	OPCODE  	Operation Code, 4 bit
* 	AA      	Authoritative Answer, 1 bit
* 	TC      	Truncation, 1 bit
* 	RD      	Recursion Desired, 1 bit
* 	RA      	Recursion Available, 1 bit
* 	Z       	Reserved, 3 bit
* 	RCODE   	Response Code, 4 bit
 */
type DNSHeader struct {
	ID      uint16 // Packet Identifier (bytes 0-1)
	FLAGS   uint16 // Bits in the middle packed into a 2 byte flag (bytes 2-3)
	QDCOUNT uint16 // Question Count (bytes 4-5)
	ANCOUNT uint16 // Answer Record Count (bytes 6-7)
	NSCOUNT uint16 // Authority Record Count (bytes 8-9)
	ARCOUNT uint16 // Additional Record Count (bytes 10-11)
}

func (h *DNSHeader) ToBytes() []byte {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, h)
	return buf.Bytes()
}

func main() {
	udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:2053")
	if err != nil {
		fmt.Println("Failed to resolve UDP address:", err)
		return
	}

	udpConn, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		fmt.Println("Failed to bind to address:", err)
		return
	}
	defer udpConn.Close()

	buf := make([]byte, 512)

	for {
		_, source, err := udpConn.ReadFromUDP(buf)
		if err != nil {
			fmt.Println("Error receiving data:", err)
			break
		}

		header := buf[:12]
		// Set QR bit to make it a response
		header[2] |= 0x80 // 10000000 in binary

		flags := binary.BigEndian.Uint16(header[2:4])
		opcode := (flags >> 11) & 0x0F // 0x0F = 0b1111 in binary, can also (flags >> 11) & 15
		if opcode != 0 {
			// Clear the bottom 4 bits (RCODE) and set to 4
			flags = (flags & 0xFFF0) | 4                   // 0xFFF0 = 0b1111111111110000
			binary.BigEndian.PutUint16(header[2:4], flags) // Write the updated flags back to the header
		}

		qdCount := binary.BigEndian.Uint16(header[4:6])
		binary.BigEndian.PutUint16(header[6:8], qdCount) // Set ANCOUNT (bytes 6-7)

		names, questionEnd := getQuestionEnd(buf, qdCount)
		question := buf[12:questionEnd]

		answer := []byte{}
		for _, name := range names {
			answer = append(answer, name...)                   // Copy name first
			answer = binary.BigEndian.AppendUint16(answer, 1)  // Type: 1 (A record)
			answer = binary.BigEndian.AppendUint16(answer, 1)  // Class: 1 (IN)
			answer = binary.BigEndian.AppendUint32(answer, 60) // TTL: 60
			answer = binary.BigEndian.AppendUint16(answer, 4)  // RDLENGTH: 4 bytes
			answer = append(answer, 8, 8, 8, 8)                // RDATA: 8.8.8.8
		}

		response := bytes.Join([][]byte{header, question, answer}, nil)

		_, err = udpConn.WriteToUDP(response, source)
		if err != nil {
			fmt.Println("Failed to send response:", err)
		}
	}
}

func getQuestionEnd(buf []byte, qdCount uint16) ([][]byte, int) {
	pos := 12 // start after header

	currQd := 0
	names := [][]byte{}
	nameMap := make(map[int][]byte)

	// iterate through domain labels till we get a null terminator
	for currQd != int(qdCount) {
		// if 1st 2 bit of 1st byte is 11, then it is pointer, the next byte is label start pos
		// if 1st 2 bit of 1st byte is 00, then treat it as length of label
		start := pos
		firstByte := buf[pos]
		if firstByte == 0xc0 { // 0b1100_0000
			pos += 1
			names = append(names, nameMap[int(buf[pos])])
			pos += 1
		} else if firstByte != 0 {
			for buf[pos] != 0 {
				labelLen := int(buf[pos])
				pos += labelLen + 1
			}

			pos += 1                        // null terminator itself
			nameMap[start] = buf[start:pos] // domain name with
			fmt.Println("found name:", string(nameMap[start]))
			names = append(names, nameMap[start])
		}

		currQd++
		pos += 4 // QTYPE and QCLASS
	}

	return names, pos
}

[tester::#YC9] Querying the following in the same request (Messages with >> prefix are part of this log)
[tester::#YC9] >> ;abc.longassdomainname.com. IN A
[tester::#YC9] >> ;def.longassdomainname.com. IN A
[tester::#YC9] Sending Request: (Messages with >>> prefix are part of this log)
[tester::#YC9] >>> ;; opcode: QUERY, status: NOERROR, id: 28447
[tester::#YC9] >>> ;; flags: rd; QUERY: 2, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
[tester::#YC9] >>>
[tester::#YC9] >>> ;; QUESTION SECTION:
[tester::#YC9] >>> ;abc.longassdomainname.com. IN A
[tester::#YC9] >>> ;def.longassdomainname.com. IN A
[tester::#YC9] >>>
[your_program] found name: abclongassdomainnamecom
[your_program] found name: def�
[tester::#YC9] Received Response: (Messages with >>> prefix are part of this log)
[tester::#YC9] >>> ;; opcode: QUERY, status: NOERROR, id: 28447
[tester::#YC9] >>> ;; flags: qr rd; QUERY: 2, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
[tester::#YC9] >>>
[tester::#YC9] >>> ;; QUESTION SECTION:
[tester::#YC9] >>> ;abc.longassdomainname.com. IN A
[tester::#YC9] >>> ;def.longassdomainname.com. IN A
[tester::#YC9] >>>
[tester::#YC9] >>> ;; ANSWER SECTION:
[tester::#YC9] >>> . 0 CLASS0 TYPE0 # 0
[tester::#YC9] >>> . 0 CLASS0 TYPE0 # 0
[tester::#YC9] >>>
[tester::#YC9] Test passed.
[tester::#YC9] Terminating program
[tester::#YC9] Shutting down DNS resolver server…
[tester::#YC9] Program terminated successfully

The same happened with me. I think tester is only checking ANCOUNT in the response header

We’ve noted this issue and will circle back once we’re working on the DNS challenge. Closing this for now.

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.