1 //! A simple tokio-based HTTP server which serves DNSSEC proofs in RFC 9102 format.
5 // const_slice_from_raw_parts was stabilized in 1.64, however we support building on 1.63 as well.
6 // Luckily, it seems to work fine in 1.63 with the feature flag (and RUSTC_BOOTSTRAP=1) enabled.
7 #![cfg_attr(all(feature = "validation", rust_1_63), feature(const_slice_from_raw_parts))]
9 #![allow(clippy::needless_lifetimes)] // lifetimes improve readability
10 #![allow(clippy::needless_borrow)] // borrows indicate read-only/non-move
11 #![allow(clippy::too_many_arguments)] // sometimes we don't have an option
12 #![allow(clippy::identity_op)] // sometimes identities improve readability for repeated actions
13 #![allow(clippy::erasing_op)] // sometimes identities improve readability for repeated actions
21 #[cfg(feature = "validation")]
23 #[cfg(feature = "validation")]
25 #[cfg(feature = "validation")]
28 #[cfg(any(feature = "build_server", all(feature = "tokio", feature = "validation")))]
29 use tokio_crate as tokio;
31 #[cfg(feature = "build_server")]
34 let resolver_sockaddr = std::env::var("RESOLVER")
35 .expect("Please set the RESOLVER env variable to the TCP socket of a recursive DNS resolver")
36 .parse().expect("RESOLVER was not a valid socket address");
37 let bind_addr = std::env::var("BIND")
38 .expect("Please set the BIND env variable to a socket address to listen on");
40 let listener = tokio::net::TcpListener::bind(bind_addr).await
41 .expect("Failed to bind to socket");
42 imp::run_server(listener, resolver_sockaddr).await;
45 #[cfg(any(feature = "build_server", all(feature = "tokio", feature = "validation")))]
52 use std::net::SocketAddr;
54 use tokio::net::TcpListener;
55 use tokio::io::{AsyncReadExt, AsyncWriteExt};
57 pub(super) async fn run_server(listener: TcpListener, resolver_sockaddr: SocketAddr) {
59 let (mut socket, _) = listener.accept().await.expect("Failed to accept new TCP connection");
60 tokio::spawn(async move {
61 let mut response = ("400 Bad Request", "Bad Request");
62 'ret_err: loop { // goto label
63 let mut buf = [0; 4096];
66 if buf_pos == buf.len() { response.1 = "Request Too Large"; break 'ret_err; }
67 let read_res = { socket.read(&mut buf[buf_pos..]).await };
72 for window in buf[..buf_pos].windows(2) {
73 if window == b"\r\n" { break 'read_req; }
76 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {},
81 if let Ok(s) = std::str::from_utf8(&buf[..buf_pos]) {
82 if let Some((r, _)) = s.split_once("\r\n") {
92 let mut parts = request.split(" ");
93 let (verb, path, http_vers);
94 if let Some(v) = parts.next() { verb = v; } else { break 'ret_err; }
95 if let Some(p) = parts.next() { path = p; } else { break 'ret_err; }
96 if let Some(v) = parts.next() { http_vers = v; } else { break 'ret_err; }
97 if parts.next().is_some() { break; }
98 if verb != "GET" { break; }
99 if http_vers != "HTTP/1.1" && http_vers != "HTTP/1.0" { break 'ret_err; }
101 const PATH_PFX: &'static str = "/dnssecproof?";
102 if !path.starts_with(PATH_PFX) {
103 response = ("404 Not Found", "Not Found");
107 let (mut d, mut t) = ("", "");
108 for arg in path[PATH_PFX.len()..].split("&") {
109 if let Some((k, v)) = arg.split_once("=") {
115 } else { break 'ret_err; }
118 if d == "" || t == "" {
119 response.1 = "Missing d or t URI parameters";
122 let query_name = if let Ok(domain) = Name::try_from(d) { domain } else {
123 response.1 = "Failed to parse domain, make sure it ends with .";
126 let proof_res = match t.to_ascii_uppercase().as_str() {
127 "TXT" => build_txt_proof_async(resolver_sockaddr, &query_name).await,
128 "TLSA" => build_tlsa_proof_async(resolver_sockaddr, &query_name).await,
129 "A" => build_a_proof_async(resolver_sockaddr, &query_name).await,
130 "AAAA" => build_aaaa_proof_async(resolver_sockaddr, &query_name).await,
133 let (proof, cache_ttl) = if let Ok(proof) = proof_res { proof } else {
134 response = ("404 Not Found", "Failed to generate proof for given domain");
138 let _ = socket.write_all(
140 "HTTP/1.1 200 OK\r\nContent-Length: {}\r\nContent-Type: application/octet-stream\r\nCache-Control: public, max-age={}, s-maxage={}\r\nAccess-Control-Allow-Origin: *\r\n\r\n",
141 proof.len(), cache_ttl, cache_ttl
144 let _ = socket.write_all(&proof).await;
147 let _ = socket.write_all(format!(
148 "HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: text/plain\r\nAccess-Control-Allow-Origin: *\r\n\r\n{}",
149 response.0, response.1.len(), response.1,
156 #[cfg(all(feature = "tokio", feature = "validation", test))]
160 use crate::ser::parse_rr_stream;
161 use crate::validation::verify_rr_stream;
165 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
166 async fn test_lookup() {
167 let ns = "8.8.8.8:53".parse().unwrap();
168 let listener = tokio::net::TcpListener::bind("127.0.0.1:17492").await
169 .expect("Failed to bind to socket");
170 tokio::spawn(imp::run_server(listener, ns));
171 let resp = minreq::get(
172 "http://127.0.0.1:17492/dnssecproof?d=matt.user._bitcoin-payment.mattcorallo.com.&t=tXt"
175 assert_eq!(resp.status_code, 200);
176 let rrs = parse_rr_stream(resp.as_bytes()).unwrap();
177 let verified_rrs = verify_rr_stream(&rrs).unwrap();
178 assert_eq!(verified_rrs.verified_rrs.len(), 1);
181 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
182 async fn test_lookup_a() {
183 let ns = "9.9.9.9:53".parse().unwrap();
184 let listener = tokio::net::TcpListener::bind("127.0.0.1:17493").await
185 .expect("Failed to bind to socket");
186 tokio::spawn(imp::run_server(listener, ns));
187 let resp = minreq::get(
188 "http://127.0.0.1:17493/dnssecproof?d=cloudflare.com.&t=a"
191 assert_eq!(resp.status_code, 200);
192 let rrs = parse_rr_stream(resp.as_bytes()).unwrap();
193 let verified_rrs = verify_rr_stream(&rrs).unwrap();
194 assert!(verified_rrs.verified_rrs.len() >= 1);
197 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
198 async fn test_lookup_tlsa() {
199 let ns = "1.1.1.1:53".parse().unwrap();
200 let listener = tokio::net::TcpListener::bind("127.0.0.1:17494").await
201 .expect("Failed to bind to socket");
202 tokio::spawn(imp::run_server(listener, ns));
203 let resp = minreq::get(
204 "http://127.0.0.1:17494/dnssecproof?d=_25._tcp.mail.as397444.net.&t=TLSA"
207 assert_eq!(resp.status_code, 200);
208 let rrs = parse_rr_stream(resp.as_bytes()).unwrap();
209 let verified_rrs = verify_rr_stream(&rrs).unwrap();
210 assert_eq!(verified_rrs.verified_rrs.len(), 1);