1 //! A simple tokio-based HTTP server which serves DNSSEC proofs in RFC 9102 format.
12 #[cfg(feature = "tokio")]
13 use tokio_crate as tokio;
15 #[cfg(feature = "build_server")]
18 let resolver_sockaddr = std::env::var("RESOLVER")
19 .expect("Please set the RESOLVER env variable to the TCP socket of a recursive DNS resolver")
20 .parse().expect("RESOLVER was not a valid socket address");
21 let bind_addr = std::env::var("BIND")
22 .expect("Please set the BIND env variable to a socket address to listen on");
24 let listener = tokio::net::TcpListener::bind(bind_addr).await
25 .expect("Failed to bind to socket");
26 imp::run_server(listener, resolver_sockaddr).await;
29 #[cfg(feature = "tokio")]
36 use std::net::SocketAddr;
38 use tokio::net::TcpListener;
39 use tokio::io::{AsyncReadExt, AsyncWriteExt};
41 pub(super) async fn run_server(listener: TcpListener, resolver_sockaddr: SocketAddr) {
43 let (mut socket, _) = listener.accept().await.expect("Failed to accept new TCP connection");
44 tokio::spawn(async move {
45 let mut response = ("400 Bad Request", "Bad Request");
46 'ret_err: loop { // goto label
47 let mut buf = [0; 4096];
50 if buf_pos == buf.len() { response.1 = "Request Too Large"; break 'ret_err; }
51 let read_res = { socket.read(&mut buf[buf_pos..]).await };
56 for window in buf[..buf_pos].windows(2) {
57 if window == b"\r\n" { break 'read_req; }
60 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {},
65 if let Ok(s) = std::str::from_utf8(&buf[..buf_pos]) {
66 if let Some((r, _)) = s.split_once("\r\n") {
76 let mut parts = request.split(" ");
77 let (verb, path, http_vers);
78 if let Some(v) = parts.next() { verb = v; } else { break 'ret_err; }
79 if let Some(p) = parts.next() { path = p; } else { break 'ret_err; }
80 if let Some(v) = parts.next() { http_vers = v; } else { break 'ret_err; }
81 if parts.next().is_some() { break; }
82 if verb != "GET" { break; }
83 if http_vers != "HTTP/1.1" && http_vers != "HTTP/1.0" { break 'ret_err; }
85 const PATH_PFX: &'static str = "/dnssecproof?";
86 if !path.starts_with(PATH_PFX) {
87 response = ("404 Not Found", "Not Found");
91 let (mut d, mut t) = ("", "");
92 for arg in path[PATH_PFX.len()..].split("&") {
93 if let Some((k, v)) = arg.split_once("=") {
99 } else { break 'ret_err; }
102 if d == "" || t == "" {
103 response.1 = "Missing d or t URI parameters";
106 let query_name = if let Ok(domain) = Name::try_from(d) { domain } else {
107 response.1 = "Failed to parse domain, make sure it ends with .";
110 let proof_res = match t.to_ascii_uppercase().as_str() {
111 "TXT" => build_txt_proof_async(resolver_sockaddr, query_name).await,
112 "TLSA" => build_tlsa_proof_async(resolver_sockaddr, query_name).await,
113 "A" => build_a_proof_async(resolver_sockaddr, query_name).await,
114 "AAAA" => build_aaaa_proof_async(resolver_sockaddr, query_name).await,
117 let proof = if let Ok(proof) = proof_res { proof } else {
118 response = ("404 Not Found", "Failed to generate proof for given domain");
122 let _ = socket.write_all(
123 format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n", proof.len()).as_bytes()
125 let _ = socket.write_all(&proof).await;
128 let _ = socket.write_all(format!(
129 "HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: text/plain\r\n\r\n{}",
130 response.0, response.1.len(), response.1,
137 #[cfg(all(feature = "tokio", test))]
141 use crate::validation::{parse_rr_stream, verify_rr_stream};
145 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
146 async fn test_lookup() {
147 let ns = "8.8.8.8:53".parse().unwrap();
148 let listener = tokio::net::TcpListener::bind("127.0.0.1:17492").await
149 .expect("Failed to bind to socket");
150 tokio::spawn(imp::run_server(listener, ns));
151 let resp = minreq::get(
152 "http://127.0.0.1:17492/dnssecproof?d=matt.user._bitcoin-payment.mattcorallo.com.&t=tXt"
155 assert_eq!(resp.status_code, 200);
156 let rrs = parse_rr_stream(resp.as_bytes()).unwrap();
157 let verified_rrs = verify_rr_stream(&rrs).unwrap();
158 assert_eq!(verified_rrs.verified_rrs.len(), 1);