From b1fb410affb7bcd2e714abac01d22c4a5332c344 Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Mon, 6 Mar 2023 18:50:08 -0500 Subject: finish dns and start webserver --- src/config.rs | 64 ++++-- src/database/mod.rs | 146 ++++++++++++ src/dns/binding.rs | 144 ++++++++++++ src/dns/mod.rs | 4 + src/dns/packet/buffer.rs | 227 +++++++++++++++++++ src/dns/packet/header.rs | 102 +++++++++ src/dns/packet/mod.rs | 128 +++++++++++ src/dns/packet/query.rs | 78 +++++++ src/dns/packet/question.rs | 31 +++ src/dns/packet/record.rs | 544 +++++++++++++++++++++++++++++++++++++++++++++ src/dns/packet/result.rs | 22 ++ src/dns/resolver.rs | 230 +++++++++++++++++++ src/dns/server.rs | 85 +++++++ src/main.rs | 44 ++-- src/packet/buffer.rs | 236 -------------------- src/packet/header.rs | 101 --------- src/packet/mod.rs | 130 ----------- src/packet/query.rs | 51 ----- src/packet/question.rs | 31 --- src/packet/record.rs | 498 ----------------------------------------- src/packet/result.rs | 22 -- src/server/binding.rs | 150 ------------- src/server/mod.rs | 3 - src/server/resolver.rs | 165 -------------- src/server/server.rs | 73 ------ src/web/api.rs | 156 +++++++++++++ src/web/extract.rs | 139 ++++++++++++ src/web/file.rs | 31 +++ src/web/http.rs | 50 +++++ src/web/mod.rs | 82 +++++++ src/web/pages.rs | 31 +++ 31 files changed, 2303 insertions(+), 1495 deletions(-) create mode 100644 src/database/mod.rs create mode 100644 src/dns/binding.rs create mode 100644 src/dns/mod.rs create mode 100644 src/dns/packet/buffer.rs create mode 100644 src/dns/packet/header.rs create mode 100644 src/dns/packet/mod.rs create mode 100644 src/dns/packet/query.rs create mode 100644 src/dns/packet/question.rs create mode 100644 src/dns/packet/record.rs create mode 100644 src/dns/packet/result.rs create mode 100644 src/dns/resolver.rs create mode 100644 src/dns/server.rs delete mode 100644 src/packet/buffer.rs delete mode 100644 src/packet/header.rs delete mode 100644 src/packet/mod.rs delete mode 100644 src/packet/query.rs delete mode 100644 src/packet/question.rs delete mode 100644 src/packet/record.rs delete mode 100644 src/packet/result.rs delete mode 100644 src/server/binding.rs delete mode 100644 src/server/mod.rs delete mode 100644 src/server/resolver.rs delete mode 100644 src/server/server.rs create mode 100644 src/web/api.rs create mode 100644 src/web/extract.rs create mode 100644 src/web/file.rs create mode 100644 src/web/http.rs create mode 100644 src/web/mod.rs create mode 100644 src/web/pages.rs (limited to 'src') diff --git a/src/config.rs b/src/config.rs index 9350adf..547e853 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,35 +1,57 @@ -use std::net::IpAddr; +use std::{env, net::IpAddr, str::FromStr, fmt::Display}; #[derive(Clone)] pub struct Config { - fallback: IpAddr, - port: u16, + pub dns_fallback: IpAddr, + pub dns_port: u16, + pub dns_cache_size: u64, + + pub db_host: String, + pub db_port: u16, + pub db_user: String, + pub db_pass: String, + + pub web_user: String, + pub web_pass: String, + pub web_port: u16, } impl Config { pub fn new() -> Self { - let fallback = "9.9.9.9" - .parse::() - .expect("Failed to create default ns fallback"); - Self { - fallback, - port: 2000, - } - } + let dns_port = Self::get_var::("WRAPPER_DNS_PORT", 53); + let dns_fallback = Self::get_var::("WRAPPER_FALLBACK_DNS", [9, 9, 9, 9].into()); + let dns_cache_size = Self::get_var::("WRAPPER_CACHE_SIZE", 1000); - pub fn get_fallback_ns(&self) -> &IpAddr { - &self.fallback - } + let db_host = Self::get_var::("WRAPPER_DB_HOST", String::from("localhost")); + let db_port = Self::get_var::("WRAPPER_DB_PORT", 27017); + let db_user = Self::get_var::("WRAPPER_DB_USER", String::from("root")); + let db_pass = Self::get_var::("WRAPPER_DB_PASS", String::from("")); - pub fn get_port(&self) -> u16 { - self.port - } + let web_user = Self::get_var::("WRAPPER_WEB_USER", String::from("admin")); + let web_pass = Self::get_var::("WRAPPER_WEB_PASS", String::from("wrapper")); + let web_port = Self::get_var::("WRAPPER_WEB_PORT", 80); + + Self { + dns_fallback, + dns_port, + dns_cache_size, - pub fn set_fallback_ns(&mut self, addr: &IpAddr) { - self.fallback = *addr; + db_host, + db_port, + db_user, + db_pass, + + web_user, + web_pass, + web_port, + } } - pub fn set_port(&mut self, port: u16) { - self.port = port; + fn get_var(name: &str, default: T) -> T + where + T: FromStr + Display, + { + let env = env::var(name).unwrap_or(format!("{default}")); + env.parse::().unwrap_or(default) } } diff --git a/src/database/mod.rs b/src/database/mod.rs new file mode 100644 index 0000000..0d81dc3 --- /dev/null +++ b/src/database/mod.rs @@ -0,0 +1,146 @@ +use futures::TryStreamExt; +use mongodb::{ + bson::doc, + options::{ClientOptions, Credential, ServerAddress}, + Client, +}; +use serde::{Deserialize, Serialize}; +use tracing::info; + +use crate::{ + config::Config, + dns::packet::{query::QueryType, record::DnsRecord}, +}; + +use crate::Result; + +#[derive(Clone)] +pub struct Database { + client: Client, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct StoredRecord { + record: DnsRecord, + domain: String, + prefix: String, +} + +impl StoredRecord { + fn get_domain_parts(domain: &str) -> (String, String) { + let parts: Vec<&str> = domain.split(".").collect(); + let len = parts.len(); + if len == 1 { + (String::new(), String::from(parts[0])) + } else if len == 2 { + (String::new(), String::from(parts.join("."))) + } else { + ( + String::from(parts[0..len - 2].join(".")), + String::from(parts[len - 2..len].join(".")), + ) + } + } +} + +impl From for StoredRecord { + fn from(record: DnsRecord) -> Self { + let (prefix, domain) = Self::get_domain_parts(&record.get_domain()); + Self { + record, + domain, + prefix, + } + } +} + +impl Into for StoredRecord { + fn into(self) -> DnsRecord { + self.record + } +} + +impl Database { + pub async fn new(config: Config) -> Result { + let options = ClientOptions::builder() + .hosts(vec![ServerAddress::Tcp { + host: config.db_host, + port: Some(config.db_port), + }]) + .credential( + Credential::builder() + .username(config.db_user) + .password(config.db_pass) + .build(), + ) + .max_pool_size(100) + .app_name(String::from("wrapper")) + .build(); + + let client = Client::with_options(options)?; + + client + .database("wrapper") + .run_command(doc! {"ping": 1}, None) + .await?; + + info!("Connection to mongodb successfully"); + + Ok(Database { client }) + } + + pub async fn get_records(&self, domain: &str, qtype: QueryType) -> Result> { + let (prefix, domain) = StoredRecord::get_domain_parts(domain); + Ok(self + .get_domain(&domain) + .await? + .into_iter() + .filter(|r| r.prefix == prefix) + .filter(|r| { + let rqtype = r.record.get_qtype(); + if qtype == QueryType::A { + return rqtype == QueryType::A || rqtype == QueryType::AR; + } else if qtype == QueryType::AAAA { + return rqtype == QueryType::AAAA || rqtype == QueryType::AAAAR; + } else { + r.record.get_qtype() == qtype + } + }) + .map(|r| r.into()) + .collect()) + } + + pub async fn get_domain(&self, domain: &str) -> Result> { + let db = self.client.database("wrapper"); + let col = db.collection::(domain); + + let filter = doc! { "domain": domain }; + let mut cursor = col.find(filter, None).await?; + + let mut records = Vec::new(); + while let Some(record) = cursor.try_next().await? { + records.push(record); + } + + Ok(records) + } + + pub async fn add_record(&self, record: DnsRecord) -> Result<()> { + let record = StoredRecord::from(record); + let db = self.client.database("wrapper"); + let col = db.collection::(&record.domain); + col.insert_one(record, None).await?; + Ok(()) + } + + pub async fn get_domains(&self) -> Result> { + let db = self.client.database("wrapper"); + Ok(db.list_collection_names(None).await?) + } + + pub async fn delete_domain(&self, domain: String) -> Result<()> { + let db = self.client.database("wrapper"); + let col = db.collection::(&domain); + Ok(col.drop(None).await?) + } +} diff --git a/src/dns/binding.rs b/src/dns/binding.rs new file mode 100644 index 0000000..4c7e15f --- /dev/null +++ b/src/dns/binding.rs @@ -0,0 +1,144 @@ +use std::{ + net::{IpAddr, SocketAddr}, + sync::Arc, +}; + +use super::packet::{buffer::PacketBuffer, Packet}; +use crate::Result; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{TcpListener, TcpStream, UdpSocket}, +}; +use tracing::trace; + +pub enum Binding { + UDP(Arc), + TCP(TcpListener), +} + +impl Binding { + pub async fn udp(addr: SocketAddr) -> Result { + let socket = UdpSocket::bind(addr).await?; + Ok(Self::UDP(Arc::new(socket))) + } + + pub async fn tcp(addr: SocketAddr) -> Result { + let socket = TcpListener::bind(addr).await?; + Ok(Self::TCP(socket)) + } + + pub fn name(&self) -> &str { + match self { + Binding::UDP(_) => "UDP", + Binding::TCP(_) => "TCP", + } + } + + pub async fn connect(&mut self) -> Result { + match self { + Self::UDP(socket) => { + let mut buf = [0; 512]; + let (_, addr) = socket.recv_from(&mut buf).await?; + Ok(Connection::UDP(socket.clone(), addr, buf)) + } + Self::TCP(socket) => { + let (stream, _) = socket.accept().await?; + Ok(Connection::TCP(stream)) + } + } + } +} + +pub enum Connection { + UDP(Arc, SocketAddr, [u8; 512]), + TCP(TcpStream), +} + +impl Connection { + pub async fn read_packet(&mut self) -> Result { + let data = self.read().await?; + let mut packet_buffer = PacketBuffer::new(data); + + let packet = Packet::from_buffer(&mut packet_buffer)?; + Ok(packet) + } + + pub async fn write_packet(self, mut packet: Packet) -> Result<()> { + let mut packet_buffer = PacketBuffer::new(Vec::new()); + packet.write(&mut packet_buffer)?; + + self.write(packet_buffer.buf).await?; + Ok(()) + } + + pub async fn request_packet(&self, mut packet: Packet, dest: (IpAddr, u16)) -> Result { + let mut packet_buffer = PacketBuffer::new(Vec::new()); + packet.write(&mut packet_buffer)?; + + let data = self.request(packet_buffer.buf, dest).await?; + let mut packet_buffer = PacketBuffer::new(data); + + let packet = Packet::from_buffer(&mut packet_buffer)?; + Ok(packet) + } + + async fn read(&mut self) -> Result> { + trace!("Reading DNS packet"); + match self { + Self::UDP(_, _, src) => Ok(Vec::from(*src)), + Self::TCP(stream) => { + let size = stream.read_u16().await?; + let mut buf = Vec::with_capacity(size as usize); + stream.read_buf(&mut buf).await?; + Ok(buf) + } + } + } + + async fn write(self, mut buf: Vec) -> Result<()> { + trace!("Returning DNS packet"); + match self { + Self::UDP(socket, addr, _) => { + if buf.len() > 512 { + buf[2] = buf[2] | 0x03; + socket.send_to(&buf[0..512], addr).await?; + } else { + socket.send_to(&buf, addr).await?; + } + Ok(()) + } + Self::TCP(mut stream) => { + stream.write_u16(buf.len() as u16).await?; + stream.write(&buf[0..buf.len()]).await?; + Ok(()) + } + } + } + + async fn request(&self, buf: Vec, dest: (IpAddr, u16)) -> Result> { + match self { + Self::UDP(_socket, _addr, _src) => { + let local_addr = "[::]:0".parse::()?; + let socket = UdpSocket::bind(local_addr).await?; + socket.send_to(&buf, dest).await?; + + let mut buf = [0; 512]; + socket.recv_from(&mut buf).await?; + + Ok(Vec::from(buf)) + } + Self::TCP(_stream) => { + let mut stream = TcpStream::connect(dest).await?; + stream.write_u16((buf.len()) as u16).await?; + stream.write_all(&buf[0..buf.len()]).await?; + + stream.readable().await?; + let size = stream.read_u16().await?; + let mut buf = Vec::with_capacity(size as usize); + stream.read_buf(&mut buf).await?; + + Ok(buf) + } + } + } +} diff --git a/src/dns/mod.rs b/src/dns/mod.rs new file mode 100644 index 0000000..6f1e59e --- /dev/null +++ b/src/dns/mod.rs @@ -0,0 +1,4 @@ +mod binding; +pub mod packet; +mod resolver; +pub mod server; diff --git a/src/dns/packet/buffer.rs b/src/dns/packet/buffer.rs new file mode 100644 index 0000000..058156e --- /dev/null +++ b/src/dns/packet/buffer.rs @@ -0,0 +1,227 @@ +use crate::Result; + +pub struct PacketBuffer { + pub buf: Vec, + pub pos: usize, + pub size: usize, +} + +impl PacketBuffer { + pub fn new(buf: Vec) -> Self { + Self { + size: buf.len(), + buf, + pos: 0, + } + } + + pub fn pos(&self) -> usize { + self.pos + } + + pub fn step(&mut self, steps: usize) -> Result<()> { + self.pos += steps; + + Ok(()) + } + + pub fn seek(&mut self, pos: usize) -> Result<()> { + self.pos = pos; + + Ok(()) + } + + pub fn read(&mut self) -> Result { + if self.pos >= self.size { + return Err("Tried to read past end of buffer".into()); + } + let res = self.buf[self.pos]; + self.pos += 1; + Ok(res) + } + + pub fn get(&mut self, pos: usize) -> Result { + if pos >= self.size { + return Err("Tried to read past end of buffer".into()); + } + Ok(self.buf[pos]) + } + + pub fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> { + if start + len >= self.size { + return Err("Tried to read past end of buffer".into()); + } + Ok(&self.buf[start..start + len]) + } + + pub fn read_u16(&mut self) -> Result { + let res = ((self.read()? as u16) << 8) | (self.read()? as u16); + + Ok(res) + } + + pub fn read_u32(&mut self) -> Result { + let res = ((self.read()? as u32) << 24) + | ((self.read()? as u32) << 16) + | ((self.read()? as u32) << 8) + | (self.read()? as u32); + + Ok(res) + } + + pub fn read_qname(&mut self, outstr: &mut String) -> Result<()> { + let mut pos = self.pos(); + let mut jumped = false; + + let mut delim = ""; + let max_jumps = 5; + let mut jumps_performed = 0; + loop { + // Dns Packets are untrusted data, so we need to be paranoid. Someone + // can craft a packet with a cycle in the jump instructions. This guards + // against such packets. + if jumps_performed > max_jumps { + return Err(format!("Limit of {max_jumps} jumps exceeded").into()); + } + + let len = self.get(pos)?; + + if (len & 0xC0) == 0xC0 { + if !jumped { + self.seek(pos + 2)?; + } + + let b2 = self.get(pos + 1)? as u16; + let offset = (((len as u16) ^ 0xC0) << 8) | b2; + pos = offset as usize; + jumped = true; + jumps_performed += 1; + continue; + } + + pos += 1; + + if len == 0 { + break; + } + + outstr.push_str(delim); + + let str_buffer = self.get_range(pos, len as usize)?; + outstr.push_str(&String::from_utf8_lossy(str_buffer).to_lowercase()); + + delim = "."; + + pos += len as usize; + } + + if !jumped { + self.seek(pos)?; + } + + Ok(()) + } + + pub fn read_string(&mut self, outstr: &mut String) -> Result<()> { + let len = self.read()?; + + self.read_string_n(outstr, len)?; + + Ok(()) + } + + pub fn read_string_n(&mut self, outstr: &mut String, len: u8) -> Result<()> { + let mut pos = self.pos; + + let str_buffer = self.get_range(pos, len as usize)?; + + let mut i = 0; + for b in str_buffer { + let c = *b as char; + if c == '\0' { + break; + } + outstr.push(c); + i += 1; + } + + pos += i; + self.seek(pos)?; + + Ok(()) + } + + pub fn write(&mut self, val: u8) -> Result<()> { + if self.size < self.pos { + self.size = self.pos; + } + + if self.buf.len() <= self.size { + self.buf.resize(self.size + 1, 0x00); + } + + self.buf[self.pos] = val; + self.pos += 1; + Ok(()) + } + + pub fn write_u8(&mut self, val: u8) -> Result<()> { + self.write(val)?; + + Ok(()) + } + + pub fn write_u16(&mut self, val: u16) -> Result<()> { + self.write((val >> 8) as u8)?; + self.write((val & 0xFF) as u8)?; + + Ok(()) + } + + pub fn write_u32(&mut self, val: u32) -> Result<()> { + self.write(((val >> 24) & 0xFF) as u8)?; + self.write(((val >> 16) & 0xFF) as u8)?; + self.write(((val >> 8) & 0xFF) as u8)?; + self.write((val & 0xFF) as u8)?; + + Ok(()) + } + + pub fn write_qname(&mut self, qname: &str) -> Result<()> { + for label in qname.split('.') { + let len = label.len(); + + self.write_u8(len as u8)?; + for b in label.as_bytes() { + self.write_u8(*b)?; + } + } + + if !qname.is_empty() { + self.write_u8(0)?; + } + + Ok(()) + } + + pub fn write_string(&mut self, text: &str) -> Result<()> { + for b in text.as_bytes() { + self.write_u8(*b)?; + } + + Ok(()) + } + + pub fn set(&mut self, pos: usize, val: u8) -> Result<()> { + self.buf[pos] = val; + + Ok(()) + } + + pub fn set_u16(&mut self, pos: usize, val: u16) -> Result<()> { + self.set(pos, (val >> 8) as u8)?; + self.set(pos + 1, (val & 0xFF) as u8)?; + + Ok(()) + } +} diff --git a/src/dns/packet/header.rs b/src/dns/packet/header.rs new file mode 100644 index 0000000..2355ecb --- /dev/null +++ b/src/dns/packet/header.rs @@ -0,0 +1,102 @@ +use super::{buffer::PacketBuffer, result::ResultCode}; +use crate::Result; + +#[derive(Clone, Debug)] +pub struct DnsHeader { + pub id: u16, // 16 bits + + pub recursion_desired: bool, // 1 bit + pub truncated_message: bool, // 1 bit + pub authoritative_answer: bool, // 1 bit + pub opcode: u8, // 4 bits + pub response: bool, // 1 bit + + pub rescode: ResultCode, // 4 bits + pub checking_disabled: bool, // 1 bit + pub authed_data: bool, // 1 bit + pub z: bool, // 1 bit + pub recursion_available: bool, // 1 bit + + pub questions: u16, // 16 bits + pub answers: u16, // 16 bits + pub authoritative_entries: u16, // 16 bits + pub resource_entries: u16, // 16 bits +} + +impl DnsHeader { + pub fn new() -> Self { + Self { + id: 0, + + recursion_desired: false, + truncated_message: false, + authoritative_answer: false, + opcode: 0, + response: false, + + rescode: ResultCode::NOERROR, + checking_disabled: false, + authed_data: false, + z: false, + recursion_available: false, + + questions: 0, + answers: 0, + authoritative_entries: 0, + resource_entries: 0, + } + } + + pub fn read(&mut self, buffer: &mut PacketBuffer) -> Result<()> { + self.id = buffer.read_u16()?; + let flags = buffer.read_u16()?; + let a = (flags >> 8) as u8; + let b = (flags & 0xFF) as u8; + self.recursion_desired = (a & (1 << 0)) > 0; + self.truncated_message = (a & (1 << 1)) > 0; + self.authoritative_answer = (a & (1 << 2)) > 0; + self.opcode = (a >> 3) & 0x0F; + self.response = (a & (1 << 7)) > 0; + + self.rescode = ResultCode::from_num(b & 0x0F); + self.checking_disabled = (b & (1 << 4)) > 0; + self.authed_data = (b & (1 << 5)) > 0; + self.z = (b & (1 << 6)) > 0; + self.recursion_available = (b & (1 << 7)) > 0; + + self.questions = buffer.read_u16()?; + self.answers = buffer.read_u16()?; + self.authoritative_entries = buffer.read_u16()?; + self.resource_entries = buffer.read_u16()?; + + // Return the constant header size + Ok(()) + } + + pub fn write(&self, buffer: &mut PacketBuffer) -> Result<()> { + buffer.write_u16(self.id)?; + + buffer.write_u8( + (self.recursion_desired as u8) + | ((self.truncated_message as u8) << 1) + | ((self.authoritative_answer as u8) << 2) + | (self.opcode << 3) + | ((self.response as u8) << 7), + )?; + + buffer.write_u8( + (self.rescode as u8) + | ((self.checking_disabled as u8) << 4) + | ((self.authed_data as u8) << 5) + | ((self.z as u8) << 6) + | ((self.recursion_available as u8) << 7), + )?; + + buffer.write_u16(self.questions)?; + buffer.write_u16(self.answers)?; + buffer.write_u16(self.authoritative_entries)?; + buffer.write_u16(self.resource_entries)?; + + Ok(()) + } +} diff --git a/src/dns/packet/mod.rs b/src/dns/packet/mod.rs new file mode 100644 index 0000000..9873b94 --- /dev/null +++ b/src/dns/packet/mod.rs @@ -0,0 +1,128 @@ +use std::net::IpAddr; + +use self::{ + buffer::PacketBuffer, header::DnsHeader, query::QueryType, question::DnsQuestion, + record::DnsRecord, +}; +use crate::Result; + +pub mod buffer; +pub mod header; +pub mod query; +pub mod question; +pub mod record; +pub mod result; + +#[derive(Clone, Debug)] +pub struct Packet { + pub header: DnsHeader, + pub questions: Vec, + pub answers: Vec, + pub authorities: Vec, + pub resources: Vec, +} + +impl Packet { + pub fn new() -> Self { + Self { + header: DnsHeader::new(), + questions: Vec::new(), + answers: Vec::new(), + authorities: Vec::new(), + resources: Vec::new(), + } + } + + pub fn from_buffer(buffer: &mut PacketBuffer) -> Result { + let mut result = Self::new(); + result.header.read(buffer)?; + + for _ in 0..result.header.questions { + let mut question = DnsQuestion::new("".to_string(), QueryType::UNKNOWN(0)); + question.read(buffer)?; + result.questions.push(question); + } + + for _ in 0..result.header.answers { + let rec = DnsRecord::read(buffer)?; + result.answers.push(rec); + } + for _ in 0..result.header.authoritative_entries { + let rec = DnsRecord::read(buffer)?; + result.authorities.push(rec); + } + for _ in 0..result.header.resource_entries { + let rec = DnsRecord::read(buffer)?; + result.resources.push(rec); + } + + Ok(result) + } + + pub fn write(&mut self, buffer: &mut PacketBuffer) -> Result<()> { + self.header.questions = self.questions.len() as u16; + self.header.answers = self.answers.len() as u16; + self.header.authoritative_entries = self.authorities.len() as u16; + self.header.resource_entries = self.resources.len() as u16; + + self.header.write(buffer)?; + + for question in &self.questions { + question.write(buffer)?; + } + for rec in &self.answers { + rec.write(buffer)?; + } + for rec in &self.authorities { + rec.write(buffer)?; + } + for rec in &self.resources { + rec.write(buffer)?; + } + + Ok(()) + } + + pub fn get_random_a(&self) -> Option { + self.answers + .iter() + .filter_map(|record| match record { + DnsRecord::A { addr, .. } => Some(IpAddr::V4(*addr)), + DnsRecord::AAAA { addr, .. } => Some(IpAddr::V6(*addr)), + _ => None, + }) + .next() + } + + fn get_ns<'a>(&'a self, qname: &'a str) -> impl Iterator { + self.authorities + .iter() + .filter_map(|record| match record { + DnsRecord::NS { domain, host, .. } => Some((domain.as_str(), host.as_str())), + _ => None, + }) + .filter(move |(domain, _)| qname.ends_with(*domain)) + } + + pub fn get_resolved_ns(&self, qname: &str) -> Option { + self.get_ns(qname) + .flat_map(|(_, host)| { + self.resources + .iter() + .filter_map(move |record| match record { + DnsRecord::A { domain, addr, .. } if domain == host => { + Some(IpAddr::V4(*addr)) + } + DnsRecord::AAAA { domain, addr, .. } if domain == host => { + Some(IpAddr::V6(*addr)) + } + _ => None, + }) + }) + .next() + } + + pub fn get_unresolved_ns<'a>(&'a self, qname: &'a str) -> Option<&'a str> { + self.get_ns(qname).map(|(_, host)| host).next() + } +} diff --git a/src/dns/packet/query.rs b/src/dns/packet/query.rs new file mode 100644 index 0000000..732b9b2 --- /dev/null +++ b/src/dns/packet/query.rs @@ -0,0 +1,78 @@ +#[derive(PartialEq, Eq, Debug, Clone, Hash, Copy)] +pub enum QueryType { + UNKNOWN(u16), + A, // 1 + NS, // 2 + CNAME, // 5 + SOA, // 6 + PTR, // 12 + MX, // 15 + TXT, // 16 + AAAA, // 28 + SRV, // 33 + OPT, // 41 + CAA, // 257 + AR, // 1000 + AAAAR, // 1001 +} + +impl QueryType { + pub fn to_num(&self) -> u16 { + match *self { + Self::UNKNOWN(x) => x, + Self::A => 1, + Self::NS => 2, + Self::CNAME => 5, + Self::SOA => 6, + Self::PTR => 12, + Self::MX => 15, + Self::TXT => 16, + Self::AAAA => 28, + Self::SRV => 33, + Self::OPT => 41, + Self::CAA => 257, + Self::AR => 1000, + Self::AAAAR => 1001, + } + } + + pub fn from_num(num: u16) -> Self { + match num { + 1 => Self::A, + 2 => Self::NS, + 5 => Self::CNAME, + 6 => Self::SOA, + 12 => Self::PTR, + 15 => Self::MX, + 16 => Self::TXT, + 28 => Self::AAAA, + 33 => Self::SRV, + 41 => Self::OPT, + 257 => Self::CAA, + 1000 => Self::AR, + 1001 => Self::AAAAR, + _ => Self::UNKNOWN(num), + } + } + + pub fn allowed_actions(&self) -> (bool, bool) { + // 0. duplicates allowed + // 1. allowed to be created by database + match self { + QueryType::UNKNOWN(_) => (false, false), + QueryType::A => (true, true), + QueryType::NS => (false, true), + QueryType::CNAME => (false, true), + QueryType::SOA => (false, false), + QueryType::PTR => (false, true), + QueryType::MX => (false, true), + QueryType::TXT => (true, true), + QueryType::AAAA => (true, true), + QueryType::SRV => (false, true), + QueryType::OPT => (false, false), + QueryType::CAA => (false, true), + QueryType::AR => (false, true), + QueryType::AAAAR => (false, true), + } + } +} diff --git a/src/dns/packet/question.rs b/src/dns/packet/question.rs new file mode 100644 index 0000000..9042e1c --- /dev/null +++ b/src/dns/packet/question.rs @@ -0,0 +1,31 @@ +use super::{buffer::PacketBuffer, query::QueryType, Result}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct DnsQuestion { + pub name: String, + pub qtype: QueryType, +} + +impl DnsQuestion { + pub fn new(name: String, qtype: QueryType) -> Self { + Self { name, qtype } + } + + pub fn read(&mut self, buffer: &mut PacketBuffer) -> Result<()> { + buffer.read_qname(&mut self.name)?; + self.qtype = QueryType::from_num(buffer.read_u16()?); // qtype + let _ = buffer.read_u16()?; // class + + Ok(()) + } + + pub fn write(&self, buffer: &mut PacketBuffer) -> Result<()> { + buffer.write_qname(&self.name)?; + + let typenum = self.qtype.to_num(); + buffer.write_u16(typenum)?; + buffer.write_u16(1)?; + + Ok(()) + } +} diff --git a/src/dns/packet/record.rs b/src/dns/packet/record.rs new file mode 100644 index 0000000..88008f0 --- /dev/null +++ b/src/dns/packet/record.rs @@ -0,0 +1,544 @@ +use std::net::{Ipv4Addr, Ipv6Addr}; + +use rand::RngCore; +use serde::{Deserialize, Serialize}; +use tracing::{trace, warn}; + +use super::{buffer::PacketBuffer, query::QueryType, Result}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub enum DnsRecord { + UNKNOWN { + domain: String, + qtype: u16, + data_len: u16, + ttl: u32, + }, // 0 + A { + domain: String, + addr: Ipv4Addr, + ttl: u32, + }, // 1 + NS { + domain: String, + host: String, + ttl: u32, + }, // 2 + CNAME { + domain: String, + host: String, + ttl: u32, + }, // 5 + SOA { + domain: String, + mname: String, + nname: String, + serial: u32, + refresh: u32, + retry: u32, + expire: u32, + minimum: u32, + ttl: u32, + }, // 6 + PTR { + domain: String, + pointer: String, + ttl: u32, + }, // 12 + MX { + domain: String, + priority: u16, + host: String, + ttl: u32, + }, // 15 + TXT { + domain: String, + text: Vec, + ttl: u32, + }, //16 + AAAA { + domain: String, + addr: Ipv6Addr, + ttl: u32, + }, // 28 + SRV { + domain: String, + priority: u16, + weight: u16, + port: u16, + target: String, + ttl: u32, + }, // 33 + CAA { + domain: String, + flags: u8, + length: u8, + tag: String, + value: String, + ttl: u32, + }, // 257 + AR { + domain: String, + ttl: u32, + }, + AAAAR { + domain: String, + ttl: u32, + }, +} + +impl DnsRecord { + pub fn read(buffer: &mut PacketBuffer) -> Result { + let mut domain = String::new(); + buffer.read_qname(&mut domain)?; + + let qtype_num = buffer.read_u16()?; + let qtype = QueryType::from_num(qtype_num); + let _ = buffer.read_u16()?; + let ttl = buffer.read_u32()?; + let data_len = buffer.read_u16()?; + + trace!("Reading DNS Record TYPE: {:?}", qtype); + + let header_pos = buffer.pos(); + + match qtype { + QueryType::A => { + let raw_addr = buffer.read_u32()?; + let addr = Ipv4Addr::new( + ((raw_addr >> 24) & 0xFF) as u8, + ((raw_addr >> 16) & 0xFF) as u8, + ((raw_addr >> 8) & 0xFF) as u8, + (raw_addr & 0xFF) as u8, + ); + + Ok(Self::A { domain, addr, ttl }) + } + QueryType::AAAA => { + let raw_addr1 = buffer.read_u32()?; + let raw_addr2 = buffer.read_u32()?; + let raw_addr3 = buffer.read_u32()?; + let raw_addr4 = buffer.read_u32()?; + let addr = Ipv6Addr::new( + ((raw_addr1 >> 16) & 0xFFFF) as u16, + (raw_addr1 & 0xFFFF) as u16, + ((raw_addr2 >> 16) & 0xFFFF) as u16, + (raw_addr2 & 0xFFFF) as u16, + ((raw_addr3 >> 16) & 0xFFFF) as u16, + (raw_addr3 & 0xFFFF) as u16, + ((raw_addr4 >> 16) & 0xFFFF) as u16, + (raw_addr4 & 0xFFFF) as u16, + ); + + Ok(Self::AAAA { domain, addr, ttl }) + } + QueryType::NS => { + let mut ns = String::new(); + buffer.read_qname(&mut ns)?; + + Ok(Self::NS { + domain, + host: ns, + ttl, + }) + } + QueryType::CNAME => { + let mut cname = String::new(); + buffer.read_qname(&mut cname)?; + + Ok(Self::CNAME { + domain, + host: cname, + ttl, + }) + } + QueryType::SOA => { + let mut mname = String::new(); + buffer.read_qname(&mut mname)?; + + let mut nname = String::new(); + buffer.read_qname(&mut nname)?; + + let serial = buffer.read_u32()?; + let refresh = buffer.read_u32()?; + let retry = buffer.read_u32()?; + let expire = buffer.read_u32()?; + let minimum = buffer.read_u32()?; + + Ok(Self::SOA { + domain, + mname, + nname, + serial, + refresh, + retry, + expire, + minimum, + ttl, + }) + } + QueryType::PTR => { + let mut pointer = String::new(); + buffer.read_qname(&mut pointer)?; + + Ok(Self::PTR { + domain, + pointer, + ttl, + }) + } + QueryType::MX => { + let priority = buffer.read_u16()?; + let mut mx = String::new(); + buffer.read_qname(&mut mx)?; + + Ok(Self::MX { + domain, + priority, + host: mx, + ttl, + }) + } + QueryType::TXT => { + let mut text = Vec::new(); + + loop { + let mut s = String::new(); + buffer.read_string(&mut s)?; + + if s.len() == 0 { + break; + } else { + text.push(s); + } + } + + Ok(Self::TXT { domain, text, ttl }) + } + QueryType::SRV => { + let priority = buffer.read_u16()?; + let weight = buffer.read_u16()?; + let port = buffer.read_u16()?; + + let mut target = String::new(); + buffer.read_qname(&mut target)?; + + Ok(Self::SRV { + domain, + priority, + weight, + port, + target, + ttl, + }) + } + QueryType::CAA => { + let flags = buffer.read()?; + let length = buffer.read()?; + + let mut tag = String::new(); + buffer.read_string_n(&mut tag, length)?; + + let value_len = (data_len as usize) + header_pos - buffer.pos; + let mut value = String::new(); + buffer.read_string_n(&mut value, value_len as u8)?; + + Ok(Self::CAA { + domain, + flags, + length, + tag, + value, + ttl, + }) + } + QueryType::UNKNOWN(_) | _ => { + buffer.step(data_len as usize)?; + + Ok(Self::UNKNOWN { + domain, + qtype: qtype_num, + data_len, + ttl, + }) + } + } + } + + pub fn write(&self, buffer: &mut PacketBuffer) -> Result { + let start_pos = buffer.pos(); + + trace!("Writing DNS Record {:?}", self); + + match *self { + Self::A { + ref domain, + ref addr, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::A.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + buffer.write_u16(4)?; + + let octets = addr.octets(); + buffer.write_u8(octets[0])?; + buffer.write_u8(octets[1])?; + buffer.write_u8(octets[2])?; + buffer.write_u8(octets[3])?; + } + Self::NS { + ref domain, + ref host, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::NS.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_qname(host)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + Self::CNAME { + ref domain, + ref host, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::CNAME.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_qname(host)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + Self::SOA { + ref domain, + ref mname, + ref nname, + serial, + refresh, + retry, + expire, + minimum, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::SOA.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_qname(mname)?; + buffer.write_qname(nname)?; + buffer.write_u32(serial)?; + buffer.write_u32(refresh)?; + buffer.write_u32(retry)?; + buffer.write_u32(expire)?; + buffer.write_u32(minimum)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + Self::PTR { + ref domain, + ref pointer, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::NS.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_qname(&pointer)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + Self::MX { + ref domain, + priority, + ref host, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::MX.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_u16(priority)?; + buffer.write_qname(host)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + Self::TXT { + ref domain, + ref text, + ttl, + } => { + buffer.write_qname(&domain)?; + buffer.write_u16(QueryType::TXT.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + if text.is_empty() { + return Ok(buffer.pos() - start_pos); + } + + for s in text { + buffer.write_u8(s.len() as u8)?; + buffer.write_string(&s)?; + } + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + Self::AAAA { + ref domain, + ref addr, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::AAAA.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + buffer.write_u16(16)?; + + for octet in &addr.segments() { + buffer.write_u16(*octet)?; + } + } + Self::SRV { + ref domain, + priority, + weight, + port, + ref target, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::SRV.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_u16(priority)?; + buffer.write_u16(weight)?; + buffer.write_u16(port)?; + buffer.write_qname(target)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + Self::CAA { + ref domain, + flags, + length, + ref tag, + ref value, + ttl, + } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::CAA.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + + let pos = buffer.pos(); + buffer.write_u16(0)?; + + buffer.write_u8(flags)?; + buffer.write_u8(length)?; + buffer.write_string(tag)?; + buffer.write_string(value)?; + + let size = buffer.pos() - (pos + 2); + buffer.set_u16(pos, size as u16)?; + } + Self::AR { ref domain, ttl } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::A.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + buffer.write_u16(4)?; + + let mut rand = rand::thread_rng(); + buffer.write_u32(rand.next_u32())?; + } + Self::AAAAR { ref domain, ttl } => { + buffer.write_qname(domain)?; + buffer.write_u16(QueryType::A.to_num())?; + buffer.write_u16(1)?; + buffer.write_u32(ttl)?; + buffer.write_u16(4)?; + + let mut rand = rand::thread_rng(); + buffer.write_u32(rand.next_u32())?; + buffer.write_u32(rand.next_u32())?; + buffer.write_u32(rand.next_u32())?; + buffer.write_u32(rand.next_u32())?; + } + Self::UNKNOWN { .. } => { + warn!("Skipping record: {self:?}"); + } + } + + Ok(buffer.pos() - start_pos) + } + + pub fn get_domain(&self) -> String { + self.get_shared_domain().0 + } + + pub fn get_qtype(&self) -> QueryType { + self.get_shared_domain().1 + } + + pub fn get_ttl(&self) -> u32 { + self.get_shared_domain().2 + } + + fn get_shared_domain(&self) -> (String, QueryType, u32) { + match self { + DnsRecord::UNKNOWN { + domain, ttl, qtype, .. + } => (domain.clone(), QueryType::UNKNOWN(*qtype), *ttl), + DnsRecord::AAAA { domain, ttl, .. } => (domain.clone(), QueryType::AAAA, *ttl), + DnsRecord::A { domain, ttl, .. } => (domain.clone(), QueryType::A, *ttl), + DnsRecord::NS { domain, ttl, .. } => (domain.clone(), QueryType::NS, *ttl), + DnsRecord::CNAME { domain, ttl, .. } => (domain.clone(), QueryType::CNAME, *ttl), + DnsRecord::SOA { domain, ttl, .. } => (domain.clone(), QueryType::SOA, *ttl), + DnsRecord::PTR { domain, ttl, .. } => (domain.clone(), QueryType::PTR, *ttl), + DnsRecord::MX { domain, ttl, .. } => (domain.clone(), QueryType::MX, *ttl), + DnsRecord::TXT { domain, ttl, .. } => (domain.clone(), QueryType::TXT, *ttl), + DnsRecord::SRV { domain, ttl, .. } => (domain.clone(), QueryType::SRV, *ttl), + DnsRecord::CAA { domain, ttl, .. } => (domain.clone(), QueryType::CAA, *ttl), + DnsRecord::AR { domain, ttl, .. } => (domain.clone(), QueryType::AR, *ttl), + DnsRecord::AAAAR { domain, ttl, .. } => (domain.clone(), QueryType::AAAAR, *ttl), + } + } +} diff --git a/src/dns/packet/result.rs b/src/dns/packet/result.rs new file mode 100644 index 0000000..41c8ba9 --- /dev/null +++ b/src/dns/packet/result.rs @@ -0,0 +1,22 @@ +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ResultCode { + NOERROR = 0, + FORMERR = 1, + SERVFAIL = 2, + NXDOMAIN = 3, + NOTIMP = 4, + REFUSED = 5, +} + +impl ResultCode { + pub fn from_num(num: u8) -> Self { + match num { + 1 => Self::FORMERR, + 2 => Self::SERVFAIL, + 3 => Self::NXDOMAIN, + 4 => Self::NOTIMP, + 5 => Self::REFUSED, + 0 | _ => Self::NOERROR, + } + } +} diff --git a/src/dns/resolver.rs b/src/dns/resolver.rs new file mode 100644 index 0000000..18b5bba --- /dev/null +++ b/src/dns/resolver.rs @@ -0,0 +1,230 @@ +use super::binding::Connection; +use super::packet::{query::QueryType, question::DnsQuestion, result::ResultCode, Packet}; +use crate::Result; +use crate::{config::Config, database::Database, get_time}; +use async_recursion::async_recursion; +use moka::future::Cache; +use std::{net::IpAddr, sync::Arc, time::Duration}; +use tracing::{error, trace}; + +pub struct Resolver { + request_id: u16, + connection: Connection, + config: Arc, + database: Arc, + cache: Cache, +} + +impl Resolver { + pub fn new( + request_id: u16, + connection: Connection, + config: Arc, + database: Arc, + cache: Cache, + ) -> Self { + Self { + request_id, + connection, + config, + database, + cache, + } + } + + async fn lookup_database(&self, question: &DnsQuestion) -> Option { + let records = match self + .database + .get_records(&question.name, question.qtype) + .await + { + Ok(record) => record, + Err(err) => { + error!("{err}"); + return None; + } + }; + + if records.is_empty() { + return None; + } + + let mut packet = Packet::new(); + + packet.header.id = self.request_id; + packet.header.questions = 1; + packet.header.answers = records.len() as u16; + packet.header.recursion_desired = true; + packet + .questions + .push(DnsQuestion::new(question.name.to_string(), question.qtype)); + + for record in records { + packet.answers.push(record); + } + + trace!( + "Found stored value for {:?} {}", + question.qtype, + question.name + ); + + Some(packet) + } + + async fn lookup_cache(&self, question: &DnsQuestion) -> Option { + let Some((packet, date)) = self.cache.get(&question) else { + return None + }; + + let now = get_time(); + let diff = Duration::from_millis(now - date).as_secs() as u32; + + for answer in &packet.answers { + let ttl = answer.get_ttl(); + if diff > ttl { + self.cache.invalidate(&question).await; + return None; + } + } + + trace!( + "Found cached value for {:?} {}", + question.qtype, + question.name + ); + + Some(packet) + } + + async fn lookup_fallback(&self, question: &DnsQuestion, server: (IpAddr, u16)) -> Packet { + let mut packet = Packet::new(); + + packet.header.id = self.request_id; + packet.header.questions = 1; + packet.header.recursion_desired = true; + packet + .questions + .push(DnsQuestion::new(question.name.to_string(), question.qtype)); + + let packet = match self.connection.request_packet(packet, server).await { + Ok(packet) => packet, + Err(e) => { + error!("Failed to complete nameserver request: {e}"); + let mut packet = Packet::new(); + packet.header.rescode = ResultCode::SERVFAIL; + packet + } + }; + + packet + } + + async fn lookup(&self, question: &DnsQuestion, server: (IpAddr, u16)) -> Packet { + if let Some(packet) = self.lookup_cache(question).await { + return packet; + }; + + if let Some(packet) = self.lookup_database(question).await { + return packet; + }; + + trace!( + "Attempting lookup of {:?} {} with ns {}", + question.qtype, + question.name, + server.0 + ); + + self.lookup_fallback(question, server).await + } + + #[async_recursion] + async fn recursive_lookup(&mut self, qname: &str, qtype: QueryType) -> Packet { + let question = DnsQuestion::new(qname.to_string(), qtype); + let mut ns = self.config.dns_fallback.clone(); + + loop { + let ns_copy = ns; + + let server = (ns_copy, 53); + let response = self.lookup(&question, server).await; + + if !response.answers.is_empty() && response.header.rescode == ResultCode::NOERROR { + self.cache + .insert(question, (response.clone(), get_time())) + .await; + return response; + } + + if response.header.rescode == ResultCode::NXDOMAIN { + self.cache + .insert(question, (response.clone(), get_time())) + .await; + return response; + } + + if let Some(new_ns) = response.get_resolved_ns(qname) { + ns = new_ns; + continue; + } + + let new_ns_name = match response.get_unresolved_ns(qname) { + Some(x) => x, + None => { + self.cache + .insert(question, (response.clone(), get_time())) + .await; + return response; + } + }; + + let recursive_response = self.recursive_lookup(new_ns_name, QueryType::A).await; + + if let Some(new_ns) = recursive_response.get_random_a() { + ns = new_ns; + } else { + self.cache + .insert(question, (response.clone(), get_time())) + .await; + return response; + } + } + } + + pub async fn handle_query(mut self) -> Result<()> { + let mut request = self.connection.read_packet().await?; + + let mut packet = Packet::new(); + packet.header.id = request.header.id; + packet.header.recursion_desired = true; + packet.header.recursion_available = true; + packet.header.response = true; + + if let Some(question) = request.questions.pop() { + trace!("Received query: {question:?}"); + + let result = self.recursive_lookup(&question.name, question.qtype).await; + packet.questions.push(question.clone()); + packet.header.rescode = result.header.rescode; + + for rec in result.answers { + trace!("Answer: {rec:?}"); + packet.answers.push(rec); + } + for rec in result.authorities { + trace!("Authority: {rec:?}"); + packet.authorities.push(rec); + } + for rec in result.resources { + trace!("Resource: {rec:?}"); + packet.resources.push(rec); + } + } else { + packet.header.rescode = ResultCode::FORMERR; + } + + self.connection.write_packet(packet).await?; + Ok(()) + } +} diff --git a/src/dns/server.rs b/src/dns/server.rs new file mode 100644 index 0000000..65d15df --- /dev/null +++ b/src/dns/server.rs @@ -0,0 +1,85 @@ +use super::{ + binding::Binding, + packet::{question::DnsQuestion, Packet}, + resolver::Resolver, +}; +use crate::{config::Config, database::Database, Result}; +use moka::future::Cache; +use std::{net::SocketAddr, sync::Arc, time::Duration}; +use tokio::task::JoinHandle; +use tracing::{error, info}; + +pub struct DnsServer { + addr: SocketAddr, + config: Arc, + database: Arc, + cache: Cache, +} + +impl DnsServer { + pub async fn new(config: Config, database: Database) -> Result { + let addr = format!("[::]:{}", config.dns_port).parse::()?; + let cache = Cache::builder() + .time_to_live(Duration::from_secs(60 * 60)) + .max_capacity(config.dns_cache_size) + .build(); + + info!("Created DNS cache with size of {}", config.dns_cache_size); + + Ok(Self { + addr, + config: Arc::new(config), + database: Arc::new(database), + cache, + }) + } + + pub async fn run(&self) -> Result<(JoinHandle<()>, JoinHandle<()>)> { + let tcp = Binding::tcp(self.addr).await?; + let tcp_handle = self.listen(tcp); + + let udp = Binding::udp(self.addr).await?; + let udp_handle = self.listen(udp); + + info!( + "Fallback DNS Server is set to: {:?}", + self.config.dns_fallback + ); + info!( + "Listening for TCP and UDP traffic on [::]:{}", + self.config.dns_port + ); + + Ok((udp_handle, tcp_handle)) + } + + fn listen(&self, mut binding: Binding) -> JoinHandle<()> { + let config = self.config.clone(); + let database = self.database.clone(); + let cache = self.cache.clone(); + tokio::spawn(async move { + let mut id = 0; + loop { + let Ok(connection) = binding.connect().await else { continue }; + info!("Received request on {}", binding.name()); + + let resolver = Resolver::new( + id, + connection, + config.clone(), + database.clone(), + cache.clone(), + ); + + let name = binding.name().to_string(); + tokio::spawn(async move { + if let Err(err) = resolver.handle_query().await { + error!("{} request {} failed: {:?}", name, id, err); + }; + }); + + id += 1; + } + }) + } +} diff --git a/src/main.rs b/src/main.rs index c891d50..679e87b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,34 @@ -use std::{time::{UNIX_EPOCH, SystemTime}, env, net::IpAddr}; +use std::time::{SystemTime, UNIX_EPOCH}; use config::Config; -use server::server::Server; -use tracing::metadata::LevelFilter; +use database::Database; +use dotenv::dotenv; +use dns::server::DnsServer; +use tracing::{error, metadata::LevelFilter}; use tracing_subscriber::{ filter::filter_fn, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer, }; +use web::WebServer; mod config; -mod packet; -mod server; +mod database; +mod dns; +mod web; + +type Error = Box; +pub type Result = std::result::Result; #[tokio::main] async fn main() { + if let Err(err) = run().await { + error!("{err}") + }; +} + +async fn run() -> Result<()> { + dotenv().ok(); + tracing_subscriber::registry() .with( tracing_subscriber::fmt::layer() @@ -24,19 +39,20 @@ async fn main() { ) .init(); - let mut config = Config::new(); + let config = Config::new(); + let database = Database::new(config.clone()).await?; - if let Ok(port) = env::var("PORT").unwrap_or(String::new()).parse::() { - config.set_port(port); - } + let dns_server = DnsServer::new(config.clone(), database.clone()).await?; + let (udp, tcp) = dns_server.run().await?; - if let Ok(fallback) = env::var("FALLBACK_DNS").unwrap_or(String::new()).parse::() { - config.set_fallback_ns(&fallback); - } + let web_server = WebServer::new(config, database).await?; + let web = web_server.run().await?; - let server = Server::new(config).await.expect("Failed to bind server"); + tokio::join!(udp).0?; + tokio::join!(tcp).0?; + tokio::join!(web).0?; - server.run().await.unwrap(); + Ok(()) } pub fn get_time() -> u64 { diff --git a/src/packet/buffer.rs b/src/packet/buffer.rs deleted file mode 100644 index 4ecc605..0000000 --- a/src/packet/buffer.rs +++ /dev/null @@ -1,236 +0,0 @@ -use super::Result; - -pub struct PacketBuffer { - pub buf: Vec, - pub pos: usize, - pub size: usize, -} - -impl PacketBuffer { - pub fn new(buf: Vec) -> Self { - Self { - buf, - pos: 0, - size: 0, - } - } - - fn check(&mut self, pos: usize) { - if self.size < pos { - self.size = pos; - } - - if self.buf.len() <= self.size { - self.buf.resize(self.size + 1, 0x00); - } - } - - pub fn pos(&self) -> usize { - self.pos - } - - pub fn step(&mut self, steps: usize) -> Result<()> { - self.pos += steps; - - Ok(()) - } - - pub fn seek(&mut self, pos: usize) -> Result<()> { - self.pos = pos; - - Ok(()) - } - - pub fn read(&mut self) -> Result { - // if self.pos >= 512 { - // error!("Tried to read past end of buffer"); - // return Err("End of buffer".into()); - // } - self.check(self.pos); - let res = self.buf[self.pos]; - self.pos += 1; - - Ok(res) - } - - pub fn get(&mut self, pos: usize) -> Result { - // if pos >= 512 { - // error!("Tried to read past end of buffer"); - // return Err("End of buffer".into()); - // } - self.check(pos); - Ok(self.buf[pos]) - } - - pub fn get_range(&mut self, start: usize, len: usize) -> Result<&[u8]> { - // if start + len >= 512 { - // error!("Tried to read past end of buffer"); - // return Err("End of buffer".into()); - // } - self.check(start + len); - Ok(&self.buf[start..start + len]) - } - - pub fn read_u16(&mut self) -> Result { - let res = ((self.read()? as u16) << 8) | (self.read()? as u16); - - Ok(res) - } - - pub fn read_u32(&mut self) -> Result { - let res = ((self.read()? as u32) << 24) - | ((self.read()? as u32) << 16) - | ((self.read()? as u32) << 8) - | (self.read()? as u32); - - Ok(res) - } - - pub fn read_qname(&mut self, outstr: &mut String) -> Result<()> { - let mut pos = self.pos(); - let mut jumped = false; - - let mut delim = ""; - let max_jumps = 5; - let mut jumps_performed = 0; - loop { - // Dns Packets are untrusted data, so we need to be paranoid. Someone - // can craft a packet with a cycle in the jump instructions. This guards - // against such packets. - if jumps_performed > max_jumps { - return Err(format!("Limit of {max_jumps} jumps exceeded").into()); - } - - let len = self.get(pos)?; - - if (len & 0xC0) == 0xC0 { - if !jumped { - self.seek(pos + 2)?; - } - - let b2 = self.get(pos + 1)? as u16; - let offset = (((len as u16) ^ 0xC0) << 8) | b2; - pos = offset as usize; - jumped = true; - jumps_performed += 1; - continue; - } - - pos += 1; - - if len == 0 { - break; - } - - outstr.push_str(delim); - - let str_buffer = self.get_range(pos, len as usize)?; - outstr.push_str(&String::from_utf8_lossy(str_buffer).to_lowercase()); - - delim = "."; - - pos += len as usize; - } - - if !jumped { - self.seek(pos)?; - } - - Ok(()) - } - - pub fn read_string(&mut self, outstr: &mut String) -> Result<()> { - let len = self.read()?; - - self.read_string_n(outstr, len)?; - - Ok(()) - } - - pub fn read_string_n(&mut self, outstr: &mut String, len: u8) -> Result<()> { - let mut pos = self.pos; - - let str_buffer = self.get_range(pos, len as usize)?; - - let mut i = 0; - for b in str_buffer { - let c = *b as char; - if c == '\0' { - break; - } - outstr.push(c); - i += 1; - } - - pos += i; - self.seek(pos)?; - - Ok(()) - } - - pub fn write(&mut self, val: u8) -> Result<()> { - self.check(self.pos); - - self.buf[self.pos] = val; - self.pos += 1; - Ok(()) - } - - pub fn write_u8(&mut self, val: u8) -> Result<()> { - self.write(val)?; - - Ok(()) - } - - pub fn write_u16(&mut self, val: u16) -> Result<()> { - self.write((val >> 8) as u8)?; - self.write((val & 0xFF) as u8)?; - - Ok(()) - } - - pub fn write_u32(&mut self, val: u32) -> Result<()> { - self.write(((val >> 24) & 0xFF) as u8)?; - self.write(((val >> 16) & 0xFF) as u8)?; - self.write(((val >> 8) & 0xFF) as u8)?; - self.write((val & 0xFF) as u8)?; - - Ok(()) - } - - pub fn write_qname(&mut self, qname: &str) -> Result<()> { - for label in qname.split('.') { - let len = label.len(); - - self.write_u8(len as u8)?; - for b in label.as_bytes() { - self.write_u8(*b)?; - } - } - - self.write_u8(0)?; - - Ok(()) - } - - pub fn write_string(&mut self, text: &str) -> Result<()> { - for b in text.as_bytes() { - self.write_u8(*b)?; - } - - Ok(()) - } - - pub fn set(&mut self, pos: usize, val: u8) -> Result<()> { - self.buf[pos] = val; - - Ok(()) - } - - pub fn set_u16(&mut self, pos: usize, val: u16) -> Result<()> { - self.set(pos, (val >> 8) as u8)?; - self.set(pos + 1, (val & 0xFF) as u8)?; - - Ok(()) - } -} diff --git a/src/packet/header.rs b/src/packet/header.rs deleted file mode 100644 index a75f6ba..0000000 --- a/src/packet/header.rs +++ /dev/null @@ -1,101 +0,0 @@ -use super::{buffer::PacketBuffer, result::ResultCode, Result}; - -#[derive(Clone, Debug)] -pub struct DnsHeader { - pub id: u16, // 16 bits - - pub recursion_desired: bool, // 1 bit - pub truncated_message: bool, // 1 bit - pub authoritative_answer: bool, // 1 bit - pub opcode: u8, // 4 bits - pub response: bool, // 1 bit - - pub rescode: ResultCode, // 4 bits - pub checking_disabled: bool, // 1 bit - pub authed_data: bool, // 1 bit - pub z: bool, // 1 bit - pub recursion_available: bool, // 1 bit - - pub questions: u16, // 16 bits - pub answers: u16, // 16 bits - pub authoritative_entries: u16, // 16 bits - pub resource_entries: u16, // 16 bits -} - -impl DnsHeader { - pub fn new() -> Self { - Self { - id: 0, - - recursion_desired: false, - truncated_message: false, - authoritative_answer: false, - opcode: 0, - response: false, - - rescode: ResultCode::NOERROR, - checking_disabled: false, - authed_data: false, - z: false, - recursion_available: false, - - questions: 0, - answers: 0, - authoritative_entries: 0, - resource_entries: 0, - } - } - - pub fn read(&mut self, buffer: &mut PacketBuffer) -> Result<()> { - self.id = buffer.read_u16()?; - let flags = buffer.read_u16()?; - let a = (flags >> 8) as u8; - let b = (flags & 0xFF) as u8; - self.recursion_desired = (a & (1 << 0)) > 0; - self.truncated_message = (a & (1 << 1)) > 0; - self.authoritative_answer = (a & (1 << 2)) > 0; - self.opcode = (a >> 3) & 0x0F; - self.response = (a & (1 << 7)) > 0; - - self.rescode = ResultCode::from_num(b & 0x0F); - self.checking_disabled = (b & (1 << 4)) > 0; - self.authed_data = (b & (1 << 5)) > 0; - self.z = (b & (1 << 6)) > 0; - self.recursion_available = (b & (1 << 7)) > 0; - - self.questions = buffer.read_u16()?; - self.answers = buffer.read_u16()?; - self.authoritative_entries = buffer.read_u16()?; - self.resource_entries = buffer.read_u16()?; - - // Return the constant header size - Ok(()) - } - - pub fn write(&self, buffer: &mut PacketBuffer) -> Result<()> { - buffer.write_u16(self.id)?; - - buffer.write_u8( - (self.recursion_desired as u8) - | ((self.truncated_message as u8) << 1) - | ((self.authoritative_answer as u8) << 2) - | (self.opcode << 3) - | ((self.response as u8) << 7), - )?; - - buffer.write_u8( - (self.rescode as u8) - | ((self.checking_disabled as u8) << 4) - | ((self.authed_data as u8) << 5) - | ((self.z as u8) << 6) - | ((self.recursion_available as u8) << 7), - )?; - - buffer.write_u16(self.questions)?; - buffer.write_u16(self.answers)?; - buffer.write_u16(self.authoritative_entries)?; - buffer.write_u16(self.resource_entries)?; - - Ok(()) - } -} diff --git a/src/packet/mod.rs b/src/packet/mod.rs deleted file mode 100644 index 0b7cb7b..0000000 --- a/src/packet/mod.rs +++ /dev/null @@ -1,130 +0,0 @@ -use std::net::IpAddr; - -use self::{ - buffer::PacketBuffer, header::DnsHeader, query::QueryType, question::DnsQuestion, - record::DnsRecord, -}; - -type Error = Box; -pub type Result = std::result::Result; - -pub mod buffer; -pub mod header; -pub mod query; -pub mod question; -pub mod record; -pub mod result; - -#[derive(Clone, Debug)] -pub struct Packet { - pub header: DnsHeader, - pub questions: Vec, - pub answers: Vec, - pub authorities: Vec, - pub resources: Vec, -} - -impl Packet { - pub fn new() -> Self { - Self { - header: DnsHeader::new(), - questions: Vec::new(), - answers: Vec::new(), - authorities: Vec::new(), - resources: Vec::new(), - } - } - - pub fn from_buffer(buffer: &mut PacketBuffer) -> Result { - let mut result = Self::new(); - result.header.read(buffer)?; - - for _ in 0..result.header.questions { - let mut question = DnsQuestion::new("".to_string(), QueryType::UNKNOWN(0)); - question.read(buffer)?; - result.questions.push(question); - } - - for _ in 0..result.header.answers { - let rec = DnsRecord::read(buffer)?; - result.answers.push(rec); - } - for _ in 0..result.header.authoritative_entries { - let rec = DnsRecord::read(buffer)?; - result.authorities.push(rec); - } - for _ in 0..result.header.resource_entries { - let rec = DnsRecord::read(buffer)?; - result.resources.push(rec); - } - - Ok(result) - } - - pub fn write(&mut self, buffer: &mut PacketBuffer) -> Result<()> { - self.header.questions = self.questions.len() as u16; - self.header.answers = self.answers.len() as u16; - self.header.authoritative_entries = self.authorities.len() as u16; - self.header.resource_entries = self.resources.len() as u16; - - self.header.write(buffer)?; - - for question in &self.questions { - question.write(buffer)?; - } - for rec in &self.answers { - rec.write(buffer)?; - } - for rec in &self.authorities { - rec.write(buffer)?; - } - for rec in &self.resources { - rec.write(buffer)?; - } - - Ok(()) - } - - pub fn get_random_a(&self) -> Option { - self.answers - .iter() - .filter_map(|record| match record { - DnsRecord::A { addr, .. } => Some(IpAddr::V4(*addr)), - DnsRecord::AAAA { addr, .. } => Some(IpAddr::V6(*addr)), - _ => None, - }) - .next() - } - - fn get_ns<'a>(&'a self, qname: &'a str) -> impl Iterator { - self.authorities - .iter() - .filter_map(|record| match record { - DnsRecord::NS { domain, host, .. } => Some((domain.as_str(), host.as_str())), - _ => None, - }) - .filter(move |(domain, _)| qname.ends_with(*domain)) - } - - pub fn get_resolved_ns(&self, qname: &str) -> Option { - self.get_ns(qname) - .flat_map(|(_, host)| { - self.resources - .iter() - .filter_map(move |record| match record { - DnsRecord::A { domain, addr, .. } if domain == host => { - Some(IpAddr::V4(*addr)) - } - DnsRecord::AAAA { domain, addr, .. } if domain == host => { - Some(IpAddr::V6(*addr)) - } - _ => None, - }) - }) - .next() - } - - pub fn get_unresolved_ns<'a>(&'a self, qname: &'a str) -> Option<&'a str> { - self.get_ns(qname).map(|(_, host)| host).next() - } -} diff --git a/src/packet/query.rs b/src/packet/query.rs deleted file mode 100644 index cae6f09..0000000 --- a/src/packet/query.rs +++ /dev/null @@ -1,51 +0,0 @@ -#[derive(PartialEq, Eq, Debug, Clone, Hash, Copy)] -pub enum QueryType { - UNKNOWN(u16), - A, // 1 - NS, // 2 - CNAME, // 5 - SOA, // 6 - PTR, // 12 - MX, // 15 - TXT, // 16 - AAAA, // 28 - SRV, // 33 - OPT, // 41 - CAA, // 257 -} - -impl QueryType { - pub fn to_num(&self) -> u16 { - match *self { - Self::UNKNOWN(x) => x, - Self::A => 1, - Self::NS => 2, - Self::CNAME => 5, - Self::SOA => 6, - Self::PTR => 12, - Self::MX => 15, - Self::TXT => 16, - Self::AAAA => 28, - Self::SRV => 33, - Self::OPT => 41, - Self::CAA => 257, - } - } - - pub fn from_num(num: u16) -> Self { - match num { - 1 => Self::A, - 2 => Self::NS, - 5 => Self::CNAME, - 6 => Self::SOA, - 12 => Self::PTR, - 15 => Self::MX, - 16 => Self::TXT, - 28 => Self::AAAA, - 33 => Self::SRV, - 41 => Self::OPT, - 257 => Self::CAA, - _ => Self::UNKNOWN(num), - } - } -} diff --git a/src/packet/question.rs b/src/packet/question.rs deleted file mode 100644 index 9042e1c..0000000 --- a/src/packet/question.rs +++ /dev/null @@ -1,31 +0,0 @@ -use super::{buffer::PacketBuffer, query::QueryType, Result}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct DnsQuestion { - pub name: String, - pub qtype: QueryType, -} - -impl DnsQuestion { - pub fn new(name: String, qtype: QueryType) -> Self { - Self { name, qtype } - } - - pub fn read(&mut self, buffer: &mut PacketBuffer) -> Result<()> { - buffer.read_qname(&mut self.name)?; - self.qtype = QueryType::from_num(buffer.read_u16()?); // qtype - let _ = buffer.read_u16()?; // class - - Ok(()) - } - - pub fn write(&self, buffer: &mut PacketBuffer) -> Result<()> { - buffer.write_qname(&self.name)?; - - let typenum = self.qtype.to_num(); - buffer.write_u16(typenum)?; - buffer.write_u16(1)?; - - Ok(()) - } -} diff --git a/src/packet/record.rs b/src/packet/record.rs deleted file mode 100644 index c29dd8f..0000000 --- a/src/packet/record.rs +++ /dev/null @@ -1,498 +0,0 @@ -use std::net::{Ipv4Addr, Ipv6Addr}; - -use tracing::{trace, warn}; - -use super::{buffer::PacketBuffer, query::QueryType, Result}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[allow(dead_code)] -pub enum DnsRecord { - UNKNOWN { - domain: String, - qtype: u16, - data_len: u16, - ttl: u32, - }, // 0 - A { - domain: String, - addr: Ipv4Addr, - ttl: u32, - }, // 1 - NS { - domain: String, - host: String, - ttl: u32, - }, // 2 - CNAME { - domain: String, - host: String, - ttl: u32, - }, // 5 - SOA { - domain: String, - mname: String, - nname: String, - serial: u32, - refresh: u32, - retry: u32, - expire: u32, - minimum: u32, - ttl: u32, - }, // 6 - PTR { - domain: String, - pointer: String, - ttl: u32, - }, // 12 - MX { - domain: String, - priority: u16, - host: String, - ttl: u32, - }, // 15 - TXT { - domain: String, - text: Vec, - ttl: u32, - }, //16 - AAAA { - domain: String, - addr: Ipv6Addr, - ttl: u32, - }, // 28 - SRV { - domain: String, - priority: u16, - weight: u16, - port: u16, - target: String, - ttl: u32, - }, // 33 - CAA { - domain: String, - flags: u8, - length: u8, - tag: String, - value: String, - ttl: u32, - }, // 257 -} - -impl DnsRecord { - - pub fn read(buffer: &mut PacketBuffer) -> Result { - let mut domain = String::new(); - buffer.read_qname(&mut domain)?; - - let qtype_num = buffer.read_u16()?; - let qtype = QueryType::from_num(qtype_num); - let _ = buffer.read_u16()?; - let ttl = buffer.read_u32()?; - let data_len = buffer.read_u16()?; - - let header_pos = buffer.pos(); - - trace!("Reading DNS Record TYPE: {:?}", qtype); - - match qtype { - QueryType::A => { - let raw_addr = buffer.read_u32()?; - let addr = Ipv4Addr::new( - ((raw_addr >> 24) & 0xFF) as u8, - ((raw_addr >> 16) & 0xFF) as u8, - ((raw_addr >> 8) & 0xFF) as u8, - (raw_addr & 0xFF) as u8, - ); - - Ok(Self::A { domain, addr, ttl }) - } - QueryType::AAAA => { - let raw_addr1 = buffer.read_u32()?; - let raw_addr2 = buffer.read_u32()?; - let raw_addr3 = buffer.read_u32()?; - let raw_addr4 = buffer.read_u32()?; - let addr = Ipv6Addr::new( - ((raw_addr1 >> 16) & 0xFFFF) as u16, - (raw_addr1 & 0xFFFF) as u16, - ((raw_addr2 >> 16) & 0xFFFF) as u16, - (raw_addr2 & 0xFFFF) as u16, - ((raw_addr3 >> 16) & 0xFFFF) as u16, - (raw_addr3 & 0xFFFF) as u16, - ((raw_addr4 >> 16) & 0xFFFF) as u16, - (raw_addr4 & 0xFFFF) as u16, - ); - - Ok(Self::AAAA { domain, addr, ttl }) - } - QueryType::NS => { - let mut ns = String::new(); - buffer.read_qname(&mut ns)?; - - Ok(Self::NS { - domain, - host: ns, - ttl, - }) - } - QueryType::CNAME => { - let mut cname = String::new(); - buffer.read_qname(&mut cname)?; - - Ok(Self::CNAME { - domain, - host: cname, - ttl, - }) - } - QueryType::SOA => { - let mut mname = String::new(); - buffer.read_qname(&mut mname)?; - - let mut nname = String::new(); - buffer.read_qname(&mut nname)?; - - let serial = buffer.read_u32()?; - let refresh = buffer.read_u32()?; - let retry = buffer.read_u32()?; - let expire = buffer.read_u32()?; - let minimum = buffer.read_u32()?; - - Ok(Self::SOA { - domain, - mname, - nname, - serial, - refresh, - retry, - expire, - minimum, - ttl, - }) - } - QueryType::PTR => { - let mut pointer = String::new(); - buffer.read_qname(&mut pointer)?; - - Ok(Self::PTR { - domain, - pointer, - ttl, - }) - } - QueryType::MX => { - let priority = buffer.read_u16()?; - let mut mx = String::new(); - buffer.read_qname(&mut mx)?; - - Ok(Self::MX { - domain, - priority, - host: mx, - ttl, - }) - } - QueryType::TXT => { - let mut text = Vec::new(); - - loop { - let mut s = String::new(); - buffer.read_string(&mut s)?; - - if s.len() == 0 { - break; - } else { - text.push(s); - } - } - - Ok(Self::TXT { domain, text, ttl }) - } - QueryType::SRV => { - let priority = buffer.read_u16()?; - let weight = buffer.read_u16()?; - let port = buffer.read_u16()?; - - let mut target = String::new(); - buffer.read_qname(&mut target)?; - - Ok(Self::SRV { - domain, - priority, - weight, - port, - target, - ttl, - }) - } - QueryType::CAA => { - let flags = buffer.read()?; - let length = buffer.read()?; - - let mut tag = String::new(); - buffer.read_string_n(&mut tag, length)?; - - let value_len = (data_len as usize) + header_pos - buffer.pos; - let mut value = String::new(); - buffer.read_string_n(&mut value, value_len as u8)?; - - Ok(Self::CAA { - domain, - flags, - length, - tag, - value, - ttl, - }) - } - QueryType::UNKNOWN(_) | _ => { - buffer.step(data_len as usize)?; - - Ok(Self::UNKNOWN { - domain, - qtype: qtype_num, - data_len, - ttl, - }) - } - } - } - - pub fn write(&self, buffer: &mut PacketBuffer) -> Result { - let start_pos = buffer.pos(); - - trace!("Writing DNS Record {:?}", self); - - match *self { - Self::A { - ref domain, - ref addr, - ttl, - } => { - buffer.write_qname(domain)?; - buffer.write_u16(QueryType::A.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - buffer.write_u16(4)?; - - let octets = addr.octets(); - buffer.write_u8(octets[0])?; - buffer.write_u8(octets[1])?; - buffer.write_u8(octets[2])?; - buffer.write_u8(octets[3])?; - } - Self::NS { - ref domain, - ref host, - ttl, - } => { - buffer.write_qname(domain)?; - buffer.write_u16(QueryType::NS.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - - let pos = buffer.pos(); - buffer.write_u16(0)?; - - buffer.write_qname(host)?; - - let size = buffer.pos() - (pos + 2); - buffer.set_u16(pos, size as u16)?; - } - Self::CNAME { - ref domain, - ref host, - ttl, - } => { - buffer.write_qname(domain)?; - buffer.write_u16(QueryType::CNAME.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - - let pos = buffer.pos(); - buffer.write_u16(0)?; - - buffer.write_qname(host)?; - - let size = buffer.pos() - (pos + 2); - buffer.set_u16(pos, size as u16)?; - } - Self::SOA { - ref domain, - ref mname, - ref nname, - serial, - refresh, - retry, - expire, - minimum, - ttl, - } => { - buffer.write_qname(domain)?; - buffer.write_u16(QueryType::SOA.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - - let pos = buffer.pos(); - buffer.write_u16(0)?; - - buffer.write_qname(mname)?; - buffer.write_qname(nname)?; - buffer.write_u32(serial)?; - buffer.write_u32(refresh)?; - buffer.write_u32(retry)?; - buffer.write_u32(expire)?; - buffer.write_u32(minimum)?; - - let size = buffer.pos() - (pos + 2); - buffer.set_u16(pos, size as u16)?; - } - Self::PTR { - ref domain, - ref pointer, - ttl, - } => { - buffer.write_qname(domain)?; - buffer.write_u16(QueryType::NS.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - - let pos = buffer.pos(); - buffer.write_u16(0)?; - - buffer.write_qname(&pointer)?; - - let size = buffer.pos() - (pos + 2); - buffer.set_u16(pos, size as u16)?; - } - Self::MX { - ref domain, - priority, - ref host, - ttl, - } => { - buffer.write_qname(domain)?; - buffer.write_u16(QueryType::MX.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - - let pos = buffer.pos(); - buffer.write_u16(0)?; - - buffer.write_u16(priority)?; - buffer.write_qname(host)?; - - let size = buffer.pos() - (pos + 2); - buffer.set_u16(pos, size as u16)?; - } - Self::TXT { - ref domain, - ref text, - ttl, - } => { - buffer.write_qname(&domain)?; - buffer.write_u16(QueryType::TXT.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - - let pos = buffer.pos(); - buffer.write_u16(0)?; - - if text.is_empty() { - return Ok(buffer.pos() - start_pos); - } - - for s in text { - buffer.write_u8(s.len() as u8)?; - buffer.write_string(&s)?; - } - let size = buffer.pos() - (pos + 2); - buffer.set_u16(pos, size as u16)?; - } - Self::AAAA { - ref domain, - ref addr, - ttl, - } => { - buffer.write_qname(domain)?; - buffer.write_u16(QueryType::AAAA.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - buffer.write_u16(16)?; - - for octet in &addr.segments() { - buffer.write_u16(*octet)?; - } - } - Self::SRV { - ref domain, - priority, - weight, - port, - ref target, - ttl, - } => { - buffer.write_qname(domain)?; - buffer.write_u16(QueryType::SRV.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - - let pos = buffer.pos(); - buffer.write_u16(0)?; - - buffer.write_u16(priority)?; - buffer.write_u16(weight)?; - buffer.write_u16(port)?; - buffer.write_qname(target)?; - - let size = buffer.pos() - (pos + 2); - buffer.set_u16(pos, size as u16)?; - } - Self::CAA { - ref domain, - flags, - length, - ref tag, - ref value, - ttl, - } => { - buffer.write_qname(domain)?; - buffer.write_u16(QueryType::CAA.to_num())?; - buffer.write_u16(1)?; - buffer.write_u32(ttl)?; - - let pos = buffer.pos(); - buffer.write_u16(0)?; - - buffer.write_u8(flags)?; - buffer.write_u8(length)?; - buffer.write_string(tag)?; - buffer.write_string(value)?; - - let size = buffer.pos() - (pos + 2); - buffer.set_u16(pos, size as u16)?; - } - Self::UNKNOWN { .. } => { - warn!("Skipping record: {self:?}"); - } - } - - Ok(buffer.pos() - start_pos) - } - - pub fn get_ttl(&self) -> u32 { - match *self { - DnsRecord::UNKNOWN { .. } => 0, - DnsRecord::AAAA { ttl, .. } => ttl, - DnsRecord::A { ttl, .. } => ttl, - DnsRecord::NS { ttl, .. } => ttl, - DnsRecord::CNAME { ttl, .. } => ttl, - DnsRecord::SOA { ttl, .. } => ttl, - DnsRecord::PTR { ttl, .. } => ttl, - DnsRecord::MX { ttl, .. } => ttl, - DnsRecord::TXT { ttl, .. } => ttl, - DnsRecord::SRV { ttl, .. } => ttl, - DnsRecord::CAA { ttl, .. } => ttl, - } - } - -} diff --git a/src/packet/result.rs b/src/packet/result.rs deleted file mode 100644 index 41c8ba9..0000000 --- a/src/packet/result.rs +++ /dev/null @@ -1,22 +0,0 @@ -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ResultCode { - NOERROR = 0, - FORMERR = 1, - SERVFAIL = 2, - NXDOMAIN = 3, - NOTIMP = 4, - REFUSED = 5, -} - -impl ResultCode { - pub fn from_num(num: u8) -> Self { - match num { - 1 => Self::FORMERR, - 2 => Self::SERVFAIL, - 3 => Self::NXDOMAIN, - 4 => Self::NOTIMP, - 5 => Self::REFUSED, - 0 | _ => Self::NOERROR, - } - } -} diff --git a/src/server/binding.rs b/src/server/binding.rs deleted file mode 100644 index 1c69651..0000000 --- a/src/server/binding.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::{ - net::{IpAddr, SocketAddr}, - sync::Arc, -}; - -use crate::packet::{buffer::PacketBuffer, Packet, Result}; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt}, - net::{TcpListener, TcpStream, UdpSocket}, -}; -use tracing::trace; - -pub enum Binding { - UDP(Arc), - TCP(TcpListener), -} - -impl Binding { - pub async fn udp(addr: SocketAddr) -> Result { - let socket = UdpSocket::bind(addr).await?; - Ok(Self::UDP(Arc::new(socket))) - } - - pub async fn tcp(addr: SocketAddr) -> Result { - let socket = TcpListener::bind(addr).await?; - Ok(Self::TCP(socket)) - } - - pub fn name(&self) -> &str { - match self { - Binding::UDP(_) => "UDP", - Binding::TCP(_) => "TCP", - } - } - - pub async fn connect(&mut self) -> Result { - match self { - Self::UDP(socket) => { - let mut buf = [0; 512]; - let (_, addr) = socket.recv_from(&mut buf).await?; - Ok(Connection::UDP(socket.clone(), addr, buf)) - } - Self::TCP(socket) => { - let (stream, _) = socket.accept().await?; - Ok(Connection::TCP(stream)) - } - } - } -} - -pub enum Connection { - UDP(Arc, SocketAddr, [u8; 512]), - TCP(TcpStream), -} - -impl Connection { - pub async fn read_packet(&mut self) -> Result { - let data = self.read().await?; - let mut packet_buffer = PacketBuffer::new(data); - - let packet = Packet::from_buffer(&mut packet_buffer)?; - Ok(packet) - } - - pub async fn write_packet(self, mut packet: Packet) -> Result<()> { - let mut packet_buffer = PacketBuffer::new(Vec::new()); - packet.write(&mut packet_buffer)?; - - self.write(packet_buffer.buf).await?; - Ok(()) - } - - pub async fn request_packet(&self, mut packet: Packet, dest: (IpAddr, u16)) -> Result { - let mut packet_buffer = PacketBuffer::new(Vec::new()); - packet.write(&mut packet_buffer)?; - - let data = self.request(packet_buffer.buf, dest).await?; - let mut packet_buffer = PacketBuffer::new(data); - - let packet = Packet::from_buffer(&mut packet_buffer)?; - Ok(packet) - } - - async fn read(&mut self) -> Result> { - trace!("Reading DNS packet"); - match self { - Self::UDP(_, _, src) => Ok(Vec::from(*src)), - Self::TCP(stream) => { - let size = stream.read_u16().await?; - let mut buf = Vec::with_capacity(size as usize); - stream.read_buf(&mut buf).await?; - Ok(buf) - } - } - } - - async fn write(self, mut buf: Vec) -> Result<()> { - trace!("Returning DNS packet"); - match self { - Self::UDP(socket, addr, _) => { - if buf.len() > 512 { - buf[2] = buf[2] | 0x03; - socket.send_to(&buf[0..512], addr).await?; - } else { - socket.send_to(&buf, addr).await?; - } - Ok(()) - } - Self::TCP(mut stream) => { - stream.write_u16(buf.len() as u16).await?; - stream.write(&buf[0..buf.len()]).await?; - Ok(()) - } - } - } - - async fn request(&self, buf: Vec, dest: (IpAddr, u16)) -> Result> { - match self { - Self::UDP(_socket, _addr, _src) => { - let local_addr = "[::]:0".parse::()?; - let socket = UdpSocket::bind(local_addr).await?; - socket.send_to(&buf, dest).await?; - - let mut buf = [0; 512]; - socket.recv_from(&mut buf).await?; - - Ok(Vec::from(buf)) - } - Self::TCP(_stream) => { - let mut stream = TcpStream::connect(dest).await?; - stream.write_u16((buf.len()) as u16).await?; - stream.write_all(&buf[0..buf.len()]).await?; - - stream.readable().await?; - let size = stream.read_u16().await?; - let mut buf = Vec::with_capacity(size as usize); - stream.read_buf(&mut buf).await?; - - Ok(buf) - } - } - } - - // fn pb(buf: &[u8]) { - // for i in 0..buf.len() { - // print!("{:02X?} ", buf[i]); - // } - // println!(""); - // } -} diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 25076ef..0000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod binding; -mod resolver; -pub mod server; diff --git a/src/server/resolver.rs b/src/server/resolver.rs deleted file mode 100644 index 464620c..0000000 --- a/src/server/resolver.rs +++ /dev/null @@ -1,165 +0,0 @@ -use super::binding::Connection; -use crate::{ - config::Config, - packet::{ - query::QueryType, question::DnsQuestion, result::ResultCode, Packet, - Result, - }, get_time, -}; -use async_recursion::async_recursion; -use moka::future::Cache; -use std::{net::IpAddr, sync::Arc, time::Duration}; -use tracing::{error, trace}; - -pub struct Resolver { - request_id: u16, - connection: Connection, - config: Arc, - cache: Cache, -} - -impl Resolver { - pub fn new( - request_id: u16, - connection: Connection, - config: Arc, - cache: Cache, - ) -> Self { - Self { - request_id, - connection, - config, - cache, - } - } - - async fn lookup_cache(&mut self, qname: &str, qtype: QueryType) -> Option { - let question = DnsQuestion::new(qname.to_string(), qtype); - let Some((packet, date)) = self.cache.get(&question) else { - return None - }; - - let now = get_time(); - let diff = Duration::from_millis(now - date).as_secs() as u32; - - for answer in &packet.answers { - let ttl = answer.get_ttl(); - if diff > ttl { - self.cache.invalidate(&question).await; - return None - } - } - - trace!("Found cached value for {qtype:?} {qname}"); - - Some(packet) - } - - async fn lookup(&mut self, qname: &str, qtype: QueryType, server: (IpAddr, u16)) -> Packet { - let mut packet = Packet::new(); - - packet.header.id = self.request_id; - packet.header.questions = 1; - packet.header.recursion_desired = true; - packet - .questions - .push(DnsQuestion::new(qname.to_string(), qtype)); - - let packet = match self.connection.request_packet(packet, server).await { - Ok(packet) => packet, - Err(e) => { - error!("Failed to complete nameserver request: {e}"); - let mut packet = Packet::new(); - packet.header.rescode = ResultCode::SERVFAIL; - packet - } - }; - - packet - } - - #[async_recursion] - async fn recursive_lookup(&mut self, qname: &str, qtype: QueryType) -> Packet { - let question = DnsQuestion::new(qname.to_string(), qtype); - let mut ns = self.config.get_fallback_ns().clone(); - - if let Some(packet) = self.lookup_cache(qname, qtype).await { return packet } - - loop { - trace!("Attempting lookup of {qtype:?} {qname} with ns {ns}"); - - let ns_copy = ns; - - let server = (ns_copy, 53); - let response = self.lookup(qname, qtype, server).await; - - if !response.answers.is_empty() && response.header.rescode == ResultCode::NOERROR { - self.cache.insert(question, (response.clone(), get_time())).await; - return response; - } - - if response.header.rescode == ResultCode::NXDOMAIN { - self.cache.insert(question, (response.clone(), get_time())).await; - return response; - } - - if let Some(new_ns) = response.get_resolved_ns(qname) { - ns = new_ns; - continue; - } - - let new_ns_name = match response.get_unresolved_ns(qname) { - Some(x) => x, - None => { - self.cache.insert(question, (response.clone(), get_time())).await; - return response - }, - }; - - let recursive_response = self.recursive_lookup(new_ns_name, QueryType::A).await; - - if let Some(new_ns) = recursive_response.get_random_a() { - ns = new_ns; - } else { - self.cache.insert(question, (response.clone(), get_time())).await; - return response; - } - } - } - - pub async fn handle_query(mut self) -> Result<()> { - let mut request = self.connection.read_packet().await?; - - let mut packet = Packet::new(); - packet.header.id = request.header.id; - packet.header.recursion_desired = true; - packet.header.recursion_available = true; - packet.header.response = true; - - if let Some(question) = request.questions.pop() { - trace!("Received query: {question:?}"); - - let result = self.recursive_lookup(&question.name, question.qtype).await; - packet.questions.push(question.clone()); - packet.header.rescode = result.header.rescode; - - for rec in result.answers { - trace!("Answer: {rec:?}"); - packet.answers.push(rec); - } - for rec in result.authorities { - trace!("Authority: {rec:?}"); - packet.authorities.push(rec); - } - for rec in result.resources { - trace!("Resource: {rec:?}"); - packet.resources.push(rec); - } - } else { - packet.header.rescode = ResultCode::FORMERR; - } - - self.connection.write_packet(packet).await?; - Ok(()) - } -} diff --git a/src/server/server.rs b/src/server/server.rs deleted file mode 100644 index e006bb1..0000000 --- a/src/server/server.rs +++ /dev/null @@ -1,73 +0,0 @@ -use moka::future::Cache; -use std::net::SocketAddr; -use std::sync::Arc; -use std::time::Duration; -use tokio::task::JoinHandle; -use tracing::{error, info}; - -use crate::config::Config; -use crate::packet::question::DnsQuestion; -use crate::packet::{Result, Packet}; - -use super::binding::Binding; -use super::resolver::Resolver; - -pub struct Server { - addr: SocketAddr, - config: Arc, - cache: Cache, -} - -impl Server { - pub async fn new(config: Config) -> Result { - let addr = format!("[::]:{}", config.get_port()).parse::()?; - let cache = Cache::builder() - .time_to_live(Duration::from_secs(60 * 60)) - .max_capacity(1_000) - .build(); - Ok(Self { - addr, - config: Arc::new(config), - cache, - }) - } - - pub async fn run(&self) -> Result<()> { - let tcp = Binding::tcp(self.addr).await?; - let tcp_handle = self.listen(tcp); - - let udp = Binding::udp(self.addr).await?; - let udp_handle = self.listen(udp); - - info!("Fallback DNS Server is set to: {:?}", self.config.get_fallback_ns()); - info!("Listening for TCP and UDP traffic on [::]:{}", self.config.get_port()); - - tokio::join!(tcp_handle) - .0 - .expect("Failed to join tcp thread"); - tokio::join!(udp_handle) - .0 - .expect("Failed to join udp thread"); - Ok(()) - } - - fn listen(&self, mut binding: Binding) -> JoinHandle<()> { - let config = self.config.clone(); - let cache = self.cache.clone(); - tokio::spawn(async move { - let mut id = 0; - loop { - let Ok(connection) = binding.connect().await else { continue }; - info!("Received request on {}", binding.name()); - - let resolver = Resolver::new(id, connection, config.clone(), cache.clone()); - - if let Err(err) = resolver.handle_query().await { - error!("{} request {} failed: {:?}", binding.name(), id, err); - }; - - id += 1; - } - }) - } -} diff --git a/src/web/api.rs b/src/web/api.rs new file mode 100644 index 0000000..1fddb5f --- /dev/null +++ b/src/web/api.rs @@ -0,0 +1,156 @@ +use std::net::IpAddr; + +use axum::{ + extract::Query, + response::Response, + routing::{get, post, put, delete}, + Extension, Router, +}; +use moka::future::Cache; +use rand::distributions::{Alphanumeric, DistString}; +use serde::Deserialize; +use tower_cookies::{Cookie, Cookies}; + +use crate::{config::Config, database::Database, dns::packet::record::DnsRecord}; + +use super::{ + extract::{Authorized, Body, RequestIp}, + http::{json, text}, +}; + +pub fn router() -> Router { + Router::new() + .route("/login", post(login)) + .route("/domains", get(list_domains)) + .route("/domains", delete(delete_domain)) + .route("/records", get(get_domain)) + .route("/records", put(add_record)) +} + +async fn list_domains(_: Authorized, Extension(database): Extension) -> Response { + let domains = match database.get_domains().await { + Ok(domains) => domains, + Err(err) => return text(500, &format!("{err}")), + }; + + let Ok(domains) = serde_json::to_string(&domains) else { + return text(500, "Failed to fetch domains") + }; + + json(200, &domains) +} + +#[derive(Deserialize)] +struct DomainRequest { + domain: String, +} + +async fn get_domain( + _: Authorized, + Extension(database): Extension, + Query(query): Query, +) -> Response { + let records = match database.get_domain(&query.domain).await { + Ok(records) => records, + Err(err) => return text(500, &format!("{err}")), + }; + + let Ok(records) = serde_json::to_string(&records) else { + return text(500, "Failed to fetch records") + }; + + json(200, &records) +} + +async fn delete_domain( + _: Authorized, + Extension(database): Extension, + Body(body): Body, +) -> Response { + + let Ok(request) = serde_json::from_str::(&body) else { + return text(400, "Missing request parameters") + }; + + let Ok(domains) = database.get_domains().await else { + return text(500, "Failed to delete domain") + }; + + if !domains.contains(&request.domain) { + return text(400, "Domain does not exist") + } + + if database.delete_domain(request.domain).await.is_err() { + return text(500, "Failed to delete domain") + }; + + return text(204, "Successfully deleted domain") +} + +async fn add_record( + _: Authorized, + Extension(database): Extension, + Body(body): Body, +) -> Response { + let Ok(record) = serde_json::from_str::(&body) else { + return text(400, "Invalid DNS record") + }; + + let allowed = record.get_qtype().allowed_actions(); + if !allowed.1 { + return text(400, "Not allowed to create record") + } + + let Ok(records) = database.get_records(&record.get_domain(), record.get_qtype()).await else { + return text(500, "Failed to complete record check"); + }; + + if !records.is_empty() && !allowed.0 { + return text(400, "Not allowed to create duplicate record") + }; + + if records.contains(&record) { + return text(400, "Not allowed to create duplicate record") + } + + if let Err(err) = database.add_record(record).await { + return text(500, &format!("{err}")); + } + + return text(201, "Added record to database successfully"); +} + +#[derive(Deserialize)] +struct LoginRequest { + user: String, + pass: String, +} + +async fn login( + Extension(config): Extension, + Extension(cache): Extension>, + RequestIp(ip): RequestIp, + cookies: Cookies, + Body(body): Body, +) -> Response { + let Ok(request) = serde_json::from_str::(&body) else { + return text(400, "Missing request parameters") + }; + + if request.user != config.web_user || request.pass != config.web_pass { + return text(400, "Invalid credentials"); + }; + + let token = Alphanumeric.sample_string(&mut rand::thread_rng(), 128); + + cache.insert(token.clone(), ip).await; + + let mut cookie = Cookie::new("auth", token); + cookie.set_secure(true); + cookie.set_http_only(true); + cookie.set_path("/"); + + cookies.add(cookie); + + text(200, "Successfully logged in") +} diff --git a/src/web/extract.rs b/src/web/extract.rs new file mode 100644 index 0000000..4b6cd7c --- /dev/null +++ b/src/web/extract.rs @@ -0,0 +1,139 @@ +use std::{ + io::Read, + net::{IpAddr, SocketAddr}, +}; + +use axum::{ + async_trait, + body::HttpBody, + extract::{ConnectInfo, FromRequest, FromRequestParts}, + http::{request::Parts, Request}, + response::Response, + BoxError, +}; +use bytes::Bytes; +use moka::future::Cache; +use tower_cookies::Cookies; + +use super::http::text; + +pub struct Authorized; + +#[async_trait] +impl FromRequestParts for Authorized +where + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let Ok(Some(cookies)) = Option::::from_request_parts(parts, state).await else { + return Err(text(403, "No cookies provided")) + }; + + let Some(token) = cookies.get("auth") else { + return Err(text(403, "No auth token provided")) + }; + + let auth_ip: IpAddr; + { + let Some(cache) = parts.extensions.get::>() else { + return Err(text(500, "Failed to load auth store")) + }; + + let Some(ip) = cache.get(token.value()) else { + return Err(text(401, "Unauthorized")) + }; + + auth_ip = ip + } + + let Ok(Some(RequestIp(ip))) = Option::::from_request_parts(parts, state).await else { + return Err(text(403, "You have no ip")) + }; + + if auth_ip != ip { + return Err(text(403, "Auth token does not match current ip")); + } + + Ok(Self) + } +} + +pub struct RequestIp(pub IpAddr); + +#[async_trait] +impl FromRequestParts for RequestIp +where + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { + let headers = &parts.headers; + + let forwardedfor = headers + .get("x-forwarded-for") + .and_then(|h| h.to_str().ok()) + .and_then(|h| { + h.split(',') + .rev() + .find_map(|s| s.trim().parse::().ok()) + }); + + if let Some(forwardedfor) = forwardedfor { + return Ok(Self(forwardedfor)); + } + + let realip = headers + .get("x-real-ip") + .and_then(|hv| hv.to_str().ok()) + .and_then(|s| s.parse::().ok()); + + if let Some(realip) = realip { + return Ok(Self(realip)); + } + + let realip = headers + .get("x-real-ip") + .and_then(|hv| hv.to_str().ok()) + .and_then(|s| s.parse::().ok()); + + if let Some(realip) = realip { + return Ok(Self(realip)); + } + + let info = parts.extensions.get::>(); + + if let Some(info) = info { + return Ok(Self(info.0.ip())); + } + + Err(text(403, "You have no ip")) + } +} + +pub struct Body(pub String); + +#[async_trait] +impl FromRequest for Body +where + B: HttpBody + Sync + Send + 'static, + B::Data: Send, + B::Error: Into, + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request(req: Request, state: &S) -> Result { + let Ok(bytes) = Bytes::from_request(req, state).await else { + return Err(text(413, "Payload too large")); + }; + + let Ok(body) = String::from_utf8(bytes.bytes().flatten().collect()) else { + return Err(text(400, "Invalid utf8 body")) + }; + + Ok(Self(body)) + } +} diff --git a/src/web/file.rs b/src/web/file.rs new file mode 100644 index 0000000..73ecdc9 --- /dev/null +++ b/src/web/file.rs @@ -0,0 +1,31 @@ +use axum::{extract::Path, response::Response}; + +use super::http::serve; + +pub async fn js(Path(path): Path) -> Response { + let path = format!("/js/{path}"); + serve(&path).await +} + +pub async fn css(Path(path): Path) -> Response { + let path = format!("/css/{path}"); + serve(&path).await +} + +pub async fn fonts(Path(path): Path) -> Response { + let path = format!("/fonts/{path}"); + serve(&path).await +} + +pub async fn image(Path(path): Path) -> Response { + let path = format!("/image/{path}"); + serve(&path).await +} + +pub async fn favicon() -> Response { + serve("/favicon.ico").await +} + +pub async fn robots() -> Response { + serve("/robots.txt").await +} diff --git a/src/web/http.rs b/src/web/http.rs new file mode 100644 index 0000000..7ab1b11 --- /dev/null +++ b/src/web/http.rs @@ -0,0 +1,50 @@ +use axum::{ + body::Body, + http::{header::HeaderName, HeaderValue, Request, StatusCode}, + response::{IntoResponse, Response}, +}; +use std::str; +use tower::ServiceExt; +use tower_http::services::ServeFile; + +pub fn text(code: u16, msg: &str) -> Response { + (status_code(code), msg.to_owned()).into_response() +} + +pub fn json(code: u16, json: &str) -> Response { + let mut res = (status_code(code), json.to_owned()).into_response(); + res.headers_mut().insert( + HeaderName::from_static("content-type"), + HeaderValue::from_static("application/json"), + ); + res +} + +pub async fn serve(path: &str) -> Response { + if !path.chars().any(|c| c == '.') { + return text(403, "Invalid file path"); + } + + let path = format!("public{path}"); + let file = ServeFile::new(path); + + let Ok(mut res) = file.oneshot(Request::new(Body::empty())).await else { + tracing::error!("Error while fetching file"); + return text(500, "Error when fetching file") + }; + + if res.status() != StatusCode::OK { + return text(404, "File not found"); + } + + res.headers_mut().insert( + HeaderName::from_static("cache-control"), + HeaderValue::from_static("max-age=300"), + ); + + res.into_response() +} + +fn status_code(code: u16) -> StatusCode { + StatusCode::from_u16(code).map_or(StatusCode::OK, |code| code) +} diff --git a/src/web/mod.rs b/src/web/mod.rs new file mode 100644 index 0000000..530a3f9 --- /dev/null +++ b/src/web/mod.rs @@ -0,0 +1,82 @@ +use std::net::{IpAddr, SocketAddr, TcpListener}; +use std::time::Duration; + +use axum::routing::get; +use axum::{Extension, Router}; +use moka::future::Cache; +use tokio::task::JoinHandle; +use tower_cookies::CookieManagerLayer; +use tracing::{error, info}; + +use crate::config::Config; +use crate::database::Database; +use crate::Result; + +mod api; +mod extract; +mod file; +mod http; +mod pages; + +pub struct WebServer { + config: Config, + database: Database, + addr: SocketAddr, +} + +impl WebServer { + pub async fn new(config: Config, database: Database) -> Result { + let addr = format!("[::]:{}", config.web_port).parse::()?; + Ok(Self { + config, + database, + addr, + }) + } + + pub async fn run(&self) -> Result> { + let config = self.config.clone(); + let database = self.database.clone(); + let listener = TcpListener::bind(self.addr)?; + + info!( + "Listening for HTTP traffic on [::]:{}", + self.config.web_port + ); + + let app = Self::router(config, database); + let server = axum::Server::from_tcp(listener)?; + + let web_handle = tokio::spawn(async move { + if let Err(err) = server + .serve(app.into_make_service_with_connect_info::()) + .await + { + error!("{err}"); + } + }); + + Ok(web_handle) + } + + fn router(config: Config, database: Database) -> Router { + let cache: Cache = Cache::builder() + .time_to_live(Duration::from_secs(60 * 15)) + .max_capacity(config.dns_cache_size) + .build(); + + Router::new() + .nest("/", pages::router()) + .nest("/api", api::router()) + .layer(Extension(config)) + .layer(Extension(cache)) + .layer(Extension(database)) + .layer(CookieManagerLayer::new()) + .route("/js/*path", get(file::js)) + .route("/css/*path", get(file::css)) + .route("/fonts/*path", get(file::fonts)) + .route("/image/*path", get(file::image)) + .route("/favicon.ico", get(file::favicon)) + .route("/robots.txt", get(file::robots)) + } +} diff --git a/src/web/pages.rs b/src/web/pages.rs new file mode 100644 index 0000000..a8605ef --- /dev/null +++ b/src/web/pages.rs @@ -0,0 +1,31 @@ +use axum::{response::Response, routing::get, Router}; + +use super::{extract::Authorized, http::serve}; + +pub fn router() -> Router { + Router::new() + .route("/", get(root)) + .route("/login", get(login)) + .route("/home", get(home)) + .route("/domain", get(domain)) +} + +async fn root(user: Option) -> Response { + if user.is_some() { + home().await + } else { + login().await + } +} + +async fn login() -> Response { + serve("/login.html").await +} + +async fn home() -> Response { + serve("/home.html").await +} + +async fn domain() -> Response { + serve("/domain.html").await +} -- cgit v1.2.3-freya