feat: init

This commit is contained in:
2026-07-01 23:30:39 +02:00
commit d920f988d7
7 changed files with 1064 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
use anyhow::Result;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct ConfigData {
pub port: u16,
}
pub fn load() -> Result<ConfigData> {
let source = config::Environment::default();
let builder = config::Config::builder()
.add_source(source)
.set_default("port", 53)?;
let config = builder.build()?.try_deserialize()?;
Ok(config)
}
+95
View File
@@ -0,0 +1,95 @@
#![allow(unused)]
use std::fmt::Display;
const QUESTION_TYPE_A: u16 = 0x01;
const QUESTION_TYPE_NS: u16 = 0x02;
const QUESTION_TYPE_CNAME: u16 = 0x05;
const QUESTION_TYPE_MX: u16 = 0x0F;
const QUESTION_TYPE_AAAA: u16 = 0x40;
const QUESTION_CLASS_IN: u16 = 0x01;
const QUESTION_CLASS_CHAOS: u16 = 0x03;
const QUESTION_CLASS_HS: u16 = 0x04;
const QUESTION_CLASS_ANY: u16 = 0xFF;
pub struct DnsHeader {
pub transaction_id: [u8; 2],
pub query_response: bool,
pub op_code: u8, // 4 bits
pub authoritative_answer: bool,
pub truncation: bool,
pub recursion_desired: bool,
pub recursion_available: bool,
pub authentic_data: bool,
pub checking_disabled: bool,
pub response_code: u8, // 4 bits
pub number_of_questions: u16,
pub number_of_answers: u16,
pub number_of_authority_resource_records: u16,
pub number_of_additional_resource_records: u16,
}
impl Display for DnsHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"transaction_id: {:x}{:x}",
self.transaction_id[0], self.transaction_id[1]
)?;
writeln!(f, "query_response: {}", self.query_response)?;
writeln!(f, "op_code: {}", self.op_code)?;
writeln!(f, "authoritative_answer: {}", self.authoritative_answer)?;
writeln!(f, "truncation: {}", self.truncation)?;
writeln!(f, "recursion_desired: {}", self.recursion_desired)?;
writeln!(f, "recursion_available: {}", self.recursion_available)?;
writeln!(f, "authentic_data: {}", self.authentic_data)?;
writeln!(f, "checking_disabled: {}", self.checking_disabled)?;
writeln!(f, "response_code: {}", self.response_code)?;
writeln!(f, "number_of_questions: {}", self.number_of_questions)?;
writeln!(f, "number_of_answers: {}", self.number_of_answers)?;
writeln!(
f,
"number_of_authority_rr: {}",
self.number_of_authority_resource_records
)?;
writeln!(
f,
"number_of_additional_rr: {}",
self.number_of_additional_resource_records
)?;
Ok(())
}
}
pub struct DnsQuestion {
pub domain: String,
pub question_type: u16,
pub question_class: u16,
}
impl Display for DnsQuestion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}, {}, {}",
self.domain, self.question_type, self.question_class
)
}
}
pub struct DnsAnswer {
pub domain: String,
}
impl DnsAnswer {
pub fn to_bytes(&self) -> Vec<u8> {
let mut result = vec![];
for part in self.domain.split(".") {
result.push(part.len() as u8);
part.bytes().for_each(|c| result.push(c));
}
result
}
}
+8
View File
@@ -0,0 +1,8 @@
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
pub fn setup() {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer().compact())
.with(EnvFilter::from_env("LOG"))
.init();
}
+77
View File
@@ -0,0 +1,77 @@
mod config;
mod dns;
mod logging;
use anyhow::Result;
use dotenv::dotenv;
use tokio::net::UdpSocket;
use crate::dns::*;
#[tokio::main]
async fn main() -> Result<()> {
dotenv().ok();
let config = config::load()?;
logging::setup();
tracing::info!("start server on port {}", config.port);
let sock = UdpSocket::bind(("0.0.0.0", config.port)).await?;
let mut buffer = [0u8; 512];
loop {
let size = sock.recv(&mut buffer).await?;
handle_datagram(buffer.clone(), size);
}
}
fn handle_datagram(buffer: [u8; 512], _buffer_size: usize) {
let datagram = DnsHeader {
transaction_id: [buffer[0], buffer[1]],
query_response: buffer[2] & (1 << 7) > 0,
op_code: (buffer[2] >> 3) & 0x0F,
authoritative_answer: buffer[2] & (1 << 2) > 0,
truncation: buffer[2] & (1 << 1) > 0,
recursion_desired: buffer[2] & (1 << 0) > 0,
recursion_available: buffer[3] & (1 << 7) > 0,
authentic_data: buffer[3] & (1 << 5) > 0,
checking_disabled: buffer[3] & (1 << 4) > 0,
response_code: buffer[3] & 0xF,
number_of_questions: ((buffer[4] as u16) << 8) | (buffer[5] as u16),
number_of_answers: ((buffer[6] as u16) << 8) | (buffer[7] as u16),
number_of_authority_resource_records: ((buffer[8] as u16) << 8) | (buffer[9] as u16),
number_of_additional_resource_records: ((buffer[10] as u16) << 8) | (buffer[11] as u16),
};
println!("Received:\n{}", datagram);
let mut pos = 12usize;
for _ in 0..(datagram.number_of_questions) {
let mut domain_parts: Vec<String> = vec![];
loop {
let len = buffer[pos] as usize;
if len == 0 {
pos += 1;
break;
}
let domain_part_bytes = &buffer[(pos + 1)..(pos + 1 + len)];
let domain_part = String::from_utf8_lossy(&domain_part_bytes);
domain_parts.push(domain_part.to_string());
pos += 1 + len;
}
let question_type = ((buffer[pos] as u16) << 8) | (buffer[pos + 1] as u16);
let question_class = ((buffer[pos + 2] as u16) << 8) | (buffer[pos + 3] as u16);
let question = DnsQuestion {
domain: domain_parts.join("."),
question_type,
question_class,
};
println!("Question: {}", question);
pos += 4;
}
}