• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 //! Format DoH requests
18 
19 use anyhow::{anyhow, Context, Result};
20 use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
21 use quiche::h3;
22 use ring::rand::SecureRandom;
23 use url::Url;
24 
25 pub type DnsRequest = Vec<quiche::h3::Header>;
26 
27 const NS_T_AAAA: u8 = 28;
28 const NS_C_IN: u8 = 1;
29 // Used to randomly generate query prefix and query id.
30 const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
31                          abcdefghijklmnopqrstuvwxyz\
32                          0123456789";
33 
34 /// Produces a DNS query with randomized query ID and random 6-byte charset-legal prefix to produce
35 /// a request for a domain of the form:
36 /// ??????-dnsohttps-ds.metric.gstatic.com
37 #[rustfmt::skip]
probe_query() -> Result<String>38 pub fn probe_query() -> Result<String> {
39     let mut rnd = [0; 8];
40     ring::rand::SystemRandom::new().fill(&mut rnd).context("failed to generate probe rnd")?;
41     let c = |byte| CHARSET[(byte as usize) % CHARSET.len()];
42     let query = vec![
43         rnd[6], rnd[7],  // [0-1]   query ID
44         1,      0,       // [2-3]   flags; query[2] = 1 for recursion desired (RD).
45         0,      1,       // [4-5]   QDCOUNT (number of queries)
46         0,      0,       // [6-7]   ANCOUNT (number of answers)
47         0,      0,       // [8-9]   NSCOUNT (number of name server records)
48         0,      0,       // [10-11] ARCOUNT (number of additional records)
49         19,     c(rnd[0]), c(rnd[1]), c(rnd[2]), c(rnd[3]), c(rnd[4]), c(rnd[5]), b'-', b'd', b'n',
50         b's',   b'o',      b'h',      b't',      b't',      b'p',      b's',      b'-', b'd', b's',
51         6,      b'm',      b'e',      b't',      b'r',      b'i',      b'c',      7,    b'g', b's',
52         b't',   b'a',      b't',      b'i',      b'c',      3,         b'c',      b'o', b'm',
53         0,                  // null terminator of FQDN (root TLD)
54         0,      NS_T_AAAA,  // QTYPE
55         0,      NS_C_IN     // QCLASS
56     ];
57     Ok(BASE64_URL_SAFE_NO_PAD.encode(query))
58 }
59 
60 /// Takes in a base64-encoded copy of a traditional DNS request and a
61 /// URL at which the DoH server is running and produces a set of HTTP/3 headers
62 /// corresponding to a DoH request for it.
dns_request(base64_query: &str, url: &Url) -> Result<DnsRequest>63 pub fn dns_request(base64_query: &str, url: &Url) -> Result<DnsRequest> {
64     let mut path = String::from(url.path());
65     path.push_str("?dns=");
66     path.push_str(base64_query);
67     let req = vec![
68         h3::Header::new(b":method", b"GET"),
69         h3::Header::new(b":scheme", b"https"),
70         h3::Header::new(
71             b":authority",
72             url.host_str().ok_or_else(|| anyhow!("failed to get host"))?.as_bytes(),
73         ),
74         h3::Header::new(b":path", path.as_bytes()),
75         h3::Header::new(b"user-agent", b"quiche"),
76         h3::Header::new(b"accept", b"application/dns-message"),
77     ];
78 
79     Ok(req)
80 }
81 
82 #[cfg(test)]
83 mod tests {
84     use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
85     use quiche::h3::NameValue;
86     use url::Url;
87 
88     const PROBE_QUERY_SIZE: usize = 56;
89     const H3_DNS_REQUEST_HEADER_SIZE: usize = 6;
90     const LOCALHOST_URL: &str = "https://mylocal.com/dns-query";
91 
92     #[test]
make_probe_query_and_request()93     fn make_probe_query_and_request() {
94         let probe_query = super::probe_query().unwrap();
95         let url = Url::parse(LOCALHOST_URL).unwrap();
96         let request = super::dns_request(&probe_query, &url).unwrap();
97         // Verify H3 DNS request.
98         assert_eq!(request.len(), H3_DNS_REQUEST_HEADER_SIZE);
99         assert_eq!(request[0].name(), b":method");
100         assert_eq!(request[0].value(), b"GET");
101         assert_eq!(request[1].name(), b":scheme");
102         assert_eq!(request[1].value(), b"https");
103         assert_eq!(request[2].name(), b":authority");
104         assert_eq!(request[2].value(), url.host_str().unwrap().as_bytes());
105         assert_eq!(request[3].name(), b":path");
106         let mut path = String::from(url.path());
107         path.push_str("?dns=");
108         path.push_str(&probe_query);
109         assert_eq!(request[3].value(), path.as_bytes());
110         assert_eq!(request[5].name(), b"accept");
111         assert_eq!(request[5].value(), b"application/dns-message");
112 
113         // Verify DNS probe packet.
114         let bytes = BASE64_URL_SAFE_NO_PAD.decode(probe_query).unwrap();
115         assert_eq!(bytes.len(), PROBE_QUERY_SIZE);
116     }
117 }
118