libpnet / libpnet/libpnet

DNS Response Record parsing error

Open
#744 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
2.6k
Forks
329
PR merge metrics
No merged PRs in 30d

Description

Hello,

I am trying to use the pnet crate to do some creative things to DNS packets, and I have encountered an unexpected parsing issue relating to DNS response records. It seems that in the case where a DNS response packet contains multiple response records, these records do not all get added to the correct packet field, but instead each get allocated to singleton vectors in the responses, authorities and additional fields. If the packet contains more than three response records (in any of those categories), these get dropped from the packet by the parser.

Example:

use pnet::packet::ipv4::Ipv4Packet;
use pnet::packet::udp::UdpPacket;
use pnet::packet::Packet;
use pnet::packet::dns::DnsPacket;

const DNS_PACKET: &[u8; 410] = b"\x45\x00\x01\x9a\x03\x59\x00\x00\x40\x11\xc1\xbe\x08\x08\x08\x08\xac\x13\xf8\x18\x00\x35\xae\x5e\x01\x86\x74\xaa\xa5\x14\x81\x80\x00\x01\x00\x03\x00\x04\x00\x05\x0c\x64\x65\x74\x65\x63\x74\x70\x6f\x72\x74\x61\x6c\x07\x66\x69\x72\x65\x66\x6f\x78\x03\x63\x6f\x6d\x00\x00\x01\x00\x01\xc0\x0c\x00\x05\x00\x01\x00\x00\x00\xb9\x00\x1e\x0c\x64\x65\x74\x65\x63\x74\x70\x6f\x72\x74\x61\x6c\x04\x70\x72\x6f\x64\x06\x6d\x6f\x7a\x61\x77\x73\x03\x6e\x65\x74\x00\xc0\x36\x00\x05\x00\x01\x00\x00\x01\xcb\x00\x29\x04\x70\x72\x6f\x64\x0c\x64\x65\x74\x65\x63\x74\x70\x6f\x72\x74\x61\x6c\x04\x70\x72\x6f\x64\x08\x63\x6c\x6f\x75\x64\x6f\x70\x73\x06\x6d\x6f\x7a\x67\x63\x70\xc0\x4f\xc0\x60\x00\x01\x00\x01\x00\x00\x02\x69\x00\x04\x22\x6b\xdd\x52\xc0\x80\x00\x02\x00\x01\x00\x00\x86\xc5\x00\x1c\x0b\x6e\x73\x2d\x63\x6c\x6f\x75\x64\x2d\x63\x31\x0d\x67\x6f\x6f\x67\x6c\x65\x64\x6f\x6d\x61\x69\x6e\x73\xc0\x21\xc0\x80\x00\x02\x00\x01\x00\x00\x86\xc5\x00\x0e\x0b\x6e\x73\x2d\x63\x6c\x6f\x75\x64\x2d\x63\x34\xc0\xb1\xc0\x80\x00\x02\x00\x01\x00\x00\x86\xc5\x00\x0e\x0b\x6e\x73\x2d\x63\x6c\x6f\x75\x64\x2d\x63\x32\xc0\xb1\xc0\x80\x00\x02\x00\x01\x00\x00\x86\xc5\x00\x0e\x0b\x6e\x73\x2d\x63\x6c\x6f\x75\x64\x2d\x63\x33\xc0\xb1\xc0\xcd\x00\x01\x00\x01\x00\x00\x04\xec\x00\x04\xd8\xef\x26\x6c\xc0\xe7\x00\x1c\x00\x01\x00\x00\x01\x2c\x00\x10\x20\x01\x48\x60\x48\x02\x00\x34\x00\x00\x00\x00\x00\x00\x00\x6c\xc1\x01\x00\x1c\x00\x01\x00\x01\xd0\x59\x00\x10\x20\x01\x48\x60\x48\x02\x00\x36\x00\x00\x00\x00\x00\x00\x00\x6c\xc0\xcd\x00\x1c\x00\x01\x00\x00\x34\x9b\x00\x10\x20\x01\x48\x60\x48\x02\x00\x38\x00\x00\x00\x00\x00\x00\x00\x6c\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00";

fn main() {
    let dns_pkt = Ipv4Packet::new(DNS_PACKET.as_slice()).and_then(|ip_pkt| UdpPacket::owned(ip_pkt.payload().to_vec())).and_then(|udp_pkt| DnsPacket::owned(udp_pkt.payload().to_vec())).unwrap();
    println!("{dns_pkt:?}");
}

This gives the following output. There should be 3 response records in the answers field, 4 in the authorities field, and 5 in the additional field. Instead, the 3 from the responses field are distributed over all three fields. The rest are missing completely.

DnsPacket { 
    id : 42260, 
    is_response : 1, 
    opcode : StandardQuery, 
    is_authoriative : 0, 
    is_truncated : 0, 
    is_recursion_desirable : 1, 
    is_recursion_available : 1, 
   zero_reserved : 0, 
   is_answer_authenticated : 0, 
   is_non_authenticated_data : 0, 
   rcode : NoError, 
   query_count : 1, 
   response_count : 3, 
   authority_rr_count : 4, 
   additional_rr_count : 5, 
   queries : [DnsQuery { qname: [12, 100, 101, 116, 101, 99, 116, 112, 111, 114, 116, 97, 108, 7, 102, 105, 114, 101, 102, 111, 120, 3, 99, 111, 109, 0], qtype: DnsType(1), qclass: DnsClass(1), payload: [] }], 
   responses : [DnsResponse { name_tag: 49164, rtype: DnsType(5), rclass: DnsClass(1), ttl: 185, data_len: 30, data: [12, 100, 101, 116, 101, 99, 116, 112, 111, 114, 116, 97, 108, 4, 112, 114, 111, 100, 6, 109, 111, 122, 97, 119, 115, 3, 110, 101, 116, 0], payload: [] }], 
   authorities : [DnsResponse { name_tag: 49206, rtype: DnsType(5), rclass: DnsClass(1), ttl: 459, data_len: 41, data: [4, 112, 114, 111, 100, 12, 100, 101, 116, 101, 99, 116, 112, 111, 114, 116, 97, 108, 4, 112, 114, 111, 100, 8, 99, 108, 111, 117, 100, 111, 112, 115, 6, 109, 111, 122, 103, 99, 112, 192, 79], payload: [] }], 
   additional : [DnsResponse { name_tag: 49248, rtype: DnsType(1), rclass: DnsClass(1), ttl: 617, data_len: 4, data: [34, 107, 221, 82], payload: [] }]
}

Compare this to Python's scapy parsing the same packet.


In [1]: from scapy.all import IP, UDP, DNS, DNSRR

In [2]: p = IP(b"\x45\x00\x01\x9a\x03\x59\x00\x00\x40\x11\xc1\xbe\x08\x08\x08\x08\xac\x13\xf8\x18\x00\x35\xae\x5e\x01\x86\x74\xaa\xa5\x1
      ⋮ 4\x81\x80\x00\x01\x00\x03\x00\x04\x00\x05\x0c\x64\x65\x74\x65\x63\x74\x70\x6f\x72\x74\x61\x6c\x07\x66\x69\x72\x65\x66\x6f\x78\x0
      ⋮ 3\x63\x6f\x6d\x00\x00\x01\x00\x01\xc0\x0c\x00\x05\x00\x01\x00\x00\x00\xb9\x00\x1e\x0c\x64\x65\x74\x65\x63\x74\x70\x6f\x72\x74\x6
      ⋮ 1\x6c\x04\x70\x72\x6f\x64\x06\x6d\x6f\x7a\x61\x77\x73\x03\x6e\x65\x74\x00\xc0\x36\x00\x05\x00\x01\x00\x00\x01\xcb\x00\x29\x04\x7
      ⋮ 0\x72\x6f\x64\x0c\x64\x65\x74\x65\x63\x74\x70\x6f\x72\x74\x61\x6c\x04\x70\x72\x6f\x64\x08\x63\x6c\x6f\x75\x64\x6f\x70\x73\x06\x6
      ⋮ d\x6f\x7a\x67\x63\x70\xc0\x4f\xc0\x60\x00\x01\x00\x01\x00\x00\x02\x69\x00\x04\x22\x6b\xdd\x52\xc0\x80\x00\x02\x00\x01\x00\x00\x8
      ⋮ 6\xc5\x00\x1c\x0b\x6e\x73\x2d\x63\x6c\x6f\x75\x64\x2d\x63\x31\x0d\x67\x6f\x6f\x67\x6c\x65\x64\x6f\x6d\x61\x69\x6e\x73\xc0\x21\xc
      ⋮ 0\x80\x00\x02\x00\x01\x00\x00\x86\xc5\x00\x0e\x0b\x6e\x73\x2d\x63\x6c\x6f\x75\x64\x2d\x63\x34\xc0\xb1\xc0\x80\x00\x02\x00\x01\x0
      ⋮ 0\x00\x86\xc5\x00\x0e\x0b\x6e\x73\x2d\x63\x6c\x6f\x75\x64\x2d\x63\x32\xc0\xb1\xc0\x80\x00\x02\x00\x01\x00\x00\x86\xc5\x00\x0e\x0
      ⋮ b\x6e\x73\x2d\x63\x6c\x6f\x75\x64\x2d\x63\x33\xc0\xb1\xc0\xcd\x00\x01\x00\x01\x00\x00\x04\xec\x00\x04\xd8\xef\x26\x6c\xc0\xe7\x0
      ⋮ 0\x1c\x00\x01\x00\x00\x01\x2c\x00\x10\x20\x01\x48\x60\x48\x02\x00\x34\x00\x00\x00\x00\x00\x00\x00\x6c\xc1\x01\x00\x1c\x00\x01\x0
      ⋮ 0\x01\xd0\x59\x00\x10\x20\x01\x48\x60\x48\x02\x00\x36\x00\x00\x00\x00\x00\x00\x00\x6c\xc0\xcd\x00\x1c\x00\x01\x00\x00\x34\x9b\x0
      ⋮ 0\x10\x20\x01\x48\x60\x48\x02\x00\x38\x00\x00\x00\x00\x00\x00\x00\x6c\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00")

In [3]: p.show()
###[ IP ]###
  version   = 4
  ihl       = 5
  tos       = 0x0
  len       = 410
  id        = 857
  flags     =
  frag      = 0
  ttl       = 64
  proto     = udp
  chksum    = 0xc1be
  src       = 8.8.8.8
  dst       = 172.19.248.24
  \options   \
###[ UDP ]###
     sport     = domain
     dport     = 44638
     len       = 390
     chksum    = 0x74aa
###[ DNS ]###
        id        = 42260
        qr        = 1
        opcode    = QUERY
        aa        = 0
        tc        = 0
        rd        = 1
        ra        = 1
        z         = 0
        ad        = 0
        cd        = 0
        rcode     = ok
        qdcount   = 1
        ancount   = 3
        nscount   = 4
        arcount   = 5
        \qd        \
         |###[ DNS Question Record ]###
         |  qname     = b'detectportal.firefox.com.'
         |  qtype     = A
         |  unicastresponse= 0
         |  qclass    = IN
        \an        \
         |###[ DNS Resource Record ]###
         |  rrname    = b'detectportal.firefox.com.'
         |  type      = CNAME
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 185
         |  rdlen     = None
         |  rdata     = b'detectportal.prod.mozaws.net.'
         |###[ DNS Resource Record ]###
         |  rrname    = b'detectportal.prod.mozaws.net.'
         |  type      = CNAME
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 459
         |  rdlen     = None
         |  rdata     = b'prod.detectportal.prod.cloudops.mozgcp.net.'
         |###[ DNS Resource Record ]###
         |  rrname    = b'prod.detectportal.prod.cloudops.mozgcp.net.'
         |  type      = A
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 617
         |  rdlen     = None
         |  rdata     = 34.107.221.82
        \ns        \
         |###[ DNS Resource Record ]###
         |  rrname    = b'mozgcp.net.'
         |  type      = NS
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 34501
         |  rdlen     = None
         |  rdata     = b'ns-cloud-c1.googledomains.com.'
         |###[ DNS Resource Record ]###
         |  rrname    = b'mozgcp.net.'
         |  type      = NS
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 34501
         |  rdlen     = None
         |  rdata     = b'ns-cloud-c4.googledomains.com.'
         |###[ DNS Resource Record ]###
         |  rrname    = b'mozgcp.net.'
         |  type      = NS
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 34501
         |  rdlen     = None
         |  rdata     = b'ns-cloud-c2.googledomains.com.'
         |###[ DNS Resource Record ]###
         |  rrname    = b'mozgcp.net.'
         |  type      = NS
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 34501
         |  rdlen     = None
         |  rdata     = b'ns-cloud-c3.googledomains.com.'
        \ar        \
         |###[ DNS Resource Record ]###
         |  rrname    = b'ns-cloud-c4.googledomains.com.'
         |  type      = A
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 1260
         |  rdlen     = None
         |  rdata     = 216.239.38.108
         |###[ DNS Resource Record ]###
         |  rrname    = b'ns-cloud-c2.googledomains.com.'
         |  type      = AAAA
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 300
         |  rdlen     = None
         |  rdata     = 2001:4860:4802:34::6c
         |###[ DNS Resource Record ]###
         |  rrname    = b'ns-cloud-c3.googledomains.com.'
         |  type      = AAAA
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 118873
         |  rdlen     = None
         |  rdata     = 2001:4860:4802:36::6c
         |###[ DNS Resource Record ]###
         |  rrname    = b'ns-cloud-c4.googledomains.com.'
         |  type      = AAAA
         |  cacheflush= 0
         |  rclass    = IN
         |  ttl       = 13467
         |  rdlen     = None
         |  rdata     = 2001:4860:4802:38::6c
         |###[ DNS OPT Resource Record ]###
         |  rrname    = b'.'
         |  type      = OPT
         |  rclass    = 4096
         |  extrcode  = 0
         |  version   = 0
         |  z         = 0
         |  rdlen     = None
         |  \rdata     \

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the DnsPacket::owned entry point in the reproduction and inspect how the DNS response, authority, and additional counts are parsed. Use the supplied packet to verify that all 3 responses, 4 authorities, and 5 additional records remain in their respective fields after parsing.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.