1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
//! DNS implementation in Rust with no dependencies other than Rand<br>

//! See [DNS RFC Notes](https://github.com/willfleetw/rusty_dns/blob/main/docs/DNS_RFC_Notes.md) for notes on DNS protocols.


//TODO Better error piping? Should we create a error type and handle each case explicitly?

//TODO Improve testing to check against invalid input


/// DNS packet structures and operations.

pub mod dns_packet;

/// DNS Domain Name operations.

pub mod domain_name;

/// Default DNS protocol port.

pub const DNS_PORT: u8 = 53;

/// The size of a valid DNS packet header.

pub const DNS_HEADER_SIZE: usize = 12;

/// DNS OPCODE values.

pub mod opcodes {
    /// A standard query (QUERY).

    pub const DNS_OPCODE_QUERY: u8 = 0;
    /// An inverse query (IQUERY).

    pub const DNS_OPCODE_IQUERY: u8 = 1;
    /// A server status request (STATUS).

    pub const DNS_OPCODE_STATUS: u8 = 2;
}

/// RCODE values.

pub mod rcodes {
    /// No error condition.

    pub const DNS_RCODE_NO_ERROR: u8 = 0;
    /// Format error - The name server was uanble to interpret the query.

    pub const DNS_RCODE_FORMAT_ERROR: u8 = 1;
    /// Server failure - The name server was uanble to process this query.

    pub const DNS_RCODE_SERVER_ERROR: u8 = 2;
    /// Name error - Meaningful only for responses from an authoritative server.

    /// This code signifies that the domain name referenced in the query does not exist.

    pub const DNS_RCODE_NAME_ERROR: u8 = 3;
    /// Not implemented - The name server does not support the requested kind of query.

    pub const DNS_RCODE_NOT_IMPLEMENTED: u8 = 4;
    /// Refused - The name server refuses to perform the specified operation.

    pub const DNS_RCODE_REFUSED: u8 = 5;
}

/// CLASS values, all of which are a subset of QCLASS values.

/// # NOTE

/// All CLASS values are a subset of QCLASS values.

pub mod classes {
    /// The Internet class.

    pub const DNS_CLASS_IN: u16 = 1;
    /// The CSNET class (Obsolete).

    pub const DNS_CLASS_CS: u16 = 2;
    /// The CHAOS class.

    pub const DNS_CLASS_CH: u16 = 3;
    /// The HESIOD class.

    pub const DNS_CLASS_HS: u16 = 4;
}

/// QCLASS values, used in the question section of a DNS packet.

/// # NOTE

/// All CLASS values are a subset of QCLASS values.

pub mod qclasses {
    /// QCLASS ANY can be used in a question to represent any possible desired class.

    pub const DNS_QCLASS_ANY: u16 = 255;
}

/// TYPE values, all of which are a subset of QTYPE values.

/// # NOTE

/// All TYPE values are a subset of QTYPES.

pub mod types {
    /// An IPv4 host address.

    pub const DNS_TYPE_A: u16 = 1;
    /// An authoritative name server.

    pub const DNS_TYPE_NS: u16 = 2;
    /// A mail destination (Obsolete - replaced by MX).

    pub const DNS_TYPE_MD: u16 = 3;
    /// A mail forwarder (Obsolete - replaced by MX).

    pub const DNS_TYPE_MF: u16 = 4;
    /// The canonical name for an alias.

    pub const DNS_TYPE_CNAME: u16 = 5;
    /// Marks the start of a zone of authority.

    pub const DNS_TYPE_SOA: u16 = 6;
    /// A mailbox domain name.

    pub const DNS_TYPE_MB: u16 = 7;
    /// A mail group member.

    pub const DNS_TYPE_MG: u16 = 8;
    /// A mail rename domain name.

    pub const DNS_TYPE_MR: u16 = 9;
    /// An experimental RR containing any possible data.

    pub const DNS_TYPE_NULL: u16 = 10;
    /// A well known service description.

    pub const DNS_TYPE_WKS: u16 = 11;
    /// A domain name pointer.

    pub const DNS_TYPE_PTR: u16 = 12;
    /// Host information.

    pub const DNS_TYPE_HINFO: u16 = 13;
    /// Mailbox or mail list information.

    pub const DNS_TYPE_MINFO: u16 = 14;
    /// Mail exchange.

    pub const DNS_TYPE_MX: u16 = 15;
    /// Text strings.

    pub const DNS_TYPE_TXT: u16 = 16;
    /// An IPv6 host address.

    pub const DNS_TYPE_AAAA: u16 = 28;
    /// Specifies location of a service for a specific protocol.

    pub const DNS_TYPE_SRV: u16 = 33;
}

/// QTYPE values, used in the question section of a DNS packet.

/// # NOTE

/// All TYPE values are a subset of QTYPES.

pub mod qtypes {
    /// A request for a transfer of an entire zone.

    pub const DNS_QTYPE_AXFR: u16 = 252;
    /// A request for mailbox-related records (MB, MG, or MR).

    pub const DNS_QTYPE_MAILB: u16 = 253;
    /// A request mail agent resource records (Obsolete - see MX).

    pub const DNS_QTYPE_MAILA: u16 = 254;
    /// A request for all records.

    pub const DNS_QTYPE_ANY: u16 = 255;
}

/// Example DNS query packets in network format

pub mod query_examples {
    pub const BASIC_QUERY: &'static [u8] = &[
        0x24, 0xB1, //ID

        0x01, 0x80, //QR=0,OPCODE=0,AA=0,TC=0,RD=1,RA=1,Z=0,RCODE=0

        0x00, 0x01, //QDCOUNT

        0x00, 0x00, //ANCOUNT

        0x00, 0x00, //NSCOUNT

        0x00, 0x00, //ARCOUNT

        0x03, 0x77, 0x77, 0x77, // www

        0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6C, 0x65, // google

        0x03, 0x63, 0x6F, 0x6D, // com

        0x00, // root

        0x00, 0x01, //QTYPE=1

        0x00, 0x01, //QCLASS=1

    ];

    pub const BASIC_QUERY_RESPONSE: &'static [u8] = &[
        0x24, 0xB1, //ID

        0x81, 0x80, //QR=1,OPCODE=0,AA=0,TC=0,RD=1,RA=1,Z=0,RCODE=0

        0x00, 0x01, //QDCOUNT

        0x00, 0x01, //ANCOUNT

        0x00, 0x00, //NSCOUNT

        0x00, 0x00, //ARCOUNT

        0x03, 0x77, 0x77, 0x77, // www

        0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6C, 0x65, // google

        0x03, 0x63, 0x6F, 0x6D, // com

        0x00, // root

        0x00, 0x01, //QTYPE=1

        0x00, 0x01, //QCLASS=1

        0xC0, 0x0C, 0x00, 0x01, //TYPE=1

        0x00, 0x01, //CLASS=1

        0x00, 0x00, 0x02, 0x58, // TTL=600

        0x00, 0x04, //RDLENGTH=4

        0xD8, 0x3A, 0xD9, 0x24, //RDATA = 216.58.217.36

    ];

    pub const NAME_COMPRESSION_QUERY: &'static [u8] = &[
        0x24, 0xB1, //ID

        0x01, 0x80, //QR=0,OPCODE=0,AA=0,TC=0,RD=1,RA=1,Z=0,RCODE=0

        0x00, 0x02, //QDCOUNT

        0x00, 0x00, //ANCOUNT

        0x00, 0x00, //NSCOUNT

        0x00, 0x00, //ARCOUNT

        0x03, 0x77, 0x77, 0x77, // www

        0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6C, 0x65, // google

        0x03, 0x63, 0x6F, 0x6D, // com

        0x00, // root

        0x00, 0x01, //QTYPE=1

        0x00, 0x01, //QCLASS=1

        0x07, 0x70, 0x6F, 0x69, 0x6E, 0x74, 0x65, 0x72, // pointer

        0xC0, 0x0C, // offset pointer to www.google.com.

        0x00, 0x02, //QTYPE=2

        0x00, 0x03, //QCLASS=3

    ];
}

/// Send a DNS packet to the given destination, returns the response

pub fn send_dns_query_to(
    dns_packet: &dns_packet::DnsPacket,
    destination: &String,
) -> Result<dns_packet::DnsPacket, String> {
    let client_socket = std::net::UdpSocket::bind("0.0.0.0:0").expect("Client could not bind");

    let serialized_dns_packet = dns_packet.serialize()?;

    client_socket
        .send_to(&serialized_dns_packet, destination)
        .expect("Client could not send data");

    match client_socket.set_read_timeout(Some(std::time::Duration::from_secs(2))) {
        Ok(_) => {}
        Err(_) => {
            return Err("Could not set query socket timeout".into());
        }
    }

    let mut buf: [u8; 65535] = [0; 65535];
    let (amt, _) = client_socket
        .recv_from(&mut buf)
        .expect("Client could not recieve data from google dns");
    let buf = &buf[..amt];

    let dns_response = dns_packet::DnsPacket::parse_dns_packet(&buf.into())?;

    Ok(dns_response)
}

/// Resolve a domain name via DNS through a Google recursive resolver

pub fn resolve_domain_name(domain_name: &String) -> Result<std::net::Ipv4Addr, String> {
    let dns_packet = dns_packet::DnsPacket::new(domain_name, types::DNS_TYPE_A)?;

    let dns_response = send_dns_query_to(&dns_packet, &String::from("8.8.8.8:53"))?;

    match dns_response.header.rcode {
        rcodes::DNS_RCODE_NO_ERROR => {}
        _ => {
            return Err(format!(
                "Recursive resolver could not find {}, returned RCODE={}",
                domain_name, dns_response.header.rcode
            ));
        }
    }

    let position = dns_response
        .answer
        .iter()
        .position(|record| record.rrtype == types::DNS_TYPE_A)
        .ok_or("DNS response had no A records")?;

    match dns_response.answer[position].rdata {
        crate::dns_packet::dns_resource_record::DnsResourceRecordData::A(address) => {
            return Ok(address);
        }
        _ => {
            return Err("Did not match A resource record".into());
        }
    }
}