From d5adb7aba3297238d548cca5ca721301dae47a66 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Tue, 6 Feb 2018 00:32:52 +0100 Subject: [PATCH] Initial import --- .gitignore | 13 ++---- Cargo.toml | 12 +++++ README.md | 3 +- src/main.rs | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 50281a4..6c74236 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,5 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock -Cargo.lock - -# These are backup files generated by rustfmt +#*# **/*.rs.bk +*~ +/target/ +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..87a9a30 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust-doh" +version = "0.1.0" +authors = ["Frank Denis "] + +[dependencies] +hyper = "*" +futures-await = "*" +tokio-core = "*" +tokio-io = "*" +tokio-timer = "*" +clippy = {version = ">=0", optional = true} \ No newline at end of file diff --git a/README.md b/README.md index 0ccf4e5..0c991e0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # rust-doh -A toy DNS-over-HTTP server proxy + +A toy DNS-over-HTTP server proxy. diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..317bab2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,128 @@ +#![feature(proc_macro, conservative_impl_trait, generators, conservative_impl_trait, + universal_impl_trait, nll)] +#![cfg_attr(feature = "clippy", feature(plugin))] +#![cfg_attr(feature = "clippy", plugin(clippy))] + +extern crate futures_await as futures; +extern crate hyper; +extern crate tokio_core; +extern crate tokio_io; +extern crate tokio_timer; + +use tokio_core::reactor::Handle; +use std::time::Duration; +use hyper::{Body, Method, StatusCode}; +use hyper::header::{ContentLength, ContentType}; +use hyper::server::{Http, Request, Response, Service}; +use futures::prelude::*; +use futures::future; +use tokio_core::reactor::Core; +use tokio_core::net::UdpSocket; + +const TIMEOUT_SEC: u64 = 10; +const LOCAL_ADDRESS: &str = "127.0.0.1:3000"; +const LOCAL_BIND_ADDRESS: &str = "0.0.0.0:0"; +const SERVER_ADDRESS: &str = "9.9.9.9:53"; +const MIN_DNS_PACKET_LEN: usize = 17; +const MAX_DNS_QUESTION_LEN: usize = 512; +const MAX_DNS_RESPONSE_LEN: usize = 4096; + +#[derive(Clone, Debug)] +struct DoH { + handle: Handle, + timers: tokio_timer::Timer, +} + +impl Service for DoH { + type Request = Request; + type Response = Response; + type Error = hyper::Error; + type Future = Box>; + + fn call(&self, req: Request) -> Self::Future { + let mut response = Response::new(); + match (req.method(), req.path()) { + (&Method::Post, "/dns-query") => { + let fut = self.body_read(req.body(), self.handle.clone()).map(|body| { + let body_len = body.len(); + response.set_body(body); + response + .with_header(ContentLength(body_len as u64)) + .with_header(ContentType( + "application/dns-udpwireformat".parse().unwrap(), + )) + }); + let timed = self.timers + .timeout(fut.map_err(|_| ()), Duration::from_secs(TIMEOUT_SEC)); + return Box::new(timed.map_err(|_| hyper::Error::Timeout)); + } + (&Method::Post, _) => { + response.set_status(StatusCode::NotFound); + } + _ => { + response.set_status(StatusCode::NotAcceptable); + } + }; + Box::new(future::ok(response)) + } +} + +impl DoH { + #[async] + fn body_read(&self, body: Body, handle: Handle) -> Result, ()> { + let query = await!( + body.concat2() + .map_err(|_err| ()) + .map(|chunk| chunk.to_vec()) + )?; + if query.len() < MIN_DNS_PACKET_LEN { + return Err(()); + } + let local_addr = LOCAL_BIND_ADDRESS.parse().unwrap(); + let socket = UdpSocket::bind(&local_addr, &handle).unwrap(); + let remote_addr = SERVER_ADDRESS.parse().unwrap(); + let (socket, _) = await!(socket.send_dgram(query, remote_addr)).map_err(|_| ())?; + let mut packet = vec![0; MAX_DNS_RESPONSE_LEN]; + let (_socket, mut packet, len, server_addr) = + await!(socket.recv_dgram(packet)).map_err(|_| ())?; + if len < MIN_DNS_PACKET_LEN || server_addr != remote_addr { + return Err(()); + } + packet.truncate(len); + Ok(packet) + } +} + +fn main() { + let mut core = Core::new().unwrap(); + let handle = core.handle(); + + let addr = LOCAL_ADDRESS.parse().unwrap(); + let handle_inner = handle.clone(); + let timers = tokio_timer::wheel().build(); + + let server = Http::new() + .keep_alive(false) + .max_buf_size(MAX_DNS_QUESTION_LEN) + .serve_addr_handle(&addr, &handle, move || { + Ok(DoH { + handle: handle_inner.clone(), + timers: timers.clone(), + }) + }) + .unwrap(); + println!("Listening on http://{}", server.incoming_ref().local_addr()); + let handle_inner = handle.clone(); + handle.spawn( + server + .for_each(move |conn| { + handle_inner.spawn( + conn.map(|_| ()) + .map_err(|err| eprintln!("server error: {:?}", err)), + ); + Ok(()) + }) + .map_err(|_| ()), + ); + core.run(futures::future::empty::<(), ()>()).unwrap(); +}