Python读取DNS数据包:手动解码缺失QR、OpCode等字段问题
Great question—let's walk through exactly how to extract those DNS flags and parse the packet structure without relying on external libraries. The core issue here is that DNS packets are binary structures, not plain ASCII text. Converting the entire packet to ASCII will only reveal the human-readable domain part, while ignoring the critical binary header fields like QR, OpCode, and others you're trying to access.
Let's break down your example packet step by step:b'\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x10googletagmanager\x03com\x00\x00\x01\x00\x01'
Step 1: Understand DNS Packet Structure
A basic DNS query packet has three core components:
- Header (12 bytes): Stores the packet ID, flags, and counters for questions/responses
- Question Section: Contains the target domain, query type, and network class
- Answer/Authority/Additional Sections: Only present in DNS responses (your packet is a query, so these are empty)
Step 2: Parse the Header Fields
First, isolate the header (dns_packet[:12]) and extract its components:
- ID (first 2 bytes):
\x01\x01→0x0101= 257 (a unique identifier for matching queries to responses) - Flags (next 2 bytes):
\x00\x00→ this 16-bit integer holds all the flags you want to extract (QR, OpCode, etc.) - QDCOUNT (next 2 bytes):
\x01\x00= 1 (number of questions in the packet) - ANCOUNT/NSCOUNT/ARCOUNT: All
\x00\x00(no responses included in this query)
Extracting Flags with Bitwise Operations
The 16-bit flags field follows this strict bit structure (from highest to lowest bit):
Bit: 15 14-11 10 9 8 7 6-4 3 2 1-0 QR OpCode AA TC RD RA Z AD CD RCODE
To pull out each value, we use bitwise shifts and masks to isolate specific bits:
QR: Is this a query (0) or response (1)? →(flags >> 15) & 1OpCode: Type of query (0 = standard, 1 = reverse, etc.) →(flags >> 11) & 0b1111AA: Authoritative Answer (only relevant for responses) →(flags >> 10) & 1TC: Truncated (packet was too large for the connection) →(flags >> 9) & 1RD: Recursion Desired (ask the server to resolve the query recursively) →(flags >> 8) & 1RA: Recursion Available (server supports recursive queries) →(flags >> 7) & 1Z: Reserved field (should always be 0) →(flags >> 4) & 0b111AD: Authenticated Data (DNSSEC verified) →(flags >> 3) & 1CD: Checking Disabled (skip DNSSEC validation) →(flags >> 2) & 1RCODE: Response Code (only relevant for responses) →flags & 0b1111
Step 3: Python Code to Manually Parse the Packet
Here's a complete, library-free example that extracts all the fields you care about:
# Your captured DNS packet dns_packet = b'\x01\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x10googletagmanager\x03com\x00\x00\x01\x00\x01' # Parse header components header = dns_packet[:12] packet_id = int.from_bytes(header[:2], byteorder='big') flags = int.from_bytes(header[2:4], byteorder='big') qd_count = int.from_bytes(header[4:6], byteorder='big') an_count = int.from_bytes(header[6:8], byteorder='big') ns_count = int.from_bytes(header[8:10], byteorder='big') ar_count = int.from_bytes(header[10:12], byteorder='big') # Extract individual flags from the 16-bit integer qr = (flags >> 15) & 1 opcode = (flags >> 11) & 0b1111 aa = (flags >> 10) & 1 tc = (flags >> 9) & 1 rd = (flags >> 8) & 1 ra = (flags >> 7) & 1 z = (flags >> 4) & 0b111 ad = (flags >> 3) & 1 cd = (flags >> 2) & 1 rcode = flags & 0b1111 # Print header information print("DNS Header Info:") print(f"ID: {packet_id}") print(f"QR: {qr} (0 = Query, 1 = Response)") print(f"OpCode: {opcode} (0 = Standard Query)") print(f"AA: {aa}") print(f"TC: {tc}") print(f"RD: {rd}") print(f"RA: {ra}") print(f"Z: {z}") print(f"AD: {ad}") print(f"CD: {cd}") print(f"RCODE: {rcode}") print(f"Question Count: {qd_count}") # Parse the question section (domain, QTYPE, QCLASS) question_section = dns_packet[12:] domain_parts = [] ptr = 0 # Extract domain name (labels are prefixed with length bytes) while True: label_length = question_section[ptr] if label_length == 0: ptr += 1 break # Extract and decode the label to ASCII label = question_section[ptr+1:ptr+1+label_length].decode('ascii') domain_parts.append(label) ptr += 1 + label_length domain = '.'.join(domain_parts) qtype = int.from_bytes(question_section[ptr:ptr+2], byteorder='big') qclass = int.from_bytes(question_section[ptr+2:ptr+4], byteorder='big') # Print question information print("\nDNS Question Info:") print(f"Domain: {domain}") print(f"QTYPE: {qtype} (1 = A Record)") print(f"QCLASS: {qclass} (1 = Internet Class)")
Step 4: Expected Output
Running this code will give you clear, parsed data:
DNS Header Info: ID: 257 QR: 0 (0 = Query, 1 = Response) OpCode: 0 (0 = Standard Query) AA: 0 TC: 0 RD: 0 RA: 0 Z: 0 AD: 0 CD: 0 RCODE: 0 Question Count: 1 DNS Question Info: Domain: www.googletagmanager.com QTYPE: 1 (1 = A Record) QCLASS: 1 (1 = Internet Class)
Key Takeaway
You can't decode the entire DNS packet as ASCII because the header uses binary numerical data. Instead, you need to:
- Split the packet into its structured sections (header, question, etc.)
- Use
int.from_bytes()to convert binary bytes to integers (DNS uses big-endian byte order) - Use bitwise operations to extract individual flags from the 16-bit flags field
内容的提问来源于stack exchange,提问作者allineone

