#dns #domain-name #query #query-response #dns-server #questions #learning

bin+lib crabby_dns

A DNS nameserver system intended to be a learning project both for myself and as an aide in teaching others more about DNS

1 unstable release

0.1.0-alpha.5 Jan 28, 2021

#5 in #question

MIT license

44KB
853 lines

Crabby DNS

A simple DNS server in Rust to help contribute further understanding to my team's office hours discussions.

I made significant reference to the following projects and/or documents in building this:

How to build it

  • Use rustup to install the rust toolchain on your local machine.
  • Navigate to your checked out repo and run cargo build

How to get oriented with the crate

  • Use cargo doc --open to generate crate documentation and view it.

How to run it

  • The unit test suite can be run using cargo test
  • For actually running it, see what you can do with cargo run -- --help as well as the sections below.

DNS Stub Resolver

Right now the Crabby DNS stub resolver implements some of the most basic relevant pieces of IETF RFC 1035, and probably not even that correctly. Only A RRs of class IN are currently supported.

Via the CLI, you specify a query domain name, and optionally a query type and query class. The stub resolver will delegate interface address binding and port selection for the UDP socket to the OS. Your query will be serialized into a DNS protocol message of query type with a header and question section and sent to the configured DNS server. The response will then be listened for (blocking) and deserialized into a DNS protocol message of response type with whatever sections + data the server responded with.

  • Invoke subcommand-specific help via cargo run -- stub --help

Example

$ cargo run -- stub -@ 8.8.8.8 -d google.com
Connected to 8.8.8.8:53 from 10.0.2.15:43506
Working on the DNS transaction now...

#################################################
#               DNS QUESTION MESSAGE            #
#################################################
Header {
    id: 0,
    message_type: Query,
    op_code: Query,
    authoritative_answer: false,
    truncation: false,
    recursion_desired: true,
    recursion_available: false,
    authentic_data: false,
    checking_disabled: false,
    response_code: NoError,
    question_count: 1,
    answer_count: 0,
    authority_count: 0,
    additional_count: 0,
}
Question {
    domain_name: DomainName(
        "google.com",
    ),
    qtype: RRType(
        A,
    ),
    qclass: RRClass(
        IN,
    ),
}

#################################################
#               DNS RESPONSE MESSAGE            #
#################################################
Header {
    id: 0,
    message_type: Response,
    op_code: Query,
    authoritative_answer: false,
    truncation: false,
    recursion_desired: true,
    recursion_available: true,
    authentic_data: false,
    checking_disabled: false,
    response_code: NoError,
    question_count: 1,
    answer_count: 1,
    authority_count: 0,
    additional_count: 0,
}
Question {
    domain_name: DomainName(
        "google.com",
    ),
    qtype: RRType(
        A,
    ),
    qclass: RRClass(
        IN,
    ),
}
ResourceRecord {
    domain_name: DomainName(
        "google.com",
    ),
    rrtype: A,
    rrclass: IN,
    ttl: 102,
    rrdata_len: 4,
    rrdata: A(
        172.217.3.110,
    ),
}

DNS datagram deserializer

  • Invoke subcommand-specific help via cargo run -- deserialize --help
  • You'll need a DNS datagram to feed into the program. Examples of a query and its response in raw form are provided in the /data folder
  • You can generate a query of your own by using netcat to listen on a port where no DNS server is listening, and then dig on that port.
    • nc -u -l 1053 > query.pkt in one terminal
    • dig +retry=0 -p 1053 @127.0.0.1 +noedns google.com in another
  • You can generate a response from this query by using netcat to redirect the query UDP datagram as input to a DNS resolver like Google's 8.8.8.8 and redirect the response to a capturing file.
    • nc -u 8.8.8.8 53 < query.pkt > response.pkt
  • You can view the packet captures as hex if desired
    • hexdump -C response.pkt
  • Tying it all together you can then invoke Crabby DNS
    • cargo run -- deserialize -f ./response.pkt

Example

$ nc -u -l 1053 > query.pkt &
[2] 28112

$ dig +retry=0 -p 1053 @127.0.0.1 +noedns google.com

; <<>> DiG 9.16.1-Ubuntu <<>> +retry -p 1053 @127.0.0.1 +noedns google.com
; (1 server found)
;; global options: +cmd
;; connection timed out; no servers could be reached


$ kill 28112
[2]-  Terminated              nc -u -l 1053 > query.pkt

$ nc -u 8.8.8.8 53 < query.pkt > response.pkt
^C

$ hexdump -C response.pkt
00000000  6e 04 81 80 00 01 00 01  00 00 00 00 06 67 6f 6f  |n............goo|
00000010  67 6c 65 03 63 6f 6d 00  00 01 00 01 c0 0c 00 01  |gle.com.........|
00000020  00 01 00 00 00 15 00 04  ac d9 0c ae              |............|
0000002c

$ cargo run -- deserialize -f ./response.pkt
#########################################
#		DNS MESSAGE		#
#########################################
Header {
    id: 28164,
    message_type: Response,
    op_code: Query,
    authoritative_answer: false,
    truncation: false,
    recursion_desired: true,
    recursion_available: true,
    authentic_data: false,
    checking_disabled: false,
    response_code: NoError,
    question_count: 1,
    answer_count: 1,
    authority_count: 0,
    additional_count: 0,
}
Question {
    domain_name: DomainName(
        "google.com",
    ),
    qtype: RRType(
        A,
    ),
    qclass: RRClass(
        IN,
    ),
}
ResourceRecord {
    domain_name: DomainName(
        "google.com",
    ),
    rrtype: A,
    rrclass: IN,
    ttl: 21,
    rrdata_len: 4,
    rrdata: A(
        172.217.12.174,
    ),
}

Dependencies

~2MB
~28K SLoC